【问题标题】:django admin list_filter "or" conditiondjango admin list_filter "or" 条件
【发布时间】:2015-01-08 06:55:02
【问题描述】:

对不起,如果这个问题之前已经回答过,但我做了很多谷歌搜索但没有成功。

我知道如何在管理视图中创建自定义 list_filters(例如子类化 SimpleFilter)。

我真正想要的是一种(在管理列表视图中)“检查”不同过滤器的方法,这些过滤器将它们组合在一个 OR 公式中。

例如,假设您有:

# models.py
class Foo(models.Model):
    foobar = ...
    foofie = ...
...

# admin.py
class FooAdmin(admin.ModelAdmin):
    list_filter = ( "foobar", "foofie" )
...

FooAdmin 生成的管理列表视图中,我可以选择通过foobarfoofie 过滤记录。有没有办法通过公式来过滤它们:foobar = X OR foofie = Y,其中XYfoobarfoofie 可以假设的两个值?

有可能吗?

我知道在 django 管理视图中并非一切皆有可能,但这似乎是一个非常常见的请求,我想知道我是否错过了理解或阅读某些内容。

也欢迎第三方应用程序允许它。谢谢:)

【问题讨论】:

  • 您能否向我们提供您的模型和您的管理员或一些可能的示例,说明您的期望。

标签: python django filter django-admin django-admin-filters


【解决方案1】:

我刚刚找到了一个第三方应用程序,它是django-advanced-filters,可能符合您的要求。

它有:

OR 字段

OR 是添加到每个可用规则的​​附加字段 字段。

它允许使用 OR 语句构造查询。你可以使用它 使用此字段“在”一组 1 个或多个“之间”创建一个“空”规则 规则。

我已经运行了一个测试,添加一个OR field 就可以了。 这是屏幕截图:

