Django admin: how to sort by one of the custom list_display fields that has no database field

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

How could I sort Customers, depending on number_of_orders they have?

admin_order_field property can't be used here, as it requires a database field to sort on. Is it possible at all, as Django relies on the underlying DB to perform sorting? Creating an aggregate field to contain the number of orders seems like an overkill here.

The fun thing: if you change url by hand in the browser to sort on this column - it works as expected!

61451 次浏览

我能想到的唯一办法就是去规范化这个场。也就是说,创建一个实际的字段,该字段将得到更新,以便与其派生自的字段保持同步。我通常通过用反规范化字段或它派生自的模型来覆盖 save:

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

我还没有对此进行测试(我很想知道它是否有效) ,但是如何为 Customer定义一个定制管理器,其中包括聚合订单的数量,然后将 admin_order_field设置为该聚合,即

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'

编辑: 我刚刚测试了这个想法,它的工作非常完美-没有 django 管理子类化需要!

我喜欢 Greg 对这个问题的解决方案,但我想指出的是,你可以直接在管理员中做同样的事情:

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'

这样你只需要在管理界面内部进行注释,而不是在每个查询中进行注释。