Django-Administrator: Sortieren nach einem der benutzerdefinierten list_display-Felder ohne Datenbankfeld

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

Wie kann ich Kunden sortieren, je nachdem number_of_orders, welche?

admin_order_fieldDie Eigenschaft kann hier nicht verwendet werden, da zum Sortieren ein Datenbankfeld erforderlich ist. Ist es überhaupt möglich, dass Django sich beim Sortieren auf die zugrunde liegende Datenbank verlässt? Das Erstellen eines Aggregatfelds mit der Anzahl der Bestellungen scheint hier ein Overkill zu sein.

Das Schöne: Wenn Sie die URL im Browser von Hand ändern, um nach dieser Spalte zu sortieren, funktioniert sie wie erwartet!

mike_k
quelle
"Das Lustige: Wenn Sie die URL im Browser von Hand ändern, um diese Spalte zu sortieren, funktioniert sie wie erwartet!" Du meinst wie: / admin / myapp / customer /? Ot = asc & o = 2 Bist du sicher?
Andy Baker
Ja, sowohl ASC als auch DSC. Vielleicht funktioniert es nur mit Dezimalstellen.
Mike_k
Ich denke nicht, dass es mit mehreren Seiten funktionieren würde.
Chase Seibert

Antworten:

159

Ich habe Gregs Lösung für dieses Problem geliebt, aber ich möchte darauf hinweisen, dass Sie dasselbe direkt im Administrator tun können:

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'

Auf diese Weise kommentieren Sie nur innerhalb der Administrationsoberfläche. Nicht bei jeder Abfrage, die Sie durchführen.

bbrik
quelle
5
Ja, das ist ein viel besserer Weg. :)
Greg
2
Es gibt eine vorgeschlagene Änderung dieser Antwort. Ich habe dafür gestimmt, weil es zu viel Text entfernt hat. Ich kenne Django nicht, ich habe keine Ahnung, ob die vorgeschlagene Codeänderung erwähnenswert ist.
Gilles 'SO - hör auf böse zu sein'
1
@ Gilles Die vorgeschlagene Bearbeitung ist in Bezug auf eine einfachere Definition der Anzahl der Bestellungen korrekt. Dies funktioniert: def number_of_orders(self, obj): return obj.order__count
Nils
1
Sollte das nicht get_queryset()stattdessen sein queryset()?
Mariusz Jamro
2
sollte get_queryset sein (self, request): ... für Django 1.6+
michael
50

Ich habe dies nicht getestet (ich würde gerne wissen, ob es funktioniert), aber was ist mit der Definition eines benutzerdefinierten Managers, für Customerden die Anzahl der aggregierten Aufträge enthalten ist, und der Einstellung admin_order_fieldauf dieses Aggregat, d. H.

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: Ich habe diese Idee gerade getestet und sie funktioniert perfekt - keine Unterklasse für Django-Administratoren erforderlich!

Greg
quelle
1
Dies ist eine bessere Antwort als die akzeptierte. Das Problem, auf das ich beim Anwenden des akzeptierten gestoßen bin, ist, dass das Durchsuchen von Daten zusammen mit dem aktualisierten Abfrageset auf Administratorebene zu lange dauert und auch eine falsche Zählung der gefundenen Ergebnisse ergibt.
Mutant
0

Ich kann mir nur vorstellen, das Feld zu denormalisieren. Das heißt - erstellen Sie ein reales Feld, das aktualisiert wird, um mit den Feldern synchron zu bleiben, von denen es abgeleitet ist. Normalerweise überschreibe ich das Speichern mit dem Modell mit den denormalisierten Feldern oder dem Modell, von dem es abgeleitet ist:

# 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
quelle
1
Dies sollte sicherlich funktionieren, kann jedoch aufgrund des zusätzlichen DB-Felds nicht als akzeptiert markiert werden. Beachten Sie auch das fehlende .count () am Ende der Abfragesatzzeile.
Mike_k
Die Anzahl () wurde korrigiert. Die einzige andere Lösung (abgesehen von der Unterklasse großer Teile von Contrib.admin) wäre ein Jquery / Ajaxy-Hack.
Andy Baker