Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Related name for recursive many to many relationship not working

Many to many (non-recursive)

class A(models.Model):
    pass

class B(models.Model):
   parents = models.ManyToManyField(A, related_name='children')


>>> A._meta.get_all_field_names()
['children', u'id']

>>> B._meta.get_all_field_names()
[u'id', 'parents']

I can get the sets of children and parents of model instances with a.children.all() and b.parents.all()

Foreign key (recursive)

class FK(models.Model):
    parent = models.ForeignKey('self', related_name='child')


>>> FK._meta.get_all_field_names()
['child', u'id', 'parent']

Any instance of FK will now be able to get both its parent and its child with fk.parent and fk.child

Many to many (recursive)

class M2M(models.Model):
    parents = models.ManyToManyField('self', related_name='children')

>>> M2M._meta.get_all_field_names()
[u'id', 'parents']

One would expect that, like I could access a.children and fk.child, I would also be able to access m2m.children. This seems to not be the case.

How do I access m2m.children?

I'm using Django 1.6.5.


For future reference

As Daniel Roseman's answer said, setting symmetrical=False solves the problem. In a Django ticket it is explained as:

In the case of parent/child, the relationship isn't symmetrical - if A is a child of B, it doesn't follow that A is a parent of B.

With symmetrical=False, the reverse relation specified in the related_name is created just like in the foreign key case:

class M2M(models.Model):
    parents = models.ManyToManyField('self', related_name='children', symmetrical=False)

>>> M2M._meta.get_all_field_names()
[u'id', 'parents', children]


>>> parent.children.add(child)
>>> parent.children.all()  # returns QuerySet containing the child
>>> child.parents.all()    # returns QuerySet containing the parent
like image 215
kerryz Avatar asked Jul 02 '14 15:07

kerryz


1 Answers

You need to set symmetrical=False. As the documentation for ManyToManyField says:

When Django processes this model, it identifies that it has a ManyToManyField on itself, and as a result, it doesn’t add a person_set attribute to the Person class. Instead, the ManyToManyField is assumed to be symmetrical – that is, if I am your friend, then you are my friend.

If you do not want symmetry in many-to-many relationships with self, set symmetrical to False. This will force Django to add the descriptor for the reverse relationship, allowing ManyToManyField relationships to be non-symmetrical.

like image 114
Daniel Roseman Avatar answered Nov 10 '22 23:11

Daniel Roseman