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 
<[email protected]<mailto:[email protected]>> 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.

While I'm here, the Django docs imply that 
<https://docs.djangoproject.com/en/3.1/ref/models/fields/#django.db.models.ManyToManyField.through>
 intermediate models can be recursive and 
symmetrical<https://docs.djangoproject.com/en/3.1/ref/models/fields/#django.db.models.ManyToManyField.through>:
"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?

-- 
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-users/0B233FC5-01A3-469D-8F85-EFFE0C8F13EC%40uniquode.io.

Reply via email to