¿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 ?

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)

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

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)

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.


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)

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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.