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

标签 django django-models django-orm

我正在尝试在 Django 中的自引用多对多字段上实现所谓的“同义”关系。

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

class Word(models.Model):
    name = models.CharField(max_length=30, unique=True)
    synonymous = models.ManyToManyField('self', blank=True,  related_name='synonymous')

    def __str__(self):
        return self.name

我想要实现的是,当我有 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])

现在当我得到rabbit的同义对象时,它就有了我想要的东西:

(Pdb) rabbit.synonymous.all()
<QuerySet [<Word: bunny>, <Word: hare>]>

但是当我获取 bunny 和 hare 的同义对象时,它们只返回rabbit。

(Pdb) bunny.synonymous.all()
<QuerySet [<Word: rabbit>]>
(Pdb) hare.synonymous.all()
<QuerySet [<Word: rabbit>]>

我想要实现的是所有同义对象都是“对称的”。现在,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>]>

我希望这是清楚的。

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

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

最佳答案

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

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

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


为了得到你想要的,你可以创建一个辅助函数来创建传递的对象之间的所有相互关系:

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)

注意事项:

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

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

关于django - Django 中的同义多对多模型关系,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59103840/

相关文章:

django - 将python/django对象从父模型移动到子模型(子类)

python - Django模型在创建后保存了两次

python - 在 Django 中序列化外键对象

django - 如果MEDIA_URL在STATIC_URL内,则runserver无法提供媒体

python - 在序列化器中从一对一关系模型添加额外字段

python - 如何使用django项目上传csv文件

Django Rest Framework APIClient不解析查询参数

python - 如何在 django 模型中使用开始时间和持续时间到达结束时间

django - MyModel.objects.update_or_create() --> Boolean 数据是否更新?

python - 按日期对记录进行分组并添加字段列表