Django 1.3或更低版本上Django Admin中的自定义过滤器


73

如何向django admin(模型仪表板右侧显示的过滤器)添加自定义过滤器?我知道很容易包含基于该模型字段的过滤器,但是像这样的“计算”字段呢:

class NewsItem(models.Model):
    headline = models.CharField(max_length=4096, blank=False)
    byline_1 = models.CharField(max_length=4096, blank=True)
    dateline = models.DateTimeField(help_text=_("date/time that appears on article"))
    body_copy = models.TextField(blank=False)

    when_to_publish = models.DateTimeField(verbose_name="When to publish",  blank=True, null=True)

    # HOW CAN I HAVE "is_live" as part of the admin filter?  It's a calculated state!!
    def is_live(self):
        if self.when_to_publish is not None:
            if ( self.when_to_publish < datetime.now() ):
                return """ <img alt="True" src="/media/img/admin/icon-yes.gif"/> """
        else:
            return """ <img alt="False" src="/media/img/admin/icon-no.gif"/> """      

    is_live.allow_tags = True

class NewsItemAdmin(admin.ModelAdmin):
    form = NewsItemAdminForm
    list_display = ('headline', 'id', 'is_live')
    list_filter = ('is_live')  #  how can i make this work??

其他人已经说过此功能在后备箱(1.4 dev)中。更多信息:发行说明文档
Paolo 2012年

1
这是文档的更好链接;扩展SimpleListFilter是这里的方法。FilterSpec已过期。 docs.djangoproject.com/en/dev/ref/contrib/admin/...
fastmultiplication

请参阅下面的Matley答案,以及指向官方文档的链接。
DenilsonSáMaia 2014年

Answers:


57

感谢gpilotino为我提供了实现正确方向的推动力。

我注意到该问题的代码正在使用datetime来确定其生效时间。因此,我使用了DateFieldFilterSpec并将其子类化。

from django.db import models
from django.contrib.admin.filterspecs import FilterSpec, ChoicesFilterSpec,DateFieldFilterSpec
from django.utils.encoding import smart_unicode
from django.utils.translation import ugettext as _
from datetime import datetime

class IsLiveFilterSpec(DateFieldFilterSpec):
    """
    Adds filtering by future and previous values in the admin
    filter sidebar. Set the is_live_filter filter in the model field attribute
    'is_live_filter'.    my_model_field.is_live_filter = True
    """

    def __init__(self, f, request, params, model, model_admin):
        super(IsLiveFilterSpec, self).__init__(f, request, params, model,
                                               model_admin)
        today = datetime.now()
        self.links = (
            (_('Any'), {}),
            (_('Yes'), {'%s__lte' % self.field.name: str(today),
                       }),
            (_('No'), {'%s__gte' % self.field.name: str(today),
                    }),

        )


    def title(self):
        return "Is Live"

# registering the filter
FilterSpec.filter_specs.insert(0, (lambda f: getattr(f, 'is_live_filter', False),
                               IsLiveFilterSpec))

要使用它,您可以将上面的代码放入filters.py,然后将其导入要向其中添加过滤器的模型


2
您能否详细说明此代码的最后一部分?
尼克·海纳尔

Rosarch,最后一行代码在Django中注册is_live_filter,然后在您的model.py中,模型类中说Article,您有一个名为publish_date的字段,您将调用publish_date.is_live_filter
Mark

1
最后一行filter_specs.insert非常重要,否则您的自定义过滤器可能不会显示,该字段类型的内置过滤器规范之一将显示。(我起初没有正确地阅读答案,而是像内置filterspecs一样使用.register方法!)
Anentropic 2011年

9
注意:在Django 1.4中,filterspecs(始终是内部黑客)已重构为ListFilter,并提供了更简洁的
Philippe Ombredanne



3

不幸的是,你不能。当前,非字段项不能用作list_filter条目。

请注意,即使您的管理类是一个字段,它也不会起作用,因为单项元组需要逗号: ('is_live',)


2
FWIW,针对#5833的修复程序现已在django 1.4的django中继中
Philippe Ombredanne

3

只是一个旁注:您可以像这样在Django管理员上更轻松地使用默认标记:

def is_live(self):
    if self.when_to_publish is not None:
        if ( self.when_to_publish < datetime.now() ):
            return True
    else:
        return False

is_live.boolean = True

3

这不是最佳方法(CPU方式),但是简单且可以工作,因此我以这种方式(对于我的小型数据库)这样做。我的Django版本是1.6。

在admin.py中:

class IsLiveFilter(admin.SimpleListFilter):
    title = 'Live'
    parameter_name = 'islive'
    def lookups(self, request, model_admin):
        return (
            ('1', 'islive'),
        )
    def queryset(self, request, queryset):
        if self.value():
            array = []
            for element in queryset:
                if element.is_live.__call__() == True:
                    q_array.append(element.id)
            return queryset.filter(pk__in=q_array)

...

class NewsItemAdmin(admin.ModelAdmin):
    form = NewsItemAdminForm
    list_display = ('headline', 'id', 'is_live')
    list_filter = (IsLiveFilter)

这里的关键思想是通过__call __()函数访问QuerySet中的自定义字段。


也许不是最优化的方法,而是一种简单有效的方法。它使我免于头痛。
d6bels 2015年

2

用户向某些国家/地区免费提供商品。我想过滤这些国家:

全部-所有国家/地区,-免邮费,-收取邮费。

我想这个问题的主要答案对我不起作用(Django 1.3),因为field_path__init__方法中没有提供参数。它也属于子类DateFieldFilterSpec。该postage字段是FloatField

from django.contrib.admin.filterspecs import FilterSpec

class IsFreePostage(FilterSpec):

    def __init__(self, f, request, params, model, model_admin, field_path=None):
        super(IsFreePostage, self).__init__(f, request, params, model,
            model_admin, field_path)

        self.removes = {
            'Yes': ['postage__gt'],
            'No': ['postage__exact'],
            'All': ['postage__exact', 'postage__gt'] }

        self.links = (
            ('All', {}),
            ('Yes', {'postage__exact': 0}),
            ('No', {'postage__gt': 0}))

        if request.GET.has_key('postage__exact'):
            self.ttl = 'Yes'
        elif request.GET.has_key('postage__gt'):
            self.ttl = 'No'
        else:
            self.ttl = 'All'

    def choices(self, cl):
        for title, param_dict in self.links:
            yield {'selected': title == self.ttl,
                   'query_string': cl.get_query_string(param_dict,
                       self.removes[title]),
                   'display': title}
    def title(self):
        return 'Free Postage'

FilterSpec.filter_specs.insert(0,
    (lambda f: getattr(f, 'free_postage', False), IsFreePostage))

在self.links中,我们提供字典。用于构造HTTP查询字符串,例如?postage__exact=0用于每个可能的过滤器。我认为过滤器是累积性的,因此,如果以前有一个“否”的请求,而现在我们有一个“是”的请求,则必须删除“否”查询。self.removes指定每个查询需要删除的内容。该choices方法构造查询字符串,说出选择了哪个过滤器并设置过滤器的显示名称。


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.