Aby to osiągnąć i mieć nadający się do użytku link „Wszystkie” na pasku bocznym (tj. Taki, który pokazuje wszystko zamiast pokazywać oczekujące), musisz utworzyć niestandardowy filtr listy, django.contrib.admin.filters.SimpleListFilter
domyślnie dziedziczący i filtrujący „oczekujące”. Coś w tym stylu powinno działać:
from datetime import date
from django.utils.translation import ugettext_lazy as _
from django.contrib.admin import SimpleListFilter
class StatusFilter(SimpleListFilter):
title = _('Status')
parameter_name = 'status'
def lookups(self, request, model_admin):
return (
(None, _('Pending')),
('activate', _('Activate')),
('rejected', _('Rejected')),
('all', _('All')),
)
def choices(self, cl):
for lookup, title in self.lookup_choices:
yield {
'selected': self.value() == lookup,
'query_string': cl.get_query_string({
self.parameter_name: lookup,
}, []),
'display': title,
}
def queryset(self, request, queryset):
if self.value() in ('activate', 'rejected'):
return queryset.filter(status=self.value())
elif self.value() == None:
return queryset.filter(status='pending')
class Admin(admin.ModelAdmin):
list_filter = [StatusFilter]
EDYCJA: Wymaga Django 1.4 (dzięki Simon)
choices
metody w rozwiązaniu, irytująco będzie nadal dodawać własną opcję Wszystkie na górze listy opcji.class MyModelAdmin(admin.ModelAdmin): def changelist_view(self, request, extra_context=None): if not request.GET.has_key('decommissioned__exact'): q = request.GET.copy() q['decommissioned__exact'] = 'N' request.GET = q request.META['QUERY_STRING'] = request.GET.urlencode() return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)
źródło
Wziął powyższą odpowiedź ha22109 i zmodyfikował, aby umożliwić wybór „Wszystkie” przez porównanie
HTTP_REFERER
iPATH_INFO
.class MyModelAdmin(admin.ModelAdmin): def changelist_view(self, request, extra_context=None): test = request.META['HTTP_REFERER'].split(request.META['PATH_INFO']) if test[-1] and not test[-1].startswith('?'): if not request.GET.has_key('decommissioned__exact'): q = request.GET.copy() q['decommissioned__exact'] = 'N' request.GET = q request.META['QUERY_STRING'] = request.GET.urlencode() return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)
źródło
KeyError
, w przypadku gdy używasz metody dyktaget()
, możesz określić domyślną. Podałem wartość domyślną pustego ciągu, aby split () nie zgłaszałAttributeError
. To wszystko.has_key()
jest jednak przestarzały na korzyśćkey in d
. Ale wiem, że właśnie wziąłeś z odpowiedzi ha22109. Jedno pytanie:request.META['PATH_INFO']
po co używać, skoro można po prostu użyćrequest.path_info
(krótszego)?Wiem, że to pytanie jest teraz dość stare, ale nadal jest aktualne. Uważam, że jest to najbardziej właściwy sposób zrobienia tego. Zasadniczo jest taka sama jak metoda Grega, ale została sformułowana jako rozszerzalna klasa do łatwego ponownego użycia.
from django.contrib.admin import SimpleListFilter from django.utils.encoding import force_text from django.utils.translation import ugettext as _ class DefaultListFilter(SimpleListFilter): all_value = '_all' def default_value(self): raise NotImplementedError() def queryset(self, request, queryset): if self.parameter_name in request.GET and request.GET[self.parameter_name] == self.all_value: return queryset if self.parameter_name in request.GET: return queryset.filter(**{self.parameter_name:request.GET[self.parameter_name]}) return queryset.filter(**{self.parameter_name:self.default_value()}) def choices(self, cl): yield { 'selected': self.value() == self.all_value, 'query_string': cl.get_query_string({self.parameter_name: self.all_value}, []), 'display': _('All'), } for lookup, title in self.lookup_choices: yield { 'selected': self.value() == force_text(lookup) or (self.value() == None and force_text(self.default_value()) == force_text(lookup)), 'query_string': cl.get_query_string({ self.parameter_name: lookup, }, []), 'display': title, } class StatusFilter(DefaultListFilter): title = _('Status ') parameter_name = 'status__exact' def lookups(self, request, model_admin): return ((0,'activate'), (1,'pending'), (2,'rejected')) def default_value(self): return 1 class MyModelAdmin(admin.ModelAdmin): list_filter = (StatusFilter,)
źródło
Oto moje ogólne rozwiązanie wykorzystujące przekierowanie, po prostu sprawdza, czy są jakieś parametry GET, jeśli nie istnieją, przekierowuje z domyślnym parametrem get. Mam też ustawiony list_filter, więc wybiera go i wyświetla wartość domyślną.
from django.shortcuts import redirect class MyModelAdmin(admin.ModelAdmin): ... list_filter = ('status', ) def changelist_view(self, request, extra_context=None): referrer = request.META.get('HTTP_REFERER', '') get_param = "status__exact=5" if len(request.GET) == 0 and '?' not in referrer: return redirect("{url}?{get_parms}".format(url=request.path, get_parms=get_param)) return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)
Jedynym zastrzeżeniem jest bezpośrednie przejście do strony za pomocą „?” obecny w adresie URL, nie ma ustawionego HTTP_REFERER, więc użyje domyślnego parametru i przekierowania. Dla mnie to jest w porządku, działa świetnie, gdy klikasz filtr administratora.
AKTUALIZACJA :
Aby obejść to zastrzeżenie, napisałem niestandardową funkcję filtru, która uprościła funkcjonalność changelist_view. Oto filtr:
class MyModelStatusFilter(admin.SimpleListFilter): title = _('Status') parameter_name = 'status' def lookups(self, request, model_admin): # Available Values / Status Codes etc.. return ( (8, _('All')), (0, _('Incomplete')), (5, _('Pending')), (6, _('Selected')), (7, _('Accepted')), ) def choices(self, cl): # Overwrite this method to prevent the default "All" from django.utils.encoding import force_text for lookup, title in self.lookup_choices: yield { 'selected': self.value() == force_text(lookup), 'query_string': cl.get_query_string({ self.parameter_name: lookup, }, []), 'display': title, } def queryset(self, request, queryset): # Run the queryset based on your lookup values if self.value() is None: return queryset.filter(status=5) elif int(self.value()) == 0: return queryset.filter(status__lte=4) elif int(self.value()) == 8: return queryset.all() elif int(self.value()) >= 5: return queryset.filter(status=self.value()) return queryset.filter(status=5)
A changelist_view teraz przekazuje parametr domyślny tylko wtedy, gdy żaden nie jest obecny. Chodziło o to, aby pozbyć się możliwości przeglądania wszystkich filtrów generycznych bez użycia parametrów get. Aby zobaczyć wszystko, przypisałem w tym celu status = 8 .:
class MyModelAdmin(admin.ModelAdmin): ... list_filter = ('status', ) def changelist_view(self, request, extra_context=None): if len(request.GET) == 0: get_param = "status=5" return redirect("{url}?{get_parms}".format(url=request.path, get_parms=get_param)) return super(MyModelAdmin, self).changelist_view(request, extra_context=extra_context)
źródło
def changelist_view( self, request, extra_context = None ): default_filter = False try: ref = request.META['HTTP_REFERER'] pinfo = request.META['PATH_INFO'] qstr = ref.split( pinfo ) if len( qstr ) < 2: default_filter = True except: default_filter = True if default_filter: q = request.GET.copy() q['registered__exact'] = '1' request.GET = q request.META['QUERY_STRING'] = request.GET.urlencode() return super( InterestAdmin, self ).changelist_view( request, extra_context = extra_context )
źródło
Możesz po prostu użyć
return queryset.filter()
lubif self.value() is None
i Override metody SimpleListFilterfrom django.utils.encoding import force_text def choices(self, changelist): for lookup, title in self.lookup_choices: yield { 'selected': force_text(self.value()) == force_text(lookup), 'query_string': changelist.get_query_string( {self.parameter_name: lookup}, [] ), 'display': title, }
źródło
Zauważ, że jeśli zamiast wstępnie wybierać wartość filtru, którą chcesz zawsze wstępnie filtrować dane przed wyświetleniem ich w panelu administracyjnym,
ModelAdmin.queryset()
zamiast tego powinieneś zastąpić metodę.źródło
Nieznaczne ulepszenie odpowiedzi Grega przy użyciu DjangoChoices, Pythona> = 2.5 i oczywiście Django> = 1.4.
from django.utils.translation import ugettext_lazy as _ from django.contrib.admin import SimpleListFilter class OrderStatusFilter(SimpleListFilter): title = _('Status') parameter_name = 'status__exact' default_status = OrderStatuses.closed def lookups(self, request, model_admin): return (('all', _('All')),) + OrderStatuses.choices def choices(self, cl): for lookup, title in self.lookup_choices: yield { 'selected': self.value() == lookup if self.value() else lookup == self.default_status, 'query_string': cl.get_query_string({self.parameter_name: lookup}, []), 'display': title, } def queryset(self, request, queryset): if self.value() in OrderStatuses.values: return queryset.filter(status=self.value()) elif self.value() is None: return queryset.filter(status=self.default_status) class Admin(admin.ModelAdmin): list_filter = [OrderStatusFilter]
Dzięki Gregowi za fajne rozwiązanie!
źródło
Wiem, że to nie jest najlepsze rozwiązanie, ale zmieniłem plik index.html w szablonie administratora, wiersz 25 i 37 w następujący sposób:
25:
<th scope="row"><a href="{{ model.admin_url }}{% ifequal model.name "yourmodelname" %}?yourflag_flag__exact=1{% endifequal %}">{{ model.name }}</a></th>
37:
<td><a href="{{ model.admin_url }}{% ifequal model.name "yourmodelname" %}?yourflag__exact=1{% endifequal %}" class="changelink">{% trans 'Change' %}</a></td>
źródło
Musiałem dokonać modyfikacji, aby filtrowanie działało poprawnie. Poprzednie rozwiązanie działało u mnie po załadowaniu strony. Jeśli wykonano „akcję”, filtr powrócił do „Wszystkie”, a nie do mojego domyślnego. To rozwiązanie ładuje stronę zmiany administratora z domyślnym filtrem, ale także zachowuje zmiany filtru lub bieżący filtr, gdy na stronie występuje inna aktywność. Nie przetestowałem wszystkich przypadków, ale w rzeczywistości może to ograniczać ustawienie domyślnego filtru, aby występował tylko po załadowaniu strony.
def changelist_view(self, request, extra_context=None): default_filter = False try: ref = request.META['HTTP_REFERER'] pinfo = request.META['PATH_INFO'] qstr = ref.split(pinfo) querystr = request.META['QUERY_STRING'] # Check the QUERY_STRING value, otherwise when # trying to filter the filter gets reset below if querystr is None: if len(qstr) < 2 or qstr[1] == '': default_filter = True except: default_filter = True if default_filter: q = request.GET.copy() q['registered__isnull'] = 'True' request.GET = q request.META['QUERY_STRING'] = request.GET.urlencode() return super(MyAdmin, self).changelist_view(request, extra_context=extra_context)
źródło
Trochę nie na temat, ale moje poszukiwania podobnego pytania doprowadziły mnie tutaj. Chciałem mieć domyślne zapytanie według daty (tj. Jeśli nie podano danych wejściowych, pokaż tylko obiekty z
timestamp
„Dzisiaj”), co nieco komplikuje pytanie. Oto co wymyśliłem:from django.contrib.admin.options import IncorrectLookupParameters from django.core.exceptions import ValidationError class TodayDefaultDateFieldListFilter(admin.DateFieldListFilter): """ If no date is query params are provided, query for Today """ def queryset(self, request, queryset): try: if not self.used_parameters: now = datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) self.used_parameters = { ('%s__lt' % self.field_path): str(now + datetime.timedelta(days=1)), ('%s__gte' % self.field_path): str(now), } # Insure that the dropdown reflects 'Today' self.date_params = self.used_parameters return queryset.filter(**self.used_parameters) except ValidationError, e: raise IncorrectLookupParameters(e) class ImagesAdmin(admin.ModelAdmin): list_filter = ( ('timestamp', TodayDefaultDateFieldListFilter), )
Jest to proste nadpisanie wartości domyślnej
DateFieldListFilter
. Ustawiającself.date_params
, zapewnia, że lista rozwijana filtru zostanie zaktualizowana do dowolnej opcji zgodnej zself.used_parameters
. Z tego powodu musisz upewnić się, żeself.used_parameters
są dokładnie tym, czego używałby jeden z tych elementów rozwijanych (tj. Dowiedzieć się, codate_params
by się stało , używając opcji „Dzisiaj” lub „Ostatnie 7 dni”) i skonstruowaćself.used_parameters
aby je dopasować).Zostało zbudowane do pracy z Django 1.4.10
źródło
To może być stary wątek, ale pomyślałem, że dodam swoje rozwiązanie, ponieważ nie mogłem znaleźć lepszych odpowiedzi w wyszukiwarkach Google.
Zrób co (nie jestem pewien, czy to Deminic Rodger lub ha22109) odpowiedział w ModelAdmin dla changelist_view
class MyModelAdmin(admin.ModelAdmin): list_filter = (CustomFilter,) def changelist_view(self, request, extra_context=None): if not request.GET.has_key('decommissioned__exact'): q = request.GET.copy() q['decommissioned__exact'] = 'N' request.GET = q request.META['QUERY_STRING'] = request.GET.urlencode() return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)
Następnie musimy utworzyć niestandardowy SimpleListFilter
class CustomFilter(admin.SimpleListFilter): title = 'Decommissioned' parameter_name = 'decommissioned' # i chose to change it def lookups(self, request, model_admin): return ( ('All', 'all'), ('1', 'Decommissioned'), ('0', 'Active (or whatever)'), ) # had to override so that we could remove the default 'All' option # that won't work with our default filter in the ModelAdmin class def choices(self, cl): yield { 'selected': self.value() is None, 'query_string': cl.get_query_string({}, [self.parameter_name]), # 'display': _('All'), } for lookup, title in self.lookup_choices: yield { 'selected': self.value() == lookup, 'query_string': cl.get_query_string({ self.parameter_name: lookup, }, []), 'display': title, } def queryset(self, request, queryset): if self.value() == '1': return queryset.filter(decommissioned=1) elif self.value() == '0': return queryset.filter(decommissioned=0) return queryset
źródło
Oto najczystsza wersja, jaką udało mi się wygenerować, filtru z przedefiniowanym „Wszystkim” i wybraną wartością domyślną.
Jeśli domyślnie pokazuje mi aktualnie trwające podróże.
class HappeningTripFilter(admin.SimpleListFilter): """ Filter the Trips Happening in the Past, Future or now. """ default_value = 'now' title = 'Happening' parameter_name = 'happening' def lookups(self, request, model_admin): """ List the Choices available for this filter. """ return ( ('all', 'All'), ('future', 'Not yet started'), ('now', 'Happening now'), ('past', 'Already finished'), ) def choices(self, changelist): """ Overwrite this method to prevent the default "All". """ value = self.value() or self.default_value for lookup, title in self.lookup_choices: yield { 'selected': value == force_text(lookup), 'query_string': changelist.get_query_string({ self.parameter_name: lookup, }, []), 'display': title, } def queryset(self, request, queryset): """ Returns the Queryset depending on the Choice. """ value = self.value() or self.default_value now = timezone.now() if value == 'future': return queryset.filter(start_date_time__gt=now) if value == 'now': return queryset.filter(start_date_time__lte=now, end_date_time__gte=now) if value == 'past': return queryset.filter(end_date_time__lt=now) return queryset.all()
źródło
Utworzono podklasę filtrów wielokrotnego użytku, zainspirowaną niektórymi odpowiedziami tutaj (głównie Grega).
Zalety:
Wielokrotnego użytku - wtykowe w dowolnych
ModelAdmin
klasach standardowychRozszerzalny - Łatwe dodawanie dodatkowej / niestandardowej logiki do
QuerySet
filtrowaniaŁatwy w użyciu - w najbardziej podstawowej formie wystarczy zaimplementować tylko jeden atrybut niestandardowy i jedną metodę niestandardową (oprócz tych wymaganych do podklasy SimpleListFilter)
Intuicyjny administrator - łącze filtru „Wszystkie” działa zgodnie z oczekiwaniami; jak wszyscy inni
Brak przekierowań - nie ma potrzeby sprawdzania
GET
ładunku żądania, agnostykaHTTP_REFERER
(ani żadnych innych rzeczy związanych z żądaniem w podstawowej formie)Brak manipulacji widokiem (lista zmian) - i brak manipulacji szablonami (nie daj Boże)
Kod:
(większość z nich
import
dotyczy tylko wskazówek dotyczących typów i wyjątków)from typing import List, Tuple, Any from django.contrib.admin.filters import SimpleListFilter from django.contrib.admin.options import IncorrectLookupParameters from django.contrib.admin.views.main import ChangeList from django.db.models.query import QuerySet from django.utils.encoding import force_str from django.utils.translation import gettext_lazy as _ from django.core.exceptions import ValidationError class PreFilteredListFilter(SimpleListFilter): # Either set this or override .get_default_value() default_value = None no_filter_value = 'all' no_filter_name = _("All") # Human-readable title which will be displayed in the # right admin sidebar just above the filter options. title = None # Parameter for the filter that will be used in the URL query. parameter_name = None def get_default_value(self): if self.default_value is not None: return self.default_value raise NotImplementedError( 'Either the .default_value attribute needs to be set or ' 'the .get_default_value() method must be overridden to ' 'return a URL query argument for parameter_name.' ) def get_lookups(self) -> List[Tuple[Any, str]]: """ Returns a list of tuples. The first element in each tuple is the coded value for the option that will appear in the URL query. The second element is the human-readable name for the option that will appear in the right sidebar. """ raise NotImplementedError( 'The .get_lookups() method must be overridden to ' 'return a list of tuples (value, verbose value).' ) # Overriding parent class: def lookups(self, request, model_admin) -> List[Tuple[Any, str]]: return [(self.no_filter_value, self.no_filter_name)] + self.get_lookups() # Overriding parent class: def queryset(self, request, queryset: QuerySet) -> QuerySet: """ Returns the filtered queryset based on the value provided in the query string and retrievable via `self.value()`. """ if self.value() is None: return self.get_default_queryset(queryset) if self.value() == self.no_filter_value: return queryset.all() return self.get_filtered_queryset(queryset) def get_default_queryset(self, queryset: QuerySet) -> QuerySet: return queryset.filter(**{self.parameter_name: self.get_default_value()}) def get_filtered_queryset(self, queryset: QuerySet) -> QuerySet: try: return queryset.filter(**self.used_parameters) except (ValueError, ValidationError) as e: # Fields may raise a ValueError or ValidationError when converting # the parameters to the correct type. raise IncorrectLookupParameters(e) # Overriding parent class: def choices(self, changelist: ChangeList): """ Overridden to prevent the default "All". """ value = self.value() or force_str(self.get_default_value()) for lookup, title in self.lookup_choices: yield { 'selected': value == force_str(lookup), 'query_string': changelist.get_query_string({self.parameter_name: lookup}), 'display': title, }
Przykład pełnego wykorzystania:
from django.contrib import admin from .models import SomeModelWithStatus class StatusFilter(PreFilteredListFilter): default_value = SomeModelWithStatus.Status.FOO title = _('Status') parameter_name = 'status' def get_lookups(self): return SomeModelWithStatus.Status.choices @admin.register(SomeModelWithStatus) class SomeModelAdmin(admin.ModelAdmin): list_filter = (StatusFilter, )
Mam nadzieję, że to komuś pomoże; opinie zawsze mile widziane.
źródło