วิธีการสร้างวัตถุสำหรับรุ่น 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()

ฉันรู้ว่ามันผิด แต่ฉันแน่ใจว่าคุณได้สิ่งที่ฉันต้องการ คุณจะทำอย่างไร

คำตอบ:


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)

อย่างที่เราเห็นการผ่านวัตถุหลายชิ้นจะบันทึกหนึ่ง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]

เนื่องจากเราไม่ได้ดึงข้อมูลทั้งสองBarมาเราจึงบันทึก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)

การส่งข้อความpkเพื่อ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
ฉันต้องการเพิ่มคุณสามารถส่งรายการที่มี * like ดังนี้: foo.bars.add (* list) และมันจะระเบิดรายการเป็นอาร์กิวเมนต์: D
Guillermo Siliceo Trueba

1
นี่ควรเป็นเอกสาร Django เกี่ยวกับ ManyToMany! ชัดเจนมากขึ้นจากนั้นdocs.djangoproject.com/en/1.10/topics/db/examples/many_to_manyหรือdocs.djangoproject.com/en/1.10/ref/models/fieldsและรวมถึงบทลงโทษสำหรับวิธีการที่แตกต่างกันรวมอยู่ด้วย บางทีคุณสามารถอัปเดตสำหรับ Django 1.9 ได้ไหม? (วิธีการตั้งค่า)
gabn88

ฉันต้องการบันทึกโมเดลด้วย id เดียวที่มีมากกว่าหนึ่งไอเท็มและปริมาณ มันจะเป็นไปได้หรือไม่? รถเข็นของชั้นเรียน (models.Model): item = models.ForeignKey (Item, verbose_name = "item") ปริมาณ = models.PositiveIntegerField (ค่าเริ่มต้น = 1)
Nitesh

ว้าว. ฉันประหลาดใจ. : D
М.Б.

111

สำหรับผู้เยี่ยมชมในอนาคตคุณสามารถสร้างวัตถุและวัตถุ m2m ทั้งหมดใน2 แบบสอบถามโดยใช้bulk_createใหม่ใน django 1.4 ทราบว่านี้สามารถใช้งานได้เฉพาะในกรณีที่คุณไม่จำเป็นต้องใด ๆก่อนหรือหลังการประมวลผลข้อมูลที่มีการบันทึก () วิธีหรือสัญญาณ สิ่งที่คุณแทรกคือสิ่งที่จะอยู่ในฐานข้อมูล

คุณสามารถทำได้โดยไม่ระบุรุ่น "ถึง" บนฟิลด์ เพื่อความสมบูรณ์ตัวอย่างด้านล่างนี้สร้างโมเดลผู้ใช้เปล่า ๆ เพื่อเลียนแบบสิ่งที่ผู้โพสต์ดั้งเดิมถาม

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

แน่นอนว่าเป็นข้อมูลที่รู้ว่าสามารถทำได้โดยไม่ต้องมีคลาส Intermediate (ถึง) ที่กำหนดไว้อย่างชัดเจนอย่างไรก็ตามสำหรับการอ่านรหัสโดยใช้คลาส Intermediate แนะนำ ดูที่นี่
colm.anseo

ทางออกที่ยอดเยี่ยม!
pymarco


8

RelatedObjectManagers ที่แตกต่างกันคือ "คุณสมบัติ" กว่าเขตข้อมูลในรูปแบบ วิธีที่ง่ายที่สุดในการบรรลุสิ่งที่คุณต้องการคือ

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 ที่อ้างอิงตนเอง รหัสของโมเดลที่เรียกว่า: "to_'model_name_id" และ "from_'model_name'_id"

หากไม่ได้ผลคุณสามารถตรวจสอบการเชื่อมต่อ django

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.