Jak stworzyć obiekt dla modelu Django z polem wiele do wielu?

138

Mój model:

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

Chcę zapisać oba user1iw user2tym modelu:

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

Wiem, że to źle, ale jestem pewien, że dostaniesz to, czego chcę. Jak byś to zrobił ?

Sai Krishna
źródło

Odpowiedzi:

241

Nie można tworzyć relacji m2m z niezapisanych obiektów. Jeśli masz pk, spróbuj tego:

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

Aktualizacja: po przeczytaniu odpowiedzi saverio , postanowiłem dokładniej zbadać problem. Oto moje ustalenia.

To była moja pierwotna sugestia. Działa, ale nie jest optymalne. (Uwaga: używam Bars i a Foozamiast Usersi a Sample, ale masz pomysł).

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

Generuje aż 7 zapytań:

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)

Jestem pewien, że możemy zrobić lepiej. Do add()metody można przekazać wiele obiektów :

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

Jak widać, przekazanie wielu obiektów oszczędza jeden 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)

Nie wiedziałem, że możesz również przypisać listę obiektów:

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

Niestety, tworzy to jeden dodatkowy 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)

Spróbujmy przypisać listę pks, jak zasugerował saverio:

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

Ponieważ nie pobieramy dwóch Bars, zapisujemy dwie SELECTinstrukcje, w sumie 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)

A zwycięzcą jest:

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

Przekazanie pks do add()daje w sumie 4 zapytania:

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
źródło
25
Dodam, że możesz przekazać listę za pomocą * tak: foo.bars.add (* list), a lista zostanie
rozbita
1
To powinny być Django Docs o ManyToMany! znacznie jaśniejsze niż docs.djangoproject.com/en/1.10/topics/db/examples/many_to_many lub docs.djangoproject.com/en/1.10/ref/models/fields , a także z karami za wydajność dla różnych metod. Może możesz zaktualizować go do Django 1.9? (metoda zestawu)
gabn88
Chcę zapisać model z jednym identyfikatorem z więcej niż jednym przedmiotem i ilością. Czy będzie to możliwe?? class Koszyk (modeles.Model): item = models.ForeignKey (Item, verbose_name = "item") quantity = model.PositiveIntegerField (domyślnie = 1)
Nitesh
Łał. Jestem pod wrażeniem. : D
М.Б.
111

Dla przyszłych odwiedzających możesz utworzyć obiekt i wszystkie jego obiekty m2m w 2 zapytaniach, używając nowego bulk_create w django 1.4. Zauważ, że jest to użyteczne tylko wtedy, gdy nie potrzebujesz żadnego przetwarzania wstępnego lub końcowego na danych za pomocą metod lub sygnałów save (). To, co wstawisz, jest dokładnie tym, co będzie w DB

Możesz to zrobić bez określania modelu „przez” na polu. Aby uzyskać kompletność, poniższy przykład tworzy pusty model użytkowników, aby naśladować to, o co prosił oryginalny plakat.

from django.db import models

class Users(models.Model):
    pass

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

Teraz w powłoce lub innym kodzie utwórz 2 użytkowników, utwórz przykładowy obiekt i zbiorczo dodaj użytkowników do tego przykładowego obiektu.

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
źródło
Próbuję użyć tutaj twojej odpowiedzi , ale utknąłem. Myśli?
Pureferret
z pewnością pouczające jest wiedzieć, że można to zrobić bez jawnie zdefiniowanej klasy pośredniej (przez), jednak dla czytelności kodu zaleca się użycie klasy pośredniej. Zobacz tutaj .
colm.anseo
super rozwiązanie!
pymarco
19

Django 1.9
Szybki przykład:

sample_object = Sample()
sample_object.save()

list_of_users = DestinationRate.objects.all()
sample_object.users.set(list_of_users)
Strumień
źródło
8

RelatedObjectManagers to inne „atrybuty” niż pola w modelu. Najprostszym sposobem na osiągnięcie tego, czego szukasz, jest

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

To to samo, co przypisanie listy użytkowników, bez dodatkowych zapytań i budowania modelu.

Jeśli przeszkadza Ci liczba zapytań (zamiast prostoty), to optymalne rozwiązanie wymaga trzech zapytań:

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)

To zadziała, ponieważ już wiemy, że lista „użytkowników” jest pusta, więc możemy bezmyślnie tworzyć.

przepisany
źródło
2

W ten sposób można zamienić zestaw powiązanych obiektów (nowość w Django 1.9):

new_list = [user1, user2, user3]
sample_object.related_set.set(new_list)
iraj jelodari
źródło
0

Jeśli ktoś chce zrobić David Marbles, odpowiedz na pole ManyToMany, które odsyła do siebie. Identyfikatory modelu przelotowego nazywane są: „to_'model_name_id” i „from_'model_name'_id”.

Jeśli to nie zadziała, możesz sprawdzić połączenie django.

jopjk
źródło