【问题标题】:Custom Filter in Django Admin on Django 1.3 or belowDjango 1.3 或更低版本的 Django Admin 中的自定义过滤器
【发布时间】: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 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??

【问题讨论】:

标签: python django django-admin


【解决方案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

    【讨论】:

    【解决方案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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-01-15
          • 2011-09-19
          • 2011-09-03
          • 1970-01-01
          • 2012-08-19
          • 2014-07-29
          • 2016-07-01
          相关资源
          最近更新 更多