예제 #1
0
class Page(MPTTModel, TranslatableModel):
    available_from = models.DateTimeField(null=True, blank=True, verbose_name=_('available from'))
    available_to = models.DateTimeField(null=True, blank=True, verbose_name=_('available to'))

    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, blank=True, null=True, related_name="+", on_delete=models.SET_NULL,
        verbose_name=_('created by')
    )
    modified_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, blank=True, null=True, related_name="+", on_delete=models.SET_NULL,
        verbose_name=_('modified by')
    )

    created_on = models.DateTimeField(auto_now_add=True, editable=False, verbose_name=_('created on'))
    modified_on = models.DateTimeField(auto_now=True, editable=False, verbose_name=_('modified on'))

    identifier = InternalIdentifierField(
        unique=True,
        help_text=_('This identifier can be used in templates to create URLs'),
        editable=True
    )

    visible_in_menu = models.BooleanField(verbose_name=_("visible in menu"), default=False)
    parent = TreeForeignKey(
        "self", blank=True, null=True, related_name="children", verbose_name=_("parent"))
    list_children_on_page = models.BooleanField(verbose_name=_("list children on page"), default=False)

    translations = TranslatedFields(
        title=models.CharField(max_length=256, verbose_name=_('title')),
        url=models.CharField(
            max_length=100, verbose_name=_('URL'),
            unique=True,
            default=None,
            blank=True,
            null=True
        ),
        content=models.TextField(verbose_name=_('content')),
    )

    objects = TreeManager.from_queryset(PageQuerySet)()

    class Meta:
        ordering = ('-id',)
        verbose_name = _('page')
        verbose_name_plural = _('pages')

    def is_visible(self, dt=None):
        if not dt:
            dt = now()

        return (
            (self.available_from and self.available_from <= dt) and
            (self.available_to is None or self.available_to >= dt)
        )

    def get_html(self):
        return markdown.markdown(self.content)

    def __str__(self):
        return force_text(self.safe_translation_getter("title", any_language=True, default=_("Untitled")))
