How do I model a symmetric relationship with django?

后端 未结 2 1957
说谎
说谎 2020-12-25 13:14

Let\'s use the classic example of friends.

class Friendship(models.Model):
    user1 = models.ForeignKey(User, related_name=\'friends1\')
    user2 = models.         


        
相关标签:
2条回答
  • 2020-12-25 13:42

    Check out the symmetrical option of ManyToManyField in the docs -- sounds like it can do what you want.

    For the specific way you're doing it, I'd do something like

    class LameUserExtension(User):
        friends = ManyToManyField("self", through=Friendship)
    
    class Friendship(models.Model):
        # the stuff you had here
    
    0 讨论(0)
  • 2020-12-25 13:48

    I found a nice article discussing that some time ago, the basics are the following:

    class Person(models.Model):
        name = models.CharField(max_length=100)
        relationships = models.ManyToManyField('self', through='Relationship', 
                                               symmetrical=False, 
                                               related_name='related_to+')
    
    RELATIONSHIP_FOLLOWING = 1
    RELATIONSHIP_BLOCKED = 2
    RELATIONSHIP_STATUSES = (
        (RELATIONSHIP_FOLLOWING, 'Following'),
        (RELATIONSHIP_BLOCKED, 'Blocked'),
    )
    
    class Relationship(models.Model):
        from_person = models.ForeignKey(Person, related_name='from_people')
        to_person = models.ForeignKey(Person, related_name='to_people')
        status = models.IntegerField(choices=RELATIONSHIP_STATUSES)
    

    Note the plus-sign at the end of related_name. This indicates to Django that the reverse relationship should not be exposed. Since the relationships are symmetrical, this is the desired behavior, after all, if I am friends with person A, then person A is friends with me. Django won't create the symmetrical relationships for you, so a bit needs to get added to the add_relationship and remove_relationship methods to explicitly handle the other side of the relationship:

    def add_relationship(self, person, status, symm=True):
        relationship, created = Relationship.objects.get_or_create(
            from_person=self,
            to_person=person,
            status=status)
        if symm:
            # avoid recursion by passing `symm=False`
            person.add_relationship(self, status, False)
        return relationship
    
    def remove_relationship(self, person, status, symm=True):
        Relationship.objects.filter(
            from_person=self, 
            to_person=person,
            status=status).delete()
        if symm:
            # avoid recursion by passing `symm=False`
            person.remove_relationship(self, status, False)
    

    Now, whenever we create a relationship going one way, its complement is created (or removed). Since the relationships go in both directions, we can simply use:

    def get_relationships(self, status):
        return self.relationships.filter(
            to_people__status=status, 
            to_people__from_person=self)
    

    Source: Self-referencing many-to-many through

    0 讨论(0)
提交回复
热议问题