【问题标题】:How to create an object for a Django model with a many to many field?如何为具有多对多字段的 Django 模型创建对象?
【发布时间】:2011-10-23 04:41:45
【问题描述】:

我的模特:

class Sample(models.Model):
    users = models.ManyToManyField(User)

我想在该模型中同时保存 user1user2

user1 = User.objects.get(pk=1)
user2 = User.objects.get(pk=2)
sample_object = Sample(users=user1, users=user2)
sample_object.save()

我知道那是错误的,但我相信你会得到我想要做的。你会怎么做?

【问题讨论】:

    标签: python django django-models django-orm


    【解决方案1】:

    您不能从未保存的对象创建 m2m 关系。如果你有pks,试试这个:

    sample_object = Sample()
    sample_object.save()
    sample_object.users.add(1,2)
    

    更新:在阅读了saverio's answer 之后,我决定更深入地调查这个问题。这是我的发现。

    这是我最初的建议。它有效,但不是最佳的。 (注意:我使用Bars 和Foo 而不是Users 和Sample,但你明白了)。

    bar1 = Bar.objects.get(pk=1)
    bar2 = Bar.objects.get(pk=2)
    foo = Foo()
    foo.save()
    foo.bars.add(bar1)
    foo.bars.add(bar2)
    

    它总共生成了 7 个查询:

    SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 1
    SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 2
    INSERT INTO "app_foo" ("name") VALUES ()
    SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (1))
    INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
    SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (2))
    INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)
    

    我相信我们可以做得更好。您可以将多个对象传递给add() 方法:

    bar1 = Bar.objects.get(pk=1)
    bar2 = Bar.objects.get(pk=2)
    foo = Foo()
    foo.save()
    foo.bars.add(bar1, bar2)
    

    如我们所见,传递多个对象可以节省一个SELECT

    SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 1
    SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 2
    INSERT INTO "app_foo" ("name") VALUES ()
    SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (1, 2))
    INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
    INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)
    

    我不知道您还可以分配对象列表:

    bar1 = Bar.objects.get(pk=1)
    bar2 = Bar.objects.get(pk=2)
    foo = Foo()
    foo.save()
    foo.bars = [bar1, bar2]
    

    不幸的是,这又创建了一个SELECT

    SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 1
    SELECT "app_bar"."id", "app_bar"."name" FROM "app_bar" WHERE "app_bar"."id" = 2
    INSERT INTO "app_foo" ("name") VALUES ()
    SELECT "app_foo_bars"."id", "app_foo_bars"."foo_id", "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE "app_foo_bars"."foo_id" = 1
    SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (1, 2))
    INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
    INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)
    

    按照 saverio 的建议,让我们尝试分配pks 的列表:

    foo = Foo()
    foo.save()
    foo.bars = [1,2]
    

    由于我们没有获取两个Bars,所以我们保存了两个SELECT语句,总共有5个:

    INSERT INTO "app_foo" ("name") VALUES ()
    SELECT "app_foo_bars"."id", "app_foo_bars"."foo_id", "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE "app_foo_bars"."foo_id" = 1
    SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (1, 2))
    INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
    INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)
    

    获胜者是:

    foo = Foo()
    foo.save()
    foo.bars.add(1,2)
    

    pks 传递给add() 一共有4 个查询:

    INSERT INTO "app_foo" ("name") VALUES ()
    SELECT "app_foo_bars"."bar_id" FROM "app_foo_bars" WHERE ("app_foo_bars"."foo_id" = 1  AND "app_foo_bars"."bar_id" IN (1, 2))
    INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 1)
    INSERT INTO "app_foo_bars" ("foo_id", "bar_id") VALUES (1, 2)
    

    【讨论】:

    • 我想补充一下,你可以像这样传递一个带有 * 的列表: foo.bars.add(*list) 它会将列表分解为参数:D
    • 这应该是关于 ManyToMany 的 Django 文档!比docs.djangoproject.com/en/1.10/topics/db/examples/many_to_manydocs.djangoproject.com/en/1.10/ref/models/fields 更清晰,并且还包含不同方法的性能损失。也许你可以为 Django 1.9 更新它? (设置方法)
    • 我想保存具有单个 id 的模型,其中包含一个以上的项目和数量。有可能吗?? class Cart(models.Model): item = models.ForeignKey(Item, verbose_name="item") 数量 = models.PositiveIntegerField(default=1)
    • 哇。我很惊讶。 :D
    【解决方案2】:

    对于未来的访问者,您可以使用 django 1.4 中的新 bulk_create2 个查询 中创建一个对象及其所有 m2m 对象。请注意,这仅在您不需要使用 save() 方法或信号对数据进行 任何 预处理或后处理时才可用。您插入的内容正是数据库中的内容

    您无需在字段上指定“直通”模型即可执行此操作。为了完整起见,下面的示例创建了一个空白用户模型来模仿原始发帖人的要求。

    from django.db import models
    
    class Users(models.Model):
        pass
    
    class Sample(models.Model):
        users = models.ManyToManyField(Users)
    

    现在,在 shell 或其他代码中,创建 2 个用户,创建一个示例对象,然后将用户批量添加到该示例对象。

    Users().save()
    Users().save()
    
    # Access the through model directly
    ThroughModel = Sample.users.through
    
    users = Users.objects.filter(pk__in=[1,2])
    
    sample_object = Sample()
    sample_object.save()
    
    ThroughModel.objects.bulk_create([
        ThroughModel(users_id=users[0].pk, sample_id=sample_object.pk),
        ThroughModel(users_id=users[1].pk, sample_id=sample_object.pk)
    ])
    

    【讨论】:

    • 我正在尝试使用您的答案here,但我遇到了困难。想法?
    • 知道一个人可以在没有明确定义的中间(通过)类的情况下做到这一点当然是有益的,但是,为了代码的可读性,建议使用中间类。见here
    • 很棒的解决方案!
    • 嗯,这对我有用,但我必须将 bulk_create 中的属性设置为模型实例,而不是 ID。
    【解决方案3】:

    Django 1.9
    一个简单的例子:

    sample_object = Sample()
    sample_object.save()
    
    list_of_users = DestinationRate.objects.all()
    sample_object.users.set(list_of_users)
    

    【讨论】:

      【解决方案4】:

      RelatedObjectManager 是与模型中的字段不同的“属性”。实现您正在寻找的最简单的方法是

      sample_object = Sample.objects.create()
      sample_object.users = [1, 2]
      

      这与分配用户列表相同,无需额外的查询和模型构建。

      如果查询的数量让您感到困扰(而不是简单),那么最佳解决方案需要三个查询:

      sample_object = Sample.objects.create()
      sample_id = sample_object.id
      sample_object.users.through.objects.create(user_id=1, sample_id=sample_id)
      sample_object.users.through.objects.create(user_id=2, sample_id=sample_id)
      

      这会起作用,因为我们已经知道“用户”列表是空的,所以我们可以随意创建。

      【讨论】:

        【解决方案5】:

        您可以用这种方式替换相关对象集(Django 1.9 中的新功能):

        new_list = [user1, user2, user3]
        sample_object.related_set.set(new_list)
        

        【讨论】:

          【解决方案6】:

          如果有人想在自指多对多字段上回答 David Marbles。通过模型的 id 被称为: “to_'model_name_id”和 “来自_'model_name'_id”。

          如果这不起作用,您可以检查 django 连接。

          【讨论】:

            猜你喜欢
            • 2013-06-04
            • 1970-01-01
            • 2018-06-28
            • 1970-01-01
            • 2018-12-19
            • 2019-06-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多