Example #1
0
    def delete(self, *args, **kwargs):
        """Calling ``delete`` on a node will delete it as well as its full
        subtree, as opposed to reattaching all the subnodes to its parent node.

        There are no argument specific to a MPTT model, all the arguments will
        be passed directly to the django's ``Model.delete``.

        ``delete`` will not return anything. """
        try:
            # We have to make sure we use database's mptt values, since they
            # could have changed between the moment the instance was retrieved and
            # the moment it is deleted.
            # This happens for example if you delete several nodes at once from a queryset.
            fields_to_refresh = [self._mptt_meta.right_attr,
                                 self._mptt_meta.left_attr,
                                 self._mptt_meta.tree_id_attr,]
            self.refresh_from_db(fields=fields_to_refresh)
        except self.__class__.DoesNotExist as e:
            # In case the object was already deleted, we don't want to throw an exception
            pass
        tree_width = (self._mpttfield('right') -
                      self._mpttfield('left') + 1)
        target_right = self._mpttfield('right')
        tree_id = self._mpttfield('tree_id')
        self._tree_manager._close_gap(tree_width, target_right, tree_id)
        parent = cached_field_value(self, self._mptt_meta.parent_attr)
        if parent:
            right_shift = -self.get_descendant_count() - 2
            self._tree_manager._post_insert_update_cached_parent_right(parent, right_shift)

        return super(MPTTModel, self).delete(*args, **kwargs)
Example #2
0
    def delete(self, *args, **kwargs):
        """Calling ``delete`` on a node will delete it as well as its full
        subtree, as opposed to reattaching all the subnodes to its parent node.

        There are no argument specific to a MPTT model, all the arguments will
        be passed directly to the django's ``Model.delete``.

        ``delete`` will not return anything."""
        try:
            # We have to make sure we use database's mptt values, since they
            # could have changed between the moment the instance was retrieved and
            # the moment it is deleted.
            # This happens for example if you delete several nodes at once from a queryset.
            fields_to_refresh = [
                self._mptt_meta.right_attr,
                self._mptt_meta.left_attr,
                self._mptt_meta.tree_id_attr,
            ]
            self.refresh_from_db(fields=fields_to_refresh)
        except self.__class__.DoesNotExist:
            # In case the object was already deleted, we don't want to throw an exception
            pass
        tree_width = self._mpttfield("right") - self._mpttfield("left") + 1
        target_right = self._mpttfield("right")
        tree_id = self._mpttfield("tree_id")
        self._tree_manager._close_gap(tree_width, target_right, tree_id)
        parent = cached_field_value(self, self._mptt_meta.parent_attr)
        if parent:
            right_shift = -self.get_descendant_count() - 2
            self._tree_manager._post_insert_update_cached_parent_right(
                parent, right_shift
            )

        return super().delete(*args, **kwargs)
Example #3
0
 def _post_insert_update_cached_parent_right(self, instance, right_shift, seen=None):
     setattr(instance, self.right_attr, getattr(instance, self.right_attr) + right_shift)
     parent = cached_field_value(instance, self.parent_attr)
     if parent:
         if not seen:
             seen = set()
         seen.add(instance)
         if parent in seen:
             # detect infinite recursion and throw an error
             raise InvalidMove
         self._post_insert_update_cached_parent_right(parent, right_shift, seen=seen)
Example #4
0
 def _post_insert_update_cached_parent_right(self, instance, right_shift, seen=None):
     setattr(instance, self.right_attr, getattr(instance, self.right_attr) + right_shift)
     parent = cached_field_value(instance, self.parent_attr)
     if parent:
         if not seen:
             seen = set()
         seen.add(instance)
         if parent in seen:
             # detect infinite recursion and throw an error
             raise InvalidMove
         self._post_insert_update_cached_parent_right(parent, right_shift, seen=seen)
Example #5
0
    def delete(self, *args, **kwargs):
        """Calling ``delete`` on a node will delete it as well as its full
        subtree, as opposed to reattaching all the subnodes to its parent node.

        There are no argument specific to a MPTT model, all the arguments will
        be passed directly to the django's ``Model.delete``.

        ``delete`` will not return anything. """
        tree_width = (self._mpttfield('right') - self._mpttfield('left') + 1)
        target_right = self._mpttfield('right')
        tree_id = self._mpttfield('tree_id')
        self._tree_manager._close_gap(tree_width, target_right, tree_id)
        parent = cached_field_value(self, self._mptt_meta.parent_attr)
        if parent:
            right_shift = -self.get_descendant_count() - 2
            self._tree_manager._post_insert_update_cached_parent_right(
                parent, right_shift)

        return super(MPTTModel, self).delete(*args, **kwargs)
