web-dev-qa-db-ja.com

多対多フィールドを持つDjangoモデルのオブジェクトを作成する方法は?

私のモデル:

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

私はそれが間違っていることを知っていますが、あなたは私がやりたいことを手に入れると確信しています。どうしますか?

124
Sai Krishna

未保存のオブジェクトから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)

ご覧のとおり、複数のオブジェクトを渡すと、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)

Saverioが示唆したように、pksのリストを割り当てましょう:

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)
217
Daniel Hepper

将来の訪問者のために、Djangoの新しいbulk_createを使用して、オブジェクトとそのすべてのm2mオブジェクトを2クエリで作成できます。 _ 1.4。これは、save()メソッドまたはシグナルを使用して、データのany前処理または後処理を必要としない場合にのみ使用できることに注意してください。挿入するのは、まさにDBにあるものです

これは、フィールドで「スルー」モデルを指定せずに実行できます。完全を期すために、以下の例では、空のユーザーモデルを作成して、元のポスターが求めていたものを模倣します。

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)
])
98
David Marble

Django 1.9
簡単な例:

sample_object = Sample()
sample_object.save()

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

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

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)

「ユーザー」リストが空であることをすでに知っているため、これは機能します。

8
rewritten

この方法で関連オブジェクトのセットを置き換えることができます(Django 1.9の新機能):

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