2010-01-30 5 views
95
# admin.py 
class CustomerAdmin(admin.ModelAdmin): 
    list_display = ('foo', 'number_of_orders') 

# models.py 
class Order(models.Model): 
    bar = models.CharField[...] 
    customer = models.ForeignKey(Customer) 

class Customer(models.Model): 
    foo = models.CharField[...] 
    def number_of_orders(self): 
     return u'%s' % Order.objects.filter(customer=self).count() 

Comment pourrais-je trier les clients, selon number_of_orders ils? Cette propriété ne peut pas être utilisée ici, car elle nécessite un champ de base de données à trier. Est-ce possible parce que Django s'appuie sur la base de données sous-jacente pour effectuer le tri? Créer un champ agrégé pour contenir le nombre de commandes semble être une surcharge ici. La chose amusante: si vous modifiez url manuellement dans le navigateur pour trier sur cette colonne - cela fonctionne comme prévu!Django admin: comment trier par l'un des champs de list_display personnalisés qui n'a pas de champ de base de données

+0

"La chose amusante: si vous modifiez url à la main dans le navigateur pour trier sur cette colonne - il fonctionne comme prévu!" Vous voulez dire:/admin/myapp/client /? Ot = asc & o = 2 Etes-vous sûr? –

+0

oui, à la fois asc et dsc. Peut-être que cela fonctionne avec des décimales. –

+0

Je ne pense pas que cela fonctionnerait avec plusieurs pages. –

Répondre

120

J'aimé la solution de Greg à ce problème, mais je voudrais souligner que vous pouvez faire la même chose directement dans l'admin:

from django.db import models 

class CustomerAdmin(admin.ModelAdmin): 
    list_display = ('number_of_orders',) 

    def get_queryset(self, request): 
    # def queryset(self, request): # For Django <1.6 
     qs = super(CustomerAdmin, self).get_queryset(request) 
     # qs = super(CustomerAdmin, self).queryset(request) # For Django <1.6 
     qs = qs.annotate(models.Count('order')) 
     return qs 

    def number_of_orders(self, obj): 
     return obj.order__count 
    number_of_orders.admin_order_field = 'order__count' 

De cette façon, vous annoter seulement dans l'interface d'administration. Pas avec toutes les requêtes que vous faites.

+2

Oui, c'est une bien meilleure façon. :) – Greg

+2

Il existe une [modification suggérée] (http://stackoverflow.com/suggested-edits/240451) sur cette réponse. J'ai voté pour le rejeter parce qu'il a supprimé trop de texte. Je ne connais pas Django, je ne sais pas si le changement de code proposé vaut la peine d'être mentionné. – Gilles

+6

Les champs list_display personnalisés prennent 2 arguments: 'self' et' obj'. 'number_of_orders' devrait être' def number_of_orders (self, obj): ' – Eric

0

La seule façon dont je peux penser est de dénormaliser le champ. C'est-à-dire créer un champ réel qui est mis à jour pour rester synchronisé avec les champs dont il est dérivé. Je fais habituellement en surchargeant d'économie sur eith le modèle avec les champs ou le modèle denormalized qu'elle tire de:

# models.py 
class Order(models.Model): 
    bar = models.CharField[...] 
    customer = models.ForeignKey(Customer) 
    def save(self): 
     super(Order, self).save() 
     self.customer.number_of_orders = Order.objects.filter(customer=self.customer).count() 
     self.customer.save() 

class Customer(models.Model): 
    foo = models.CharField[...] 
    number_of_orders = models.IntegerField[...] 
+1

Cela devrait certainement fonctionner, mais ne peut pas le marquer comme accepté en raison du champ DB supplémentaire impliqué. Notez également manquant .count() à la fin de la ligne d'ensemble de requêtes. –

+0

a corrigé le nombre(). La seule autre solution (à part le fait de sous-classer de gros morceaux de contrib.admin) serait un hack Jquery/Ajaxy. –

41

Je n'ai pas testé (je serais intéressé de savoir si cela fonctionne), mais ce sur la définition d'un gestionnaire personnalisé pour Customer qui comprend le nombre de commandes agrégées, puis la mise en admin_order_field à cet agrégat, à savoir

from django.db import models 


class CustomerManager(models.Manager): 
    def get_query_set(self): 
     return super(CustomerManager, self).get_query_set().annotate(models.Count('order')) 

class Customer(models.Model): 
    foo = models.CharField[...] 

    objects = CustomerManager() 

    def number_of_orders(self): 
     return u'%s' % Order.objects.filter(customer=self).count() 
    number_of_orders.admin_order_field = 'order__count' 

EDIT: Je viens de tester cette idée et il fonctionne parfaitement - pas django admin subclassing nécessaire !

+0

il suffit de lire la documentation :) – Vladimir

Questions connexes