2009-06-14 22 views
72

Come posso aggiungere un filtro personalizzato all'amministratore di django (i filtri che appaiono sul lato destro di una dashboard del modello)? So che la sua facile comprendono un filtro basato su un campo di quel modello, ma che dire di un campo "calcolato" come questo:Filtro personalizzato in Django Admin su Django 1.3 o inferiore

class NewsItem(models.Model): 
    headline = models.CharField(max_length=4096, blank=False) 
    byline_1 = models.CharField(max_length=4096, blank=True) 
    dateline = models.DateTimeField(help_text=_("date/time that appears on article")) 
    body_copy = models.TextField(blank=False) 

    when_to_publish = models.DateTimeField(verbose_name="When to publish", blank=True, null=True) 

    # HOW CAN I HAVE "is_live" as part of the admin filter? It's a calculated state!! 
    def is_live(self): 
     if self.when_to_publish is not None: 
      if (self.when_to_publish < datetime.now()): 
       return """ <img alt="True" src="/media/img/admin/icon-yes.gif"/> """ 
     else: 
      return """ <img alt="False" src="/media/img/admin/icon-no.gif"/> """  

    is_live.allow_tags = True 

class NewsItemAdmin(admin.ModelAdmin): 
    form = NewsItemAdminForm 
    list_display = ('headline', 'id', 'is_live') 
    list_filter = ('is_live') # how can i make this work?? 
+0

