Адміністратор Django: як сортувати по одному зі спеціальних полів list_display, у яких немає поля бази даних


122
# 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Тут не можна використовувати властивість, оскільки для сортування потрібне поле бази даних. Чи взагалі це можливо, оскільки Джанго покладається на базовий БД для сортування? Створення сукупного поля, яке б містило кількість замовлень, тут здається непосильним.

Найцікавіше: якщо ви змінили URL-адресу вручну в браузері, щоб сортувати цей стовпець - він працює як очікувалося!


"Найцікавіше: якщо змінити URL-адресу вручну в браузері, щоб сортувати цей стовпець - він працює як очікується!" Ви маєте на увазі, як: / admin / myapp / customer /? Ot = asc & o = 2 Ви впевнені?
Енді Бейкер

так, і asc, і dsc. Можливо, це просто працює з десятковими знаками.
mike_k

Я не думаю, що це буде працювати з декількома сторінками.
Чейз Сейберт

Відповіді:


159

Мені подобалося рішення Грега щодо цієї проблеми, але я хотів би зазначити, що ви можете зробити те саме, що безпосередньо в адміністраторі:

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'

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


5
Так, це набагато кращий спосіб. :)
Грег

2
На цю відповідь запропоновано редагувати . Я проголосував за його відхилення, оскільки він видалив занадто багато тексту. Я не знаю Джанго, я не маю уявлення, чи варто згадати запропоновану зміну коду.
Жил "ТАК - перестань бути злим"

1
@Gilles запропонована редакція є правильною щодо більш простого визначення числа_of_orders. Це працює: def number_of_orders(self, obj): return obj.order__count
Нілс

1
Хіба це не повинно бути get_queryset()замість queryset()?
Маріуш Джамро

2
має бути get_queryset (я, запит): ... для Django 1.6+
michael

50

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


1
Це краща відповідь порівняно з прийнятим. Проблема, з якою я стикався із застосуванням прийнятого, полягає в тому, що коли ви шукаєте щось разом із оновленим набором запитів на рівні адміністратора, це займає занадто багато часу, а також підходить до неправильного підрахунку результатів, які він знайшов.
Мутант

0

Єдиний спосіб, який я можу придумати, - це денормалізація поля. Тобто - створити реальне поле, яке оновлюється, щоб залишатися синхронізованим з полями, з яких він походить. Зазвичай я це роблю, переосмислюючи збереження на 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[...]

1
Це, безумовно, повинно працювати, але не може позначати його як прийняте через додаткове поле БД. Також зверніть увагу на відсутність .count () в кінці рядка набору запитів.
mike_k

зафіксовано кількість (). Єдиним іншим рішенням (за винятком підкласифікації великих фрагментів contrib.admin) буде хак Jquery / Ajaxy.
Енді Бейкер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.