Как создать объект для модели 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)

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.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.