Comment créer un objet pour un modèle Django avec un champ plusieurs à plusieurs?

138

Mon modele:

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

Je veux enregistrer les deux user1et user2dans ce modèle:

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

Je sais que c'est faux, mais je suis sûr que tu comprends ce que je veux faire. Comment feriez-vous cela?

Sai Krishna
la source

Réponses:

241

Vous ne pouvez pas créer de relations m2m à partir d'objets non enregistrés. Si vous avez le pks, essayez ceci:

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

Mise à jour: après avoir lu la réponse du saverio , j'ai décidé d'étudier le problème un peu plus en profondeur. Voici mes conclusions.

C'était ma suggestion originale. Cela fonctionne, mais n'est pas optimal. (Remarque: j'utilise Bars et a Fooau lieu de Users et a Sample, mais vous voyez l'idée).

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

Il génère un total énorme de 7 requêtes:

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)

Je suis sûr que nous pouvons faire mieux. Vous pouvez transmettre plusieurs objets à la add()méthode:

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

Comme nous pouvons le voir, passer plusieurs objets en sauve un 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)

Je ne savais pas que vous pouvez également attribuer une liste d'objets:

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

Malheureusement, cela crée un autre 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)

Essayons d'attribuer une liste de pks, comme l'a suggéré saverio:

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

Comme nous ne récupérons pas les deux Bars, nous sauvegardons deux SELECTinstructions, ce qui donne un total de 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)

Et le gagnant est:

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

Passer pks à add()nous donne un total de 4 requêtes:

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
la source
25
Je voudrais ajouter, vous pouvez passer une liste avec * comme ceci: foo.bars.add (* list) et cela explosera la liste en arguments: D
Guillermo Siliceo Trueba
1
Cela devrait être la documentation Django sur ManyToMany! beaucoup plus clair que docs.djangoproject.com/en/1.10/topics/db/examples/many_to_many ou docs.djangoproject.com/en/1.10/ref/models/fields , et aussi avec les pénalités de performance pour les différentes méthodes incluses. Peut-être pouvez-vous le mettre à jour pour Django 1.9? (la méthode d'ensemble)
gabn88
Je veux enregistrer le modèle avec un identifiant unique avec plus d'un article et une quantité. Sera-t-il possible?? class Cart (models.Model): item = models.ForeignKey (Item, verbose_name = "item") quantity = models.PositiveIntegerField (default = 1)
Nitesh
Sensationnel. Je suis étonné. : D
М.Б.
111

Pour les futurs visiteurs, vous pouvez créer un objet et tous ses objets m2m en 2 requêtes en utilisant le nouveau bulk_create dans django 1.4. Notez que cela n'est utilisable que si vous n'avez besoin d' aucun pré ou post-traitement des données avec les méthodes ou signaux save (). Ce que vous insérez est exactement ce qui sera dans la base de données

Vous pouvez le faire sans spécifier de modèle «à travers» sur le terrain. Par souci d'exhaustivité, l'exemple ci-dessous crée un modèle d'utilisateurs vierge pour imiter ce que l'affiche originale demandait.

from django.db import models

class Users(models.Model):
    pass

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

Maintenant, dans un shell ou un autre code, créez 2 utilisateurs, créez un exemple d'objet et ajoutez en bloc les utilisateurs à cet exemple d'objet.

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
la source
J'essaie d'utiliser votre réponse ici mais je suis coincé. Pensées?
Pureferret
il est certainement instructif de savoir que l'on peut faire cela sans une classe Intermediate (through) explicitement définie, cependant, pour la lisibilité du code en utilisant une classe Intermediate est recommandée. Regardez ici .
colm.anseo
solution géniale!
pymarco
19

Django 1.9
Un exemple rapide:

sample_object = Sample()
sample_object.save()

list_of_users = DestinationRate.objects.all()
sample_object.users.set(list_of_users)
Slipstream
la source
8

Les RelatedObjectManagers sont des «attributs» différents des champs d'un modèle. Le moyen le plus simple d'atteindre ce que vous recherchez est

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

C'est la même chose que d'attribuer une liste d'utilisateurs, sans les requêtes supplémentaires et la construction du modèle.

Si le nombre de requêtes est ce qui vous dérange (au lieu de la simplicité), alors la solution optimale nécessite trois requêtes:

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)

Cela fonctionnera car nous savons déjà que la liste des «utilisateurs» est vide, nous pouvons donc créer sans réfléchir.

réécrit
la source
2

Vous pouvez remplacer l'ensemble des objets associés de cette manière (nouveau dans Django 1.9):

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

Si quelqu'un cherche à faire David Marbles, répondez sur un champ ManyToMany auto-référencé. Les identifiants du modèle through sont appelés: "to_'model_name_id" et "from_'model_name'_id".

Si cela ne fonctionne pas, vous pouvez vérifier la connexion django.

jopjk
la source