Domyślny filtr w panelu administracyjnym Django

94

Jak mogę zmienić domyślny wybór filtru z „WSZYSTKIE”? Mam pole o nazwie jako statusktóry ma trzy wartości: activate, pendingi rejected. Kiedy używam list_filterw panelu administracyjnym Django, filtr jest domyślnie ustawiony na „Wszystkie”, ale chcę ustawić go jako oczekujący.

ha22109
źródło

Odpowiedzi:

105

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.SimpleListFilterdomyś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)

Greg
źródło
3
Jest to najczystsze rozwiązanie ze wszystkich, ale ma najmniej głosów pozytywnych ... wymaga jednak Django 1.4, chociaż powinno to być już pewne.
Simon
@Greg Jak całkowicie usunąć funkcjonalność filtra i zakładki filtru ze strony administratora?
2
To rozwiązanie ma małą wadę. Kiedy filtry są puste (faktycznie używany filtr „oczekujący”), Django 1.8 nieprawidłowo określa pełną liczbę wyników i nie wyświetla liczby wyników, jeśli show_full_result_count ma wartość True (domyślnie). -
Alexander Fedotov
Zwróć uwagę, że jeśli nie uda Ci się zastąpić choicesmetody w rozwiązaniu, irytująco będzie nadal dodawać własną opcję Wszystkie na górze listy opcji.
Richard
47
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)
ha22109
źródło
18
To rozwiązanie ma tę wadę, że chociaż opcja „Wszystkie” jest nadal wyświetlana w interfejsie użytkownika, wybranie jej nadal powoduje zastosowanie domyślnego filtrowania.
akaihola
mam to samo pytanie, ale rozumiem powtórkę ... przepraszam, jestem
Asinox
To jest dobre, ale musiałem zobaczyć parametr get w adresie URL, aby mój filtr mógł go odebrać i pokazać wybrany. Wkrótce publikuję moje rozwiązanie.
radtek
brak wyjaśnienia. samo wysłanie fragmentu kodu może nie pomóc wszystkim. w
dodatku
19

Wziął powyższą odpowiedź ha22109 i zmodyfikował, aby umożliwić wybór „Wszystkie” przez porównanie HTTP_REFERERi PATH_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)
opalowy
źródło
3
Zepsuło się to dla mnie, ponieważ HTTP_REFERER nie zawsze był obecny. Zrobiłem 'referer = request.META.get (' HTTP_REFERER ',' '); test = referer.split (request.META ['PATH_INFO']) `
ben autor
@Ben Używam twoich dwóch wierszy referer = request.META.get ('HTTP_REFERER', '') test = referer.split (request.META ['PATH_INFO']). Nie mam zbyt wiele na temat HTTP_REFERER. Czy problem został całkowicie rozwiązany z tych wierszy, jeśli nie ma protokołu HTTP_REFERER.
the_game
@the_game tak, pomysł jest taki, że jeśli użyjesz nawiasów kwadratowych, aby spróbować uzyskać dostęp do klucza, który nie istnieje, rzuca się KeyError, w przypadku gdy używasz metody dykta get(), możesz określić domyślną. Podałem wartość domyślną pustego ciągu, aby split () nie zgłaszał AttributeError. To wszystko.
autor ben
@ Ben. Dzięki, że działa dla mnie. Czy możesz również odpowiedzieć na to pytanie, uważam, że jest to rozszerzenie tego pytania tylko stackoverflow.com/questions/10410982/… . Czy możesz mi podać rozwiązanie tego problemu.
the_game
1
To działa dobrze. 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)?
Nick
19

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,)
Andrew Hows
źródło
8

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)
radtek
źródło
Mam poprawkę na moje zastrzeżenie, niestandardowy filtr. Przedstawię to jako alternatywne rozwiązanie.
radtek
Dziękuję, uważam, że przekierowanie jest najczystszym i najprostszym rozwiązaniem. Nie rozumiem też „zastrzeżenia”. Zawsze osiągam pożądany efekt, czy to klikając, czy używając bezpośredniego linku (nie użyłem niestandardowego filtra).
Dennis Golomazov
6
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 )
user1163719
źródło
4

Możesz po prostu użyć return queryset.filter()lub if self.value() is Nonei Override metody SimpleListFilter

from 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,
        }
Jay Dave
źródło
3

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ę.

akaihola
źródło
Jest to dość czyste i szybkie rozwiązanie, chociaż nadal może powodować problemy. Gdy opcje filtrowania są włączone w panelu administracyjnym, użytkownik może uzyskać pozornie niepoprawne wyniki. Jeśli nadpisany zestaw zapytań zawiera klauzulę .exclude (), wówczas przechwycone przez nią rekordy nigdy nie zostaną wymienione, ale opcje filtrowania administratora, aby jawnie je pokazać, będą nadal oferowane przez interfejs użytkownika administratora.
Tomas Andrle,
Istnieją inne, bardziej poprawne odpowiedzi z niższą liczbą głosów, które odnoszą się do tej sytuacji, ponieważ PO wyraźnie zażądał, aby umieścił filtr, w którym zestaw zapytań byłby niewłaściwym rozwiązaniem, jak również wskazał @TomasAndrle powyżej.
eskhool
Dziękuję za wskazanie tego @eskhool, próbowałem obniżyć moją odpowiedź do zera, ale wydaje się, że nie wolno głosować przeciw sobie.
akaihola
3

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!

Ben Konrath
źródło
2

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>

Mauro De Giorgi
źródło
1

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)
mhck
źródło
1

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ąc self.date_params, zapewnia, że ​​lista rozwijana filtru zostanie zaktualizowana do dowolnej opcji zgodnej z self.used_parameters. Z tego powodu musisz upewnić się, że self.used_parameterssą dokładnie tym, czego używałby jeden z tych elementów rozwijanych (tj. Dowiedzieć się, co date_paramsby 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

alukach
źródło
1

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
warath-coder
źródło
Zauważyłem, że muszę użyć funkcji „force_text” (aka force_unicode) w wywołaniu yield w funkcji wyborów, w przeciwnym razie wybrana opcja filtru nie byłaby wyświetlana jako „wybrana”. To jest „'selected': self.value () == force_text (lookup),"
MagicLAMP,
1

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()
Jerome Millet
źródło
0

Utworzono podklasę filtrów wielokrotnego użytku, zainspirowaną niektórymi odpowiedziami tutaj (głównie Grega).

Zalety:

Wielokrotnego użytku - wtykowe w dowolnych ModelAdminklasach standardowych

Rozszerzalny - Łatwe dodawanie dodatkowej / niestandardowej logiki do QuerySetfiltrowania

Ł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, agnostyka HTTP_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 importdotyczy 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.

JohnGalt
źródło