Django 中同义的多对多模型关系
Synonymous many to many model relationship in Django
我正在尝试在 Django 中的自引用多对多字段上实现您所谓的 "synonymous" 关系。
以这个模型为例(实际上我并没有用真词,而是用类目标签代替):
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>]>
但是当我取兔子和兔子的同义词时,它们只有return兔子。
(Pdb) bunny.synonymous.all()
<QuerySet [<Word: rabbit>]>
(Pdb) hare.synonymous.all()
<QuerySet [<Word: rabbit>]>
我想要实现的是所有同义对象,成为"symmetrical"。现在,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 来完成,但我有疑问。
我最好只写一个手动管理这些关系的信号吗?
我认为实现此目的的最简单方法是通过以下代码:
for word in synonymous:
word.synonymous.set(synonymous.exclude(pk=word.pk))
编辑
放置此代码的最佳位置是您的 views.py
。如果您使用的是 Django Admin,则应该使用 save_related.
没有像那样直接创建所有对称关系的直接方法,因为:
rabbit.synonymous
是一个描述符 (ManyToManyDescriptor
),实际上 returns ManyRelatedManager
实例属性访问
由于多对多管理器将关系附加到单个对象,ManyRelatedManager
没有预期的任何此类方法,因为它是单个实例的属性(rabbit
在这种情况下)不同于适用于 rows/instances
集合的正向模型管理器(例如 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
操作留作 reader.
的练习
当您创建与所有对象的关系时,这些对象对于 ManyToManyDescriptor
-- synonymous
具有相同的名称,它在函数中被硬编码并且在此函数中可以正常工作案件。但是如果你想要一个更通用的解决方案,请考虑到这一点并进行相应的修改。
作为,没有办法直接通过ORM来做。
所以我最终制作了 2 个函数,一个用于 getting/finding 所有同义关系,另一个用于 setting/syncing 它们
对于getting/finding:
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
对于setting/syncing:
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)
这样,当您 update/create 个单词时,您可以找到所有组合并更新它们,而无需预先定义同义词列表。
当我使用 django-rest-framework 时,我通过覆盖 perform_update/perform_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)
我正在尝试在 Django 中的自引用多对多字段上实现您所谓的 "synonymous" 关系。
以这个模型为例(实际上我并没有用真词,而是用类目标签代替):
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>]>
但是当我取兔子和兔子的同义词时,它们只有return兔子。
(Pdb) bunny.synonymous.all()
<QuerySet [<Word: rabbit>]>
(Pdb) hare.synonymous.all()
<QuerySet [<Word: rabbit>]>
我想要实现的是所有同义对象,成为"symmetrical"。现在,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 来完成,但我有疑问。
我最好只写一个手动管理这些关系的信号吗?
我认为实现此目的的最简单方法是通过以下代码:
for word in synonymous:
word.synonymous.set(synonymous.exclude(pk=word.pk))
编辑
放置此代码的最佳位置是您的 views.py
。如果您使用的是 Django Admin,则应该使用 save_related.
没有像那样直接创建所有对称关系的直接方法,因为:
rabbit.synonymous
是一个描述符 (ManyToManyDescriptor
),实际上 returnsManyRelatedManager
实例属性访问由于多对多管理器将关系附加到单个对象,
ManyRelatedManager
没有预期的任何此类方法,因为它是单个实例的属性(rabbit
在这种情况下)不同于适用于 rows/instances 集合的正向模型管理器(例如
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
操作留作 reader. 的练习
当您创建与所有对象的关系时,这些对象对于
ManyToManyDescriptor
--synonymous
具有相同的名称,它在函数中被硬编码并且在此函数中可以正常工作案件。但是如果你想要一个更通用的解决方案,请考虑到这一点并进行相应的修改。
作为
所以我最终制作了 2 个函数,一个用于 getting/finding 所有同义关系,另一个用于 setting/syncing 它们
对于getting/finding:
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
对于setting/syncing:
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)
这样,当您 update/create 个单词时,您可以找到所有组合并更新它们,而无需预先定义同义词列表。
当我使用 django-rest-framework 时,我通过覆盖 perform_update/perform_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)