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)
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)
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 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)
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)