kerryz
kerryz

Reputation: 381

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

Upvotes: 15

Views: 3779

Answers (1)

Daniel Roseman
Daniel Roseman

Reputation: 599866

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.

Upvotes: 13

Related Questions