Django admin中的默认过滤器


94

如何从“全部”更改默认过滤器选择?我有一个名为领域status它有三个值:activatependingrejected。当我list_filter在Django admin中使用时,过滤器默认情况下设置为“全部”,但我想默认将其设置为待处理。

Answers:


102

为了实现这一点,在侧边栏中有一个可用的“全部”链接(即显示全部而不显示未决的链接),您需要创建一个自定义列表过滤器,django.contrib.admin.filters.SimpleListFilter默认情况下继承并过滤“待处理”。遵循这些原则的方法应该起作用:

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] 

编辑:需要Django 1.4(感谢Simon)


3
这是所有解决方案中最干净的解决方案,但支持票数最少……尽管它现在应该是给定的,但它需要Django 1.4。
西蒙(Simon)

@Greg如何从管理页面中完全删除过滤器和过滤器选项卡的功能


2
该解决方案具有小的缺点。当过滤器为空(实际上使用的“待定”过滤器)时,如果show_full_result_count为True(默认情况),则Django 1.8错误地确定了完整结果计数,并且不显示结果计数。–
亚历山大·费多托夫

请注意,如果您无法覆盖choices解决方案中的方法,它将烦人地继续在选择列表的顶部添加其自己的“ 全部”选项。
理查德

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)

18
该解决方案的缺点在于,尽管“全部”选项仍显示在UI中,但选择它仍将应用默认过滤。
akaihola

我有同样的问题,但我能理解重播...对不起即时通讯新Django的...但也许这将工作 blog.dougalmatthews.com/2008/10/...
Asinox

很好,但是我需要在url中查看get参数,以便我的过滤器可以选择它并显示为选中状态。很快发布我的解决方案。
radtek 2014年

缺少说明。仅仅发布一段代码可能对所有人都没有帮助。最重要的是,它不起作用,没有一点上下文,很难找出原因
EvilSmurf

19

接受了ha22109的解答,并进行了修改,以允许通过比较HTTP_REFERER和选择“全部” 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)

3
这对我来说很麻烦,因为HTTP_REFERER并不总是存在。我做了'referer = request.META.get('HTTP_REFERER',''); 测试= Referer.split(request.META ['PATH_INFO'])`
ben作者

@Ben我正在使用您的两行Referer = request.META.get('HTTP_REFERER','')test = Referer.split(request.META ['PATH_INFO'])。我对HTTP_REFERER的了解不多。如果不存在HTTP_REFERER,是否已从这些行完全解决了问题。
the_game 2012年

@the_game是的,这个想法是,如果您使用方括号尝试访问不存在的键,则抛出该错误KeyError,如果您使用dict的get()方法,则可以指定一个默认值。我指定了默认的空字符串,以使split()不会抛出异常AttributeError。就这样。
本作者作者

@Ben。谢谢它为我工作。您也可以回答这个问题,我相信这是对这个问题的扩展,仅 stackoverflow.com/questions/10410982/…。您能为我提供一个解决方案吗?
thegame

1
这很好。has_key()不推荐使用key in d,但是。但我知道您只是从ha22109的答案中获取的。一个问题:为什么request.META['PATH_INFO']在您可以使用时request.path_info(较短)使用?
尼克

19

我知道这个问题现在已经很老了,但仍然有效。我相信这是最正确的方法。它本质上与Greg的方法相同,但公式化为可扩展的类,以方便重用。

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

8

这是我使用重定向的通用解决方案,它仅检查是否有任何GET参数,如果不存在,则使用默认的get参数进行重定向。我还设置了一个list_filter,因此它将其选中并显示默认值。

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)

唯一需要注意的是,当您使用“?”直接进入页面时 网址中没有设置HTTP_REFERER,因此它将使用默认参数并重定向。这对我来说很好,当您单击管理过滤器时,效果很好。

更新

为了解决这个问题,我最终编写了一个自定义过滤器函数,该函数简化了changelist_view功能。这是过滤器:

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)

现在,changelist_view仅在不存在默认参数的情况下才传递默认参数。这个想法是通过不使用get参数来摆脱泛型过滤器查看所有内容的功能。要查看全部,为此我分配了状态= 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 2014年

