Як створити об’єкт для моделі Джанго з полем від багатьох до багатьох?


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)

Оновлення: Прочитавши відповідь саверіо , я вирішив вивчити проблему дещо глибше. Ось мої висновки.

Це було моєю оригінальною пропозицією. Це працює, але не є оптимальним. (Примітка. Я використовую 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]

Оскільки ми не отримуємо обидва 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 (* список), і він підірве список в аргументи: D
Гільєрмо Сілікео Істиба

1
Це повинні бути Документи Джанго про 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

Я хочу зберегти модель з одним ідентифікатором з більш ніж одним елементом і кількістю. Чи вдасться ?? class Cart (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

Безумовно, інформативно знати, що можна зробити це без чітко визначеного проміжного (наскрізного) класу, однак для читабельності коду з використанням класу «Проміжний» рекомендується. Дивіться тут .
colm.anseo

приголомшливе рішення!
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 відрізняються "атрибутами", ніж поля в моделі. Найпростіший спосіб досягти того, що ви шукаєте, - це

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

Якщо хтось прагне зробити Девід Марблс відповідь на самовідвідання поля ManyToMany. Ідентифікатори наскрізної моделі називаються: "to_'model_name_id" та "from_'model_name'_id".

Якщо це не працює, ви можете перевірити підключення django.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.