Come creare un oggetto per un modello Django con un campo molti a molti?


138

Il mio modello:

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

Voglio salvare entrambi user1e user2in quel modello:

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

So che è sbagliato, ma sono sicuro che ottieni quello che voglio fare. Come lo faresti ?

Risposte:


241

Non è possibile creare relazioni m2m da oggetti non salvati. Se hai la pks, prova questo:

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

Aggiornamento: dopo aver letto la risposta di Saverio , ho deciso di approfondire un po 'il problema. Ecco i miei risultati.

Questo era il mio suggerimento originale. Funziona, ma non è ottimale. (Nota: sto usando Bars e a Fooinvece di Users e a Sample, ma ottieni l'idea).

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

Genera un totale enorme di 7 query:

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)

Sono sicuro che possiamo fare di meglio. È possibile passare più oggetti al add()metodo:

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

Come possiamo vedere, il passaggio di più oggetti ne salva uno 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)

Non sapevo che puoi anche assegnare un elenco di oggetti:

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

Sfortunatamente, questo crea un ulteriore 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)

Proviamo ad assegnare un elenco di pks, come suggerito saverio:

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

Poiché non recuperiamo le due Bars, salviamo due SELECTistruzioni, per un totale di 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)

E il vincitore è:

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

Passando pks a add()ci dà un totale di 4 query:

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
Vorrei aggiungere, puoi passare un elenco con * in questo modo: foo.bars.add (* list) e esploderà l'elenco in argomenti: D
Guillermo Siliceo Trueba

1
Questo dovrebbe essere il Django Docs su ManyToMany! molto più chiaro di docs.djangoproject.com/en/1.10/topics/db/examples/many_to_many o docs.djangoproject.com/en/1.10/ref/models/fields e anche con le penalità di rendimento per i diversi metodi inclusi. Forse puoi aggiornarlo per Django 1.9? (il metodo impostato)
gabn88

Voglio salvare il modello con un singolo ID con più di un articolo e quantità. Sarà possibile ?? class Cart (models.Model): item = models.ForeignKey (Item, verbose_name = "item") quantità = models.PositiveIntegerField (default = 1)
Nitesh

Wow. Sono stupito. : D
М.Б.

111

Per i futuri visitatori, puoi creare un oggetto e tutti i suoi oggetti m2m in 2 query utilizzando il nuovo bulk_create in django 1.4. Si noti che questo è utilizzabile solo se non si richiede alcun pre o post-elaborazione dei dati con Save metodi o segnali (). Quello che inserisci è esattamente ciò che sarà nel DB

Puoi farlo senza specificare un modello "through" sul campo. Per completezza, l'esempio seguente crea un modello Utenti vuoto per imitare ciò che il poster originale stava chiedendo.

from django.db import models

class Users(models.Model):
    pass

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

Ora, in una shell o altro codice, creare 2 utenti, creare un oggetto campione e aggiungere in blocco gli utenti a quell'oggetto campione.

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

Sto cercando di usare la tua risposta qui ma mi sto bloccando. Pensieri?
Pureferret,

è certamente informativo sapere che si può fare ciò senza una classe Intermedia (esplicitamente definita), tuttavia, per la leggibilità del codice si consiglia di utilizzare una classe Intermedia. Vedi qui .
colm.anseo,

soluzione fantastica!
pymarco,

19

Django 1.9
Un rapido esempio:

sample_object = Sample()
sample_object.save()

list_of_users = DestinationRate.objects.all()
sample_object.users.set(list_of_users)

8

RelatedObjectManagers sono "attributi" diversi dai campi in un modello. Il modo più semplice per ottenere ciò che stai cercando è

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

È come assegnare un elenco utenti, senza le query aggiuntive e la creazione del modello.

Se il numero di query è ciò che ti disturba (anziché la semplicità), la soluzione ottimale richiede tre query:

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)

Funzionerà perché sappiamo già che l'elenco "utenti" è vuoto, quindi possiamo creare senza pensarci.


2

È possibile sostituire il set di oggetti correlati in questo modo (nuovo in Django 1.9):

new_list = [user1, user2, user3]
sample_object.related_set.set(new_list)

0

Se qualcuno sta cercando di fare David Marbles, rispondi in un campo ManyToMany che si riferisce da solo. Gli ID del modello passante sono chiamati: "to_'model_name_id" e "from_'model_name'_id".

Se il problema persiste, puoi controllare la connessione Django.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.