¿Cómo crear un objeto para un modelo Django con un campo de muchos a muchos?

138

Mi modelo:

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

Quiero guardar ambos user1y user2en ese modelo:

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

Sé que eso está mal, pero estoy seguro de que obtienes lo que quiero hacer. Como lo harias ?

Sai Krishna
fuente

Respuestas:

241

No puede crear relaciones m2m a partir de objetos no guardados. Si tienes el pks, prueba esto:

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

Actualización: Después de leer la respuesta del saverio , decidí investigar el tema un poco más en profundidad. Aquí están mis hallazgos.

Esta fue mi sugerencia original. Funciona, pero no es óptimo. (Nota: estoy usando Bars y a en Foolugar de Users y a Sample, pero entiendes la 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 total de 7 consultas:

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)

Estoy seguro de que podemos hacerlo mejor. Puede pasar varios objetos al add()método:

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

Como podemos ver, pasar varios objetos guarda 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)

No sabía que también puedes asignar una lista de objetos:

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

Desafortunadamente, eso crea uno adicional 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)

Intentemos asignar una lista de pks, como sugirió saverio:

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

Como no buscamos las dos Bars, guardamos dosSELECT declaraciones, lo que resulta en 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)

Y el ganador es:

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

Pasar pks a add()nos da un total de 4 consultas:

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
fuente
25
Me gustaría agregar, puede pasar una lista con * así: foo.bars.add (* list) y explotará la lista en argumentos: D
Guillermo Siliceo Trueba
1
¡Estos deberían ser los Django Docs sobre ManyToMany! mucho más claro que docs.djangoproject.com/en/1.10/topics/db/examples/many_to_many o docs.djangoproject.com/en/1.10/ref/models/fields , y también con las penalizaciones de rendimiento para los diferentes métodos incluidos. ¿Quizás pueda actualizarlo para Django 1.9? (el método establecido)
gabn88
Quiero guardar el modelo con una sola identificación con más de un artículo y cantidad. ¿¿Será posible?? class Carrito (models.Model): item = models.ForeignKey (Item, verbose_name = "item") cantidad = models.PositiveIntegerField (default = 1)
Nitesh
Guau. Estoy impresionado. : D
М.Б.
111

Para futuros visitantes, puede crear un objeto y todos sus objetos m2m en 2 consultas utilizando el nuevo bulk_create en django 1.4. Tenga en cuenta que esto solo se puede usar si no requiere ningún procesamiento previo o posterior de los datos con métodos o señales save (). Lo que inserte es exactamente lo que estará en el DB

Puede hacer esto sin especificar un modelo "a través" en el campo. Para completar, el siguiente ejemplo crea un modelo de Usuarios en blanco para imitar lo que el cartel original estaba pidiendo.

from django.db import models

class Users(models.Model):
    pass

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

Ahora, en un shell u otro código, cree 2 usuarios, cree un objeto de muestra y agregue en masa los usuarios a ese objeto de muestra.

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
fuente
Estoy tratando de usar tu respuesta aquí, pero me estoy atascando. Pensamientos?
Pureferret
Sin duda, es informativo saber que se puede hacer esto sin una clase Intermedia (explícitamente) definida, sin embargo, para la legibilidad del código se recomienda usar una clase Intermedia. Ver aquí .
colm.anseo
¡Una solución increíble!
pymarco
19

Django 1.9
Un ejemplo rápido:

sample_object = Sample()
sample_object.save()

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

RelatedObjectManagers son diferentes "atributos" que los campos en un Modelo. La forma más sencilla de lograr lo que estás buscando es

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

Es lo mismo que asignar una lista de usuarios, sin las consultas adicionales y la construcción del modelo.

Si el número de consultas es lo que le molesta (en lugar de la simplicidad), entonces la solución óptima requiere tres consultas:

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)

Esto funcionará porque ya sabemos que la lista de 'usuarios' está vacía, por lo que podemos crear sin pensar.

reescrito
fuente
2

Puede reemplazar el conjunto de objetos relacionados de esta manera (nuevo en Django 1.9):

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

Si alguien está buscando responder a David Marbles en un campo ManyToMany autorreferido. Los identificadores del modelo through se denominan: "to_'model_name_id" y "from_'model_name'_id".

Si eso no funciona, puede verificar la conexión de django.

jopjk
fuente