Example #6
0
    def delete(self, *args, **kwargs):
        """Calling ``delete`` on a node will delete it as well as its full
        subtree, as opposed to reattaching all the subnodes to its parent node.

        There are no argument specific to a MPTT model, all the arguments will
        be passed directly to the django's ``Model.delete``.

        ``delete`` will not return anything. """
        tree_width = (self._mpttfield('right') -
                      self._mpttfield('left') + 1)
        target_right = self._mpttfield('right')
        tree_id = self._mpttfield('tree_id')
        self._tree_manager._close_gap(tree_width, target_right, tree_id)
        parent = cached_field_value(self, self._mptt_meta.parent_attr)
        if parent:
            right_shift = -self.get_descendant_count() - 2
            self._tree_manager._post_insert_update_cached_parent_right(parent, right_shift)

        return super(MPTTModel, self).delete(*args, **kwargs)
Example #7
0
    def save(self, *args, **kwargs):
        """
        If this is a new node, sets tree fields up before it is inserted
        into the database, making room in the tree structure as necessary,
        defaulting to making the new node the last child of its parent.

        It the node's left and right edge indicators already been set, we
        take this as indication that the node has already been set up for
        insertion, so its tree fields are left untouched.

        If this is an existing node and its parent has been changed,
        performs reparenting in the tree structure, defaulting to making the
        node the last child of its new parent.

        In either case, if the node's class has its ``order_insertion_by``
        tree option set, the node will be inserted or moved to the
        appropriate position to maintain ordering by the specified field.
        """
        do_updates = self.__class__._mptt_updates_enabled
        track_updates = self.__class__._mptt_is_tracking

        opts = self._mptt_meta

        if not (do_updates or track_updates):
            # inside manager.disable_mptt_updates(), don't do any updates.
            # unless we're also inside TreeManager.delay_mptt_updates()
            if self._mpttfield("left") is None:
                # we need to set *some* values, though don't care too much what.
                parent = cached_field_value(self, opts.parent_attr)
                # if we have a cached parent, have a stab at getting
                # possibly-correct values.  otherwise, meh.
                if parent:
                    left = parent._mpttfield("left") + 1
                    setattr(self, opts.left_attr, left)
                    setattr(self, opts.right_attr, left + 1)
                    setattr(self, opts.level_attr, parent._mpttfield("level") + 1)
                    setattr(self, opts.tree_id_attr, parent._mpttfield("tree_id"))
                    self._tree_manager._post_insert_update_cached_parent_right(
                        parent, 2
                    )
                else:
                    setattr(self, opts.left_attr, 1)
                    setattr(self, opts.right_attr, 2)
                    setattr(self, opts.level_attr, 0)
                    setattr(self, opts.tree_id_attr, 0)
            return super().save(*args, **kwargs)

        parent_id = opts.get_raw_field_value(self, opts.parent_attr)

        # determine whether this instance is already in the db
        force_update = kwargs.get("force_update", False)
        force_insert = kwargs.get("force_insert", False)
        collapse_old_tree = None
        deferred_fields = self.get_deferred_fields()
        if force_update or (
            not force_insert and self._is_saved(using=kwargs.get("using"))
        ):
            # it already exists, so do a move
            old_parent_id = self._mptt_cached_fields[opts.parent_attr]
            if old_parent_id is DeferredAttribute:
                same_order = True
            else:
                same_order = old_parent_id == parent_id

            if same_order and len(self._mptt_cached_fields) > 1:
                for field_name, old_value in self._mptt_cached_fields.items():
                    if (
                        old_value is DeferredAttribute
                        and field_name not in deferred_fields
                    ):
                        same_order = False
                        break
                    if old_value != opts.get_raw_field_value(self, field_name):
                        same_order = False
                        break
                if not do_updates and not same_order:
                    same_order = True
                    self.__class__._mptt_track_tree_modified(self._mpttfield("tree_id"))
            elif (not do_updates) and not same_order and old_parent_id is None:
                # the old tree no longer exists, so we need to collapse it.
                collapse_old_tree = self._mpttfield("tree_id")
                parent = getattr(self, opts.parent_attr)
                tree_id = parent._mpttfield("tree_id")
                left = parent._mpttfield("left") + 1
                self.__class__._mptt_track_tree_modified(tree_id)
                setattr(self, opts.tree_id_attr, tree_id)
                setattr(self, opts.left_attr, left)
                setattr(self, opts.right_attr, left + 1)
                setattr(self, opts.level_attr, parent._mpttfield("level") + 1)
                same_order = True

            if not same_order:
                parent = getattr(self, opts.parent_attr)
                opts.set_raw_field_value(self, opts.parent_attr, old_parent_id)
                try:
                    right_sibling = opts.get_ordered_insertion_target(self, parent)

                    if parent_id is not None:
                        # If we aren't already a descendant of the new parent,
                        # we need to update the parent.rght so things like
                        # get_children and get_descendant_count work correctly.
                        #
                        # parent might be None if parent_id was assigned
                        # directly -- then we certainly do not have to update
                        # the cached parent.
                        update_cached_parent = parent and (
                            getattr(self, opts.tree_id_attr)
                            != getattr(parent, opts.tree_id_attr)
                            or getattr(self, opts.left_attr)  # noqa
                            < getattr(parent, opts.left_attr)
                            or getattr(self, opts.right_attr)
                            > getattr(parent, opts.right_attr)
                        )

                    if right_sibling:
                        self._tree_manager._move_node(
                            self,
                            right_sibling,
                            "left",
                            save=False,
                            refresh_target=False,
                        )
                    else:
                        # Default movement
                        if parent_id is None:
                            root_nodes = self._tree_manager.root_nodes()
                            try:
                                rightmost_sibling = root_nodes.exclude(
                                    pk=self.pk
                                ).order_by("-" + opts.tree_id_attr)[0]
                                self._tree_manager._move_node(
                                    self,
                                    rightmost_sibling,
                                    "right",
                                    save=False,
                                    refresh_target=False,
                                )
                            except IndexError:
                                pass
                        else:
                            self._tree_manager._move_node(
                                self, parent, "last-child", save=False
                            )

                    if parent_id is not None and update_cached_parent:
                        # Update rght of cached parent
                        right_shift = 2 * (self.get_descendant_count() + 1)
                        self._tree_manager._post_insert_update_cached_parent_right(
                            parent, right_shift
                        )
                finally:
                    # Make sure the new parent is always
                    # restored on the way out in case of errors.
                    opts.set_raw_field_value(self, opts.parent_attr, parent_id)

                # If there were no exceptions raised then send a moved signal
                node_moved.send(
                    sender=self.__class__,
                    instance=self,
                    target=getattr(self, opts.parent_attr),
                )
            else:
                opts.set_raw_field_value(self, opts.parent_attr, parent_id)
                if not track_updates:
                    # When not using delayed/disabled updates,
                    # populate update_fields with user defined model fields.
                    # This helps preserve tree integrity when saving model on top
                    # of a modified tree.
                    if len(args) > 3:
                        if not args[3]:
                            args = list(args)
                            args[3] = self._get_user_field_names()
                            args = tuple(args)
                    else:
                        if not kwargs.get("update_fields", None):
                            kwargs["update_fields"] = self._get_user_field_names()

        else:
            # new node, do an insert
            if getattr(self, opts.left_attr) and getattr(self, opts.right_attr):
                # This node has already been set up for insertion.
                pass
            else:
                parent = getattr(self, opts.parent_attr)

                right_sibling = None
                # if we're inside delay_mptt_updates, don't do queries to find
                # sibling position.  instead, do default insertion. correct
                # positions will be found during partial rebuild later.
                # *unless* this is a root node. (as update tracking doesn't
                # handle re-ordering of trees.)
                if do_updates or parent is None:
                    if opts.order_insertion_by:
                        right_sibling = opts.get_ordered_insertion_target(self, parent)

                if right_sibling:
                    self.insert_at(
                        right_sibling,
                        "left",
                        allow_existing_pk=True,
                        refresh_target=False,
                    )

                    if parent:
                        # since we didn't insert into parent, we have to update parent.rght
                        # here instead of in TreeManager.insert_node()
                        right_shift = 2 * (self.get_descendant_count() + 1)
                        self._tree_manager._post_insert_update_cached_parent_right(
                            parent, right_shift
                        )
                else:
                    # Default insertion
                    self.insert_at(
                        parent, position="last-child", allow_existing_pk=True
                    )
        try:
            super().save(*args, **kwargs)
        finally:
            if collapse_old_tree is not None:
                self._tree_manager._create_tree_space(collapse_old_tree, -1)

        self._mptt_saved = True
        opts.update_mptt_cached_fields(self)
