Campos calculados en django


Las operaciones sobre los objetos model de django se mapean a SQL mediante el ORM (Object-relational mapping). Un problema que tiene el ORM de django es que no tiene soporte directo para los campos calculados, que son aquellos que se obtienen a partir de los valores de los campos del registro para cada registro.

Por ejemplo, tenemos unos movimientos en los que interviene cantidad, precio y comisión. ¿Qué sentido tiene guardar en la BD el campo importe si en realidad ya tenemos toda la información para calcularlo?.

class Movimiento(models.Model):
   cantidad=models.DecimalField()
   precio=models.DecimalField()
   comision=models.DecimalField()

¿Cómo calculamos entonces el importe?. Hay dos alternativas: Una usando una property de python en el propio objeto model y otra usando extra en el queryset. Veamos:

class Movimiento(models.Model):
   cantidad=models.DecimalField()
   precio=models.DecimalField()
   comision=models.DecimalField()

   def _get_importe(self):
      return self.cantidad*self.cambio*(1-self.comision)
   importe = property(_get_importe)

Y ahí tenemos el importe del movimiento via movimiento.importe

La otra solución es usar extra en el queryset para añadir "a mano" el campo en la consulta SQL, sería:

target=movimiento.objects.extra(select={
'importe': 'cantidad*cambio*(1-comision)',})
for f in target:
print f.importe

Entonces, ¿Cuál es el problema?. Pues que todo esto son soluciones "de mentirijilla" puesto que realmente el campo importe no existe como tal en el gestor de BD y no podemos hacer cosas como sumar todos los importes haciendo una agregación, así esto

total_importe=modelo.aggregate(Sum('importe'))

Nos genera un error diciendo que el campo importe no existe.

Sin duda, el soporte para campos calculados es uno de las mejoras del ORM que puede trabajarse.


Fuentes:
http://stackoverflow.com/questions/3690343/django-orm-equivalent-for-this-sql-calculated-field-derived-from-related-table