【问题标题】:Default filter in Django adminDjango admin 中的默认过滤器
【发布时间】:2010-10-25 11:55:51
【问题描述】:

如何将默认过滤器选项从“全部”更改?我有一个名为status 的字段,它具有三个值:activatependingrejected。当我在 Django admin 中使用 list_filter 时,过滤器默认设置为“全部”,但我想将其默认设置为挂起。

【问题讨论】:

    标签: python django django-admin


    【解决方案1】:

    为了实现这一点并且在您的侧边栏中有一个可用的“全部”链接(即显示全部而不是显示待处理的链接),您需要创建一个自定义列表过滤器,继承来自django.contrib.admin.filters.SimpleListFilter 并默认过滤“待处理”。这些方面的东西应该可以工作:

    from datetime import date
    
    from django.utils.translation import ugettext_lazy as _
    from django.contrib.admin import SimpleListFilter
    
    class StatusFilter(SimpleListFilter):
        title = _('Status')
    
        parameter_name = 'status'
    
        def lookups(self, request, model_admin):
            return (
                (None, _('Pending')),
                ('activate', _('Activate')),
                ('rejected', _('Rejected')),
                ('all', _('All')),
            )
    
        def choices(self, cl):
            for lookup, title in self.lookup_choices:
                yield {
                    'selected': self.value() == lookup,
                    'query_string': cl.get_query_string({
                        self.parameter_name: lookup,
                    }, []),
                    'display': title,
                }
    
        def queryset(self, request, queryset):
            if self.value() in ('activate', 'rejected'):
                return queryset.filter(status=self.value())    
            elif self.value() == None:
                return queryset.filter(status='pending')
    
    
    class Admin(admin.ModelAdmin): 
        list_filter = [StatusFilter] 
    

    编辑:需要 Django 1.4(感谢 Simon)

    【讨论】:

    • 这是所有解决方案中最干净的解决方案,但它获得的支持最少......不过,它需要 Django 1.4,尽管现在应该已经给出了。
    • @Greg 你如何完全删除过滤器的功能和管理页面的过滤器标签?
    • 这个解决方案有一个小缺点。当过滤器为空时(实际使用“待处理”过滤器),如果 show_full_result_count 为 True(默认情况下),Django 1.8 会错误地确定完整结果计数并且不显示结果计数。 ——
    • 请注意,如果您未能在解决方案中覆盖 choices 方法,它会烦人地继续添加自己的 All 选项选择列表的顶部。
    【解决方案2】:
    class MyModelAdmin(admin.ModelAdmin):   
    
        def changelist_view(self, request, extra_context=None):
    
            if not request.GET.has_key('decommissioned__exact'):
    
                q = request.GET.copy()
                q['decommissioned__exact'] = 'N'
                request.GET = q
                request.META['QUERY_STRING'] = request.GET.urlencode()
            return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)
    

    【讨论】:

    • 此解决方案的缺点是,虽然 UI 中仍显示“全部”选项,但选择它仍会应用默认过滤。
    • 我有同样的问题,但我可以理解回放...对不起,我是 Django 的新手...但也许这会工作blog.dougalmatthews.com/2008/10/…
    • 这很好,但我需要在 url 中查看 get 参数,以便我的过滤器可以选择它并显示它。很快发布我的解决方案。
    • 缺少解释。仅仅发布一段代码可能对每个人都没有帮助。最重要的是它不起作用,没有一点上下文很难找出原因
    【解决方案3】:

    采用上面 ha22109 的答案并修改为允许通过比较 HTTP_REFERERPATH_INFO 来选择“全部”。

    class MyModelAdmin(admin.ModelAdmin):
    
        def changelist_view(self, request, extra_context=None):
    
            test = request.META['HTTP_REFERER'].split(request.META['PATH_INFO'])
    
            if test[-1] and not test[-1].startswith('?'):
                if not request.GET.has_key('decommissioned__exact'):
    
                    q = request.GET.copy()
                    q['decommissioned__exact'] = 'N'
                    request.GET = q
                    request.META['QUERY_STRING'] = request.GET.urlencode()
            return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)
    

    【讨论】:

    • 这对我来说很糟糕,因为 HTTP_REFERER 并不总是存在。我做了'referer = request.META.get('HTTP_REFERER', ''); test = referer.split(request.META['PATH_INFO'])`
    • @Ben 我正在使用你的两行 referer = request.META.get('HTTP_REFERER', '') test = referer.split(request.META['PATH_INFO'])。我不太关心 HTTP_REFERER 。如果 HTTP_REFERER 不存在,问题是否从这些行中完全解决?
    • @the_game 是的,这个想法是,如果您使用方括号尝试访问不存在的密钥,它会抛出 KeyError,而如果您使用 dict 的 get() 方法,您可以指定默认值。我指定了默认的空字符串,这样 split() 就不会抛出 AttributeError。就是这样。
    • @Ben。谢谢它对我有用。你也能回答这个问题吗?我相信这只是这个问题的延伸stackoverflow.com/questions/10410982/…。你能给我一个解决方案吗?
    • 这很好用。不过,has_key() 已被弃用,取而代之的是 key in d。但我知道你只是从 ha22109 的回答中得到的。一个问题:当你可以使用request.path_info(更短)时,为什么还要使用request.META['PATH_INFO']
    【解决方案4】:

    我知道这个问题现在已经很老了,但它仍然有效。我相信这是最正确的做法。它本质上与 Greg 的方法相同,但被表述为一个可扩展的类以便于重用。

    from django.contrib.admin import SimpleListFilter
    from django.utils.encoding import force_text
    from django.utils.translation import ugettext as _
    
    class DefaultListFilter(SimpleListFilter):
        all_value = '_all'
    
        def default_value(self):
            raise NotImplementedError()
    
        def queryset(self, request, queryset):
            if self.parameter_name in request.GET and request.GET[self.parameter_name] == self.all_value:
                return queryset
    
            if self.parameter_name in request.GET:
                return queryset.filter(**{self.parameter_name:request.GET[self.parameter_name]})
    
            return queryset.filter(**{self.parameter_name:self.default_value()})
    
        def choices(self, cl):
            yield {
                'selected': self.value() == self.all_value,
                'query_string': cl.get_query_string({self.parameter_name: self.all_value}, []),
                'display': _('All'),
            }
            for lookup, title in self.lookup_choices:
                yield {
                    'selected': self.value() == force_text(lookup) or (self.value() == None and force_text(self.default_value()) == force_text(lookup)),
                    'query_string': cl.get_query_string({
                        self.parameter_name: lookup,
                    }, []),
                    'display': title,
                }
    
    class StatusFilter(DefaultListFilter):
        title = _('Status ')
        parameter_name = 'status__exact'
    
        def lookups(self, request, model_admin):
            return ((0,'activate'), (1,'pending'), (2,'rejected'))
    
        def default_value(self):
            return 1
    
    class MyModelAdmin(admin.ModelAdmin):
        list_filter = (StatusFilter,)
    

    【讨论】:

      【解决方案5】:

      这是我使用重定向的通用解决方案,它只是检查是否有任何 GET 参数,如果不存在,则使用默认的 get 参数重定向。我还设置了一个 list_filter,因此它会选择它并显示默认值。

      from django.shortcuts import redirect
      
      class MyModelAdmin(admin.ModelAdmin):   
      
          ...
      
          list_filter = ('status', )
      
          def changelist_view(self, request, extra_context=None):
              referrer = request.META.get('HTTP_REFERER', '')
              get_param = "status__exact=5"
              if len(request.GET) == 0 and '?' not in referrer:
                  return redirect("{url}?{get_parms}".format(url=request.path, get_parms=get_param))
              return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)
      

      唯一需要注意的是,当您使用“?”直接访问该页面时。存在于 url 中,没有设置 HTTP_REFERER,因此它将使用默认参数并重定向。这对我来说很好,当您点击管理过滤器时效果很好。

      更新

      为了规避警告,我最终编写了一个自定义过滤器函数来简化 changelist_view 功能。这是过滤器:

      class MyModelStatusFilter(admin.SimpleListFilter):
          title = _('Status')
          parameter_name = 'status'
      
          def lookups(self, request, model_admin):  # Available Values / Status Codes etc..
              return (
                  (8, _('All')),
                  (0, _('Incomplete')),
                  (5, _('Pending')),
                  (6, _('Selected')),
                  (7, _('Accepted')),
              )
      
          def choices(self, cl):  # Overwrite this method to prevent the default "All"
              from django.utils.encoding import force_text
              for lookup, title in self.lookup_choices:
                  yield {
                      'selected': self.value() == force_text(lookup),
                      'query_string': cl.get_query_string({
                          self.parameter_name: lookup,
                      }, []),
                      'display': title,
                  }
      
          def queryset(self, request, queryset):  # Run the queryset based on your lookup values
              if self.value() is None:
                  return queryset.filter(status=5)
              elif int(self.value()) == 0:
                  return queryset.filter(status__lte=4)
              elif int(self.value()) == 8:
                  return queryset.all()
              elif int(self.value()) >= 5:
                  return queryset.filter(status=self.value())
              return queryset.filter(status=5)
      

      并且 changelist_view 现在只在不存在的情况下传递默认参数。这个想法是摆脱泛型过滤器的能力,通过不使用获取参数来查看所有内容。查看我为此目的分配了 status = 8 的所有内容。:

      class MyModelAdmin(admin.ModelAdmin):   
      
          ...
      
          list_filter = ('status', )
      
          def changelist_view(self, request, extra_context=None):
              if len(request.GET) == 0:
                  get_param = "status=5"
                  return redirect("{url}?{get_parms}".format(url=request.path, get_parms=get_param))
              return super(MyModelAdmin, self).changelist_view(request, extra_context=extra_context)
      

      【讨论】:

      • 我有一个修复我的警告,一个自定义过滤器。我将把它作为替代解决方案。
      • 谢谢,我发现重定向是最干净和最简单的解决方案。我也不明白“警告”。我总是得到想要的结果,无论是通过点击还是使用直接链接(我没有使用自定义过滤器)。
      【解决方案6】:
      def changelist_view( self, request, extra_context = None ):
          default_filter = False
          try:
              ref = request.META['HTTP_REFERER']
              pinfo = request.META['PATH_INFO']
              qstr = ref.split( pinfo )
      
              if len( qstr ) < 2:
                  default_filter = True
          except:
              default_filter = True
      
          if default_filter:
              q = request.GET.copy()
              q['registered__exact'] = '1'
              request.GET = q
              request.META['QUERY_STRING'] = request.GET.urlencode()
      
          return super( InterestAdmin, self ).changelist_view( request, extra_context = extra_context )
      

      【讨论】:

        【解决方案7】:

        你可以简单地使用return queryset.filter()if self.value() is None和SimpleListFilter的Override方法

        from django.utils.encoding import force_text
        
        def choices(self, changelist):
            for lookup, title in self.lookup_choices:
                yield {
                    'selected': force_text(self.value()) == force_text(lookup),
                    'query_string': changelist.get_query_string(
                        {self.parameter_name: lookup}, []
                    ),
                    'display': title,
                }
        

        【讨论】:

          【解决方案8】:

          受此处的一些答案(主要是 Greg 的)启发,创建了一个可重用的 Filter 子类。

          优点:

          可重用 - 可插入任何标准ModelAdmin

          可扩展 - 轻松为QuerySet 过滤添加额外/自定义逻辑

          易于使用 - 在其最基本的形式中,只需要实现一个自定义属性和一个自定义方法(除了 SimpleListFilter 子类化所需的那些)

          直观的管理 - “全部”过滤器链接按预期工作;和其他人一样

          无重定向 - 无需检查 GET 请求有效负载,与 HTTP_REFERER 无关(或任何其他与请求相关的内容,其基本形式)

          没有(更改列表)视图操作 - 并且没有模板操作(上帝保佑)

          代码:

          (大部分imports 仅用于类型提示和异常)

          from typing import List, Tuple, Any
          
          from django.contrib.admin.filters import SimpleListFilter
          from django.contrib.admin.options import IncorrectLookupParameters
          from django.contrib.admin.views.main import ChangeList
          from django.db.models.query import QuerySet
          from django.utils.encoding import force_str
          from django.utils.translation import gettext_lazy as _
          from django.core.exceptions import ValidationError
          
          
          class PreFilteredListFilter(SimpleListFilter):
          
              # Either set this or override .get_default_value()
              default_value = None
          
              no_filter_value = 'all'
              no_filter_name = _("All")
          
              # Human-readable title which will be displayed in the
              # right admin sidebar just above the filter options.
              title = None
          
              # Parameter for the filter that will be used in the URL query.
              parameter_name = None
          
              def get_default_value(self):
                  if self.default_value is not None:
                      return self.default_value
                  raise NotImplementedError(
                      'Either the .default_value attribute needs to be set or '
                      'the .get_default_value() method must be overridden to '
                      'return a URL query argument for parameter_name.'
                  )
          
              def get_lookups(self) -> List[Tuple[Any, str]]:
                  """
                  Returns a list of tuples. The first element in each
                  tuple is the coded value for the option that will
                  appear in the URL query. The second element is the
                  human-readable name for the option that will appear
                  in the right sidebar.
                  """
                  raise NotImplementedError(
                      'The .get_lookups() method must be overridden to '
                      'return a list of tuples (value, verbose value).'
                  )
          
              # Overriding parent class:
              def lookups(self, request, model_admin) -> List[Tuple[Any, str]]:
                  return [(self.no_filter_value, self.no_filter_name)] + self.get_lookups()
          
              # Overriding parent class:
              def queryset(self, request, queryset: QuerySet) -> QuerySet:
                  """
                  Returns the filtered queryset based on the value
                  provided in the query string and retrievable via
                  `self.value()`.
                  """
                  if self.value() is None:
                      return self.get_default_queryset(queryset)
                  if self.value() == self.no_filter_value:
                      return queryset.all()
                  return self.get_filtered_queryset(queryset)
          
              def get_default_queryset(self, queryset: QuerySet) -> QuerySet:
                  return queryset.filter(**{self.parameter_name: self.get_default_value()})
          
              def get_filtered_queryset(self, queryset: QuerySet) -> QuerySet:
                  try:
                      return queryset.filter(**self.used_parameters)
                  except (ValueError, ValidationError) as e:
                      # Fields may raise a ValueError or ValidationError when converting
                      # the parameters to the correct type.
                      raise IncorrectLookupParameters(e)
          
              # Overriding parent class:
              def choices(self, changelist: ChangeList):
                  """
                  Overridden to prevent the default "All".
                  """
                  value = self.value() or force_str(self.get_default_value())
                  for lookup, title in self.lookup_choices:
                      yield {
                          'selected': value == force_str(lookup),
                          'query_string': changelist.get_query_string({self.parameter_name: lookup}),
                          'display': title,
                      }
          

          完整使用示例:

          from django.contrib import admin
          from .models import SomeModelWithStatus
          
          
          class StatusFilter(PreFilteredListFilter):
              default_value = SomeModelWithStatus.Status.FOO
              title = _('Status')
              parameter_name = 'status'
          
              def get_lookups(self):
                  return SomeModelWithStatus.Status.choices
          
          
          @admin.register(SomeModelWithStatus)
          class SomeModelAdmin(admin.ModelAdmin):
              list_filter = (StatusFilter, )
          

          希望这对某人有所帮助;反馈总是很感激。

          【讨论】:

            【解决方案9】:

            请注意,如果您不想预先选择过滤器值,而是希望始终在将数据显示在管理员中之前对其进行预先过滤,则应改写 ModelAdmin.queryset() 方法。

            【讨论】:

            • 这是一个非常干净和快速的解决方案,尽管它仍然可能会导致问题。在管理员中启用过滤选项后,用户可能会得到看似不正确的结果。如果被覆盖的查询集包含一个 .exclude() 子句,那么被该子句捕获的记录将永远不会被列出,但管理 UI 仍将提供显式显示它们的管理过滤选项。
            • 还有其他更正确但票数较低的答案适用于这种情况,因为 OP 已明确要求他将放置一个过滤器,其中查询集将是错误的解决方案,正如 @ 所指出的那样上面的 TomasAndrle。
            • 感谢@eskhool 指出这一点,我试图将我的答案否决为零,但似乎不允许对自己投反对票。
            【解决方案10】:

            使用 DjangoChoices、Python >= 2.5 和当然 Django >= 1.4 对 Greg 的回答略有改进。

            from django.utils.translation import ugettext_lazy as _
            from django.contrib.admin import SimpleListFilter
            
            class OrderStatusFilter(SimpleListFilter):
                title = _('Status')
            
                parameter_name = 'status__exact'
                default_status = OrderStatuses.closed
            
                def lookups(self, request, model_admin):
                    return (('all', _('All')),) + OrderStatuses.choices
            
                def choices(self, cl):
                    for lookup, title in self.lookup_choices:
                        yield {
                            'selected': self.value() == lookup if self.value() else lookup == self.default_status,
                            'query_string': cl.get_query_string({self.parameter_name: lookup}, []),
                            'display': title,
                        }
            
                def queryset(self, request, queryset):
                    if self.value() in OrderStatuses.values:
                        return queryset.filter(status=self.value())
                    elif self.value() is None:
                        return queryset.filter(status=self.default_status)
            
            
            class Admin(admin.ModelAdmin):
                list_filter = [OrderStatusFilter] 
            

            感谢 Greg 的出色解决方案!

            【讨论】:

              【解决方案11】:

              我知道这不是最好的解决方案,但我更改了管理模板中的 index.html,第 25 行和第 37 行,如下所示:

              25:&lt;th scope="row"&gt;&lt;a href="{{ model.admin_url }}{% ifequal model.name "yourmodelname" %}?yourflag_flag__exact=1{% endifequal %}"&gt;{{ model.name }}&lt;/a&gt;&lt;/th&gt;

              37:&lt;td&gt;&lt;a href="{{ model.admin_url }}{% ifequal model.name "yourmodelname" %}?yourflag__exact=1{% endifequal %}" class="changelink"&gt;{% trans 'Change' %}&lt;/a&gt;&lt;/td&gt;

              【讨论】:

                【解决方案12】:

                这是我能够生成的最干净的过滤器版本,该过滤器具有重新定义的“全部”和选中的默认值。

                如果默认显示当前正在发生的旅行。

                class HappeningTripFilter(admin.SimpleListFilter):
                    """
                    Filter the Trips Happening in the Past, Future or now.
                    """
                    default_value = 'now'
                    title = 'Happening'
                    parameter_name = 'happening'
                
                    def lookups(self, request, model_admin):
                        """
                        List the Choices available for this filter.
                        """
                        return (
                            ('all', 'All'),
                            ('future', 'Not yet started'),
                            ('now', 'Happening now'),
                            ('past', 'Already finished'),
                        )
                
                    def choices(self, changelist):
                        """
                        Overwrite this method to prevent the default "All".
                        """
                        value = self.value() or self.default_value
                        for lookup, title in self.lookup_choices:
                            yield {
                                'selected': value == force_text(lookup),
                                'query_string': changelist.get_query_string({
                                    self.parameter_name: lookup,
                                }, []),
                                'display': title,
                            }
                
                    def queryset(self, request, queryset):
                        """
                        Returns the Queryset depending on the Choice.
                        """
                        value = self.value() or self.default_value
                        now = timezone.now()
                        if value == 'future':
                            return queryset.filter(start_date_time__gt=now)
                        if value == 'now':
                            return queryset.filter(start_date_time__lte=now, end_date_time__gte=now)
                        if value == 'past':
                            return queryset.filter(end_date_time__lt=now)
                        return queryset.all()
                

                【讨论】:

                  【解决方案13】:

                  我必须进行修改才能使过滤正常工作。加载页面时,以前的解决方案对我有用。如果执行了“操作”,过滤器将返回“全部”,而不是我的默认设置。此解决方案使用默认过滤器加载管理员更改页面,但当页面上发生其他活动时,还会维护过滤器更改或当前过滤器。我没有测试所有情况,但实际上它可能会限制默认过滤器的设置仅在页面加载时发生。

                  def changelist_view(self, request, extra_context=None):
                      default_filter = False
                  
                      try:
                          ref = request.META['HTTP_REFERER']
                          pinfo = request.META['PATH_INFO']
                          qstr = ref.split(pinfo)
                          querystr = request.META['QUERY_STRING']
                  
                          # Check the QUERY_STRING value, otherwise when
                          # trying to filter the filter gets reset below
                          if querystr is None:
                              if len(qstr) < 2 or qstr[1] == '':
                                  default_filter = True
                      except:
                          default_filter = True
                  
                      if default_filter:
                          q = request.GET.copy()
                          q['registered__isnull'] = 'True'
                          request.GET = q
                          request.META['QUERY_STRING'] = request.GET.urlencode()
                  
                      return super(MyAdmin, self).changelist_view(request, extra_context=extra_context)
                  

                  【讨论】:

                    【解决方案14】:

                    有点跑题了,但我对类似问题的搜索把我带到了这里。我希望按日期进行默认查询(即,如果未提供输入,则仅显示带有timestamp of 'Today' 的对象),这使问题有点复杂化。这是我想出的:

                    from django.contrib.admin.options import IncorrectLookupParameters
                    from django.core.exceptions import ValidationError
                    
                    class TodayDefaultDateFieldListFilter(admin.DateFieldListFilter):
                        """ If no date is query params are provided, query for Today """
                    
                        def queryset(self, request, queryset):
                            try:
                                if not self.used_parameters:
                                    now = datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
                                    self.used_parameters = {
                                        ('%s__lt' % self.field_path): str(now + datetime.timedelta(days=1)),
                                        ('%s__gte' % self.field_path): str(now),
                                    }
                                    # Insure that the dropdown reflects 'Today'
                                    self.date_params = self.used_parameters
                                return queryset.filter(**self.used_parameters)
                            except ValidationError, e:
                                raise IncorrectLookupParameters(e)
                    
                    class ImagesAdmin(admin.ModelAdmin):
                        list_filter = (
                            ('timestamp', TodayDefaultDateFieldListFilter),
                        )
                    

                    这是对默认 DateFieldListFilter 的简单覆盖。通过设置self.date_params,它确保过滤器下拉列表将更新为与self.used_parameters 匹配的任何选项。出于这个原因,您必须确保 self.used_parameters 正是这些下拉选择之一将使用的内容(即,找出使用“今天”或“过去 7 天”时的 date_params 并构造self.used_parameters 匹配那些)。

                    这是为与 Django 1.4.10 一起工作而构建的

                    【讨论】:

                      【解决方案15】:

                      这可能是一个旧线程,但我想我会添加我的解决方案,因为我在谷歌搜索中找不到更好的答案。

                      在 ModelAdmin 中为 changelist_view 回答了什么(不确定是 Deminic Rodger 还是 ha22109)

                      class MyModelAdmin(admin.ModelAdmin):   
                          list_filter = (CustomFilter,)
                      
                          def changelist_view(self, request, extra_context=None):
                      
                              if not request.GET.has_key('decommissioned__exact'):
                      
                                  q = request.GET.copy()
                                  q['decommissioned__exact'] = 'N'
                                  request.GET = q
                                  request.META['QUERY_STRING'] = request.GET.urlencode()
                              return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)
                      

                      然后我们需要创建一个自定义的 SimpleListFilter

                      class CustomFilter(admin.SimpleListFilter):
                          title = 'Decommissioned'
                          parameter_name = 'decommissioned'  # i chose to change it
                      
                      def lookups(self, request, model_admin):
                          return (
                              ('All', 'all'),
                              ('1', 'Decommissioned'),
                              ('0', 'Active (or whatever)'),
                          )
                      
                      # had to override so that we could remove the default 'All' option
                      # that won't work with our default filter in the ModelAdmin class
                      def choices(self, cl):
                          yield {
                              'selected': self.value() is None,
                              'query_string': cl.get_query_string({}, [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,
                              }
                      
                      def queryset(self, request, queryset):
                          if self.value() == '1':
                              return queryset.filter(decommissioned=1)
                          elif self.value() == '0':
                              return queryset.filter(decommissioned=0)
                          return queryset
                      

                      【讨论】:

                      • 我发现我需要在选择函数的 yield 调用中使用“force_text”(又名 force_unicode)函数,否则选择的过滤器选项不会显示为“已选择”。也就是“'selected': self.value() == force_text(lookup),"
                      【解决方案16】:

                      使用 ha22109 的回答,我为 ModelAdmin 类编写了一个 mixin:

                      from urllib.parse import urlencode
                      from django.contrib.admin.views.main import SEARCH_VAR
                      from django.http import HttpRequest, QueryDict
                      
                      
                      class DefaultFilterMixin:
                      
                          default_filters: Sequence[tuple[str, Any]] | dict[str, Any] | None = None
                      
                          def get_default_filters(
                              self,
                              request: HttpRequest,
                          ) -> Sequence[tuple[str, Any]] | dict[str, Any] | None:
                              return self.default_filters
                      
                          def changelist_view(
                              self,
                              request: HttpRequest,
                              extra_context: dict[str, str] | None = None,
                          ):
                              if request.method == 'GET' and not request.GET:
                                  if default_filters := self.get_default_filters(request):
                                      request.GET = QueryDict(
                                          f"{urlencode(default_filters)}&{SEARCH_VAR}=",
                                          encoding=request.encoding,
                                      )
                                      request.META['QUERY_STRING'] = request.GET.urlencode()
                      
                              return super().changelist_view(request, extra_context=extra_context)
                      

                      简单示例:

                      class MyModelAdmin(DefaultFilterMixin, admin.ModelAdmin):
                      
                          default_filters = (("status__exact", "pending"),)
                          ...
                      

                      或者更复杂的 rangefilter.DateTimeRangeFilter 动态过滤器:

                      class MyModelAdmin(DefaultFilterMixin, admin.ModelAdmin):
                      
                              def get_default_filters(
                                  self,
                                  request: HttpRequest,
                              ) -> Sequence[tuple[str, Any]] | dict[str, Any] | None:
                                  now = timezone.now()
                                  date_fmt = '%d.%m.%Y'
                                  return (
                                      ('created_at__range__gte_0', now.strftime(date_fmt)),
                                      ('created_at__range__gte_1', '00:00:00'),
                                      ('created_at__range__lte_0', (now + timedelta(1)).strftime(date_fmt)),
                                      ('created_at__range__lte_1', '00:00:00'),
                                  )
                      

                      【讨论】:

                        【解决方案17】:

                        使用默认 mixin 回复第一个答案(来自 Evgeni Shudzel)...这是一个导入和复杂性较少的解决方案

                        from urllib.parse import urlencode
                        from django.contrib.admin.views.main import SEARCH_VAR
                        from django.http import HttpRequest, QueryDict
                        
                        class DefaultFilterMixin:
                            default_filters: None
                        
                            def get_default_filters(self, request: HttpRequest):
                                return self.default_filters
                        
                            def changelist_view(self, request: HttpRequest, extra_context=None):
                                if request.method == 'GET' and not request.GET:
                                    if default_filters := self.get_default_filters(request):
                                        request.GET = QueryDict(
                                            f"{urlencode(default_filters)}&{SEARCH_VAR}=",
                                            encoding=request.encoding,
                                        )
                                        request.META['QUERY_STRING'] = request.GET.urlencode()
                        
                                return super().changelist_view(request, extra_context=extra_context)
                        
                        class SampleUsageAdmin(DefaultFilterMixin, admin.ModelAdmin):
                            default_filters = (("fulfillments_complete__exact", "0"),)
                        

                        【讨论】:

                          猜你喜欢
                          • 2012-05-11
                          • 2022-01-01
                          • 1970-01-01
                          • 2014-01-30
                          • 2013-02-02
                          • 2021-03-19
                          • 2015-06-10
                          • 2011-04-05
                          相关资源
                          最近更新 更多