2009-10-30 4 views
20

Je veux avoir un modèle avec des champs calculés sur lesquels je peux appliquer le tri. Par exemple, disons que j'ai le modèle suivant:django - ordonner un jeu de requête par un champ calculé

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

Je veux avoir un D et un champ E qui sont calculées par les formules suivantes:

  1. D = A - B
  2. E = a - X (où X est un champ de l'enregistrement pertinent du modèle C)

la mise en œuvre ce serait trivial si je ne l'ai pas besoin d'appliquer le tri; Je voudrais juste ajouter des propriétés à la classe du modèle. Cependant, j'ai besoin de commander par ces champs.

Une solution est d'aller chercher tous les enregistrements en mémoire et de faire le tri là-bas, que je conçois en dernier recours (ça va casser les choses concernant la pagination).

Existe-t-il un moyen de réaliser ce que j'essaie? Toute orientation est appréciée.

EDIT: La dénormalisation est interdite. La valeur du champ X change très fréquemment et beaucoup d'enregistrements Foo sont liés à un enregistrement du modèle C. Une mise à jour de X nécessitera des milliers de mises à jour de E.

+1

qu'une question similaire a été posée: http : //stackoverflow.com/questions/930865/how-to-sort-by-a-computed-value-in-django – cethegeek

Répondre

15

Je voudrais jeter un oeil à la méthode extra sur Queryset et spécifiez le paramètre order_by.

+0

A moins que D et E ne soient matérialisés dans la base de données, je ne vois pas comment l'extra (order_by) va aider. – cethegeek

+0

La commande @celopes sql fonctionne avec des champs calculés à la volée, donc une méthode supplémentaire convient ici. – shanyu

+0

Ne comptez pas sur 'extra()' pour cela, car il est censé être déprécié. –

1

Je n'ai pas actuellement d'installation de Django en cours d'exécution, mais je pense que vous demandez comment effectuer une sauvegarde personnalisée, de sorte que D et E soient générés automatiquement. Je ne sais pas quel est le retour de votre ForeignKey sur unicode est, donc je suppose que ce n'est pas une chaîne et l'attribution de "valueName" en tant que jeton vlaue pour l'entier que vous souhaitez utiliser.

Quoi qu'il en soit, il faut aller un peu comme ceci:

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

Tout avant la dernière ligne de ce (super()) sera PRE sauver, tout est après le POST. C'est vraiment le point le plus important là-bas.

+1

Cela est OK si shanyu ne dérange pas la matérialisation des champs calculés dans la base de données. – cethegeek

+1

S'il vous plaît voir mon édition, la dénormalisation n'est pas une option. Merci pour l'aide. – shanyu

27

Si vous ne me dérangerait pas une duplicaton logique, alors ce qui suit fonctionnera:

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

Si vous avez besoin de passer des paramètres, vous devez utiliser OrderedDict: https://docs.djangoproject.com/fr/1.8/ref/models/querysets/#extra – fjsj

+0

Ne comptez pas sur 'extra()' pour cela c'est censé être déprécié. –

+1

'Foo.objects.extra (select = {'d_field': 'A - B'}, order_by = ['d_field'])' un 'extra' est assez – WeizhongTu

0

Je trouve que sans les * args et ** kwargs dans la méthode de sauvegarde, il renvoie une erreur. Et comme celopes l'a indiqué, ce n'est qu'une solution si cela ne vous dérange pas matérialiser le champ calculé dans la base de données.

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.C.X 
     super(Foo, self).save(*args, **kwargs) 

    class Meta: 
     ordering = ["E", "D"] 
13

S'il vous plaît s'abstenir d'utiliser extra() comme il est destiné à être dépréciée à l'avenir.

Depuis Django 1.7, vous pouvez utiliser une combinaison de annotate() et order_by() pour atteindre cet

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

Il y a aussi le travail ungoing pour permettre des expressions à utiliser dans le ORM donc ce qui suit devrait fonctionner dans les futures versions de Django:

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

En tant que Simon says, vous pouvez désormais utiliser des expressions dans les requêtes et ces valeurs seront calculées dans la base de données. Voici les questions que vous avez posées au sujet de la nouvelle technique de tri:

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

Voici un exemple complet runnable qui joue avec ces expressions: juste signaler

# 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() 
Questions connexes