176 votes

Comment créer un objet pour un modèle Django avec un champ many to many ?

Mon modèle :

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 mal, mais je suis sûr que tu comprends ce que je veux faire. Comment le feriez-vous ?

289voto

Daniel Hepper Points 4992

Vous ne pouvez pas créer de relations m2m à partir d'objets non sauvegardés. Si vous avez le pk 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 donc décidé d'étudier la question de manière un peu plus approfondie. Voici mes conclusions.

C'était ma suggestion initiale. Elle fonctionne, mais n'est pas optimale. (Note : J'utilise Bar et un Foo au lieu de User 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 impressionnant 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 qu'on peut faire mieux. Vous pouvez passer plusieurs objets à la fonction 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 permet d'économiser 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 l'on pouvait é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 problème supplémentaire 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'assigner une liste de pk comme l'a suggéré Saverio :

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

Comme nous ne récupérons pas les deux Bar nous économisons deux SELECT ce qui donne un total de 5 déclarations :

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)

Passing pk s à 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)

134voto

David Marble Points 693

Pour les futurs visiteurs, vous pouvez créer un objet et tous ses objets m2m dans 2 requêtes en utilisant le nouveau création en masse dans django 1.4. Notez que ceci n'est utilisable que si vous n'avez pas besoin de tout le pré ou post-traitement des données à l'aide de méthodes ou de signaux de type save(). Ce que vous insérez est exactement ce qui se trouvera dans la base de données.

Vous pouvez le faire sans spécifier un modèle "traversant" sur le champ. Pour être complet, l'exemple ci-dessous crée un modèle d'utilisateur vierge pour imiter la question posée par l'auteur de l'article original.

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 objet échantillon, et ajoutez en vrac les utilisateurs à cet objet échantillon.

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

20voto

Kamika Points 328

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)

9voto

rewritten Points 7430

Les RelatedObjectManagers sont des "attributs" différents des champs dans un modèle. La façon la plus simple d'obtenir ce que vous recherchez est la suivante

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 c'est le nombre de requêtes qui vous préoccupe (plutôt que 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, et nous pouvons donc créer sans réfléchir.

4voto

iraj jelodari Points 111

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

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X