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)
精彩评论