如何为具有多对多字段的Django模型创建对象?


138

我的模特:

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

我想同时保存user1并保存user2在该模型中:

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

我知道这是错误的,但是我敢肯定,您会明白我的意思。你会怎么做?

Answers:


241

您不能从未保存的对象创建m2m关系。如果有pk,请尝试以下操作:

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

更新:阅读了saverio的答案后,我决定对这个问题进行更深入的研究。这是我的发现。

这是我最初的建议。它可以工作,但不是最佳选择。(注意:我使用Bar的是s和a Foo而不是Users和a 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)

让我们尝试分配一个pks 列表,如saverio建议的那样:

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)

25
我想添加一下,您可以像这样传递带有*的列表:foo.bars.add(* list),它将列表扩展为参数:D
Guillermo Siliceo Trueba 2013年

1
这应该是关于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更新它?(设置方法)
gabn88

我要保存具有多个项目和数量的单个ID的模型。有可能吗?类Cart(models.Model):item = models.ForeignKey(Item,verbose_name =“ item”)数量= models.PositiveIntegerField(default = 1)
Nitesh

哇。我很惊讶。:d
М.Б.

111

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

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

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)
])

我正在尝试在这里使用您的答案但我被卡住了。有什么想法吗?
Pureferret

知道没有显式定义的中间(通过)类就可以做到这一点当然很有帮助,但是,建议使用中间类来提高代码的可读性。看这里
colm.anseo,2013年

很棒的解决方案!
pymarco

19

Django 1.9
一个简单的例子:

sample_object = Sample()
sample_object.save()

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

8

RelatedObjectManagers与Model中的字段是不同的“属性”。实现您想要的最简单的方法是

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)

这将起作用,因为我们已经知道“用户”列表为空,因此我们可以轻松创建。


2

您可以通过以下方式替换相关对象集(Django 1.9中的新增功能):

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

0

如果有人想做David Marbles,请回答自我引用ManyToMany字段。直通模型的ID称为:“ to_'model_name_id”和“ from_'model_name'_id”。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.