Example #1
0
	def insert_node(self, node, target, position='last-child', save=False, allow_existing_pk=False):
		"""
		Sets up the tree state for ``node`` (which has not yet been
		inserted into in the database) so it will be positioned relative
		to a given ``target`` node as specified by ``position`` (when
		appropriate) it is inserted, with any neccessary space already
		having been made for it.

		A ``target`` of ``None`` indicates that ``node`` should be
		the last root node.

		If ``save`` is ``True``, ``node``'s ``save()`` method will be
		called before it is returned.
		
		NOTE: This is a low-level method; it does NOT respect ``MPTTMeta.order_insertion_by``.
		In most cases you should just set the node's parent and let mptt call this during save.
		"""
		
		if self._base_manager:
			return self._base_manager.insert_node(node, target, position=position, save=save)
		
		if node.pk and not allow_existing_pk and _exists(self.filter(pk=node.pk)):
			raise ValueError(_('Cannot insert a node which has already been saved.'))

		if target is None:
			setattr(node, self.left_attr, 1)
			setattr(node, self.right_attr, 2)
			setattr(node, self.level_attr, 0)
			setattr(node, self.tree_id_attr, self._get_next_tree_id())
			setattr(node, self.parent_attr, None)
		elif target.is_root_node() and position in ['left', 'right']:
			target_tree_id = getattr(target, self.tree_id_attr)
			if position == 'left':
				tree_id = target_tree_id
				space_target = target_tree_id - 1
			else:
				tree_id = target_tree_id + 1
				space_target = target_tree_id

			self._create_tree_space(space_target)

			setattr(node, self.left_attr, 1)
			setattr(node, self.right_attr, 2)
			setattr(node, self.level_attr, 0)
			setattr(node, self.tree_id_attr, tree_id)
			setattr(node, self.parent_attr, None)
		else:
			setattr(node, self.left_attr, 0)
			setattr(node, self.level_attr, 0)

			space_target, level, left, parent, right_shift = \
				self._calculate_inter_tree_move_values(node, target, position)
			tree_id = getattr(parent, self.tree_id_attr)

			self._create_space(2, space_target, tree_id)

			setattr(node, self.left_attr, -left)
			setattr(node, self.right_attr, -left + 1)
			setattr(node, self.level_attr, -level)
			setattr(node, self.tree_id_attr, tree_id)
			setattr(node, self.parent_attr, parent)
	
			if parent:
				self._post_insert_update_cached_parent_right(parent, right_shift)

		if save:
			node.save()
		return node
Example #2
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.
		"""
		opts = self._mptt_meta
		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)
		using = kwargs.get('using', None)
		manager = self.__class__._base_manager
		if hasattr(manager, 'using'):
			# multi db support was added in django 1.2
			manager = manager.using(using)
		
		if self.pk and (force_update or getattr(self, '_mptt_saved', False) or \
					(not force_insert and _exists(manager.filter(pk=self.pk)))):
			# 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:
				for field_name, old_value in self._mptt_cached_fields.items():
					if old_value != opts.get_raw_field_value(self, field_name):
						same_order = False
						break
			
			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 right_sibling:
						self.move_to(right_sibling, 'left')
					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('-%s' % opts.tree_id_attr)[0]
								self.move_to(rightmost_sibling, position='right')
							except IndexError:
								pass
						else:
							parent = getattr(self, opts.parent_attr)
							self.move_to(parent, position='last-child')
				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)
		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 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)
		super(MPTTModel, self).save(*args, **kwargs)
		self._mptt_saved = True
		opts.update_mptt_cached_fields(self)