Administrator Django: jak sortować według jednego z niestandardowych pól list_display, które nie ma pola bazy danych

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

Jak mogę sortować klientów, w zależności od number_of_orderstego, czy mają?

admin_order_fieldnie można tutaj użyć właściwości, ponieważ wymaga ona pola bazy danych do sortowania. Czy to w ogóle możliwe, skoro Django polega na bazowej bazie danych do wykonywania sortowania? Tworzenie pola zbiorczego zawierającego liczbę zamówień wydaje się tutaj przesadą.

Fajna rzecz: jeśli ręcznie zmienisz adres URL w przeglądarce, aby posortować według tej kolumny - działa zgodnie z oczekiwaniami!

mike_k
źródło
„Fajna rzecz: jeśli ręcznie zmienisz adres URL w przeglądarce, aby posortować według tej kolumny - to działa zgodnie z oczekiwaniami!” Masz na myśli: / admin / myapp / customer /? Ot = asc & o = 2 Czy na pewno?
Andy Baker
tak, zarówno asc, jak i dsc. Może po prostu działa z liczbami dziesiętnymi.
mike_k
Nie sądzę, żeby to działało z wieloma stronami.
Chase Seibert,

Odpowiedzi:

159

Spodobało mi się rozwiązanie tego problemu Grega, ale chciałbym zaznaczyć, że możesz zrobić to samo bezpośrednio w panelu administracyjnym:

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'

W ten sposób możesz dodawać adnotacje tylko w interfejsie administratora. Nie przy każdym zadanym zapytaniu.

bbrik
źródło
5
Tak, to znacznie lepszy sposób. :)
Greg
2
Ta odpowiedź jest sugerowana . Głosowałem za jego odrzuceniem, ponieważ usunięto za dużo tekstu. Nie znam Django, nie mam pojęcia, czy warto wspomnieć o proponowanej zmianie kodu.
Gilles 'SO- przestań być złym'
1
@Gilles - sugerowana edycja jest poprawna i dotyczy prostszej definicji liczby_zamówień. To działa: def number_of_orders(self, obj): return obj.order__count
Nils
1
Czy nie powinno być get_queryset()zamiast tego queryset()?
Mariusz Jamro
2
powinno być get_queryset (self, request): ... dla Django 1.6+
michael
50

Nie testowałem tego (chciałbym wiedzieć, czy to działa), ale co ze zdefiniowaniem niestandardowego menedżera, dla Customerktórego liczba zagregowanych zamówień, a następnie ustawienie admin_order_fieldtego agregatu, tj.

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'

EDYCJA: Właśnie przetestowałem ten pomysł i działa doskonale - nie jest wymagana żadna podklasa administratora django!

Greg
źródło
1
To lepsza odpowiedź w porównaniu z przyjętą. Problem, na który natknąłem się przy stosowaniu zaakceptowanego, polega na tym, że gdy wyszukujesz coś wraz z tym zaktualizowanym zestawem zapytań na poziomie administratora, zajmuje to zbyt dużo czasu, a także pojawia się niewłaściwa liczba znalezionych wyników.
Mutant
0

Jedyny sposób, jaki przychodzi mi do głowy, to denormalizacja tego pola. To znaczy - utwórz prawdziwe pole, które zostanie zaktualizowane, aby pozostać zsynchronizowane z polami, z których pochodzi. Zwykle robię to przez nadpisanie zapisu na modelu ze zdenormalizowanymi polami lub modelem, z którego pochodzi:

# 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[...]
Andy Baker
źródło
1
To z pewnością powinno działać, ale nie można oznaczyć go jako zaakceptowanego ze względu na dodatkowe pole DB. Zwróć również uwagę na brak .count () na końcu wiersza zestawu zapytań.
mike_k
naprawiono count (). Jedynym innym rozwiązaniem (poza podklasą dużych fragmentów contrib.admin) byłby hack Jquery / Ajaxy.
Andy Baker