Django中的同义多对多模型关系

Django中的同义多对多模型关系,django,django-models,django-orm,Django,Django Models,Django Orm,我试图在Django的一个自引用多对多字段上实现一种你们可以称之为“同义”的关系 以这个模型为例(实际上我不使用真实的单词,而是使用类别标签): 我想要实现的是,当我有3个对象,并将它们的任意组合添加到同义字段中时,我希望它们都连接起来 # Create synonymous words bunny = Word.objects.create(name='bunny') hare = Word.objects.create(name='hare') rabbit = Word.objects.c

我试图在Django的一个自引用多对多字段上实现一种你们可以称之为“同义”的关系

以这个模型为例(实际上我不使用真实的单词,而是使用类别标签):

我想要实现的是,当我有3个对象,并将它们的任意组合添加到同义字段中时,我希望它们都连接起来

# Create synonymous words
bunny = Word.objects.create(name='bunny')
hare = Word.objects.create(name='hare')
rabbit = Word.objects.create(name='rabbit')
# Set synonymous words to rabbit
rabbit.synonymous.set([bunny, hare])
现在,当我得到兔子的同义词时,它有我想要的:

(Pdb) rabbit.synonymous.all()
<QuerySet [<Word: bunny>, <Word: hare>]>
(Pdb)rabbit.synonymous.all()
但当我拿兔子和兔子这两个同义词时,它们只返回兔子

(Pdb) bunny.synonymous.all()
<QuerySet [<Word: rabbit>]>
(Pdb) hare.synonymous.all()
<QuerySet [<Word: rabbit>]>
(Pdb)bunny.synonymous.all()
(Pdb)hare.同义词.all()
我想实现的是,所有的同义对象都是“对称的”。现在,m2m场已经是对称的,但它只停在一个对象上,而不是所有给定的同义对象上

因此,理想的结果是:

# Create synonymous words
bunny = Word.objects.create(name='bunny')
hare = Word.objects.create(name='hare')
rabbit = Word.objects.create(name='rabbit')
# Set synonymous ON ONE WORD
rabbit.synonymous.set([bunny, hare])

# And now ALL the objects, which have at least ONE related synonym, would automatically be assigned to the other words as well
(Pdb) rabbit.synonymous.all()
<QuerySet [<Word: bunny>, <Word: hare>]>
(Pdb) hare.synonymous.all()
<QuerySet [<Word: bunny>, <Word: rabbit>]>
(Pdb) bunny.synonymous.all()
<QuerySet [<Word: rabbit>, <Word: hare>]>
#创建同义词
bunny=Word.objects.create(name='bunny')
hare=Word.objects.create(name='hare')
rabbit=Word.objects.create(name='rabbit')
#在一个单词上设置同义词
兔子。同义词。集合([兔子,兔子])
#现在所有的对象,至少有一个相关的同义词,也会自动分配给其他单词
(Pdb)兔子。同义词。全部()
(Pdb)hare.同义词.all()
(Pdb)bunny.同义词.all()
我希望这是清楚的

我想知道实现这一目标最干净的方法是什么?也许有一些方法可以通过ORM实现,但我有疑问


我是否最好只编写一个手动管理这些关系的信号?

我认为实现这一点的最简单方法是通过以下代码:

for word in synonymous:
    word.synonymous.set(synonymous.exclude(pk=word.pk))
编辑


放置此代码的最佳位置是
视图.py
中。如果您使用的是Django Admin,您应该使用。

没有直接的方法可以一次创建所有对称关系,因为:

  • rabbit.synonymous
    是一个描述符(
    ManyToManyDescriptor
    ),它实际返回实例属性访问的
    ManyRelatedManager

  • 由于多目标管理器将关系附加到单个对象,因此
    ManyRelatedManager
    没有任何预期的方法,因为它是单个实例的属性(
    rabbit
    ,在本例中),与前向模型管理器不同(例如,
    models.manager
    作为
    对象访问)这适用于行/实例的集合


要获得所需的内容,可以创建一个helper函数来创建传递对象之间的所有相互关系:

