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

Posted

技术标签:

【中文标题】Django 1.3 或更低版本的 Django Admin 中的自定义过滤器【英文标题】:Custom Filter in Django Admin on Django 1.3 or below 【发布时间】:2010-11-02 18:40:30 【问题描述】:

如何向 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  src="/media/img/admin/icon-yes.gif"/> """
        else:
            return """ <img  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)中。更多信息:release note 和 documentation。 这里有一个更好的文档链接;扩展 SimpleListFilter 是要走的路。 FilterSpecs 已过期。 docs.djangoproject.com/en/dev/ref/contrib/admin/… 见下面的matley answer,带有官方文档的链接。 【参考方案1】:

感谢 gpilotino 让我朝着正确的方向推进。

我注意到问题的代码使用日期时间来确定它的活动时间。所以我使用了 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中,然后在你想要添加过滤器的模型中导入

【讨论】:

您能否详细说明您使用此代码所做的最后一部分? Rosarch,最后一行代码在 django 中注册了 is_live_filter,然后在模型类的 models.py 中让我们说文章,你有一个名为 publish_date 的字段,你可以调用 publish_date.is_live_filter 最后一行 filter_specs.insert 非常重要,否则您的自定义过滤器可能不会显示,而是显示该字段类型的内置过滤器规格之一. (我一开始没有正确阅读答案,并且正在使用 .register 方法,就像内置的 filterspecs 使用一样!) 注意:在 Django 1.4 中,filterspecs(一直是内部黑客)已被重构为 ListFilter 并提供更清晰的自定义路径:请参阅 code.djangoproject.com/ticket/5833【参考方案2】:

你必须编写一个自定义的 FilterSpec(不是任何地方的文档)。 看这里的例子:

http://www.djangosnippets.org/snippets/1051/

【讨论】:

【参考方案3】:

在当前的 django 开发版本中,支持自定义过滤器:https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter

【讨论】:

更具体一点:docs.djangoproject.com/en/dev/ref/contrib/admin/… @Webthusiast 随意编辑任何答案以改进它。在这种情况下,我已经将您更具体的 URL 合并到了答案中。【参考方案4】:

很遗憾,你不能。目前非字段项不能作为list_filter条目。

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

【讨论】:

FWIW,#5833 的修复现在在 django 1.4 的 django 主干中【参考方案5】:

只是一个旁注:您可以像这样更轻松地在 Django admin 上使用默认刻度:

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

【讨论】:

【参考方案6】:

不是最佳方式(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 中的自定义字段。

【讨论】:

也许不是最优化的方式,但一种简单且有效的方式。它让我头疼。【参考方案7】:

用户向部分国家免费提供商品。我想过滤这些国家:

所有 - 所有国家, - 免邮费, - 收取邮费。

我认为这个问题的主要答案对我不起作用(Django 1.3),因为__init__ 方法中没有提供field_path 参数。它也是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 方法构造查询字符串,说明选择了哪个过滤器并设置过滤器的显示名称。

【讨论】:

【参考方案8】:

这是答案并尽可能简单地实现自定义过滤器,这可能会有所帮助

Django admin date range filter

【讨论】:

以上是关于Django 1.3 或更低版本的 Django Admin 中的自定义过滤器的主要内容,如果未能解决你的问题,请参考以下文章

将旧 (Django 0.97) 模型数据导入/迁移到 Django 1.8 或更高版本

django 找不到新的 sqlite 版本? (需要 SQLite 3.8.3 或更高版本(找到 3.7.17))

Django - 安装 mysqlclient 错误:需要 mysqlclient 1.3.13 或更高版本;你有 0.9.3

在 Django 1.8 或更高版本中填充时出现“模型尚未加载”错误

AWS Elastic Beanstalk 需要 SQLite 3.8.3 或更高版本才能用于 Django 应用程序

django找不到新的sqlite版本? (需要SQLite 3.8.3或更高版本(找到3.7.17))