使用 get_or_create 创建带 ManyToMany 字段的 Django 模型
假设我有三个 Django 模型:
class Section(models.Model):
name = models.CharField()
class Size(models.Model):
section = models.ForeignKey(Section)
size = models.IntegerField()
class Obj(models.Model):
name = models.CharField()
sizes = models.ManyToManyField(Size)
我想导入大量的 Obj 数据,其中很多的尺寸字段会是相同的。不过,由于 Obj 有一个多对多的字段,我不能像平常那样简单地检查是否存在。我希望能做一些类似这样的事情:
try:
x = Obj(name='foo')
x.sizes.add(sizemodel1) # these can be looked up with get_or_create
...
x.sizes.add(sizemodelN) # these can be looked up with get_or_create
# Now test whether x already exists, so I don't add a duplicate
try:
Obj.objects.get(x)
except Obj.DoesNotExist:
x.save()
但是,我不知道有没有办法这样获取一个对象,你必须传入关键字参数,而这对于多对多字段是行不通的。
有没有什么好的方法可以做到这一点?我唯一想到的办法是构建一组 Q 对象来传递给获取方法:
myq = myq & Q(sizes__id=sizemodelN.id)
但我不确定这样做是否有效……
2 个回答
使用一个中间模型,然后对它使用 .get() 方法。
http://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships
一旦你有了这个中间模型,你可以使用 .get()、.filter() 或 .exists() 来检查一个对象是否存在,这样你就可以决定是否需要创建它。需要注意的是,.get() 主要是用来查找数据库中那些唯一的字段 - 如果只是想检查对象是否存在,使用 .exists() 可能会更快。
如果这个方法对你来说太复杂或者不方便,你也可以直接获取 ManyRelatedManager,然后遍历它来判断对象是否存在:
object_sizes = obj.sizes.all()
exists = object_sizes.filter(id__in = some_bunch_of_size_object_ids_you_are_curious_about).exists()
if not exists:
(your creation code here)
你的例子有点不太合理,因为在一个x
还没保存之前,你不能添加多对多的关系。不过,它很好地说明了你想做的事情。你有一个通过get_or_create()
创建的Size
对象列表,想要在没有重复的obj-size关系时创建一个Obj
,对吗?
可惜,这个操作并不是特别简单。用Q(id=F) & Q(id=O) & Q(id=O)
这样的方式来连接是无法处理多对多关系的。
你当然可以使用Obj.objects.filter(size__in=Sizes)
,但这样的话,你会得到一个Obj
,它的size
在一个庞大的尺寸列表中匹配。
可以看看这个帖子,里面有关于__in
的具体问题,是Malcolm回答的,所以我觉得这个答案挺靠谱的。
我写了一些Python代码来处理这个问题。
这是一次性的导入,对吧?
def has_exact_m2m_match(match_list):
"""
Get exact Obj m2m match
"""
if isinstance(match_list, QuerySet):
match_list = [x.id for x in match_list]
results = {}
match = set(match_list)
for obj, size in \
Obj.sizes.through.objects.filter(size__in=match).values_list('obj', 'size'):
# note: we are accessing the auto generated through model for the sizes m2m
try:
results[obj].append(size)
except KeyError:
results[obj] = [size]
return bool(filter(lambda x: set(x) == match, results.values()))
# filter any specific objects that have the exact same size IDs
# if there is a match, it means an Obj exists with exactly
# the sizes you provided to the function, no more.
sizes = [size1, size2, size3, sizeN...]
if has_exact_m2m_match(sizes):
x = Obj.objects.create(name=foo) # saves so you can use x.sizes.add
x.sizes.add(sizes)