多対多フィールドを持つDjangoモデルのオブジェクトを作成するにはどうすればよいですか?


138

私のモデル:

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

それは間違っていることはわかっていますが、私がやりたいことはきっと理解できます。どうしますか?

回答:


241

保存されていないオブジェクトからm2m関係を作成することはできません。あなたがpks を持っているなら、これを試してください:

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

更新:saverioの回答を読んだ後、問題をもう少し詳しく調査することにしました。これが私の発見です。

これは私の最初の提案でした。動作しますが、最適ではありません。(注:Barsと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)

ご覧のとおり、複数のオブジェクトを渡すと1つ節約できます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]

残念ながら、これにより1つの追加が作成されます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)

pksaverioが提案したように、sのリストを割り当ててみましょう:

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

2つBarのをフェッチしないので、2つの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

1
これは、ManyToManyに関するDjangoドキュメントです。docs.djangoproject.com/en/1.10/topics/db/examples/many_to_manyまたはdocs.djangoproject.com/en/1.10/ref/models/fieldsよりもはるかに明確であり、さまざまなメソッドのパフォーマンスペナルティも含まれています。Django 1.9用に更新できるでしょうか?(setメソッド)
gabn88

複数のアイテムと数量を持つ単一のIDを持つモデルを保存したい。可能でしょうか?Cart(models.Model)クラス:item = models.ForeignKey(Item、verbose_name = "item")数量= models.PositiveIntegerField(default = 1)
Nitesh

ワオ。私は驚いています。:D
М.Б.

111

将来の訪問者のために、django 1.4の新しいbulk_createを使用して、2つのクエリでオブジェクトとそのすべてのm2mオブジェクトを作成できます。あなたが必要としない場合にのみ使用可能であることに注意してください任意の()メソッドや信号の保存とデータの事前または事後処理を。あなたが挿入するものは正確にDBにあるものです

フィールドで「スルー」モデルを指定せずにこれを行うことができます。完全を期すために、以下の例では、元の投稿者の要求を模倣する空のUsersモデルを作成しています。

from django.db import models

class Users(models.Model):
    pass

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

次に、シェルまたは他のコードで、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、2015

明示的に定義された中間(スルー)クラスなしでこれを実行できることを知ることは確かに有益ですが、中間クラスを使用してコードを読みやすくすることをお勧めします。こちらをご覧ください
colm.anseo 2016年

素晴らしいソリューション!
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

RelatedObjectManagerは、モデルのフィールドとは異なる「属性」です。あなたが探しているものを達成する最も簡単な方法は

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

これは、ユーザーリストを割り当てるのと同じですが、追加のクエリとモデルの作成はありません。

クエリの数が煩わしい場合(単純化ではなく)、最適なソリューションには3つのクエリが必要です。

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"と呼ばれます。

それが機能しない場合は、ジャンゴ接続を確認できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.