You can not refer to the object itself by default. One of the major problems with this is that even if you could set item.parent = item, then if you would save this to the database, it would have trouble with the item.parent, since at that time, the item has no primary key (yet).
What you can do is override the create(…) method of the QuerySet and let this handle most of the logic. We thus define a subclass of a QuerySet:
class TreeQuerySet(QuerySet):
def create(self, *args, **kwargs):
item = super().create(*args, **kwargs)
if item.parent is None:
item.parent_id = item.pk
return super().create(*args, **kwargs)
return item
Then in the model we make the field nullable and set it by default to None:
class Foo(models.Model):
b = models.CharField(max_length=10)
parent = models.ForeignKey(
'self',
null=True,
on_delete=models.CASCADE,
default=None
)
objects = TreeQuerySet.as_manager()
You can then create such Foo object with:
# no parent specified ↓
Foo.objects.create(b='foobar')
Creating a Foo object in a different way however will not work.
That being said, you might better just set the field to None if there is no parent anymore, that will also avoid getting stuck in infinite loops. It also simplifies the modeling, since None can be used as a default value.
You can also store NULL/None in the object, but work with a property that will refer to the node in case that parent has no other parent, so something like:
class Foo(models.Model):
b = models.CharField(max_length=10)
parent = models.ForeignKey(
'self',
null=True,
on_delete=models.CASCADE,
default=None
)
@property
def parent_or_self(self):
if self.parent is not None:
return self.parent
return self
and then thus use the item.parent_or_self, although I'm not convinced that this is better than just use None/NULL.