Как создать объект для модели Django с полем многие ко многим?

143

Моя модель:

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

Я знаю, что это неправильно, но уверен, что ты понимаешь то, что я хочу делать. Как бы ты это сделал?

Саи Кришна
источник

Ответы:

254

Невозможно создать отношения 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]

Поскольку мы не получаем два 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 (* list), и он разделит список на аргументы: D
Гильермо Силичео Труба
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
Я хочу сохранить модель с одним идентификатором с более чем одним товаром и количеством. Будет ли это возможно ?? class Cart (models.Model): item = models.ForeignKey (Item, verbose_name = "item") amount = models.PositiveIntegerField (default = 1)
Нитеш
Вау. Я удивлен. : D
М.Б.
114

Для будущих посетителей вы можете создать объект и все его объекты m2m за 2 запроса, используя новый bulk_create в django 1.4. Обратите внимание, что это можно использовать только в том случае, если вам не требуется никакой предварительной или последующей обработки данных с помощью методов или сигналов save (). То, что вы вставляете, - это именно то, что будет в БД

Вы можете сделать это без указания «сквозной» модели в поле. Для полноты изложения в приведенном ниже примере создается пустая модель «Пользователи», имитирующая то, что просил исходный плакат.

from django.db import models

class Users(models.Model):
    pass

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

Теперь в оболочке или другом коде создайте двух пользователей, создайте образец объекта и массово добавьте пользователей к этому образцу объекта.

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 09
классное решение!
pymarco
Хм, это сработало для меня, но мне пришлось установить атрибуты в bulk_create для экземпляров модели, а не для идентификаторов.
Аарон
19

Django 1.9
Быстрый пример:

sample_object = Sample()
sample_object.save()

list_of_users = DestinationRate.objects.all()
sample_object.users.set(list_of_users)
Slipstream
источник
8

RelatedObjectManager - это разные «атрибуты», чем поля в Модели. Самый простой способ добиться того, что вы ищете, - это

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)
Ирадж Джелодари
источник
-1

Если кто-то хочет заняться Дэвидом Марблсом, ответьте на саморегулирующееся поле ManyToMany. Идентификаторы сквозной модели называются: "to_'model_name_id" и "from_'model_name'_id".

Если это не сработает, вы можете проверить соединение django.

Jopjk
источник