开发者

In Django how to create a "super symmetrical" relation on a ManyToMany self relation

I have this model:

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

So friends relation is symmetrical. So if you're my friend, I'm your friend.

What I would like also is that all my friend's friends be automatically my friends. Example:

If A and B are friends (AB, BA) and we add a new friend C to B, C will be automatically added also to A (AB, BA, BC, CB, AC, CA). If we remove C from B, C will automatically be removed from A.

I need this to work in a normal Admin page. When submitting a form, for a ManyToManyField, Django call first clean(), erasing all relations related to the current instance, then add(), adding all the relations coming from the form.

I was able to get the good behavio开发者_如何学JAVAr when adding a new relation with this code (but it doesn't work when removing relation):

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)

When searching for solutions, I have hard time not creating an infinite loop.


I finally found a solution. The problem was that I needed to clear all the friends relations of the current group but I couldn't find a way to do it without going into in infinite loop of clear signals.

So, the solution was to bypass the clear() method and directly use the delete() method of the manager. I finally use the same approach for the 3 signals. Here's the modified code of the add_friends function:

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)
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