web-dev-qa-db-de.com

Django admin: Sortierung nach einem der benutzerdefinierten list_display-Felder ohne Datenbankfeld

# 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, abhängig von number_of_orders, Die sie haben?

Die Eigenschaft admin_order_field Kann hier nicht verwendet werden, da ein Datenbankfeld zum Sortieren erforderlich ist. Ist es überhaupt möglich, da Django zur Sortierung auf die zugrunde liegende Datenbank angewiesen ist? Die Erstellung eines aggregierten Felds zur Aufnahme der Anzahl von Aufträgen scheint hier ein Overkill zu sein.

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

110
mike_k

Ich mochte Gregs Lösung für dieses Problem, aber ich möchte darauf hinweisen, dass Sie dasselbe direkt im Admin 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 können Sie nur innerhalb der Administrationsoberfläche Anmerkungen hinzufügen. Nicht bei jeder Abfrage, die Sie durchführen.

144
bbrik

Ich habe das noch nicht getestet (ich würde gerne wissen, ob es funktioniert), aber wie wäre es, einen benutzerdefinierten Manager für Customer zu definieren, der die Anzahl der aggregierten Bestellungen enthält und dann admin_order_field zu diesem Aggregat, dh

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'

BEARBEITEN: Ich habe diese Idee gerade getestet und sie funktioniert einwandfrei - keine Django admin-Unterklassen erforderlich!

47
Greg

Der einzige Weg, den ich mir vorstellen kann, ist, das Feld zu denormalisieren. Das heißt: Erstellen Sie ein reales Feld, das aktualisiert wird, um mit den Feldern, von denen es abgeleitet ist, synchron zu bleiben. Normalerweise überschreibe ich dies, indem ich das Modell mit den denormalisierten Feldern oder dem Modell, von dem es abgeleitet ist, außer Kraft setze:

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