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? Oct 16, 2020, 16:48 by [email protected]: > Just to add, I don't think django supports symmetrical M2M relations with > additional data / explicit through model without the shim I suggested. > > For example, this works: > > from > django.db > import > models > > > class > Person(models.Model): > name = models.CharField(> max_length> => 255> ) > friends = models.ManyToManyField(> "self"> , > symmetrical> => True> ) > > > def > __str__> (> self> ): > > return > self> .name > > >>> from people.models import Person > >>> bill = Person.objects.create(name='bill') > >>> ted = Person.objects.create(name='ted') > >>> bill.friends.add(ted) > >>> bill.friends.all() > <QuerySet [<Person: ted>]> > >>> ted.friends.all() > <QuerySet [<Person: bill>]> > > > > > > >> On 17 Oct 2020, at 10:28, coolguy <>> [email protected]>> > >> wrote: >> >> >> With your example, you can also find the records through>>> >> ted.person_set.all(). >> >> >> On Friday, October 16, 2020 at 7:05:51 PM UTC-4 David Nugent wrote: >> >>> This is expected with your code. You've created an asymmetric relationship >>> from bill to ted, but not the reverse. This would be appropriate in a >>> "follow" relationship. For symmetric relationships you need to create >>> records in both directions. There are a few ways to do this but a helper >>> function on the Person model is the most direct approach, something along >>> the lines: >>> >>> def add_friendship(self, person, symmetric=True): >>> friendship = Friendship.objects.get_or_create(personA=self, >>> personB=person) >>> if symmetric: >>> # avoid recursion >>> person.add_friendship(self, False) >>> return friendship >>> >>> >>> Regards, David >>> >>> >>>> >>>> >>>> On 17 Oct 2020, at 05:38, gjgilles via Django users <>>>> >>>> [email protected] <>>>>> > wrote: >>>> >>>> >>>> There are no responses to the same question on stackoverflow, so hopefully >>>> someone here can provide a solution. >>>> <https://stackoverflow.com/questions/64346385/how-to-make-a-recursive-manytomany-relationship-symmetrical-with-django> >>>> >>>> >>>> I've read the docs. >>>> <https://docs.djangoproject.com/en/3.0/ref/models/fields/#django.db.models.ManyToManyField.symmetrical>>>>> >>>> >>>> I've>>>> >>>> read this question too, >>>> <https://stackoverflow.com/questions/4129439/how-to-make-recursive-manytomanyfield-relationships-that-have-extra-fields-symme>>>>> >>>> >>>> but the following code is not working as the Django docs describe. >>>> >>>> If>>>> >>>> bill>>>> >>>> and>>>> >>>> ted >>>> are friends,>>>> >>>> >>>> bill.friends.all()>>>> >>>> should include>>>> >>>> ted, >>>> >>>> >>>> and>>>> >>>> ted.friends.all()>>>> >>>> should include>>>> >>>> >>>> bill>>>> . This is not what Django does.>>>> >>>> ted>>>> 's query is >>>> empty, while>>>> >>>> bill>>>> 's query includes>>>> >>>> ted>>>> . >>>> # people.models>>>> from>>>> django.db >>>> import>>>> models>>>> >>>> class>>>> >>>> Person>>>> (>>>> models.Model>>>> ):>>>> name = >>>> models.CharField(max_length=>>>> 255>>>> ) friends = >>>> models.ManyToManyField(>>>> "self">>>> , >>>> through=>>>> 'Friendship'>>>> , >>>> through_fields=(>>>> 'personA'>>>> , >>>> 'personB'>>>> ), >>>> symmetrical=>>>> True>>>> , >>>> ) >>>> def>>>> >>>> __str__>>>> (>>>> self>>>> ):>>>> >>>> >>>> return>>>> >>>> self.name <http://self.name/>>>>> class>>>> >>>> >>>> Friendship>>>> (>>>> models.Model>>>> ):>>>> personA = >>>> models.ForeignKey(Person, on_delete=models.CASCADE, related_name=>>>> >>>> 'personA'>>>> ) personB = models.ForeignKey(Person, >>>> on_delete=models.CASCADE, related_name=>>>> 'personB'>>>> ) start = >>>> models.DateField(null=>>>> True>>>> , blank=>>>> True>>>> ) end = >>>> models.DateField(null=>>>> True>>>> , blank=>>>> True>>>> ) >>>> >>>> def>>>> >>>> __str__>>>> (>>>> self>>>> ):>>>> >>>> return>>>> >>>> >>>> ' and '>>>> .join([str(self.personA), str(self.personB)]) >>>> >>>> >>>> >>>> >>> import django >>>> >>> django.__version__ >>>> '3.1.2'>>> >>>> from>>>> people.models >>>> import>>>> Person, >>>> Friendship>>>> >>> >>>> bill = Person(name=>>>> 'bill'>>>> )>>>> >>> >>>> >>>> bill.save()>>>> >>> >>>> ted = Person(name=>>>> 'ted'>>>> )>>>> >>> >>>> >>>> ted.save()>>>> >>> >>>> bill_and_ted = Friendship(personA=bill, >>>> personB=ted)>>>> >>> >>>> bill_and_ted.save()>>>> >>> >>>> >>>> bill.friends.all()<QuerySet [<Person: ted>]>>>>> >>> >>>> >>>> ted.friends.all()<QuerySet []>>>>> >>> >>>> ted.refresh_from_db()>>>> >>> >>>> >>>> ted.friends.all()<QuerySet []>>>>> >>> >>>> ted = >>>> Person.objects.get(name=>>>> 'ted'>>>> )>>>> >>> >>>> >>>> ted.friends.all()<QuerySet []> >>>> Can someone please show me how to make this behave as expected. >>>> >>>> >>>> >>>> -- >>>> 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/MJmh5Qw--3-2%40tutanota.com >>>> <https://groups.google.com/d/msgid/django-users/MJmh5Qw--3-2%40tutanota.com?utm_medium=email&utm_source=footer>>>>> >>>> . >>>> >>> >>> >> >> -- >> 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/f1b39d06-6100-4b6c-94a5-1ec60fa45f80n%40googlegroups.com >> >> <https://groups.google.com/d/msgid/django-users/f1b39d06-6100-4b6c-94a5-1ec60fa45f80n%40googlegroups.com?utm_medium=email&utm_source=footer>>> >> . >> > > > > > -- > 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/A978B24A-1041-4850-AABA-224F2EFD2E9E%40uniquode.io > > <https://groups.google.com/d/msgid/django-users/A978B24A-1041-4850-AABA-224F2EFD2E9E%40uniquode.io?utm_medium=email&utm_source=footer>> > . > -- 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/MJnvNVy--3-2%40tutanota.com.

