2011-07-16 35 views
0

創建一個「超級對稱」關係我有這樣的模式:在Django中如何在多對多的自我關係

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

所以friends關係對稱。所以如果你是我的朋友,我是你的朋友。

我也想爲我所有的朋友的朋友是自動我的朋友。例如:

如果A和B是朋友(AB,BA),並且我們向B添加新朋友C,則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

我終於找到了解決方案。問題是我需要清除當前組中的所有朋友關係,但是我沒有辦法在沒有進入無限循環的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)