Django admin: come ordinare in base a uno dei campi list_display personalizzati che non hanno un campo 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()  

Come posso ordinare i clienti, a seconda di quelli number_of_ordersche hanno?

admin_order_fieldla proprietà non può essere utilizzata qui, poiché richiede un campo del database per l'ordinamento. È possibile, dato che Django si affida al DB sottostante per eseguire l'ordinamento? La creazione di un campo aggregato per contenere il numero di ordini sembra eccessivo qui.

La cosa divertente: se cambi l'URL a mano nel browser per ordinare su questa colonna, funziona come previsto!


"La cosa divertente: se modifichi l'URL manualmente nel browser per ordinare su questa colonna, funziona come previsto!" Intendi come: / admin / miaapp / cliente /? Ot = asc & o = 2 Sei sicuro?
Andy Baker

sì, sia asc che dsc. Forse funziona solo con i decimali.
mike_k

Non credo che funzionerebbe con più pagine.
Chase Seibert,

Risposte:


159

Mi è piaciuta la soluzione di Greg a questo problema, ma vorrei sottolineare che puoi fare la stessa cosa direttamente nell'amministratore:

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'

In questo modo annoti solo all'interno dell'interfaccia di amministrazione. Non con tutte le domande che fai.


5
Sì, questo è un modo molto migliore. :)
Greg

2
C'è una modifica suggerita su questa risposta. Ho votato per respingerla perché rimuoveva troppo testo. Non conosco Django, non ho idea se valga la pena menzionare la modifica del codice proposta.
Gilles "SO- smettila di essere malvagio"

1
@Gilles la modifica suggerita è corretta per una più semplice definizione di number_of_orders. Funziona: def number_of_orders(self, obj): return obj.order__count
Nils

1
Non dovrebbe essere al get_queryset()posto di queryset()?
Mariusz Jamro

2
dovrebbe essere get_queryset (self, request): ... for Django 1.6+
michael

50

Non l'ho testato (sarei interessato a sapere se funziona) ma per quanto riguarda la definizione di un gestore personalizzato per il Customerquale includa il numero di ordini aggregati e quindi l'impostazione admin_order_fielddi tale aggregato, ad es.

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: Ho appena testato questa idea e funziona perfettamente - non è richiesta alcuna sottoclasse di amministrazione di django!


1
Questa è una risposta migliore rispetto a quella accettata. Il problema che ho riscontrato nell'applicazione di quello accettato è che quando cerchi qualcosa insieme a quel set di query aggiornato a livello di amministratore, ci vuole troppo tempo e si presenta anche con un conteggio errato dei risultati trovati.
Mutant

0

L'unico modo a cui riesco a pensare è denormalizzare il campo. Cioè: crea un campo reale che venga aggiornato per rimanere sincronizzato con i campi da cui deriva. Di solito lo faccio sovrascrivendo save on eith il modello con i campi denormalizzati o il modello da cui deriva:

# 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
Questo dovrebbe certamente funzionare, ma non è possibile contrassegnarlo come accettato a causa del campo DB aggiuntivo coinvolto. Notare anche la mancanza di .count () alla fine della riga di set di query.
mike_k

corretto il conteggio (). L'unica altra soluzione (a parte la sottoclasse di grandi blocchi di contrib.admin) sarebbe un hack Jquery / Ajaxy.
Andy Baker
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.