Bagaimana cara membuat objek untuk model Django dengan bidang banyak ke banyak?

138

Model saya:

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

Saya ingin menyimpan keduanya user1dan user2dalam model itu:

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

Saya tahu itu salah, tapi saya yakin Anda mendapatkan apa yang ingin saya lakukan. Bagaimana Anda melakukannya?

Sai Krishna
sumber

Jawaban:

241

Anda tidak dapat membuat hubungan m2m dari objek yang belum disimpan. Jika Anda memiliki pks, coba ini:

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

Pembaruan: Setelah membaca jawaban saverio , saya memutuskan untuk menyelidiki masalah ini sedikit lebih mendalam. Inilah temuan saya.

Ini adalah saran asli saya. Ini bekerja, tetapi tidak optimal. (Catatan: Saya menggunakan Bars dan a Foobukan Users dan a Sample, tetapi Anda mendapatkan idenya).

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

Ini menghasilkan total 7 pertanyaan:

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)

Saya yakin kita bisa melakukan yang lebih baik. Anda dapat mengirimkan beberapa objek ke add()metode:

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

Seperti yang bisa kita lihat, melewati beberapa objek menyimpan satu 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)

Saya tidak menyadari bahwa Anda juga dapat menetapkan daftar objek:

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

Sayangnya, itu menciptakan satu tambahan 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)

Mari kita coba menetapkan daftar pks, seperti yang disarankan saverio:

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

Karena kami tidak mengambil keduanya Bar, kami menyimpan dua SELECTpernyataan, menghasilkan total 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)

Dan pemenangnya adalah:

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

Lulus pks untuk add()memberi kami total 4 pertanyaan:

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)
Daniel Hepper
sumber
25
Saya ingin menambahkan, Anda dapat memberikan daftar dengan * seperti: foo.bars.add (* list) dan itu akan meledakkan daftar menjadi argumen: D
Guillermo Siliceo Trueba
1
Ini seharusnya menjadi Django Docs tentang ManyToMany! jauh lebih jelas maka docs.djangoproject.com/en/1.10/topics/db/examples/many_to_many atau docs.djangoproject.com/en/1.10/ref/models/fields , dan juga dengan penalti kinerja untuk metode yang berbeda termasuk. Mungkin Anda dapat memperbaruinya untuk Django 1.9? (metode yang ditetapkan)
gabn88
Saya ingin menyimpan model dengan id tunggal dengan lebih dari satu item dan kuantitas. Apakah mungkin ?? kelas Cart (models.Model): item = models.ForeignKey (Item, verbose_name = "item") jumlah = models.PositiveIntegerField (default = 1)
Nitesh
Wow. Saya terkagum. : D
М.Б.
111

Untuk pengunjung masa depan, Anda dapat membuat objek dan semua objek m2m dalam 2 kueri menggunakan bulk_create di Django 1.4. Catatan bahwa ini hanya dapat digunakan jika Anda tidak memerlukan setiap pra atau pasca-pengolahan data dengan save () metode atau sinyal. Apa yang Anda masukkan persis apa yang akan ada di DB

Anda dapat melakukan ini tanpa menentukan model "through" di lapangan. Untuk kelengkapan, contoh di bawah ini membuat model Pengguna kosong untuk meniru apa yang diminta poster asli.

from django.db import models

class Users(models.Model):
    pass

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

Sekarang, dalam sebuah shell atau kode lain, buat 2 pengguna, buat objek sampel, dan tambahkan banyak pengguna ke objek sampel itu.

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)
])
David Marble
sumber
Saya mencoba menggunakan jawaban Anda di sini tetapi saya macet. Pikiran?
Pureferret
itu tentu informatif untuk mengetahui seseorang dapat melakukan ini tanpa kelas Menengah (melalui) didefinisikan secara eksplisit, namun, untuk pembacaan kode menggunakan kelas Menengah dianjurkan. Lihat di sini .
colm.anseo
solusi yang luar biasa!
pymarco
19

Django 1.9
Contoh cepat:

sample_object = Sample()
sample_object.save()

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

RelatedObjectManagers berbeda "atribut" dari bidang dalam Model. Cara paling sederhana untuk mencapai apa yang Anda cari adalah

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

Itu sama dengan menetapkan daftar Pengguna, tanpa permintaan tambahan dan pembuatan model.

Jika jumlah kueri yang mengganggu Anda (alih-alih kesederhanaan), maka solusi optimal membutuhkan tiga kueri:

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)

Ini akan berhasil karena kita sudah tahu bahwa daftar 'pengguna' kosong, sehingga kita dapat membuat tanpa berpikir.

ditulis ulang
sumber
2

Anda bisa mengganti set objek terkait dengan cara ini (baru di Django 1.9):

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

Jika seseorang ingin melakukan jawaban David Marbles pada bidang ManyToMany yang merujuk sendiri. Id dari model through disebut: "to_'model_name_id" dan "from_'model_name'_id".

Jika itu tidak berhasil, Anda dapat memeriksa koneksi Django.

jopjk
sumber