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?

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)

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

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)

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.


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)

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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.