谢谢,我发现重定向是最干净,最简单的解决方案。我也不理解“警告”。无论通过单击还是使用直接链接,我总是会得到理想的结果(我没有使用自定义过滤器)。
丹尼斯·戈洛马佐夫

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 )

4

您可以简单地使用return queryset.filter()if self.value() is None重写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

请注意,如果您不想始终选择过滤器值,而是希望始终在对数据进行管理之前对其进行预过滤,则应改写该ModelAdmin.queryset()方法。


尽管这可能仍然会引起问题,但这是一种非常干净快捷的解决方案。在管理员中启用过滤选项后,用户可能会得到看似不正确的结果。如果重写的查询集包含.exclude()子句,则将永远不会列出由该查询集捕获的记录,但仍将由admin UI提供用于显式显示它们的admin过滤选项。
Tomas Andrle

还有其他更正确的答案,但票数较低,这适用于这种情况,因为OP明确要求他将放置一个过滤器,在该过滤器中,查询集将是错误的解决方案,正如上面的@TomasAndrle所指出的那样。
eskhool 2015年

感谢您指出@eskhool,我尝试将答案减为零,但似乎不允许自己减数。
akaihola 2015年

3

Greg使用DjangoChoices,Python> = 2.5,当然还有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] 

感谢格雷格提供了不错的解决方案!


2

我知道这不是最好的解决方案,但是我在管理模板的第25行和第37行中更改了index.html,如下所示:

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

我必须进行修改才能使过滤正常工作。页面加载时,先前的解决方案对我有用。如果执行了“操作”,则过滤器将返回“全部”,而不是我的默认设置。此解决方案使用默认过滤器加载管理更改页面,但是当页面上发生其他活动时,也可以维护过滤器更改或当前过滤器。我没有测试所有情况,但实际上,它可能限制了默认过滤器的设置,使其仅在页面加载时才发生。

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

有点题外话,但我对类似问题的搜索将我引到了这里。我希望按日期进行默认查询(即,如果未提供任何输入,则仅显示带有timestamp“ Today”的对象),这使问题变得有些复杂。这是我想出的:

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

这是对default的简单覆盖DateFieldListFilter。通过设置self.date_params,可以确保过滤器下拉列表将更新为与匹配的任何选项self.used_parameters。出于这个原因,您必须确保self.used_parameters下拉列表选择项中的一个恰好会使用。(即,找出date_params使用“今天”或“过去7天”时的含义,并构造与之self.used_parameters匹配的)。

旨在与Django 1.4.10一起使用


1

这可能是一个旧线程,但是我想添加我的解决方案,因为我无法在Google搜索中找到更好的答案。

在ModelAdmin中为changelist_view做什么(不确定其Deminic Rodger还是ha22109)

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)

然后我们需要创建一个自定义的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

我发现我需要在choices函数的yield调用中使用“ force_text”(又名force_unicode)函数,否则所选过滤器选项将不会显示为“ selected”。那就是“'选定':self.value()== force_text(lookup),”
MagicLAMP 2015年

1

这是我能够生成的具有筛选器的最干净的版本,该筛选器具有重新定义的“全部”和已选择的默认值。

如果默认情况下显示我,则当前正在发生的旅行。

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

0

创建了一个可重用的Filter子类,其灵感来自此处的某些答案(主要是Greg的答案)。

优点:

可重复使用 -可插入任何标准ModelAdmin类别

可扩展 -易于添加其他/自定义逻辑进行QuerySet过滤

易于使用 -以其最基本的形式,只需实现一个自定义属性和一种自定义方法(除了SimpleListFilter子类所需的那些属性)

直观的管理员 -“所有”过滤器链接按预期工作;和所有其他人一样

无需重定向 -无需检查GET不可知的请求有效负载HTTP_REFERER(或其他任何与请求相关的内容,基本形式)

否(更改列表)视图操纵 -也没有模板操作(上帝禁止)

码:

(大多数imports仅用于类型提示和异常)

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

完整用法示例:

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

希望这对某人有帮助;反馈总是赞赏。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.