예제 #2
0
class BaseTermManager(RebuildTreeMixin,
                      TreeManager.from_queryset(BaseTermQuerySet)):
    """
    ENG: Customized model manager for our Term model.
    RUS: Адаптированная модель менеджера для модели Терминов.
    """
    '''
예제 #3
0
    def as_manager(cls):
        # Address the circular dependency between `Queryset` and `Manager`.
        from mptt.managers import TreeManager

        manager = TreeManager.from_queryset(cls)()
        manager._built_with_as_manager = True
        return manager
예제 #4
0
class Comment(MPTTModel):
    author = models.ForeignKey(User,
                               verbose_name=_('Author'),
                               related_name='comments',
                               on_delete=models.CASCADE)

    content_type_id = models.IntegerField(_('Content type'))
    object_id = models.IntegerField(_('Object id'))

    parent = TreeForeignKey(
        'self',
        verbose_name=_('Parent comment'),
        null=True,
        blank=True,
        related_name='children',
        db_index=True,
        on_delete=models.CASCADE,
    )
    created_at = models.DateTimeField(_('Date of creation'),
                                      auto_now_add=True,
                                      db_index=True)
    modified_at = models.DateTimeField(_('Date of modification'),
                                       auto_now=True)
    content = models.TextField(_('Content'),
                               max_length=settings.COMMENT_CONTENT_MAX_LENGTH)

    is_deleted = models.BooleanField(_('Is deleted'),
                                     default=False,
                                     db_index=True)
    is_edited = models.BooleanField(_('Is edited'), default=False)

    objects = TreeManager.from_queryset(CommentQuerySet)()

    class Meta:
        verbose_name = _('Comment')
        verbose_name_plural = _('Comments')
        index_together = [
            ['content_type_id', 'object_id', 'is_deleted'],
            ['content_type_id', 'object_id'],
            ['tree_id', 'lft'],
        ]

    class MPTTMeta:
        order_insertion_by = ['created_at']

    def __str__(self):
        return f'{self.pk}'
예제 #5
0
파일: manager.py 프로젝트: toccotedd/studio
class CustomTreeManager(TreeManager.from_queryset(CustomTreeQuerySet)):
    pass
예제 #6
0
class Page(MPTTModel, TranslatableModel):
    shop = models.ForeignKey("shuup.Shop", verbose_name=_('shop'))
    available_from = models.DateTimeField(
        null=True,
        blank=True,
        verbose_name=_('available from'),
        help_text=
        _("Set an available from date to restrict the page to be available only after a certain date and time. "
          "This is useful for pages describing sales campaigns or other time-sensitive pages."
          ))
    available_to = models.DateTimeField(
        null=True,
        blank=True,
        verbose_name=_('available to'),
        help_text=
        _("Set an available to date to restrict the page to be available only after a certain date and time. "
          "This is useful for pages describing sales campaigns or other time-sensitive pages."
          ))

    created_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                   blank=True,
                                   null=True,
                                   related_name="+",
                                   on_delete=models.SET_NULL,
                                   verbose_name=_('created by'))
    modified_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                    blank=True,
                                    null=True,
                                    related_name="+",
                                    on_delete=models.SET_NULL,
                                    verbose_name=_('modified by'))

    created_on = models.DateTimeField(auto_now_add=True,
                                      editable=False,
                                      verbose_name=_('created on'))
    modified_on = models.DateTimeField(auto_now=True,
                                       editable=False,
                                       verbose_name=_('modified on'))

    identifier = InternalIdentifierField(
        unique=False,
        help_text=_('This identifier can be used in templates to create URLs'),
        editable=True)

    visible_in_menu = models.BooleanField(
        verbose_name=_("visible in menu"),
        default=False,
        help_text=
        _("Check this if this page should have a link in the top menu of the store front."
          ))
    parent = TreeForeignKey(
        "self",
        blank=True,
        null=True,
        related_name="children",
        verbose_name=_("parent"),
        help_text=
        _("Set this to a parent page if this page should be subcategorized under another page."
          ))
    list_children_on_page = models.BooleanField(
        verbose_name=_("list children on page"),
        default=False,
        help_text=_("Check this if this page should list its children pages."))
    page_type = EnumIntegerField(PageType,
                                 default=PageType.NORMAL,
                                 db_index=True,
                                 verbose_name=_("page type"))
    deleted = models.BooleanField(default=False, verbose_name=_("deleted"))

    translations = TranslatedFields(
        title=models.CharField(
            max_length=256,
            verbose_name=_('title'),
            help_text=
            _("The page title. This is shown anywhere links to your page are shown."
              )),
        url=models.CharField(
            max_length=100,
            verbose_name=_('URL'),
            default=None,
            blank=True,
            null=True,
            help_text=
            _("The page url. Choose a descriptive url so that search engines can rank your page higher. "
              "Often the best url is simply the page title with spaces replaced with dashes."
              )),
        content=models.TextField(
            verbose_name=_('content'),
            help_text=
            _("The page content. This is the text that is displayed when customers click on your page link."
              )),
    )

    objects = TreeManager.from_queryset(PageQuerySet)()

    class Meta:
        ordering = ('-id', )
        verbose_name = _('page')
        verbose_name_plural = _('pages')
        unique_together = ("shop", "identifier")

    def delete(self, using=None):
        raise NotImplementedError("Not implemented: Use `soft_delete()`")

    def soft_delete(self, user=None):
        if not self.deleted:
            self.deleted = True
            self.add_log_entry("Deleted.",
                               kind=LogEntryKind.DELETION,
                               user=user)
            # Bypassing local `save()` on purpose.
            super(Page, self).save(update_fields=("deleted", ))

    def make_gdpr_old_version(self):
        self.identifier = slugify("{}-{}".format(self.identifier, self.pk))
        self.save(update_fields=["identifier"])
        page_translation_model = self._meta.model._parler_meta.root_model
        for page_translation in page_translation_model.objects.filter(
                master_id=self.pk):
            page_translation.url = "{}-{}".format(page_translation.url,
                                                  self.pk)
            page_translation.save(update_fields=["url"])

    def clean(self):
        url = getattr(self, "url", None)
        if url:
            page_translation = self._meta.model._parler_meta.root_model
            shop_pages = Page.objects.for_shop(self.shop).values_list(
                "id", flat=True)
            url_checker = page_translation.objects.filter(
                url=url, master_id__in=shop_pages)
            if self.pk:
                url_checker = url_checker.exclude(master_id=self.pk)
            if url_checker.exists():
                raise ValidationError(_("URL already exists."),
                                      code="invalid_url")

        if self.pk:
            original_page = Page.objects.get(id=self.pk)
            if original_page.page_type == PageType.GDPR_CONSENT_DOCUMENT:
                # prevent changing content when page type is GDPR_CONSENT_DOCUMENT
                content = getattr(self, "content", None)
                if original_page.content != content or original_page.page_type != self.page_type:
                    msg = _(
                        "This page is protected against changes because it is a GDPR consent document."
                    )
                    raise ValidationError(msg, code="gdpr-protected")

    def is_visible(self, dt=None):
        if not dt:
            dt = now()

        return ((self.available_from and self.available_from <= dt)
                and (self.available_to is None or self.available_to >= dt))

    def get_html(self):
        return self.content

    def __str__(self):
        return force_text(
            self.safe_translation_getter("title",
                                         any_language=True,
                                         default=_("Untitled")))
예제 #7
0
    for menu in MenuItem.objects.all():
        cache.delete('menu-%s' % menu.slug)
        cache.delete('menu-tree-%s' % menu.slug)


class MenuUnCacheQuerySet(TreeQuerySet):
    def delete(self, *args, **kwargs):
        delete_cache()
        super(MenuUnCacheQuerySet, self).delete(*args, **kwargs)

    def update(self, *args, **kwargs):
        delete_cache()
        super(MenuUnCacheQuerySet, self).update(*args, **kwargs)


MenuItemManager = TreeManager.from_queryset(MenuUnCacheQuerySet)


class MenuItem(MPTTModel):

    parent = TreeForeignKey('self',
                            null=True,
                            blank=True,
                            related_name='children')
    label = models.CharField(
        _('label'),
        max_length=255,
        help_text="The display name on the web site.",
    )
    slug = models.SlugField(
        _('slug'),
예제 #8
0
파일: models.py 프로젝트: fmipsylone/shuup
class Page(MPTTModel, TranslatableModel):
    shop = models.ForeignKey("shuup.Shop", verbose_name=_('shop'))
    supplier = models.ForeignKey("shuup.Supplier", null=True, blank=True, verbose_name=_('supplier'))
    available_from = models.DateTimeField(
        default=now, null=True, blank=True, db_index=True,
        verbose_name=_('available from'), help_text=_(
            "Set an available from date to restrict the page to be available only after a certain date and time. "
            "This is useful for pages describing sales campaigns or other time-sensitive pages."
        )
    )
    available_to = models.DateTimeField(
        null=True, blank=True, db_index=True,
        verbose_name=_('available to'), help_text=_(
            "Set an available to date to restrict the page to be available only after a certain date and time. "
            "This is useful for pages describing sales campaigns or other time-sensitive pages."
        )
    )

    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, blank=True, null=True, related_name="+", on_delete=models.SET_NULL,
        verbose_name=_('created by')
    )
    modified_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, blank=True, null=True, related_name="+", on_delete=models.SET_NULL,
        verbose_name=_('modified by')
    )

    created_on = models.DateTimeField(auto_now_add=True, editable=False, verbose_name=_('created on'))
    modified_on = models.DateTimeField(auto_now=True, editable=False, verbose_name=_('modified on'))

    identifier = InternalIdentifierField(
        unique=False,
        help_text=_('This identifier can be used in templates to create URLs'),
        editable=True
    )

    visible_in_menu = models.BooleanField(verbose_name=_("visible in menu"), default=False, help_text=_(
        "Check this if this page should have a link in the top menu of the store front."
    ))
    parent = TreeForeignKey(
        "self", blank=True, null=True, related_name="children", verbose_name=_("parent"), help_text=_(
            "Set this to a parent page if this page should be subcategorized under another page."
        ))
    list_children_on_page = models.BooleanField(verbose_name=_("list children on page"), default=False, help_text=_(
        "Check this if this page should list its children pages."
    ))
    show_child_timestamps = models.BooleanField(verbose_name=_("show child page timestamps"), default=True, help_text=_(
        "Check this if you want to show timestamps on the child pages. Please note, that this "
        "requires the children to be listed on the page as well."
    ))
    deleted = models.BooleanField(default=False, verbose_name=_("deleted"))

    translations = TranslatedFields(
        title=models.CharField(max_length=256, verbose_name=_('title'), help_text=_(
            "The page title. This is shown anywhere links to your page are shown."
        )),
        url=models.CharField(
            max_length=100, verbose_name=_('URL'),
            default=None,
            blank=True,
            null=True,
            help_text=_(
                "The page url. Choose a descriptive url so that search engines can rank your page higher. "
                "Often the best url is simply the page title with spaces replaced with dashes."
            )
        ),
        content=models.TextField(verbose_name=_('content'), help_text=_(
            "The page content. This is the text that is displayed when customers click on your page link."
            "You can leave this empty and add all page content through placeholder editor in shop front."
            "To edit the style of the page you can use the Snippet plugin which is in shop front editor."
        ))
    )
    template_name = models.TextField(
        max_length=500,
        verbose_name=_("Template path"),
        default=settings.SHUUP_SIMPLE_CMS_DEFAULT_TEMPLATE
    )
    render_title = models.BooleanField(verbose_name=_("render title"), default=True, help_text=_(
        "Check this if this page should have a visible title"
    ))

    objects = TreeManager.from_queryset(PageQuerySet)()

    class Meta:
        ordering = ('-id',)
        verbose_name = _('page')
        verbose_name_plural = _('pages')
        unique_together = ("shop", "identifier")

    def delete(self, using=None):
        raise NotImplementedError("Not implemented: Use `soft_delete()`")

    def soft_delete(self, user=None):
        if not self.deleted:
            self.deleted = True
            self.add_log_entry("Deleted.", kind=LogEntryKind.DELETION, user=user)
            # Bypassing local `save()` on purpose.
            super(Page, self).save(update_fields=("deleted",))

    def clean(self):
        url = getattr(self, "url", None)
        if url:
            page_translation = self._meta.model._parler_meta.root_model
            shop_pages = Page.objects.for_shop(self.shop).values_list("id", flat=True)
            url_checker = page_translation.objects.filter(url=url, master_id__in=shop_pages)
            if self.pk:
                url_checker = url_checker.exclude(master_id=self.pk)
            if url_checker.exists():
                raise ValidationError(_("URL already exists."), code="invalid_url")

    def is_visible(self, dt=None):
        if not dt:
            dt = now()

        return (
            (self.available_from and self.available_from <= dt) and
            (self.available_to is None or self.available_to >= dt)
        )

    def save(self, *args, **kwargs):
        with reversion.create_revision():
            super(Page, self).save(*args, **kwargs)

    def get_html(self):
        return self.content

    @classmethod
    def create_initial_revision(cls, page):
        from reversion.models import Version
        if not Version.objects.get_for_object(page).exists():
            with reversion.create_revision():
                page.save()

    def __str__(self):
        return force_text(self.safe_translation_getter("title", any_language=True, default=_("Untitled")))
예제 #9
0
    for menu in MenuItem.objects.all():
        cache.delete('menu-%s' % menu.slug)
        cache.delete('menu-tree-%s' % menu.slug)


class MenuUnCacheQuerySet(TreeQuerySet):
    def delete(self, *args, **kwargs):
        delete_cache()
        super(MenuUnCacheQuerySet, self).delete(*args, **kwargs)

    def update(self, *args, **kwargs):
        delete_cache()
        super(MenuUnCacheQuerySet, self).update(*args, **kwargs)


MenuItemManager = TreeManager.from_queryset(MenuUnCacheQuerySet)


class MenuItem(MPTTModel):

    parent = TreeForeignKey('self', null=True, blank=True, related_name='children',
                            on_delete=models.CASCADE)
    label = models.CharField(
        _('label'),
        max_length=255,
        help_text="The display name on the web site.",
    )
    slug = models.SlugField(
        _('slug'),
        unique=True,
        max_length=255,
예제 #10
0
class Page(MPTTModel, TranslatableModel):
    shop = models.ForeignKey("wshop.Shop", verbose_name=_('shop'))
    available_from = models.DateTimeField(null=True, blank=True, verbose_name=_('available from'), help_text=_(
        "Set an available from date to restrict the page to be available only after a certain date and time. "
        "This is useful for pages describing sales campaigns or other time-sensitive pages."
    ))
    available_to = models.DateTimeField(null=True, blank=True, verbose_name=_('available to'), help_text=_(
        "Set an available to date to restrict the page to be available only after a certain date and time. "
        "This is useful for pages describing sales campaigns or other time-sensitive pages."
    ))

    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, blank=True, null=True, related_name="+", on_delete=models.SET_NULL,
        verbose_name=_('created by')
    )
    modified_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, blank=True, null=True, related_name="+", on_delete=models.SET_NULL,
        verbose_name=_('modified by')
    )

    created_on = models.DateTimeField(auto_now_add=True, editable=False, verbose_name=_('created on'))
    modified_on = models.DateTimeField(auto_now=True, editable=False, verbose_name=_('modified on'))

    identifier = InternalIdentifierField(
        unique=False,
        help_text=_('This identifier can be used in templates to create URLs'),
        editable=True
    )

    visible_in_menu = models.BooleanField(verbose_name=_("visible in menu"), default=False, help_text=_(
        "Check this if this page should have a link in the top menu of the store front."
    ))
    parent = TreeForeignKey(
        "self", blank=True, null=True, related_name="children", verbose_name=_("parent"), help_text=_(
            "Set this to a parent page if this page should be subcategorized under another page."
        ))
    list_children_on_page = models.BooleanField(verbose_name=_("list children on page"), default=False, help_text=_(
        "Check this if this page should list its children pages."
    ))

    translations = TranslatedFields(
        title=models.CharField(max_length=256, verbose_name=_('title'), help_text=_(
            "The page title. This is shown anywhere links to your page are shown."
        )),
        url=models.CharField(
            max_length=100, verbose_name=_('URL'),
            default=None,
            blank=True,
            null=True,
            help_text=_(
                "The page url. Choose a descriptive url so that search engines can rank your page higher. "
                "Often the best url is simply the page title with spaces replaced with dashes."
            )
        ),
        content=models.TextField(verbose_name=_('content'), help_text=_(
            "The page content. This is the text that is displayed when customers click on your page link."
        )),
    )

    objects = TreeManager.from_queryset(PageQuerySet)()

    class Meta:
        ordering = ('-id',)
        verbose_name = _('page')
        verbose_name_plural = _('pages')
        unique_together = ("shop", "identifier")

    def clean(self):
        url = getattr(self, "url", None)
        if url:
            page_translation = self._meta.model._parler_meta.root_model
            shop_pages = Page.objects.for_shop(self.shop).values_list("id", flat=True)
            url_checker = page_translation.objects.filter(url=url, master_id__in=shop_pages)
            if self.pk:
                url_checker = url_checker.exclude(master_id=self.pk)
            if url_checker.exists():
                raise ValidationError(_("URL already exists."), code="invalid_url")

    def is_visible(self, dt=None):
        if not dt:
            dt = now()

        return (
            (self.available_from and self.available_from <= dt) and
            (self.available_to is None or self.available_to >= dt)
        )

    def get_html(self):
        return self.content

    def __str__(self):
        return force_text(self.safe_translation_getter("title", any_language=True, default=_("Untitled")))
예제 #11
0
파일: manager.py 프로젝트: pcenov/studio
class CustomContentNodeTreeManager(
        TreeManager.from_queryset(CustomTreeQuerySet)):
    # Added 7-31-2018. We can remove this once we are certain we have eliminated all cases
    # where root nodes are getting prepended rather than appended to the tree list.
    def _create_tree_space(self, target_tree_id, num_trees=1):
        """
        Creates space for a new tree by incrementing all tree ids
        greater than ``target_tree_id``.
        """

        if target_tree_id == -1:
            raise Exception(
                "ERROR: Calling _create_tree_space with -1! Something is attempting to sort all MPTT trees root nodes!"
            )

        return super(CustomContentNodeTreeManager,
                     self)._create_tree_space(target_tree_id, num_trees)

    def _get_next_tree_id(self, *args, **kwargs):
        from contentcuration.models import MPTTTreeIDManager

        new_id = MPTTTreeIDManager.objects.create().id
        return new_id

    @contextlib.contextmanager
    def _attempt_lock(self, tree_ids, shared_tree_ids=None):
        """
        Internal method to allow the lock_mptt method to do retries in case of deadlocks
        """
        shared_tree_ids = shared_tree_ids or []

        start = time.time()
        with transaction.atomic():
            # Issue a separate lock on each tree_id
            # in a predictable order.
            # This will mean that every process acquires locks in the same order
            # and should help to minimize deadlocks
            for tree_id in tree_ids:
                advisory_lock(TREE_LOCK,
                              key2=tree_id,
                              shared=tree_id in shared_tree_ids)
            yield
            log_lock_time_spent(time.time() - start)

    @contextlib.contextmanager
    def lock_mptt(self, *tree_ids, **kwargs):
        tree_ids = sorted((t for t in set(tree_ids) if t is not None))
        shared_tree_ids = kwargs.pop('shared_tree_ids', [])
        # If this is not inside the context of a delay context manager
        # or updates are not disabled set a lock on the tree_ids.
        if (not self.model._mptt_is_tracking
                and self.model._mptt_updates_enabled and tree_ids):
            try:
                with self._attempt_lock(tree_ids,
                                        shared_tree_ids=shared_tree_ids):
                    yield
            except OperationalError as e:
                if "deadlock detected" in e.args[0]:
                    logging.error(
                        "Deadlock detected while trying to lock ContentNode trees for mptt operations, retrying"
                    )
                    with self._attempt_lock(tree_ids,
                                            shared_tree_ids=shared_tree_ids):
                        yield
                else:
                    raise
        else:
            # Otherwise just let it carry on!
            yield

    def partial_rebuild(self, tree_id):
        with self.lock_mptt(tree_id):
            return super(CustomContentNodeTreeManager,
                         self).partial_rebuild(tree_id)

    def _move_child_to_new_tree(self, node, target, position):
        from contentcuration.models import PrerequisiteContentRelationship

        super(CustomContentNodeTreeManager,
              self)._move_child_to_new_tree(node, target, position)
        PrerequisiteContentRelationship.objects.filter(
            Q(prerequisite_id=node.id) | Q(target_node_id=node.id)).delete()

    def _mptt_refresh(self, *nodes):
        """
        This is based off the MPTT model method mptt_refresh
        except that handles an arbitrary list of nodes to get
        the updated values in a single DB query.
        """
        ids = [node.id for node in nodes if node.id]
        # Don't bother doing a query if no nodes
        # were passed in
        if not ids:
            return
        opts = self.model._mptt_meta
        # Look up all the mptt field values
        # and the id so we can marry them up to the
        # passed in nodes.
        values_lookup = {
            # Create a lookup dict to cross reference
            # with the passed in nodes.
            c["id"]: c
            for c in self.filter(id__in=ids).values(
                "id",
                opts.left_attr,
                opts.right_attr,
                opts.level_attr,
                opts.tree_id_attr,
            )
        }
        for node in nodes:
            # Set the values on each of the nodes
            if node.id and node.id in values_lookup:
                values = values_lookup[node.id]
                for k, v in values.items():
                    setattr(node, k, v)

    def move_node(self, node, target, position="last-child"):
        """
        Vendored from mptt - by default mptt moves then saves
        This is updated to call the save with the skip_lock kwarg
        to prevent a second atomic transaction and tree locking context
        being opened.

        Moves ``node`` relative to a given ``target`` node as specified
        by ``position`` (when appropriate), by examining both nodes and
        calling the appropriate method to perform the move.
        A ``target`` of ``None`` indicates that ``node`` should be
        turned into a root node.
        Valid values for ``position`` are ``'first-child'``,
        ``'last-child'``, ``'left'`` or ``'right'``.
        ``node`` will be modified to reflect its new tree state in the
        database.
        This method explicitly checks for ``node`` being made a sibling
        of a root node, as this is a special case due to our use of tree
        ids to order root nodes.
        NOTE: This is a low-level method; it does NOT respect
        ``MPTTMeta.order_insertion_by``.  In most cases you should just
        move the node yourself by setting node.parent.
        """
        old_parent = node.parent
        with self.lock_mptt(node.tree_id, target.tree_id):
            # Call _mptt_refresh to ensure that the mptt fields on
            # these nodes are up to date once we have acquired a lock
            # on the associated trees. This means that the mptt data
            # will remain fresh until the lock is released at the end
            # of the context manager.
            self._mptt_refresh(node, target)
            # N.B. this only calls save if we are running inside a
            # delay MPTT updates context
            self._move_node(node, target, position=position)
            node.save(skip_lock=True)
        node_moved.send(
            sender=node.__class__,
            instance=node,
            target=target,
            position=position,
        )
        # when moving to a new tree, like trash, we'll blanket reset the modified for the
        # new root and the old root nodes
        if old_parent.tree_id != target.tree_id:
            for size_cache in [
                    ResourceSizeCache(target.get_root()),
                    ResourceSizeCache(old_parent.get_root())
            ]:
                size_cache.reset_modified(None)

    def get_source_attributes(self, source):
        """
        These attributes will be copied when the node is copied
        and also when a copy is synced with its source
        """
        return {
            "content_id": source.content_id,
            "kind_id": source.kind_id,
            "title": source.title,
            "description": source.description,
            "language_id": source.language_id,
            "license_id": source.license_id,
            "license_description": source.license_description,
            "thumbnail_encoding": source.thumbnail_encoding,
            "extra_fields": source.extra_fields,
            "copyright_holder": source.copyright_holder,
            "author": source.author,
            "provider": source.provider,
            "role_visibility": source.role_visibility,
        }

    def _clone_node(self, source, parent_id, source_channel_id,
                    can_edit_source_channel, pk, mods):
        copy = {
            "id": pk or uuid.uuid4().hex,
            "node_id": uuid.uuid4().hex,
            "aggregator": source.aggregator,
            "cloned_source": source,
            "source_channel_id": source_channel_id,
            "source_node_id": source.node_id,
            "original_channel_id": source.original_channel_id,
            "original_source_node_id": source.original_source_node_id,
            "freeze_authoring_data": not can_edit_source_channel
            or source.freeze_authoring_data,
            "changed": True,
            "published": False,
            "parent_id": parent_id,
            "complete": source.complete,
        }

        copy.update(self.get_source_attributes(source))

        if isinstance(mods, dict):
            copy.update(mods)

        # There might be some legacy nodes that don't have these, so ensure they are added
        if (copy["original_channel_id"] is None
                or copy["original_source_node_id"] is None):
            original_node = source.get_original_node()
            if copy["original_channel_id"] is None:
                original_channel = original_node.get_channel()
                copy["original_channel_id"] = (original_channel.id
                                               if original_channel else None)
            if copy["original_source_node_id"] is None:
                copy["original_source_node_id"] = original_node.node_id

        return copy

    def _recurse_to_create_tree(
        self,
        source,
        parent_id,
        source_channel_id,
        nodes_by_parent,
        source_copy_id_map,
        can_edit_source_channel,
        pk,
        mods,
    ):
        copy = self._clone_node(
            source,
            parent_id,
            source_channel_id,
            can_edit_source_channel,
            pk,
            mods,
        )

        if source.kind_id == content_kinds.TOPIC and source.id in nodes_by_parent:
            children = sorted(nodes_by_parent[source.id], key=lambda x: x.lft)
            copy["children"] = list(
                map(
                    lambda x: self._recurse_to_create_tree(
                        x,
                        copy["id"],
                        source_channel_id,
                        nodes_by_parent,
                        source_copy_id_map,
                        can_edit_source_channel,
                        None,
                        None,
                    ),
                    children,
                ))
        source_copy_id_map[source.id] = copy["id"]
        return copy

    def _all_nodes_to_copy(self, node, excluded_descendants):
        nodes_to_copy = node.get_descendants(include_self=True)

        if excluded_descendants:
            excluded_descendants = self.filter(
                node_id__in=excluded_descendants.keys()).get_descendants(
                    include_self=True)
            nodes_to_copy = nodes_to_copy.difference(excluded_descendants)
        return nodes_to_copy

    def copy_node(self,
                  node,
                  target=None,
                  position="last-child",
                  pk=None,
                  mods=None,
                  excluded_descendants=None,
                  can_edit_source_channel=None,
                  batch_size=None,
                  progress_tracker=None):
        """
        :type progress_tracker: contentcuration.utils.celery.ProgressTracker|None
        """
        if batch_size is None:
            batch_size = BATCH_SIZE
        source_channel_id = node.get_channel_id()

        total_nodes = self._all_nodes_to_copy(node,
                                              excluded_descendants).count()
        if progress_tracker:
            progress_tracker.set_total(total_nodes)

        return self._copy(
            node,
            target,
            position,
            source_channel_id,
            pk,
            mods,
            excluded_descendants,
            can_edit_source_channel,
            batch_size,
            progress_tracker=progress_tracker,
        )

    def _copy(
        self,
        node,
        target,
        position,
        source_channel_id,
        pk,
        mods,
        excluded_descendants,
        can_edit_source_channel,
        batch_size,
        progress_tracker=None,
    ):
        """
        :type progress_tracker: contentcuration.utils.celery.ProgressTracker|None
        """
        if node.rght - node.lft < batch_size:
            copied_nodes = self._deep_copy(
                node,
                target,
                position,
                source_channel_id,
                pk,
                mods,
                excluded_descendants,
                can_edit_source_channel,
            )
            if progress_tracker:
                progress_tracker.increment(len(copied_nodes))
            return copied_nodes
        else:
            node_copy = self._shallow_copy(
                node,
                target,
                position,
                source_channel_id,
                pk,
                mods,
                can_edit_source_channel,
            )
            if progress_tracker:
                progress_tracker.increment()
            children = node.get_children().order_by("lft")
            if excluded_descendants:
                children = children.exclude(
                    node_id__in=excluded_descendants.keys())
            for child in children:
                self._copy(
                    child,
                    node_copy,
                    "last-child",
                    source_channel_id,
                    None,
                    None,
                    excluded_descendants,
                    can_edit_source_channel,
                    batch_size,
                    progress_tracker=progress_tracker,
                )
            return [node_copy]

    def _copy_tags(self, source_copy_id_map):
        from contentcuration.models import ContentTag

        node_tags_mappings = list(
            self.model.tags.through.objects.filter(
                contentnode_id__in=source_copy_id_map.keys()))

        tags_to_copy = ContentTag.objects.filter(
            tagged_content__in=source_copy_id_map.keys(),
            channel__isnull=False)

        # Get a lookup of all existing null channel tags so we don't duplicate
        existing_tags_lookup = {
            t["tag_name"]: t["id"]
            for t in ContentTag.objects.filter(
                tag_name__in=tags_to_copy.values_list("tag_name", flat=True),
                channel__isnull=True,
            ).values("tag_name", "id")
        }
        tags_to_copy = list(tags_to_copy)

        tags_to_create = []

        tag_id_map = {}

        for tag in tags_to_copy:
            if tag.tag_name in existing_tags_lookup:
                tag_id_map[tag.id] = existing_tags_lookup.get(tag.tag_name)
            else:
                new_tag = ContentTag(tag_name=tag.tag_name)
                tag_id_map[tag.id] = new_tag.id
                tags_to_create.append(new_tag)

        ContentTag.objects.bulk_create(tags_to_create)

        mappings_to_create = [
            self.model.tags.through(
                contenttag_id=tag_id_map.get(mapping.contenttag_id,
                                             mapping.contenttag_id),
                contentnode_id=source_copy_id_map.get(mapping.contentnode_id),
            ) for mapping in node_tags_mappings
        ]

        self.model.tags.through.objects.bulk_create(mappings_to_create)

    def _copy_assessment_items(self, source_copy_id_map):
        from contentcuration.models import File
        from contentcuration.models import AssessmentItem

        node_assessmentitems = list(
            AssessmentItem.objects.filter(
                contentnode_id__in=source_copy_id_map.keys()))
        node_assessmentitem_files = list(
            File.objects.filter(assessment_item__in=node_assessmentitems))

        assessmentitem_old_id_lookup = {}

        for assessmentitem in node_assessmentitems:
            old_id = assessmentitem.id
            assessmentitem.id = None
            assessmentitem.contentnode_id = source_copy_id_map[
                assessmentitem.contentnode_id]
            assessmentitem_old_id_lookup[assessmentitem.contentnode_id + ":" +
                                         assessmentitem.assessment_id] = old_id

        node_assessmentitems = AssessmentItem.objects.bulk_create(
            node_assessmentitems)

        assessmentitem_new_id_lookup = {}

        for assessmentitem in node_assessmentitems:
            old_id = assessmentitem_old_id_lookup[assessmentitem.contentnode_id
                                                  + ":" +
                                                  assessmentitem.assessment_id]
            assessmentitem_new_id_lookup[old_id] = assessmentitem.id

        for file in node_assessmentitem_files:
            file.id = None
            file.assessment_item_id = assessmentitem_new_id_lookup[
                file.assessment_item_id]

        File.objects.bulk_create(node_assessmentitem_files)

    def _copy_files(self, source_copy_id_map):
        from contentcuration.models import File

        node_files = list(
            File.objects.filter(contentnode_id__in=source_copy_id_map.keys()))

        for file in node_files:
            file.id = None
            file.contentnode_id = source_copy_id_map[file.contentnode_id]

        File.objects.bulk_create(node_files)

    def _copy_associated_objects(self, source_copy_id_map):
        self._copy_files(source_copy_id_map)

        self._copy_assessment_items(source_copy_id_map)

        self._copy_tags(source_copy_id_map)

    def _shallow_copy(
        self,
        node,
        target,
        position,
        source_channel_id,
        pk,
        mods,
        can_edit_source_channel,
    ):
        data = self._clone_node(
            node,
            None,
            source_channel_id,
            can_edit_source_channel,
            pk,
            mods,
        )
        with self.lock_mptt(target.tree_id if target else None):
            node_copy = self.model(**data)
            if target:
                self._mptt_refresh(target)
            self.insert_node(node_copy, target, position=position, save=False)
            node_copy.save(force_insert=True)

        self._copy_associated_objects({node.id: node_copy.id})
        return node_copy

    def _deep_copy(
        self,
        node,
        target,
        position,
        source_channel_id,
        pk,
        mods,
        excluded_descendants,
        can_edit_source_channel,
    ):
        # lock mptt source tree with shared advisory lock
        with self.lock_mptt(node.tree_id, shared_tree_ids=[node.tree_id]):
            nodes_to_copy = list(
                self._all_nodes_to_copy(node, excluded_descendants))

        nodes_by_parent = {}
        for copy_node in nodes_to_copy:
            if copy_node.parent_id not in nodes_by_parent:
                nodes_by_parent[copy_node.parent_id] = []
            nodes_by_parent[copy_node.parent_id].append(copy_node)

        source_copy_id_map = {}

        parent_id = None
        # If the position is *-child then parent is target
        # but if it is not - then our parent is the same as the target's parent
        if target:
            if position in ["last-child", "first-child"]:
                parent_id = target.id
            else:
                parent_id = target.parent_id

        data = self._recurse_to_create_tree(
            node,
            parent_id,
            source_channel_id,
            nodes_by_parent,
            source_copy_id_map,
            can_edit_source_channel,
            pk,
            mods,
        )

        with self.lock_mptt(target.tree_id if target else None):
            if target:
                self._mptt_refresh(target)
            nodes_to_create = self.build_tree_nodes(data,
                                                    target=target,
                                                    position=position)
            new_nodes = self.bulk_create(nodes_to_create)
        if target:
            self.filter(pk=target.pk).update(changed=True)

        self._copy_associated_objects(source_copy_id_map)

        return new_nodes

    def build_tree_nodes(self, data, target=None, position="last-child"):
        """
        vendored from:
        https://github.com/django-mptt/django-mptt/blob/fe2b9cc8cfd8f4b764d294747dba2758147712eb/mptt/managers.py#L614
        """
        opts = self.model._mptt_meta
        if target:
            tree_id = target.tree_id
            if position in ("left", "right"):
                level = getattr(target, opts.level_attr)
                if position == "left":
                    cursor = getattr(target, opts.left_attr)
                else:
                    cursor = getattr(target, opts.right_attr) + 1
            else:
                level = getattr(target, opts.level_attr) + 1
                if position == "first-child":
                    cursor = getattr(target, opts.left_attr) + 1
                else:
                    cursor = getattr(target, opts.right_attr)
        else:
            tree_id = self._get_next_tree_id()
            cursor = 1
            level = 0

        stack = []

        def treeify(data, cursor=1, level=0):
            data = dict(data)
            children = data.pop("children", [])
            node = self.model(**data)
            stack.append(node)
            setattr(node, opts.tree_id_attr, tree_id)
            setattr(node, opts.level_attr, level)
            setattr(node, opts.left_attr, cursor)
            for child in children:
                cursor = treeify(child, cursor=cursor + 1, level=level + 1)
            cursor += 1
            setattr(node, opts.right_attr, cursor)
            return cursor

        treeify(data, cursor=cursor, level=level)

        if target:
            self._create_space(2 * len(stack), cursor - 1, tree_id)

        return stack
예제 #12
0
#
# Copyright (C) 2016-2020 TU Muenchen and contributors of ANEXIA Internetdienstleistungs GmbH
# SPDX-License-Identifier: AGPL-3.0-or-later
#
from eric.core.models import BaseManager
from eric.projects.models.querysets import ProjectQuerySet, ProjectRoleUserAssignmentQuerySet, RoleQuerySet, \
    ResourceQuerySet, UserStorageLimitQuerySet, ElementLockQuerySet
from mptt.managers import TreeManager

# create managers for all our important objects
ProjectManager = TreeManager.from_queryset(ProjectQuerySet)
ProjectRoleUserAssignmentManager = BaseManager.from_queryset(
    ProjectRoleUserAssignmentQuerySet)
ResourceManager = BaseManager.from_queryset(ResourceQuerySet)
RoleManager = BaseManager.from_queryset(RoleQuerySet)
UserStorageLimitManager = BaseManager.from_queryset(UserStorageLimitQuerySet)
ElementLockManager = BaseManager.from_queryset(ElementLockQuerySet)
예제 #13
0
    def user_access_filter(self, user):
        # personal folders
        filter = Q(owned_by=user)

        # folders owned by orgs in which the user a member
        orgs = user.get_orgs()
        if orgs:
            filter |= Q(organization__in=orgs)

        return filter

    def accessible_to(self, user):
        return self.filter(self.user_access_filter(user))


FolderManager = TreeManager.from_queryset(FolderQuerySet)


class Folder(MPTTModel):
    name = models.CharField(max_length=255, null=False, blank=False)
    parent = TreeForeignKey('self', null=True, blank=True, related_name='children')
    creation_timestamp = models.DateTimeField(auto_now_add=True)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, related_name='folders_created',)

    # this may be null if this is the shared folder for a org
    owned_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, related_name='folders',)

    # this will be set if this is inside a shared folder
    organization = models.ForeignKey(Organization, null=True, blank=True, related_name='folders')

    # true if this is the apex shared folder (not subfolder) for a org
예제 #14
0
class ModeratedTreeManager(TreeManager.from_queryset(ModeratedTreeQuerySet)):
    pass