在Django中如何在ManyToMany自我关系上创建一个“超对称”关系

时间:2011-07-16 14:37:08

标签: django many-to-many admin

我有这个型号:

class People(models.Model):
    name = models.CharField(max_length=128, db_index=True)
    friends = models.ManyToManyField('self')

所以friends关系对称。所以如果你是我的朋友,我就是你的朋友。

我还希望所有朋友的朋友自动成为我的朋友。例如:

如果A和B是朋友(AB,BA)并且我们将新朋友C添加到B,则C也将自动添加到A(AB,BA,BC,CB,AC,CA)。如果我们从B中删除C,C将自动从A中删除。

我需要在普通的管理页面中使用它。提交表单时,对于ManyToManyField,Django首先调用clean(),删除与当前实例相关的所有关系,然后add(),添加来自表单的所有关系。

在使用此代码添加新关系时,我能够获得良好的行为(但在删除关系时不起作用):

def add_friends(sender, instance, action, reverse, model, pk_set, **kwargs):
    if action == 'post_add':
        if len(pk_set) > 1:
            pk = pk_set.pop()
            next = People.objects.get(pk=pk)
            next.friends.add(*pk_set)

m2m_changed.connect(add_friends, sender=People.friends.through)

在搜索解决方案时,我很难创建无限循环。

1 个答案:

答案 0 :(得分:1)

我终于找到了解决方案。问题是我需要清除当前组中的所有朋友关系,但是如果不进入无限循环的clear信号,我就找不到办法。

因此,解决方案是绕过clear()方法并直接使用管理器的delete()方法。我最终对3个信号使用相同的方法。这是add_friends函数的修改代码:

def add_friends(sender, instance, action, reverse, model, pk_set, **kwargs):
    # On clear, clear all indirect relations of this instance
    if action == 'pre_clear':
        instance.friends.through.objects.filter(from_people__in=instance.friends.all()).delete()

    # Delete all relations of the objects in the removed set
    # (not just the ones related to the instance)
    elif action == 'post_remove':
        instance.friends.through.objects.filter(
                Q(from_people__in=pk_set) | Q(to_people__in=pk_set)
            ).delete()

    # Clear all relations of People moved from one group to another
    elif action == 'pre_add' and pk_set:
        if instance.pk in pk_set:
            raise ValueError(_(u"You can't add self as a friend."))
        instance.friends.through.objects.filter(
                (Q(from_people__in=pk_set) & ~Q(to_people=instance.pk)) |
                (Q(to_people__in=pk_set) & ~Q(from_people=instance.pk))
            ).delete()

    # Add all indirect relations of this instance
    elif action == 'post_add' and pk_set:
        manager = instance.friends.through.objects
        # Get all the pk pairs
        pk_set.add(instance.pk)
        pk_set.update(instance.friends.all().values_list('pk', flat=True))
        pairs = set(permutations(pk_set, 2))
        # Get the pairs already in the DB
        vals = manager.values_list('from_people', 'to_people')
        vals = vals.filter(from_people__in=pk_set, to_people__in=pk_set)
        # Keep only pairs that are not in DB
        pairs = pairs - set(vals)

        # Add them
        for from_pk, to_pk in pairs:
            manager.create(from_people_id=from_pk, to_people_id=to_pk)

m2m_changed.connect(add_friends, sender=People.friends.through)