【讨论】:

    【解决方案2】:

    首先我尝试解释 django 管理过滤器的工作原理。当您想在管理页面中过滤查询集时,django 会查找所有已注册的过滤器。如果您使用此值设置过滤器 django 过滤器查询集的值。如果您设置多个过滤器 django 过滤您的查询集两次,这等于 queryset = queryset.filter(param1=1).filter(param2=2) 或在 SQL 中:SELECT ... WHERE param1=1 AND param2=2。这是因为您无法使用标准 django 的过滤器来做到这一点。但是您可以像这样编写自己的过滤器:

    from django.contrib.admin import SimpleListFilter
    from django.db.models import Q
    from functools import reduce
    import operator
    from django.core.exceptions import FieldError
    
    
    class ORListFilter(SimpleListFilter):
    title = ''
    parameter_name = ''
    search_field = ('',)
    
    def queryset(self, request, queryset):
        filters = request.GET.copy()
        try: #for search
            search_field_value = filters.pop('q')[0]
            query_params = [Q((key, search_field_value)) for key in self.search_field]
            try:
                queryset = queryset.filter(reduce(operator.or_, query_params))
            except FieldError:
                pass
        except KeyError:
            pass
        try:
            query_params = [Q((key, value)) for key, value in filters.dict().items()]
            queryset = queryset.filter(reduce(operator.or_, query_params))
        except TypeError:
            pass
        return queryset
    
    def lookups(self, request, model_admin):
        qs = model_admin.get_queryset(request)
        parameters = qs.all().values(self.parameter_name).distinct()
        for parameter in parameters:
            value = dict(parameter).pop(self.parameter_name, None)
            if value:
                yield (value, value)
            else:
                yield (None, 'NULL')
    
    class Field1Filter(ORListFilter):
        title = 'title'
        parameter_name = 'field1'
        search_field = ('search1', 'search2')
    
    
    class Field2Filter(ORListFilter):
        title = 'title'
        parameter_name = 'field2'
        search_field = ('search1', 'search2')
    

    并在管理员中注册:

    search_fields = ('search1', 'search2')
    list_filter = (Field1Filter, Field2Filter)
    

    它不适用于标准 django 的过滤器,并且 list_filter 中的所有值都必须继承自 ORListFilter 类。它也不适用于日期时间过滤器,但您可以添加此功能。

    【讨论】:

      【解决方案3】:

      想出了一个解决方案:

      import operator
      from functools import reduce
      from django.contrib.admin import ListFilter, FieldListFilter
      from django.db.models import Q
      from django.contrib.admin.utils import (
          get_fields_from_path, lookup_needs_distinct, prepare_lookup_value,
      )
      from django.http import QueryDict
      
      
      class OrListFilter(ListFilter):
          parameter_prefix = None
          fields = None
      
          def __init__(self, request, params, model, model_admin):
              super(OrListFilter, self).__init__(
                  request, params, model, model_admin)
              if self.parameter_prefix is None:
                  raise ImproperlyConfigured(
                      "The list filter '%s' does not specify "
                      "a 'parameter_prefix'." % self.__class__.__name__)
      
              self.model_admin = model_admin
              self.model = model
              self.request = request
              self.filter_specs = self.get_filters(request, {}, prefix=self.parameter_prefix+'-')
      
              for p in self.expected_parameters():
                  if p in params:
                      value = params.pop(p)
                      field = p.split('-')[1]
                      self.used_parameters[field] = prepare_lookup_value(field, value)
      
          def has_output(self):
              return True
      
          # see https://github.com/django/django/blob/1.8.5/django/contrib/admin/views/main.py#L104
          def get_filters(self, request, params, prefix=''):
              filter_specs = []
              for field_path in self.fields:
                  field = get_fields_from_path(self.model, field_path)[-1]
                  field_list_filter_class = FieldListFilter.create
                  spec = field_list_filter_class(field, request, params,
                      self.model, self.model_admin, field_path=prefix + field_path)
                  # Check if we need to use distinct()
                  # use_distinct = (use_distinct or
                  #                 lookup_needs_distinct(self.lookup_opts,
                  #                                       field_path))
                  filter_specs.append(spec)
              return filter_specs
      
          def expected_parameters(self):
              parameters = []
              for spec in self.filter_specs:
                  parameters += spec.expected_parameters()
              return parameters
      
          def choices(self, cl):
              return []
      
          def queryset(self, request, queryset):
              origin_GET = request.GET.copy()
              fake_GET = QueryDict(mutable=True)
              fake_GET.update(self.used_parameters)
              request.GET = fake_GET
              all_params = {}
              for spec in self.get_filters(request, self.used_parameters):
                  if spec and spec.has_output():
                      all_params.update(spec.used_parameters)
      
              try:
                  query_params = [Q((key, value)) for key, value in all_params.items()]
                  queryset = queryset.filter(reduce(operator.or_, query_params))
              except TypeError as e:
                  pass
      
              # restore
              request.GET = origin_GET
              return queryset
      
      
      class OrFilter(OrListFilter):
          title = 'Or filter'
          parameter_prefix = 'or1'
          fields = ("foobar", "foofie")
      
      
      class FooAdmin(admin.ModelAdmin):
          list_filter = (OrFilter, )
      

      app_name/templates/admin/app_name/change_list.html:

      {% extends "admin/change_list.html" %}
      {% load i18n admin_list %}
      
      {% block filters %}
        {% if cl.has_filters %}
          <div id="changelist-filter">
            <h2>{% trans 'Filter' %}</h2>
            {% for spec in cl.filter_specs %}
              {% if spec.filter_specs %}
                {% admin_list_filter cl spec %}
                <ul>
                  {% for sub_spec in spec.filter_specs %}
                    <li>{% admin_list_filter cl sub_spec %}</li>
                  {% endfor %}
                </ul>
              {% else %}
                {% admin_list_filter cl spec %}
              {% endif %}
            {% endfor %}
          </div>
        {% endif %}
      {% endblock %}
      

      从@dima-kudosh 借用一些代码。

      说明

      ChangeList.get_filters()ModelAdmin.list_filter 创建ListFilters (filter_specs),然后使用ListFilter.queryset()get_queryset()

      FieldListFilter.queryset() 使用used_parameters 过滤查询集:queryset.filter(**self.used_parameters)

      所以我们可以从OrListFilter.fields 创建FieldListFilters 并使用他们的used_parameters 来构造OR 查询:

      all_params = {}
      for spec in self.get_filters(request, self.used_parameters):
          if spec and spec.has_output():
              all_params.update(spec.used_parameters)
      
      try:
          query_params = [Q((key, value)) for key, value in all_params.items()]
          queryset = queryset.filter(reduce(operator.or_, query_params))
      except TypeError as e:
          pass
      

      【讨论】:

        【解决方案4】:

        Django Admin Multiple Choice List Filter 是一个 Django 应用程序,我在搜索了许多类似这样的帖子后,为满足这一要求而编写。

        MultipleChoiceListFilter 扩展了 SimpleListFilter 以允许您过滤多个选项。

        UI 使用可点击的链接从“OR”查询中“包含”和“排除”选项,而不是勾选/取消勾选复选框。因此,您必须在每次单击后等待往返服务器,并等待页面刷新。这可能是性能/用户体验问题,尤其是对于大量对象。

        “全部”链接和每个选项链接的行为都从 SimpleListFilter 中保留 - 即您可以将过滤器重置为所有选项,或仅选项之一。

        当前包含的选项在过滤器中突出显示(在下面的屏幕截图中为蓝色)。

        模板是可覆盖的,因此您可以根据需要更改界面。就我个人而言,我认为选择名称和包含/排除链接之间的更多空间可能有助于区分两者。或者,开关图标可能比“包含”/“排除”更直观。

        【讨论】:

          猜你喜欢
          • 2012-03-30
          • 2012-04-06
          • 2016-06-02
          • 2015-03-16
          • 2012-02-19
          • 2012-10-18
          • 2020-09-20
          • 2018-06-25
          • 2011-01-16
          相关资源
          最近更新 更多