Friday, October 16, 2020

Re: How to make recursive ManyToMany relationships through an intermediate model symmetrical

After playing with this to answer your question and to correct my previous response, I found that it does work as documented when using a "through" model without using "through_fields".

from django.db import models


class Person(models.Model):
name = models.CharField(max_length=255)
friends = models.ManyToManyField("self", through='Friendship', symmetrical=True)

def __str__(self):
return self.name


class Friendship(models.Model):
person_a = models.ForeignKey(Person, on_delete=models.CASCADE, related_name='a')
person_b = models.ForeignKey(Person, on_delete=models.CASCADE, related_name='b')
start = models.DateField(auto_now_add=True)

def __str__(self):
return f'{self.person_a.name} => {self.person_b.name}: {self.start}'

>>> from people.models import Person, Friendship
>>> bill = Person.objects.create(name='bill')
>>> rufus = Person.objects.create(name='rufus')
>>> bill.friends.add(ted)
>>> bill.friends.add(rufus)
>>> rufus.friends.add(bill)
>>> rufus.friends.add(ted)
>>> bill.friends.all()
<QuerySet [<Person: ted>, <Person: rufus>]>
>>> ted.friends.all()
<QuerySet [<Person: bill>, <Person: rufus>]>
>>> rufus.friends.all()
<QuerySet [<Person: bill>, <Person: ted>]>
>>> Friendship.objects.all()
<QuerySet [
  <Friendship: bill => ted: 2020-10-17>, 
  <Friendship: ted => bill: 2020-10-17>,
  <Friendship: bill => rufus: 2020-10-17>,
  <Friendship: rufus => bill: 2020-10-17>,
  <Friendship: rufus => ted: 2020-10-17>,
  <Friendship: ted => rufus: 2020-10-17>
]>


In your case, naming the related_name the same as the field name may be an issue. Since related_name becomes a pseudo field on the model in which it is defined, so there is a potential clash in the namespace?

On 17 Oct 2020, at 11:20, gjgilles via Django users <django-users@googlegroups.com> wrote:

Thanks for all for the replies!

@David, the helper function works as expected.

>>> from people.models import Person, Friendship
>>> bill = Person(name='bill')
>>> bill.save()
>>> ted = Person(name='ted')
>>> ted.save()
>>> bill.add_friendship(ted, True)
(<Friendship: bill and ted>, True)
>>> bill.friends.all()
<QuersySet [<Person: ted>]>
>>> ted.friends.all()
<QuerySet [<Person: bill>]>


Also, @coolguy for my code, the correct call is >>> ted.personB.all() without the helper function. ted.personA.all() returns an empty queryset without the helper function.

"Recursive relationships using an intermediary model and defined as symmetrical (that is, with symmetrical=True, which is default) can't determine the accessory names, as they would be the same. You need to set a related_name to at least one of them. If you'd prefer Django not to create a backwards relation, set related_name to '+'."

This implies Django makes the reverse relation by default. Am I misunderstanding something?

No comments:

Post a Comment