Django管理后台默认过滤器

21

我知道我以前已经成功做到了这一点,但是我不记得如何操作,也找不到任何相关文档...

在管理员页面中,如何默认应用一个对象列表的筛选器?

我有一个应用程序,它列出报价单,这些报价单有一个状态(例如:已接受、已拒绝、挂起等)。

我想默认设置筛选器为status='accepted'。


1
仅为记录:此问题的正确答案在此处回答:https://dev59.com/VXRA5IYBdhLWcg3wuAYo#3783930 - Francisco Puga
1
可能是在Django管理界面中的默认过滤器的重复问题。 - Amir Ali Akbari
9个回答

22
一个更具可重用性的方法:
class DefaultFilterMixIn(admin.ModelAdmin):
    def changelist_view(self, request, *args, **kwargs):
        from django.http import HttpResponseRedirect
        if self.default_filters:
            try:
                test = request.META['HTTP_REFERER'].split(request.META['PATH_INFO'])
                if test and test[-1] and not test[-1].startswith('?'):
                    url = reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.module_name))
                    filters = []
                    for filter in self.default_filters:
                        key = filter.split('=')[0]
                        if not request.GET.has_key(key):
                            filters.append(filter)
                    if filters:                        
                        return HttpResponseRedirect("%s?%s" % (url, "&".join(filters)))
            except: pass
        return super(DefaultFilterMixIn, self).changelist_view(request, *args, **kwargs)            

然后在你的ModelAdmin上定义一个default_filters:

class YourModelAdmin(DefaultFilterMixIn):
    ....
    default_filters = ('snapshot__exact=0',)

对我来说(可能因为我使用django-grappelli),这个代码可以工作,但只有当它是snapshot=0而不是snapshot__exact=0时才有效。另外,由于javascript出现问题(控制台显示:“Uncaught TypeError: Object [object Object] has no method 'actions'”),过滤器不再起作用,这可能是由于grappelli引起的。抱歉这是一个长句子,哈哈。 - Dan Mantyla
使用django==1.3.4和grappelli==2.3.9对我完美适用。对于这个mixin真是太棒了。 - Eli Albért
1
谢谢您提供这段代码片段,但它不适用于Python 3和Django 1.9.7。仅仅静默异常并不是一个好主意,所以我将其注释掉了。然后我发现self.opts.module_name应该改为self.opts.model_name,而“request.GET.has_key(key)”已被替换为“key in request.GET”。(更新的代码太长无法在此处接受) - Jens-Erik Weber
根据Django>=1.8的发布说明:https://docs.djangoproject.com/en/1.8/internals/deprecation/,必须将`self.opts.module_name`替换为`self.opts.model_name`。__Model._meta.module_name已被重命名为model_name。 - AlexisCaffa

8

最终,这就是我一直在寻找的:

def changelist_view(self, request, extra_context=None):
    if not request.GET.has_key('status__exact'):
        q = request.GET.copy()
        q['status__exact'] = '1'
        request.GET = q
        request.META['QUERY_STRING'] = request.GET.urlencode()
    return super(SoumissionAdmin,self).changelist_view(request, extra_context=extra_context)

另一种方法是在管理类中使用queryset方法,但这种方法不起作用。实际上,它确实过滤了结果,但它会破坏过滤功能。
我找到的解决方案也不完美,当使用它来选择“全部/筛选器”时是不可能的。尽管在我的情况下并不严重,但它已经足够好了。

7
您可以覆盖查询集。
class QuoteAdmin(admin.ModelAdmin):
    def get_queryset(self, request):
        return super(QuoteAdmin,self).get_queryset(request).filter(status="accepted")

然而,通过覆盖查询集,您将无法查看状态不为 "已接受" 的报价。

或者,您可以链接到以下URL,并将筛选器添加到GET参数中。

/admin/myapp/quote/?status=accepted

6

我使用支持“all”的方式解决了这个问题。

在models.py中:

STATUSES_CHOICE = (
    ('0', 'Active'),
    ('1', 'Deactive'),
    ('2', 'Suspended'),
)

class Client(models.Model):
    ...
    status = models.IntegerField(verbose_name=_('Status'),
                                 default=0,
                                 db_index=True)

在admin.py中:

class StatusAdminFilter(SimpleListFilter):
    title = _('Status')
    parameter_name = 'status'
    all_param_value = 'all'

    def lookups(self, request, model_admin):
        return STATUSES_CHOICE

    def queryset(self, request, queryset):
        status = self.value()
        try:
            return (queryset if status == self.all_param_value else
                    queryset.filter(status=int(status)))
        except ValueError:
            raise Http404

    def choices(self, cl):
        yield {'selected': self.value() == self.all_param_value,
               'query_string': cl.get_query_string(
                   {self.parameter_name: self.all_param_value}, 
                   [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}


class ClientAdmin(admin.ModelAdmin):
    list_filter = (StatusAdminFilter,)

    def changelist_view(self, request, extra_context=None):
        if not request.GET.has_key('status'):
            q = request.GET.copy()
            q['status'] = '0'  # default value for status
            request.GET = q
            request.META['QUERY_STRING'] = request.GET.urlencode()
        return super(ClientAdmin, self).changelist_view(
            request, extra_context=extra_context)

谢天谢地。我找不到删除“全部”的方法。其他人都说你只需要在“查找”函数中留下它。实际上,你需要在“选择”函数中将其留空。 - MagicLAMP

5
简短而简洁的解决方案。当单击更改列表视图上的“全部”选项时,可以很好地工作。
    def changelist_view(self, request, extra_context=None):
        if not request.META['QUERY_STRING'] and \
            not request.META.get('HTTP_REFERER', '').startswith(request.build_absolute_uri()):
            return HttpResponseRedirect(request.path + "?status__exact=1")
        return super(YourModelAdmin,self).changelist_view(request, extra_context=extra_context)

2

我认为我找到了一种不限制用户的方法来实现这个问题。只需查看引荐者以确定用户是否刚刚到达此页面。如果是,根据该过滤器将其重定向到您想要的默认URL。

def changelist_view(self, request, extra_context=None):
    try:
        test = request.META['HTTP_REFERER'].split(request.META['PATH_INFO'])
        if test and test[-1] and not test[-1].startswith('?') and not request.GET.has_key('status__exact'):
            return HttpResponseRedirect("/admin/app/model/?status__exact=1")
    except: pass # In case there is no referer
    return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)

这是一个相当硬编码的hack,但这个答案是有效的。我通过将“/admin/app/model/”替换为request.path并在发送http响应重定向之前将我的等效“?status__exact=1”连接到它上面,使其稍微不那么hackish。 - Archie1986

1

这是我对glic3rinu的代码更新(请参见评论),适用于Python 3.4和Django 1.9.7:

class DefaultFilterMixIn(admin.ModelAdmin):
    def changelist_view(self, request, *args, **kwargs):
        from django.http import HttpResponseRedirect
        if self.default_filters:
            #try:
                test = request.META['HTTP_REFERER'].split(request.META['PATH_INFO'])
                if test and test[-1] and not test[-1].startswith('?'):
                    url = reverse('admin:{}_{}_changelist'.format(self.opts.app_label, self.opts.model_name))
                    filters = []
                    for filter in self.default_filters:
                        key = filter.split('=')[0]
                        if not key in request.GET:
                            filters.append(filter)
                    if filters:                     
                        return HttpResponseRedirect("{}?{}".format(url, "&".join(filters)))
            #except: pass
        return super(DefaultFilterMixIn, self).changelist_view(request, *args, **kwargs)            

HTTP_REFERER不总是存在(或相关),REQUEST_URI似乎也不会存在(我不知道为什么但我没有它)...除此之外,我看不出有安全的方法可以知道URL中是否存在“?”。 - bparker

1

这对我有用,并避免了h3提到的“全部”问题。

class MyAdmin(admin.ModelAdmin):
  def changelist_view(self, request, extra_context=None):
    referer = request.META.get('HTTP_REFERER', '')
    showall = request.META['PATH_INFO'] in referer and not request.GET.has_key('timeframe')
    if not showall and not request.GET.has_key('param_name_here'):
        q = request.GET.copy()
        q['param_name_here'] = 'default_value_here'
        request.GET = q
        request.META['QUERY_STRING'] = request.GET.urlencode()
    return super(SerializableAdmin,self).changelist_view(request, extra_context=extra_context)

0

这是我尝试在管理员中设置默认过滤器的方法(仅在Django 1.11中进行了测试):

class ZeroCountListFilter(admin.SimpleListFilter):
    title = _('include zero count')
    parameter_name = 'count'

    def choices(self, changelist):
        yield {
            'selected': self.value() is None or self.value() == 0,
            'query_string': changelist.get_query_string({}, [self.parameter_name]),
            'display': _('No'),
        }
        yield {
            'selected': self.value() == '1',
            'query_string': changelist.get_query_string({self.parameter_name: '1'}, []),
            'display': _("Yes"),
        }

    def lookups(self, request, model_admin):
        return (
            ('0', _('No')),
            ('1', _('Yes')),
        )

    def queryset(self, request, queryset):
        if self.value() is None or self.value() == '0':
            return queryset.exclude(count=0)
        else:
            return queryset

诀窍在于检查self.value() is None以获取默认行为


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接