Django admin: Как сортировать по одному из пользовательских полей отображения списка, который не имеет поля базы данных


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

как я могу сортировать клиентов, в зависимости от number_of_orders у них есть?

admin_order_field свойство не может быть использовано здесь, так как для его сортировки требуется поле базы данных. Возможно ли вообще, поскольку Django полагается на базовую БД для выполнения сортировки? Создание агрегированного поля, содержащего количество заказов, кажется здесь излишним.

забавная вещь: если вы измените url вручную в браузере, чтобы отсортировать этот столбец - он работает так, как ожидалось!

3 97

3 ответа:

Мне понравилось решение Грега этой проблемы, но я хотел бы отметить, что вы можете сделать то же самое прямо в 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'

таким образом, вы только аннотировать внутри интерфейса администратора. Не с каждым запросом, который вы делаете.

Я не проверял это (мне было бы интересно узнать, работает ли это), но как насчет определения пользовательского менеджера для 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'

EDIT: я только что протестировал эту идею, и она отлично работает - не требуется подкласс Django admin!

единственный способ, который я могу придумать, это денормализация поля. То есть-создайте реальное поле, которое обновляется, чтобы оставаться в синхронизации с полями, из которых оно получено. Обычно я делаю это, переопределяя save on eith модель с денормализованными полями или модель, из которой она происходит:

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