Подтвердить что ты не робот

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

# 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-адрес вручную в браузере, чтобы сортировать по этому столбцу - он работает так, как ожидалось!

4b9b3361

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

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