Example #8
0
    def save(self, *args, **kwargs):
        """
        If this is a new node, sets tree fields up before it is inserted
        into the database, making room in the tree structure as neccessary,
        defaulting to making the new node the last child of its parent.

        It the node's left and right edge indicators already been set, we
        take this as indication that the node has already been set up for
        insertion, so its tree fields are left untouched.

        If this is an existing node and its parent has been changed,
        performs reparenting in the tree structure, defaulting to making the
        node the last child of its new parent.

        In either case, if the node's class has its ``order_insertion_by``
        tree option set, the node will be inserted or moved to the
        appropriate position to maintain ordering by the specified field.
        """
        do_updates = self.__class__._mptt_updates_enabled
        track_updates = self.__class__._mptt_is_tracking

        opts = self._mptt_meta

        if not (do_updates or track_updates):
            # inside manager.disable_mptt_updates(), don't do any updates.
            # unless we're also inside TreeManager.delay_mptt_updates()
            if self._mpttfield('left') is None:
                # we need to set *some* values, though don't care too much what.
                parent = cached_field_value(self, opts.parent_attr)
                # if we have a cached parent, have a stab at getting
                # possibly-correct values.  otherwise, meh.
                if parent:
                    left = parent._mpttfield('left') + 1
                    setattr(self, opts.left_attr, left)
                    setattr(self, opts.right_attr, left + 1)
                    setattr(self, opts.level_attr, parent._mpttfield('level') + 1)
                    setattr(self, opts.tree_id_attr, parent._mpttfield('tree_id'))
                    self._tree_manager._post_insert_update_cached_parent_right(parent, 2)
                else:
                    setattr(self, opts.left_attr, 1)
                    setattr(self, opts.right_attr, 2)
                    setattr(self, opts.level_attr, 0)
                    setattr(self, opts.tree_id_attr, 0)
            return super(MPTTModel, self).save(*args, **kwargs)

        parent_id = opts.get_raw_field_value(self, opts.parent_attr)

        # determine whether this instance is already in the db
        force_update = kwargs.get('force_update', False)
        force_insert = kwargs.get('force_insert', False)
        collapse_old_tree = None
        deferred_fields = self.get_deferred_fields()
        if force_update or (not force_insert and self._is_saved(using=kwargs.get('using'))):
            # it already exists, so do a move
            old_parent_id = self._mptt_cached_fields[opts.parent_attr]
            if old_parent_id is DeferredAttribute:
                same_order = True
            else:
                same_order = old_parent_id == parent_id

            if same_order and len(self._mptt_cached_fields) > 1:
                for field_name, old_value in self._mptt_cached_fields.items():
                    if old_value is DeferredAttribute and field_name not in deferred_fields:
                        same_order = False
                        break
                    if old_value != opts.get_raw_field_value(self, field_name):
                        same_order = False
                        break
                if not do_updates and not same_order:
                    same_order = True
                    self.__class__._mptt_track_tree_modified(self._mpttfield('tree_id'))
            elif (not do_updates) and not same_order and old_parent_id is None:
                # the old tree no longer exists, so we need to collapse it.
                collapse_old_tree = self._mpttfield('tree_id')
                parent = getattr(self, opts.parent_attr)
                tree_id = parent._mpttfield('tree_id')
                left = parent._mpttfield('left') + 1
                self.__class__._mptt_track_tree_modified(tree_id)
                setattr(self, opts.tree_id_attr, tree_id)
                setattr(self, opts.left_attr, left)
                setattr(self, opts.right_attr, left + 1)
                setattr(self, opts.level_attr, parent._mpttfield('level') + 1)
                same_order = True

            if not same_order:
                opts.set_raw_field_value(self, opts.parent_attr, old_parent_id)
                try:
                    right_sibling = None
                    if opts.order_insertion_by:
                        right_sibling = opts.get_ordered_insertion_target(
                            self, getattr(self, opts.parent_attr))

                    if parent_id is not None:
                        parent = getattr(self, opts.parent_attr)
                        # If we aren't already a descendant of the new parent,
                        # we need to update the parent.rght so things like
                        # get_children and get_descendant_count work correctly.
                        #
                        # parent might be None if parent_id was assigned
                        # directly -- then we certainly do not have to update
                        # the cached parent.
                        update_cached_parent = parent and (
                            getattr(self, opts.tree_id_attr) != getattr(parent, opts.tree_id_attr) or  # noqa
                            getattr(self, opts.left_attr) < getattr(parent, opts.left_attr) or
                            getattr(self, opts.right_attr) > getattr(parent, opts.right_attr))

                    if right_sibling:
                        self._tree_manager._move_node(
                            self, right_sibling, 'left', save=False,
                            refresh_target=False)
                    else:
                        # Default movement
                        if parent_id is None:
                            root_nodes = self._tree_manager.root_nodes()
                            try:
                                rightmost_sibling = root_nodes.exclude(
                                    pk=self.pk).order_by('-' + opts.tree_id_attr)[0]
                                self._tree_manager._move_node(
                                    self, rightmost_sibling, 'right', save=False,
                                    refresh_target=False)
                            except IndexError:
                                pass
                        else:
                            self._tree_manager._move_node(
                                self, parent, 'last-child', save=False)

                    if parent_id is not None and update_cached_parent:
                        # Update rght of cached parent
                        right_shift = 2 * (self.get_descendant_count() + 1)
                        self._tree_manager._post_insert_update_cached_parent_right(
                            parent, right_shift)
                finally:
                    # Make sure the new parent is always
                    # restored on the way out in case of errors.
                    opts.set_raw_field_value(self, opts.parent_attr, parent_id)

                # If there were no exceptions raised then send a moved signal
                node_moved.send(sender=self.__class__, instance=self,
                                target=getattr(self, opts.parent_attr))
            else:
                opts.set_raw_field_value(self, opts.parent_attr, parent_id)
                if not track_updates:
                    # When not using delayed/disabled updates,
                    # populate update_fields with user defined model fields.
                    # This helps preserve tree integrity when saving model on top
                    # of a modified tree.
                    if len(args) > 3:
                        if not args[3]:
                            args = list(args)
                            args[3] = self._get_user_field_names()
                            args = tuple(args)
                    else:
                        if not kwargs.get("update_fields", None):
                            kwargs["update_fields"] = self._get_user_field_names()

        else:
            # new node, do an insert
            if (getattr(self, opts.left_attr) and getattr(self, opts.right_attr)):
                # This node has already been set up for insertion.
                pass
            else:
                parent = getattr(self, opts.parent_attr)

                right_sibling = None
                # if we're inside delay_mptt_updates, don't do queries to find
                # sibling position.  instead, do default insertion. correct
                # positions will be found during partial rebuild later.
                # *unless* this is a root node. (as update tracking doesn't
                # handle re-ordering of trees.)
                if do_updates or parent is None:
                    if opts.order_insertion_by:
                        right_sibling = opts.get_ordered_insertion_target(self, parent)

                if right_sibling:
                    self.insert_at(right_sibling, 'left', allow_existing_pk=True,
                                   refresh_target=False)

                    if parent:
                        # since we didn't insert into parent, we have to update parent.rght
                        # here instead of in TreeManager.insert_node()
                        right_shift = 2 * (self.get_descendant_count() + 1)
                        self._tree_manager._post_insert_update_cached_parent_right(
                            parent, right_shift)
                else:
                    # Default insertion
                    self.insert_at(parent, position='last-child', allow_existing_pk=True)
        try:
            super(MPTTModel, self).save(*args, **kwargs)
        finally:
            if collapse_old_tree is not None:
                self._tree_manager._create_tree_space(collapse_old_tree, -1)

        self._mptt_saved = True
        opts.update_mptt_cached_fields(self)