Wie erstelle ich ein Objekt für ein Django-Modell mit vielen zu vielen Feldern?

138

Mein Modell:

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

Ich möchte beide user1und user2in diesem Modell speichern :

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

Ich weiß, dass das falsch ist, aber ich bin sicher, Sie bekommen, was ich tun möchte. Wie würdest du es machen ?

Sai Krishna
quelle

Antworten:

240

Sie können keine m2m-Beziehungen aus nicht gespeicherten Objekten erstellen. Wenn Sie das pks haben, versuchen Sie dies:

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

Update: Nachdem ich die Antwort des Sparers gelesen hatte , beschloss ich, das Problem etwas eingehender zu untersuchen. Hier sind meine Ergebnisse.

Dies war mein ursprünglicher Vorschlag. Es funktioniert, ist aber nicht optimal. (Hinweis: Ich verwende Bars und a Fooanstelle von Users und a Sample, aber Sie haben die Idee).

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

Es werden insgesamt 7 Abfragen generiert:

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)

Ich bin sicher, wir können es besser machen. Sie können mehrere Objekte an die add()Methode übergeben:

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

Wie wir sehen können, spart das Übergeben mehrerer Objekte eines 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)

Mir war nicht bewusst, dass Sie auch eine Liste von Objekten zuweisen können:

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

Leider schafft das eine zusätzliche 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)

Versuchen wir, eine Liste von pks zuzuweisen , wie von saverio vorgeschlagen:

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

Da wir die beiden Bars nicht abrufen , speichern wir zwei SELECTAnweisungen, was insgesamt 5 ergibt:

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)

Und der Gewinner ist:

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

Wenn Sie pks an übergeben add(), erhalten Sie insgesamt 4 Abfragen:

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
quelle
25
Ich möchte hinzufügen, Sie können eine Liste mit * wie folgt übergeben: foo.bars.add (* Liste) und es wird die Liste in Argumente explodieren: D
Guillermo Siliceo Trueba
1
Dies sollten die Django Docs über ManyToMany sein! viel klarer als docs.djangoproject.com/de/1.10/topics/db/examples/many_to_many oder docs.djangoproject.com/de/1.10/ref/models/fields und auch mit den Leistungseinbußen für die verschiedenen Methoden. Vielleicht können Sie es für Django 1.9 aktualisieren? (die eingestellte Methode)
gabn88
Ich möchte ein Modell mit einer einzelnen ID mit mehr als einem Artikel und einer Menge speichern. Wird es möglich sein?? Klasse Cart (models.Model): item = models.ForeignKey (Item, verbose_name = "item") Quantität = models.PositiveIntegerField (Standard = 1)
Nitesh
Beeindruckend. Ich bin erstaunt. : D
М.Б.
111

Für zukünftige Besucher können Sie ein Objekt und alle seine m2m-Objekte in 2 Abfragen mit dem neuen Bulk_Create in Django 1.4 erstellen. Beachten Sie, dass dies nur verwendet werden kann, wenn Sie keine Vor- oder Nachbearbeitung der Daten mit save () -Methoden oder -Signalen benötigen . Was Sie einfügen, ist genau das, was in der DB sein wird

Sie können dies tun, ohne ein "Durch" -Modell auf dem Feld anzugeben. Der Vollständigkeit halber erstellt das folgende Beispiel ein leeres Benutzermodell, um die Anforderungen des Originalplakats nachzuahmen.

from django.db import models

class Users(models.Model):
    pass

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

Erstellen Sie nun in einer Shell oder einem anderen Code zwei Benutzer, erstellen Sie ein Beispielobjekt und fügen Sie die Benutzer in großen Mengen zu diesem Beispielobjekt hinzu.

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
quelle
Ich versuche hier Ihre Antwort zu verwenden , aber ich stecke fest. Gedanken?
Pureferret
Es ist sicherlich informativ zu wissen, dass dies ohne eine explizit definierte Intermediate (through) -Klasse möglich ist. Aus Gründen der Lesbarkeit des Codes wird jedoch die Verwendung einer Intermediate-Klasse empfohlen. Siehe hier .
colm.anseo
tolle Lösung!
Pymarco
19

Django 1.9
Ein kurzes Beispiel:

sample_object = Sample()
sample_object.save()

list_of_users = DestinationRate.objects.all()
sample_object.users.set(list_of_users)
Windschatten
quelle
8

RelatedObjectManager sind andere "Attribute" als Felder in einem Modell. Der einfachste Weg, um das zu erreichen, wonach Sie suchen, ist

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

Dies entspricht dem Zuweisen einer Benutzerliste ohne die zusätzlichen Abfragen und die Modellbildung.

Wenn Sie die Anzahl der Abfragen stört (anstelle der Einfachheit), erfordert die optimale Lösung drei Abfragen:

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)

Dies funktioniert, da wir bereits wissen, dass die Benutzerliste leer ist, sodass wir sinnlos erstellen können.

umgeschrieben
quelle
2

Sie können den Satz verwandter Objekte auf diese Weise ersetzen (neu in Django 1.9):

new_list = [user1, user2, user3]
sample_object.related_set.set(new_list)
iraj jelodari
quelle
0

Wenn jemand nach David Marbles sucht, antworten Sie auf ein selbstverweisendes ManyToMany-Feld. Die IDs des Durchgangsmodells heißen: "to_'model_name_id" und "from_'model_name'_id".

Wenn das nicht funktioniert, können Sie die Django-Verbindung überprüfen.

jopjk
quelle