В Django, как создать «суперсимметричное» отношение на самоотчете ManyToMany - PullRequest
1 голос
/ 16 июля 2011

У меня есть эта модель:

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),Если мы удалим C из B, 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 Ответ

1 голос
/ 18 июля 2011

Я наконец нашел решение.Проблема заключалась в том, что мне нужно было очистить все отношения с друзьями в текущей группе, но я не мог найти способ сделать это, не входя в бесконечный цикл из 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)
...