Example #1
0
    def move_node(self, node, target, position='last-child'):
        """
        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.
        """
        self._move_node(node, target, position=position)
        node.save()
        node_moved.send(sender=node.__class__, instance=node,
                        target=target, position=position)
Example #2
0
    def move_node(self, node, target, position='last-child'):
        """
        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.
        """
        self._move_node(node, target, position=position)
        node_moved.send(sender=node.__class__, instance=node,
                        target=target, position=position)
Example #3
0
    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)
Example #4
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 = getattr(self, '_%s_cache' % opts.parent_attr, None)
                # 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)
Example #5
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 = getattr(self, '_%s_cache' % opts.parent_attr, None)
                # 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
        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]
            same_order = old_parent_id == parent_id
            if same_order and len(self._mptt_cached_fields) > 1:
                get_raw_field_value = opts.get_raw_field_value
                for field_name, old_value in self._mptt_cached_fields.items():
                    if old_value != 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.
                        update_cached_parent = (
                            getattr(self, opts.tree_id_attr) != getattr(parent, opts.tree_id_attr) or
                            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)
                    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)
                            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)
        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)

                    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)