如何使用django建模对称关系?

时间:2022-09-01 07:24:46

Let's use the classic example of friends.

让我们使用朋友的经典例子。

class Friendship(models.Model):
    user1 = models.ForeignKey(User, related_name='friends1')
    user2 = models.ForeignKey(User, related_name='friends2')
    handshakes = models.PositiveIntegerField()
    hugs = models.PositiveIntegerField()
    # other silly data

Two friends in a friendship (user1 and user2) should be completely equal. I should be able to say that (user1, user2) are unique_together and not have to worry about (user2, user1) accidentally showing up. I should be able to get all the friends of a given user easily, but instead I'd have to write a custom manager or create some other way of getting all the Friendships where that user is user1 in the relationship, and all the Friendships where that user is user2.

友谊中的两个朋友(user1和user2)应该完全相同。我应该可以说(user1,user2)是unique_together而不必担心(user2,user1)意外出现。我应该能够轻松地获得给定用户的所有朋友,但相反,我必须编写一个自定义管理器或创建一些其他方式来获取所有友谊,其中该用户是user1在关系中,以及所有友谊所在的地方该用户是user2。

I'm considering trying to write my own SymmetricKey. Someone please stop me.

我正在考虑尝试编写自己的SymmetricKey。有人请阻止我

2 个解决方案

#1


7  

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

在文档中查看ManyToManyField的对称选项 - 听起来它可以做你想要的。

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

#2


2  

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:

注意related_name末尾的加号。这向Django表明不应该暴露反向关系。由于关系是对称的,这是理想的行为,毕竟,如果我是A人的朋友,那么A人就是我的朋友。 Django不会为你创建对称关系,所以有点需要添加到add_relationship和remove_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

来源:通过自我引用多对多

#1


7  

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

在文档中查看ManyToManyField的对称选项 - 听起来它可以做你想要的。

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

#2


2  

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:

注意related_name末尾的加号。这向Django表明不应该暴露反向关系。由于关系是对称的,这是理想的行为,毕竟,如果我是A人的朋友,那么A人就是我的朋友。 Django不会为你创建对称关系,所以有点需要添加到add_relationship和remove_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

来源:通过自我引用多对多