Django ManyToMany字段的bulk_create的正确方法?

Geo*_*rgy 5 python django many-to-many

我有用于表填充的这段代码。

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)
Run Code Online (Sandbox Code Playgroud)

这是模型:

class Tag(models.Model):
    tag = models.CharField(max_length=20,unique=True)
    photos = models.ManyToManyField(Photo)
Run Code Online (Sandbox Code Playgroud)

据我理解的答案:Django:此函数的无效关键字参数我必须先保存标签对象(由于ManyToMany字段),然后通过将照片附加到它们add()。但是,从count长远来看,此过程耗时太长。有什么方法可以重构此代码以使其更快?

通常,我想用随机虚拟数据填充Tag模型。

编辑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
Run Code Online (Sandbox Code Playgroud)

Du *_* D. 8

这是我所知道的最快的方法,我一直在使用它来创建测试数据。我可以在几分钟内生成数百万条记录。

从乔治编辑:

def add_tags(count):
    new_tags = []
    for t in range(count):
        tag = Tag(tag='tag%s' % t)
        new_tags.append(tag)
    Tag.objects.bulk_create(new_tags)

    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 table 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)
Run Code Online (Sandbox Code Playgroud)

我没有创建要测试的模型,但是这里的结构可能需要进行一些调整才能使其正常工作。让我知道您是否遇到任何问题。

[编辑]

  • 只是更新.. Tag.photos.through.bulk_insert() 将导致某些东西没有属性bulk_insert()。相反,我们应该使用 Tag.photos.through.objects.bulk_create()。 (5认同)
  • 你好 我之前在您的答案中添加了修改,但被拒绝了。您的代码中有一些错误,请您对其进行修改以作记录?1)不是“ Tag.Photos.through”,而是“ Tag.photos.through”。2)将“ Photo.objects.value_list”转换为“ Photo.objects.values_list”(此处为输入错误)。3)您不能以这种方式随机播放tag_ids,请使用`list()`进行转换。4)而且您必须将最后的`bulk_create()`行移出`forloop`之外,否则代码会尝试添加重复项。预先感谢您的宝贵时间! (2认同)

Sha*_*lor 7

正如杜D的答案显示,Django的多对多字段使用一个称为表through包含三列:关系的ID,链接对象的ID和链接对象的ID。您可以使用bulk_createonthrough批量创建多对多关系。

举个简单的例子,你可以像这样批量创建标签到照片的关系:

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)
Run Code Online (Sandbox Code Playgroud)

通用解决方案

这是一个通用解决方案,您可以运行它来在任何对象对列表之间设置多对多关系。

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)
Run Code Online (Sandbox Code Playgroud)

示例用法

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),
    ]
)
Run Code Online (Sandbox Code Playgroud)