135 votes

Django admin: comment trier par l'un de la coutume list_display champs qui n'a pas de champ de base de données

# 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 ai-je pu trier les Clients, selon number_of_orders ils ont?

admin_order_field de la propriété ne peut pas être utilisé ici, car il requiert un champ de base de données à trier. Est-il possible à tous, comme Django s'appuie sur le sous-jacent DB pour effectuer le tri? La création d'un champ d'agrégation pour contenir le nombre de commandes qui semble exagéré ici.

La chose amusante: si vous modifiez l'url à la main dans le navigateur d'effectuer un tri sur cette colonne, il fonctionne comme prévu!

174voto

bbrik Points 676

J'ai adoré Greg solution à ce problème, mais je tiens à 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 queryset(self, request):
        qs = super(CustomerAdmin, self).queryset(request)
        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 n'annoter à l'intérieur de l'interface d'administration. Pas avec toutes les requêtes que vous faites.

52voto

Greg Points 3197

Je n'ai pas testé (je serais curieux de savoir si ça marche), mais ce que sur la définition d'une custom manager pour Customer qui comprend le nombre de commandes regroupées, puis en définissant admin_order_field de cet agrégat, c'est à dire

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 testé cette idée et il fonctionne parfaitement - pas de django admin sous-classement requis!

0voto

andybak Points 7081

La seule façon que je peux penser à est de dénormaliser le terrain. Qu'est - créer un véritable champ de mise à jour pour rester synchronisé avec les champs, il est dérivé. J'ai l'habitude de le faire en remplaçant enregistrer sur avec le modèle avec la dénormalisée champs ou le modèle, il en découle:

# 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[...]

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X