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