from itertools import combinations

def create_m2m_inter_relations(*objs): 
    if len(objs) < 2: 
        raise ValueError(
            'There must be at least two objects '
            'passed to create relationship.'
        ) 

    if len(objs) == 2: 
        objs[0].set([objs[1]]) 
        return 

    objs = set(objs) 
    relations_map = {
        next(iter(objs.difference(comb))): comb
        for comb in combinations(objs, len(objs) - 1)
    }  
    for instance, relations in relations_map.items():
        instance.synonymous.set(relations)
来自itertools导入组合的

def创建_m2m_内部关系(*objs):
如果len(objs)<2:
升值误差(
“必须至少有两个对象”
'传递以创建关系。'
) 
如果len(objs)==2:
objs[0]。设置([objs[1]])
返回
objs=集合(objs)
关系图={
下一步(国际热核实验堆(objs.difference(comb))):comb
用于梳状组合(objs、len(objs)-1)
}  
例如,relations_map.items()中的关系:
instance.synonymous.set(关系)
注意事项:

  • 由于Django M2M关系是对称的,上述操作重复了循环早期迭代中已建立的关系的相同
    set
    操作。删除重复的
    set
    操作留给读者作为练习

  • 当您创建与所有对象的关系时,这些对象都具有相同的
    ManyToManyDescriptor
    --
    同义词
    ,它在函数中是硬编码的,在这种情况下可以正常工作。但是,如果您想要一个更通用的解决方案,请考虑这一点,并进行相应的修改

因为,没有办法直接通过ORM完成

因此,我最终制作了两个函数,一个用于获取/查找所有同义关系,另一个用于设置/同步它们

获取/查找:

def get_all_synonymous_words(word):
    # Start with the word's own synonymous words, and their synonymous words
    found_syn_ids = set(word.synonymous.values_list('id', flat=True)) | set(word.synonymous.values_list('synonymous', flat=True))

    while found_syn_ids:
        words = Word.objects.filter(id__in=found_syn_ids)
        batch_syn_ids = set(words.values_list('synonymous', flat=True))

        # If the batch syn ids are equal to last batch ids, all relationships found
        if batch_syn_ids == found_syn_ids:
            return batch_syn_ids

        found_syn_ids = batch_syn_ids
对于设置/同步:

def set_all_synonymous_words(word):
    syn_ids = get_all_synonymous_words(word)
    if syn_ids:
        syn_qs = Word.objects.filter(id__in=syn_ids)
        for word in syn_qs:
            # Exclude current word to not self reference
            excluded_qs = syn_qs.exclude(id=word.id)
            if not set(word.synonymous.exclude(id=word.id)) == excluded_qs:
                word.synonymous.set(excluded_qs)
这样,当您更新/创建单词时,您可以找到所有的组合并进行更新,而无需预先定义同义词列表

在使用django rest框架时,我通过重写perform\u update/perform\u create方法在视图中调用它:

    def perform_update(self, serializer):
        serializer.save()

        # Get the updated word, and sync the synonymous words to be all symmetrical
        set_all_synonymous_words(Word.objects.get(id=serializer.data['id']))


    def perform_create(self, serializer):
        serializer.save()

        # Get the updated word, and sync the synonymous words to be all symmetrical
        set_all_synonymous_words(Word.objects.get(id=serializer.data['id']))
或者您可以手动调出:

    # Create synonymous words
    bunny = Word.objects.create(name='bunny')
    hare = Word.objects.create(name='hare')
    rabbit = Word.objects.create(name='rabbit')
    # Set synonymous
    rabbit.synonymous.set([bunny, hare])
    # Sync synonymous
    set_all_synonymous_words(bunny)
    # Create synonymous words
    bunny = Word.objects.create(name='bunny')
    hare = Word.objects.create(name='hare')
    rabbit = Word.objects.create(name='rabbit')
    # Set synonymous
    rabbit.synonymous.set([bunny, hare])
    # Sync synonymous
    set_all_synonymous_words(bunny)