web-dev-qa-db-fra.com

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

Mon modele:

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

Je veux sauver les deux user1 et user2 dans 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 vous obtenez ce que je veux faire. Comment le ferais-tu?

124
Sai Krishna

Vous ne pouvez pas créer de relations m2m à partir d'objets non sauvegardé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 le réponse de saverio , j'ai décidé d'examiner le problème un peu plus en profondeur. Voici mes conclusions.

C'était ma suggestion initiale. Cela fonctionne, mais n'est pas optimal. (Remarque: j'utilise Bars et un Foo à la place de Users et un 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 méthode add():

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 constater, passer plusieurs objets enregistre 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 aussi assigner 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 SELECT supplémentaire:

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 suggéré par saverio:

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

Comme nous ne récupérons pas les deux Bars, nous sauvegardons deux instructions SELECT, 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)
217
Daniel Hepper

Pour les futurs visiteurs, vous pouvez créer un objet et tous ses objets m2m en 2 requêtes à l'aide de la nouvelle - bulk_create dans Django 1.4. Notez que cela n’est utilisable que si vous n’avez pas besoin de aucun pré ou post-traitement des données avec des méthodes ou des 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 Utilisateurs vierge pour imiter ce que demandait l'affiche originale.

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)
])
98
David Marble

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)
16
Slipstream

Les gestionnaires associés sont des "attributs" différents des champs d'un modèle. Le moyen le plus simple de réaliser ce que vous recherchez est

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

Cela revient à assigner une liste d'utilisateurs, sans les requêtes supplémentaires et la construction du modèle.

Si le nombre de requêtes vous dérange (au lieu de la simplicité), 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 et que nous pouvons créer sans réfléchir.

8
rewritten

Vous pouvez remplacer l'ensemble d'objets liés de cette manière (nouveauté Django 1.9):

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