django – orden de interrogación por campo calculado

Quiero tener un modelo con campos calculados que pueda aplicar ordenando. Por ejemplo, digamos que tengo el siguiente modelo:

class Foo(models.Model): A = models.IntegerField(..) B = models.IntegerField(..) C = models.ForeignKey(..) 

Quiero tener un D y un campo E que se calculan mediante las siguientes fórmulas:

  1. D = A – B
  2. E = A – X (donde X es un campo del registro relevante del modelo C)

Implementar esto sería trivial si no tuviera que aplicar la clasificación; Solo agregaría propiedades a la clase de modelo. Sin embargo, necesito ordenar por estos campos.

Una solución es traer todos los registros a la memoria y hacer la clasificación allí, lo que concibo como último recurso (romperá las cosas con respecto a la paginación).

¿Hay alguna manera de lograr lo que estoy intentando? Cualquier orientación es apreciada.

EDITAR: La desnormalización es un no-go. El valor del campo X cambia con mucha frecuencia y muchos registros de Foo están relacionados con un registro del modelo C. Una actualización de X requerirá miles de actualizaciones de E.

Me gustaría echar un vistazo al método adicional en Queryset y especificar el parámetro order_by .

Si no te importa un poco de duplicación lógica, entonces funcionará lo siguiente:

 Foo.objects.extra(select={'d_field': 'A - B'}).extra(order_by=['d_field']) 

Por favor absténgase de usar extra() ya que está destinado a ser obsoleto en el futuro.

Desde Django 1.7 puedes usar una combinación de annotate() y order_by() para lograr esto

 Foo.objects.annotate(ordering=F('A') - F('B')).order_by('ordering') 

También hay trabajo pendiente para permitir que las expresiones se utilicen en todo el ORM, por lo que lo siguiente debería funcionar en las versiones futuras de Django:

 Foo.objects.order_by(F('A') - F('B')) 

Como dice Simon , ahora puede usar expresiones en las consultas, y esos valores se calcularán en la base de datos. Estas son las consultas sobre las que ha preguntado con la nueva técnica de clasificación:

 Foo.objects.order_by(F('a') - F('b')) Foo.objects.order_by(F('a') - F('bar__x')) 

Aquí hay un ejemplo ejecutable completo que juega con estas expresiones:

 # Tested with Django 1.9.2 import logging import sys import django from django.apps import apps from django.apps.config import AppConfig from django.conf import settings from django.db import connections, models, DEFAULT_DB_ALIAS from django.db.models import F from django.db.models.base import ModelBase from django.db.models.functions import Concat, Value from mock import patch, PropertyMock, MagicMock NAME = 'udjango' def main(): setup() class Bar(models.Model): x = models.IntegerField() class Foo(models.Model): a = models.IntegerField() b = models.IntegerField() bar = models.ForeignKey(Bar) syncdb(Bar) syncdb(Foo) bar1 = Bar.objects.create(x=1) bar5 = Bar.objects.create(x=5) Foo.objects.create(a=10, b=3, bar=bar1) Foo.objects.create(a=13, b=3, bar=bar5) Foo.objects.create(a=15, b=9, bar=bar1) print(Foo.objects.annotate(ordering=F('a') - F('b')) .order_by('ordering').values_list('a', 'b', 'bar__x', 'ordering')) # >>> [(15, 9, 1, 6), (10, 3, 1, 7), (13, 3, 5, 10)] print(Foo.objects.annotate(ordering=F('a') - F('bar__x')) .order_by('ordering').values_list('a', 'b', 'bar__x', 'ordering')) # >>> [(13, 3, 5, 8), (10, 3, 1, 9), (15, 9, 1, 14)] print(Foo.objects.order_by(F('a') - F('b')).values_list('a', 'b', 'bar__x')) # >>> [(15, 9, 1), (10, 3, 1), (13, 3, 5)] print(Foo.objects.order_by(F('a') - F('bar__x')).values_list('a', 'b', 'bar__x')) # >>> [(13, 3, 5), (10, 3, 1), (15, 9, 1)] logging.info('Done.') def setup(): db_file = NAME + '.db' with open(db_file, 'w'): pass # wipe the database settings.configure( DEBUG=True, DATABASES={ DEFAULT_DB_ALIAS: { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': db_file}}, LOGGING={'version': 1, 'disable_existing_loggers': False, 'formatters': { 'debug': { 'format': '%(asctime)s[%(levelname)s]' '%(name)s.%(funcName)s(): %(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S'}}, 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'debug'}}, 'root': { 'handlers': ['console'], 'level': 'INFO'}, 'loggers': { "django.db": {"level": "DEBUG"}}}) app_config = AppConfig(NAME, sys.modules['__main__']) apps.populate([app_config]) django.setup() original_new_func = ModelBase.__new__ # noinspection PyDecorator @staticmethod def patched_new(cls, name, bases, attrs): if 'Meta' not in attrs: class Meta: app_label = NAME attrs['Meta'] = Meta return original_new_func(cls, name, bases, attrs) ModelBase.__new__ = patched_new def syncdb(model): """ Standard syncdb expects models to be in reliable locations. Based on https://github.com/django/django/blob/1.9.3 /django/core/management/commands/migrate.py#L285 """ connection = connections[DEFAULT_DB_ALIAS] with connection.schema_editor() as editor: editor.create_model(model) main() 

Actualmente no tengo una instalación de Django en ejecución, pero creo que lo que estás preguntando es cómo hacer un guardado personalizado, de forma que D y E se generan automáticamente. No sé cuál es el retorno de su ForeignKey en Unicode , así que supongo que no es una cadena y la asignación de “valueName” como token vlaue para el número entero que desea utilizar.

De todos modos, debería ser un poco como esto:

 class Foo(models.Model): A = models.IntegerField(..) B = models.IntegerField(..) C = models.ForeignKey(..) D = models.IntegerField(..) E = models.IntegerField(..) def save(self): self.D = self.A - self.B self.E = self.A - self.C.valueName super(Foo, self).save() 

Cualquier cosa anterior a la última línea de eso (super ()) será PRE save, cualquier cosa después es POST. Ese es realmente el punto más importante allí.

Me parece que sin * args y ** kwargs en el método de guardar, devuelve un error. Y como dijo Celopes, esta es solo una solución si no te importa materializar el campo calculado en la base de datos.

 class Foo(models.Model): A = models.IntegerField(..) B = models.IntegerField(..) C = models.ForeignKey(..) D = models.IntegerField(..) E = models.IntegerField(..) def save(self, *args, **kwargs): self.D = self.A - self.B self.E = self.A - self.CX super(Foo, self).save(*args, **kwargs) class Meta: ordering = ["E", "D"]