queryset filtros example cual create consultas avanzadas python django django-orm

python - filtros - ¿Cuándo busca Django la clave principal de las claves externas?



django shell (5)

Buscando en la fuente de Django , la respuesta está en algunos de los usos mágicos de Django para proporcionar su API agradable.

Cuando crea una instancia de un objeto de Rating , Django se establece (aunque con un poco más de self.movie indirecto para hacer este genérico) self.movie a the_hobbit . Sin embargo, self.movie no es una propiedad normal, sino que se establece a través de __set__ . El método __set__ (vinculado anteriormente) mira el valor ( the_hobbit ) e intenta establecer la propiedad movie_id lugar de movie , ya que es un campo ForeignKey . Sin embargo, dado que the_hobbit.pk es Ninguno, simplemente establece la movie en the_hobbit en the_hobbit lugar. Una vez que intentas guardar tu calificación, intenta volver a buscar movie_id , lo que falla (ni siquiera intenta ver la movie ).

Curiosamente, parece que este comportamiento está cambiando en Django 1.5 .

En lugar de

setattr(value, self.related.field.attname, getattr( instance, self.related.field.rel.get_related_field().attname)) # "self.movie_id = movie.pk"

ahora lo hace

related_pk = getattr(instance, self.related.field.rel.get_related_field().attname) if related_pk is None: raise ValueError(''Cannot assign "%r": "%s" instance isn/'t saved in the database.'' % (value, instance._meta.object_name))

que en su caso daría como resultado un mensaje de error más útil.

Tengo dos modelos simples, uno representa una película y el otro representa una calificación para una película.

class Movie(models.Model): id = models.AutoField(primary_key=True) title = models.TextField() class Rating(models.Model): id = models.AutoField(primary_key=True) movie = models.ForeignKey(Movie) rating = models.FloatField()

Mi expectativa es que primero podría crear una Movie y una Review que Review referencia a esa película y luego enviarlas a la base de datos, siempre y cuando haya confirmado la Movie primero para que se le proporcione una clave principal a la que se refiera la Review .

the_hobbit = Movie(title="The Hobbit") my_rating = Rating(movie=the_hobbit, rating=8.5) the_hobbit.save() my_rating.save()

Para mi sorpresa, todavía generó un IntegrityError quejándose de que estaba tratando de especificar una clave externa nula, incluso la Movie había comprometido y ahora tenía una clave principal.

IntegrityError: null value in column "movie_id" violates not-null constraint

Confirmé esto agregando algunas declaraciones print :

print "the_hobbit.id =", the_hobbit.id # None print "my_rating.movie.id =", my_rating.movie.id # None print "my_rating.movie_id =", my_rating.movie_id # None the_hobbit.save() print "the_hobbit.id =", the_hobbit.id # 3 print "my_rating.movie.id =", my_rating.movie.id # 3 print "my_rating.movie_id =", my_rating.movie_id # None my_rating.save() # raises IntegrityError

El atributo .movie se refiere a una instancia de Movie que no tiene un .id , pero .movie_id mantiene el valor None que tenía cuando se creó la instancia de Movie .

Esperaba que Django buscara .movie.id cuando intenté realizar la Review , pero aparentemente eso no es lo que está haciendo.

Aparte

En mi caso, he manejado este comportamiento anulando el método .save() en algunos modelos para que busquen las claves primarias de las claves externas antes de guardarlas.

def save(self, *a, **kw): for field in self._meta.fields: if isinstance(field, ForeignKey): id_attname = field.attname instance_attname = id_attname.rpartition("_id")[0] instance = getattr(self, instance_attname) instance_id = instance.pk setattr(self, id_attname, instance_id) return Model.save(self, *a, **kw)

Esto es intrépido, pero funciona para mí, así que no estoy buscando realmente una solución a este problema en particular .

Estoy buscando una explicación del comportamiento de Django. ¿En qué puntos busca Django la clave principal para las claves externas? Por favor sea especifico; Las referencias al código fuente de Django serían las mejores.


El tema principal tiene que ver con los efectos secundarios que se quieren o no. Y con las variables realmente siendo punteros a objetos en Python.

Cuando creas un objeto a partir de un modelo, aún no tiene una clave principal, ya que aún no lo has guardado. Pero, al guardarlo, ¿debería Django asegurarse de actualizar los atributos en el objeto ya existente? Una clave principal es lógica, pero también lo llevaría a esperar que se actualicen otros atributos.

Un ejemplo de eso es el manejo de Unicode de Django. Cualquiera que sea el conjunto de caracteres que le dé al texto que ingresa en una base de datos: Django le da unicode una vez que lo saca de nuevo. Pero si crea un objeto (con algún atributo no Unicode) y lo guarda, ¿debería Django modificar ese atributo de texto en su objeto existente? Eso ya suena un poco más peligroso. ¿Cuál es (probablemente) la razón por la que Django no realiza ninguna actualización sobre la marcha de los objetos que le pide que almacene en la base de datos?

Volver a cargar el objeto desde la base de datos le brinda un objeto perfecto con todo lo establecido, pero también hace que su variable apunte a un objeto diferente. Por lo tanto, eso no ayudaría en su ejemplo en caso de que ya haya asignado un puntero a la Calificación en su objeto "Movie" antiguo.

El Movie.objects.create(title="The Hobbit") mencionado por Hedde es el truco aquí. Devuelve un objeto de película de la base de datos , por lo que ya tiene una identificación.

the_hobbit = Movie.objects.create(title="The Hobbit") my_rating = Rating(movie=the_hobbit, rating=8.5) # No need to save the_hobbit, btw, it is already saved. my_rating.save()

(También tuve problemas con la diferencia entre mis objetos y objetos de la base de datos, cuando mi objeto recién creado no generó unicode. La explanation que coloco en mi weblog es la misma que la anterior, pero redactada de manera un poco diferente).


Mi opinión es que después de llamar al método save () en su objeto hobbit, ese objeto se guarda. pero la referencia local que está presente en su objeto my_rating no sabe realmente que tiene que actualizarse con los valores que están presentes en la base de datos.

Entonces, cuando llama a my_rating.movie.id, django no reconoce la necesidad de una consulta de db en el objeto de la película de nuevo y, por lo tanto, obtiene Ninguno , que es el valor que contiene la instancia local de ese objeto.

pero my_rating.movie_id no depende de qué datos están presentes en sus instancias locales, es una forma explícita de pedir a django que examine la base de datos y vea qué información hay a través de la relación de clave externa.


Según lo indicado por los documentos:

Los argumentos de palabras clave son simplemente los nombres de los campos que ha definido en su modelo. Tenga en cuenta que crear una instancia de un modelo de ninguna manera toca su base de datos; para eso, necesitas guardar ().

Agregue un método de clase en la clase modelo:

class Book(models.Model): title = models.CharField(max_length=100) @classmethod def create(cls, title): book = cls(title=title) # do something with the book return book book = Book.create("Pride and Prejudice")

Agregue un método en un administrador personalizado (generalmente preferido):

class BookManager(models.Manager): def create_book(self, title): book = self.create(title=title) # do something with the book return book class Book(models.Model): title = models.CharField(max_length=100) objects = BookManager() book = Book.objects.create_book("Pride and Prejudice")

origen: https://docs.djangoproject.com/en/dev/ref/models/instances/?from=olddocs#creating-objects

Cuando asigna the_hobbit, está asignando una instancia de Película, por lo que no llega a la base de datos. Una vez que llama a ''guardar'', la base de datos se llena, sin embargo, su variable sigue apuntando al objeto en la memoria, sin darse cuenta del cambio repentino en la base de datos.

Dicho esto, cambiar el orden de su secuencia también debería crear efectivamente los objetos:

the_hobbit = Movie(title="The Hobbit") the_hobbit.save() my_rating = Rating(movie=the_hobbit, rating=8.5) my_rating.save()


Solo para completar, ya que no puedo comentar ...

También puede (pero no en este caso) estar dispuesto a cambiar el comportamiento en el lado de la base de datos. Esto puede ser útil para ejecutar algunas pruebas que pueden conducir a problemas similares (ya que se realizan en una confirmación y se retrotraen). A veces puede ser mejor usar este comando pirateado para mantener las pruebas lo más cerca posible del comportamiento real de la aplicación en lugar de empaquetarlas en un TransactionalTestCase:

Tiene que ver con las propiedades de las restricciones ... Ejecutar el siguiente comando SQL también solucionará el problema (solo PostgreSQL):

SET CONSTRAINTS [ALL / NAME] DEFERRABLE INITIALLY IMMEDIATE;