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<mailto: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.

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 django-users+unsubscr...@googlegroups.com.
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