Admin Django: bagaimana cara mengurutkan berdasarkan salah satu bidang list_display kustom yang tidak memiliki bidang database

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

Bagaimana saya bisa menyortir Pelanggan, tergantung pada yang number_of_ordersmereka miliki?

admin_order_fieldproperti tidak dapat digunakan di sini, karena memerlukan bidang database untuk mengurutkan. Apakah mungkin sama sekali, karena Django mengandalkan DB yang mendasari untuk melakukan penyortiran? Membuat kolom agregat yang berisi jumlah pesanan tampaknya berlebihan di sini.

Hal yang menyenangkan: jika Anda mengubah url dengan tangan di browser untuk mengurutkan kolom ini - berfungsi seperti yang diharapkan!

mike_k
sumber
"Hal yang menyenangkan: jika Anda mengubah url dengan tangan di browser untuk mengurutkan kolom ini - berfungsi seperti yang diharapkan!" Maksud Anda seperti: / admin / myapp / customer /? Ot = asc & o = 2 Anda yakin?
Andy Baker
ya, baik asc maupun dsc. Mungkin itu hanya bekerja dengan desimal.
mike_k
Saya tidak berpikir itu akan berfungsi dengan banyak halaman.
Chase Seibert

Jawaban:

159

Saya menyukai solusi Greg untuk masalah ini, tetapi saya ingin menunjukkan bahwa Anda dapat melakukan hal yang sama langsung di 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'

Dengan cara ini Anda hanya membuat anotasi di dalam antarmuka admin. Tidak dengan setiap pertanyaan yang Anda lakukan.

bbrik
sumber
5
Ya, ini cara yang jauh lebih baik. :)
Greg
2
Ada pengeditan yang disarankan untuk jawaban ini. Saya memilih untuk menolaknya karena menghapus terlalu banyak teks. Saya tidak tahu Django, saya tidak tahu apakah perubahan kode yang diusulkan layak untuk disebutkan.
Gilles 'SO- berhenti bersikap jahat'
1
@ Gilles pengeditan yang disarankan benar tentang definisi number_of_orders yang lebih sederhana. Ini bekerja: def number_of_orders(self, obj): return obj.order__count
Nils
1
Shoudn't yang menjadi get_queryset()bukan queryset()?
Mariusz Jamro
2
harus get_queryset (self, request): ... untuk Django 1.6+
michael
50

Saya belum mengujinya (saya akan tertarik untuk mengetahui apakah ini berfungsi) tetapi bagaimana dengan menentukan pengelola khusus Customeryang menyertakan jumlah pesanan yang dikumpulkan, dan kemudian menetapkan admin_order_fieldke agregat itu, yaitu

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: Saya baru saja menguji ide ini dan bekerja dengan sempurna - tidak diperlukan subclass admin django!

Greg
sumber
1
Ini jawaban yang lebih baik dibandingkan dengan yang diterima. Masalah yang saya hadapi saat menerapkan yang diterima adalah ketika Anda mencari sesuatu bersama dengan queryset yang diperbarui di tingkat admin, itu membutuhkan terlalu banyak waktu dan juga muncul dengan hitungan yang salah untuk hasil yang ditemukannya.
Mutan
0

Satu-satunya cara yang dapat saya pikirkan adalah dengan mendenormalisasi lapangan. Yaitu - buat bidang nyata yang diperbarui agar tetap sinkron dengan bidang asalnya. Saya biasanya melakukan ini dengan mengganti save on eith model dengan bidang yang dinormalisasi atau model yang berasal dari:

# 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
sumber
1
Ini tentu harus berfungsi, tetapi tidak dapat menandainya sebagai diterima karena bidang DB ekstra terlibat. Perhatikan juga kehilangan .count () di akhir baris query-set.
mike_k
memperbaiki hitungan (). Satu-satunya solusi lain (kependekan dari subclassing potongan besar contrib.admin) adalah hack Jquery / Ajaxy.
Andy Baker