web-dev-qa-db-fra.com

Filtre par défaut dans Django admin

Comment puis-je changer le choix de filtre par défaut de 'TOUS'? J'ai un champ nommé status qui a trois valeurs: activate, pending et rejected. Lorsque j'utilise list_filter in Django admin, le filtre est par défaut réglé sur 'All' mais je veux le mettre en attente par défaut.

85
ha22109
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)
44
ha22109

Pour y parvenir et avoir un lien "Tous" utilisable dans votre barre latérale (c'est-à-dire un qui montre tout plutôt que de montrer en attente), vous devez créer un filtre de liste personnalisé, héritant de Django.contrib.admin.filters.SimpleListFilter et filtrage sur "en attente" par défaut. Quelque chose dans ce sens devrait fonctionner:

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] 

EDIT: Nécessite Django 1.4 (merci Simon)

90
Greg

Pris la réponse de ha22109 ci-dessus et modifié pour permettre la sélection de "Tous" en comparant HTTP_REFERER et 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)
18
iridescent

Je sais que cette question est assez ancienne maintenant, mais elle est toujours valable. Je pense que c'est la façon la plus correcte de procéder. C'est essentiellement la même que la méthode de Greg, mais formulée comme une classe extensible pour une réutilisation facile.

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,)
16
Andrew Hows

Voici ma solution générique utilisant la redirection, elle vérifie simplement s'il y a des paramètres GET, si aucun n'existe, puis elle redirige avec le paramètre get par défaut. J'ai également un ensemble list_filter donc il reprend cela et affiche la valeur par défaut.

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)

La seule mise en garde est lorsque vous accédez directement à la page avec "?" présent dans l'url, il n'y a pas de HTTP_REFERER défini donc il utilisera le paramètre par défaut et redirigera. C'est très bien pour moi, cela fonctionne très bien lorsque vous cliquez sur le filtre d'administration.

MISE À JOUR :

Afin de contourner la mise en garde, j'ai fini par écrire une fonction de filtre personnalisé qui simplifiait la fonctionnalité changelist_view. Voici le filtre:

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)

Et le changelist_view passe désormais uniquement le paramètre par défaut si aucun n'est présent. L'idée était de se débarrasser de la capacité des filtres génériques pour tout voir en n'utilisant aucun paramètre get. Pour voir tout j'ai assigné le statut = 8 à cet effet .:

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)
7
radtek
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 )
4
user1163719

Vous pouvez simplement utiliserreturn queryset.filter() ou if self.value() is None et la méthode Override de 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,
        }
3
Jay Dave

Notez que si, au lieu de présélectionner une valeur de filtre, vous souhaitez toujours pré-filtrer les données avant de les afficher dans l'admin, vous devez remplacer la méthode ModelAdmin.queryset() à la place.

2
akaihola

Une légère amélioration de la réponse de Greg en utilisant DjangoChoices, Python> = 2.5 et bien sûr 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] 

Merci à Greg pour la belle solution!

2
Ben Konrath

Je sais que ce n'est pas la meilleure solution, mais j'ai changé le fichier index.html dans le modèle d'administration, lignes 25 et 37 comme ceci:

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>

1
Mauro De Giorgi

J'ai dû faire une modification pour que le filtrage fonctionne correctement. La solution précédente a fonctionné pour moi lors du chargement de la page. Si une "action" était effectuée, le filtre revenait à "Tout" et non à ma valeur par défaut. Cette solution charge la page de modification d'administration avec le filtre par défaut, mais conserve également les modifications de filtre ou le filtre actuel lorsqu'une autre activité se produit sur la page. Je n'ai pas testé tous les cas, mais en réalité, cela peut limiter le réglage d'un filtre par défaut pour ne se produire que lorsque la page se charge.

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)
1
mhck

Un peu hors sujet mais ma recherche d'une question similaire m'a conduit ici. Je cherchais à avoir une requête par défaut par une date (c'est-à-dire si aucune entrée n'est fournie, n'afficher que les objets avec timestamp de 'Aujourd'hui'), ce qui complique un peu la question. Voici ce que j'ai trouvé:

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),
    )

Il s'agit d'une simple substitution de la valeur par défaut DateFieldListFilter. En définissant self.date_params, il garantit que la liste déroulante du filtre sera mise à jour en fonction de l'option correspondant à self.used_parameters. Pour cette raison, vous devez vous assurer que le self.used_parameters sont exactement ce qui serait utilisé par l'une de ces sélections déroulantes (c.-à-d., découvrez ce que le date_params serait lorsque vous utilisez le "Aujourd'hui" ou les "7 derniers jours" et construisez le self.used_parameters pour correspondre à ceux-ci).

Ceci a été construit pour fonctionner avec Django 1.4.10

0
alukach

Cela peut être un vieux fil de discussion, mais j'ai pensé ajouter ma solution car je n'ai pas pu trouver de meilleures réponses dans les recherches Google.

Faites quoi (vous ne savez pas si son Deminic Rodger ou ha22109) a répondu dans le ModelAdmin pour 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)

Ensuite, nous devons créer un SimpleListFilter personnalisé

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
0
warath-coder