Altre persone già detto questa caratteristica è nel bagagliaio (1.4 dev). Altre informazioni: [note sulla versione] (https://code.djangoproject.com/browser/django/trunk/docs/releases/1.4.txt?rev=16144#L40) e [documentazione] (https: // code. djangoproject.com/browser/django/trunk/docs/ref/contrib/admin/index.txt#L604). – Paolo

+1

Ecco un link migliore alla documentazione; estendere SimpleListFilter è la strada da percorrere qui. FilterSpecs non è aggiornato. https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter – fastmultiplication

+0

Vedi [risposta matley] (https://stackoverflow.com/a/6355234/) sotto, con un link alla documentazione ufficiale. –

risposta

57

Grazie a gpilotino per avermi dato la spinta nella giusta direzione per l'attuazione del presente.

Ho notato che il codice della domanda utilizza un datetime per capire quando è in diretta. Quindi ho usato il DateFieldFilterSpec e lo ho suddiviso in sottoclassi.

from django.db import models 
from django.contrib.admin.filterspecs import FilterSpec, ChoicesFilterSpec,DateFieldFilterSpec 
from django.utils.encoding import smart_unicode 
from django.utils.translation import ugettext as _ 
from datetime import datetime 

class IsLiveFilterSpec(DateFieldFilterSpec): 
    """ 
    Adds filtering by future and previous values in the admin 
    filter sidebar. Set the is_live_filter filter in the model field attribute 
    'is_live_filter'. my_model_field.is_live_filter = True 
    """ 

    def __init__(self, f, request, params, model, model_admin): 
     super(IsLiveFilterSpec, self).__init__(f, request, params, model, 
               model_admin) 
     today = datetime.now() 
     self.links = (
      (_('Any'), {}), 
      (_('Yes'), {'%s__lte' % self.field.name: str(today), 
         }), 
      (_('No'), {'%s__gte' % self.field.name: str(today), 
        }), 

     ) 


    def title(self): 
     return "Is Live" 

# registering the filter 
FilterSpec.filter_specs.insert(0, (lambda f: getattr(f, 'is_live_filter', False), 
           IsLiveFilterSpec)) 

Per utilizzare è possibile inserire il codice sopra in un filters.py, e importarlo nel modello che si desidera aggiungere il filtro

+2

Puoi approfondire un po 'l'ultima parte di ciò che fai con questo codice? –

+0

Rosarch, l'ultima riga di codice registra is_live_filter in django, quindi nella tua model.py nella classe model diciamo Article, hai un campo chiamato publish_date che chiameredi publish_date.is_live_filter –

+1

L'ultima riga * filter_specs.insert * è molto importante, altrimenti il ​​tuo filtro personalizzato probabilmente non verrà visualizzato, verrà mostrato uno dei filtri predefiniti per quel tipo di campo. (Non ho letto correttamente la risposta all'inizio e stavo usando il metodo .register come l'uso di filterspecs incorporato!) – Anentropic

3

Non è possibile, purtroppo. Attualmente gli elementi non di campo non possono essere utilizzati come voci list_filter.

Nota che la classe di amministrazione non avrebbe funzionato, anche se si trattava di un campo, come tuple singolo elemento ha bisogno di una virgola: ('is_live',)

+4

un giorno mabye, è un biglietto aperto http://code.djopoproject.com/ticket/5833 – imjoevasquez

+2

FWIW, una correzione per # 5833 è ora in django trunk per django 1.4 –

9

Nella versione di sviluppo Django corrente c'è il supporto per filtri personalizzati : https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter

+1

Per essere un po 'più specifici: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django .contrib.admin.ModelAdmin.list_filter – Webthusiast

+1

@Webthusiast Sentiti libero di modificare qualsiasi risposta per migliorarla. In questo caso, ho già incorporato il tuo URL più specifico nella risposta. –

2

L'utente fornisce le merci ad alcuni paesi senza spedizione. Volevo filtrare questi paesi:

Tutti - tutti i paesi, - spedizione gratuita, No - spese di spedizione a carico.

La risposta principale per questa domanda non ha funzionato per me (Django 1.3). Penso che non esistesse il parametro field_path fornito nel metodo __init__. Inoltre ha sottoclasse DateFieldFilterSpec. Il campo è un postage FloatField

from django.contrib.admin.filterspecs import FilterSpec 

class IsFreePostage(FilterSpec): 

    def __init__(self, f, request, params, model, model_admin, field_path=None): 
     super(IsFreePostage, self).__init__(f, request, params, model, 
      model_admin, field_path) 

     self.removes = { 
      'Yes': ['postage__gt'], 
      'No': ['postage__exact'], 
      'All': ['postage__exact', 'postage__gt'] } 

     self.links = (
      ('All', {}), 
      ('Yes', {'postage__exact': 0}), 
      ('No', {'postage__gt': 0})) 

     if request.GET.has_key('postage__exact'): 
      self.ttl = 'Yes' 
     elif request.GET.has_key('postage__gt'): 
      self.ttl = 'No' 
     else: 
      self.ttl = 'All' 

    def choices(self, cl): 
     for title, param_dict in self.links: 
      yield {'selected': title == self.ttl, 
        'query_string': cl.get_query_string(param_dict, 
         self.removes[title]), 
        'display': title} 
    def title(self): 
     return 'Free Postage' 

FilterSpec.filter_specs.insert(0, 
    (lambda f: getattr(f, 'free_postage', False), IsFreePostage)) 

In self.links forniamo dicts. utilizzato per costruire stringhe di query HTTP come ?postage__exact=0 per ciascuno dei possibili filtri. Filtri Penso che siano cumulativi quindi se c'era una richiesta precedente per "No" e ora abbiamo una richiesta per "Sì", dobbiamo rimuovere la query "No" . self.removes specifica cosa deve essere rimosso per ogni query. Il metodo choices costruisce le stringhe di query, dice quale filtro è stato selezionato e imposta il nome visualizzato del filtro.

3

Solo un sidenote: È possibile utilizzare il deafult zecche su Django di amministrazione più facilmente in questo modo:

def is_live(self): 
    if self.when_to_publish is not None: 
     if (self.when_to_publish < datetime.now()): 
      return True 
    else: 
     return False 

is_live.boolean = True 
3

Non maniera ottimale (CPU-saggio), ma semplice e funziona, in modo da fare in questo modo (per il mio piccolo database). La mia versione di Django è 1.6.

In admin.py:

class IsLiveFilter(admin.SimpleListFilter): 
    title = 'Live' 
    parameter_name = 'islive' 
    def lookups(self, request, model_admin): 
     return (
      ('1', 'islive'), 
     ) 
    def queryset(self, request, queryset): 
     if self.value(): 
      array = [] 
      for element in queryset: 
       if element.is_live.__call__() == True: 
        q_array.append(element.id) 
      return queryset.filter(pk__in=q_array) 

...

class NewsItemAdmin(admin.ModelAdmin): 
    form = NewsItemAdminForm 
    list_display = ('headline', 'id', 'is_live') 
    list_filter = (IsLiveFilter) 

idea chiave è qui per accedere ai campi personalizzati in un QuerySet via __call __() funzione.

+0

Forse non il modo più ottimizzato, ma semplice e funzionante. Mi ha salvato mal di testa. – d6bels