ManyToMany 字段 bulk_create 的正确方法,Django?
Proper way to bulk_create for ManyToMany field, Django?
我有此代码用于 table 填充。
def add_tags(count):
print "Add tags"
insert_list = []
photo_pk_lower_bound = Photo.objects.all().order_by("id")[0].pk
photo_pk_upper_bound = Photo.objects.all().order_by("-id")[0].pk
for i in range(count):
t = Tag( tag = 'tag' + str(i) )
insert_list.append(t)
Tag.objects.bulk_create(insert_list)
for i in range(count):
random_photo_pk = randint(photo_pk_lower_bound, photo_pk_upper_bound)
p = Photo.objects.get( pk = random_photo_pk )
t = Tag.objects.get( tag = 'tag' + str(i) )
t.photos.add(p)
这是模型:
class Tag(models.Model):
tag = models.CharField(max_length=20,unique=True)
photos = models.ManyToManyField(Photo)
据我了解这个答案:Django: invalid keyword argument for this function 我必须先保存标签对象(由于 ManyToMany 字段),然后通过 add()
将照片附加到它们。但是对于大的 count
这个过程花费的时间太长了。有什么方法可以重构此代码以使其更快?
一般来说,我想用随机虚拟数据填充标签模型。
编辑 1(照片模型)
class Photo(models.Model):
photo = models.ImageField(upload_to="images")
created_date = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User)
def __unicode__(self):
return self.photo.name
TL;DR
使用 "through" 模型批量插入 m2m 关系。
"Tag.photos.through" => Django generated Model with 3 fields [ id, photo, tag ]
photo_tag_1 = Tag.photos.through(photo_id=1, tag_id=1)
photo_tag_2 = Tag.photos.through(photo_id=1, tag_id=2)
Tag.photos.through.objects.bulk_insert([photo_tag_1, photo_tag_2, ...])
这是我所知道的最快的方法,我一直使用它来创建测试数据。我可以在几分钟内生成数百万条记录。
来自乔治的编辑:
def add_tags(count):
Tag.objects.bulk_create([Tag(tag='tag%s' % t) for t in range(count)])
tag_ids = list(Tag.objects.values_list('id', flat=True))
photo_ids = Photo.objects.values_list('id', flat=True)
tag_count = len(tag_ids)
for photo_id in photo_ids:
tag_to_photo_links = []
shuffle(tag_ids)
rand_num_tags = randint(0, tag_count)
photo_tags = tag_ids[:rand_num_tags]
for tag_id in photo_tags:
# through is the model generated by django to link m2m between tag and photo
photo_tag = Tag.photos.through(tag_id=tag_id, photo_id=photo_id)
tag_to_photo_links.append(photo_tag)
Tag.photos.through.objects.bulk_create(tag_to_photo_links, batch_size=7000)
我没有创建要测试的模型,但结构就在那里,您可能需要调整一些东西才能使其正常工作。如果您 运行 遇到任何问题,请告诉我。
[已编辑]
如 Du D 的回答所示,Django ManyToMany 字段使用一个名为 through
的 table,它包含三列:关系的 ID、链接对象的 ID to 和链接对象的 ID from。您可以在 through
上使用 bulk_create
来批量创建 ManyToMany 关系。
举个简单的例子,您可以像这样批量创建标签到照片的关系:
tag1 = Tag.objects.get(id=1)
tag2 = Tag.objects.get(id=2)
photo1 = Photo.objects.get(id=1)
photo2 = Photo.objects.get(id=2)
through_objs = [
Tag.photos.through(
photo_id=photo1.id,
tag_id=tag1.id,
),
Tag.photos.through(
photo_id=photo1.id,
tag_id=tag2.id,
),
Tag.photos.through(
photo_id=photo2.id,
tag_id=tag2.id,
),
]
Tag.photos.through.objects.bulk_create(through_objs)
一般解决方案
这是一个通用的解决方案,您可以运行在任何对象对列表之间建立多对多关系。
from typing import Iterable
from collections import namedtuple
ManyToManySpec = namedtuple(
"ManyToManySpec", ["from_object", "to_object"]
)
def bulk_create_manytomany_relations(
model_from,
field_name: str,
model_from_name: str,
model_to_name: str,
specs: Iterable[ManyToManySpec]
):
through_objs = []
for spec in specs:
through_objs.append(
getattr(model_from, field_name).through(
**{
f"{model_from_name.lower()}_id": spec.from_object.id,
f"{model_to_name.lower()}_id": spec.to_object.id,
}
)
)
getattr(model_from, field_name).through.objects.bulk_create(through_objs)
用法示例
tag1 = Tag.objects.get(id=1)
tag2 = Tag.objects.get(id=2)
photo1 = Photo.objects.get(id=1)
photo2 = Photo.objects.get(id=2)
bulk_create_manytomany_relations(
model_from=Tag,
field_name="photos",
model_from_name="tag",
model_to_name="photo",
specs=[
ManyToManySpec(from_object=tag1, to_object=photo1),
ManyToManySpec(from_object=tag1, to_object=photo2),
ManyToManySpec(from_object=tag2, to_object=photo2),
]
)
我有此代码用于 table 填充。
def add_tags(count):
print "Add tags"
insert_list = []
photo_pk_lower_bound = Photo.objects.all().order_by("id")[0].pk
photo_pk_upper_bound = Photo.objects.all().order_by("-id")[0].pk
for i in range(count):
t = Tag( tag = 'tag' + str(i) )
insert_list.append(t)
Tag.objects.bulk_create(insert_list)
for i in range(count):
random_photo_pk = randint(photo_pk_lower_bound, photo_pk_upper_bound)
p = Photo.objects.get( pk = random_photo_pk )
t = Tag.objects.get( tag = 'tag' + str(i) )
t.photos.add(p)
这是模型:
class Tag(models.Model):
tag = models.CharField(max_length=20,unique=True)
photos = models.ManyToManyField(Photo)
据我了解这个答案:Django: invalid keyword argument for this function 我必须先保存标签对象(由于 ManyToMany 字段),然后通过 add()
将照片附加到它们。但是对于大的 count
这个过程花费的时间太长了。有什么方法可以重构此代码以使其更快?
一般来说,我想用随机虚拟数据填充标签模型。
编辑 1(照片模型)
class Photo(models.Model):
photo = models.ImageField(upload_to="images")
created_date = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User)
def __unicode__(self):
return self.photo.name
TL;DR 使用 "through" 模型批量插入 m2m 关系。
"Tag.photos.through" => Django generated Model with 3 fields [ id, photo, tag ]
photo_tag_1 = Tag.photos.through(photo_id=1, tag_id=1)
photo_tag_2 = Tag.photos.through(photo_id=1, tag_id=2)
Tag.photos.through.objects.bulk_insert([photo_tag_1, photo_tag_2, ...])
这是我所知道的最快的方法,我一直使用它来创建测试数据。我可以在几分钟内生成数百万条记录。
来自乔治的编辑:
def add_tags(count):
Tag.objects.bulk_create([Tag(tag='tag%s' % t) for t in range(count)])
tag_ids = list(Tag.objects.values_list('id', flat=True))
photo_ids = Photo.objects.values_list('id', flat=True)
tag_count = len(tag_ids)
for photo_id in photo_ids:
tag_to_photo_links = []
shuffle(tag_ids)
rand_num_tags = randint(0, tag_count)
photo_tags = tag_ids[:rand_num_tags]
for tag_id in photo_tags:
# through is the model generated by django to link m2m between tag and photo
photo_tag = Tag.photos.through(tag_id=tag_id, photo_id=photo_id)
tag_to_photo_links.append(photo_tag)
Tag.photos.through.objects.bulk_create(tag_to_photo_links, batch_size=7000)
我没有创建要测试的模型,但结构就在那里,您可能需要调整一些东西才能使其正常工作。如果您 运行 遇到任何问题,请告诉我。
[已编辑]
如 Du D 的回答所示,Django ManyToMany 字段使用一个名为 through
的 table,它包含三列:关系的 ID、链接对象的 ID to 和链接对象的 ID from。您可以在 through
上使用 bulk_create
来批量创建 ManyToMany 关系。
举个简单的例子,您可以像这样批量创建标签到照片的关系:
tag1 = Tag.objects.get(id=1)
tag2 = Tag.objects.get(id=2)
photo1 = Photo.objects.get(id=1)
photo2 = Photo.objects.get(id=2)
through_objs = [
Tag.photos.through(
photo_id=photo1.id,
tag_id=tag1.id,
),
Tag.photos.through(
photo_id=photo1.id,
tag_id=tag2.id,
),
Tag.photos.through(
photo_id=photo2.id,
tag_id=tag2.id,
),
]
Tag.photos.through.objects.bulk_create(through_objs)
一般解决方案
这是一个通用的解决方案,您可以运行在任何对象对列表之间建立多对多关系。
from typing import Iterable
from collections import namedtuple
ManyToManySpec = namedtuple(
"ManyToManySpec", ["from_object", "to_object"]
)
def bulk_create_manytomany_relations(
model_from,
field_name: str,
model_from_name: str,
model_to_name: str,
specs: Iterable[ManyToManySpec]
):
through_objs = []
for spec in specs:
through_objs.append(
getattr(model_from, field_name).through(
**{
f"{model_from_name.lower()}_id": spec.from_object.id,
f"{model_to_name.lower()}_id": spec.to_object.id,
}
)
)
getattr(model_from, field_name).through.objects.bulk_create(through_objs)
用法示例
tag1 = Tag.objects.get(id=1)
tag2 = Tag.objects.get(id=2)
photo1 = Photo.objects.get(id=1)
photo2 = Photo.objects.get(id=2)
bulk_create_manytomany_relations(
model_from=Tag,
field_name="photos",
model_from_name="tag",
model_to_name="photo",
specs=[
ManyToManySpec(from_object=tag1, to_object=photo1),
ManyToManySpec(from_object=tag1, to_object=photo2),
ManyToManySpec(from_object=tag2, to_object=photo2),
]
)