Is it possible to add parent to child for ForeignKey relationship?

1.1k Views Asked by At

Say I have a ForeignKey field called "parent" with a related_name of "children":

class Item(PolymorphicModel):
    title = models.CharField()
    parent = models.ForeignKey(
        "self", related_name='children', null=True, blank=True, on_delete=models.CASCADE)

class Parent(Item):
    pass

class Child(Item):
    pass

Why is it that I can only add the child to the parent but I get an error if I try to add a parent to the child?

So this works:

p1 = Parent.objects.create(title="Parent 1")
c1 = Child.objects.create(title="Child 1")

print(p1.children)
#<PolymorphicQuerySet []>

p1.children.add(c1)

But this doesn't:

p1 = Parent.objects.create(title="Parent 1")
c1 = Child.objects.create(title="Child 1")
print(c1.parent)
# None

c1.parent.add(p1)
# AttributeError: 'NoneType' object has no attribute 'add'

Do I just have to add to the Parent's children field each time? Is there no way to add to the Child's parent instead? Is there any reason why adding to the Child's parent doesn't work or shouldn't be used?

I'm also a little confused about when/how to use "_set" in this circumstance (if that's relevant). So following the format of Django's Many-to-one example, the following doesn't work for me either:

p1 = Parent.objects.create(title="Parent 1")
c1 = Child.objects.create(title="Child 1")
p1.children.add(c1)

print(p1.children_set.all())
# AttributeError: 'p1' object has no attribute 'children_set'

print(c1.parent_set.all())
# AttributeError: 'c1' object has no attribute 'parent_set'

print(p1.item_set.all())
# AttributeError: 'p1' object has no attribute 'item_set'
1

There are 1 best solutions below

0
rchurch4 On

So I think that you are confusing yourself here with parent and child naming here. The related_name field in ForeignKey essentially tells the model to set up a relationship to quickly find all related instances of the model with the given ForeignKey. When you call p1.children, you are getting the correct output because there are no related instances to p1. You similarly get the correct output when you call c1.parent and get None. The line that I copied and pasted below is what causes this. By setting null=True, you are saying to instantiate the Item instance (whether it's Parent or Child), with a null parent field (in python, null is None).

Your perceived problem comes when you call c1.parent.add(). Since you have not set parent to anything, its value is None, and it likewise does not have an add() method. What you should be doing is setting parent=[some instance of Parent]. Then, when you want to get the children of a given Parent instance, let's say p1, you can call p1.children, and you will get a queryset full of Child instances whose parent field has been set to the foreign key of that Parent instance.

parent = models.ForeignKey(
    "self", related_name='children', null=True, blank=True, on_delete=models.CASCADE)