如何在Django 1.11.4 Admin中添加双过滤器并对比两组查询结果?
在Django 1.11.4 Admin后台添加双日期范围过滤器实现结果对比
要实现同时添加两个日期范围过滤器并直观对比两组结果,我们可以通过自定义Admin列表过滤器+自定义列表字段标记分组来完成,具体步骤和代码如下:
1. 自定义两个独立的日期范围过滤器
Django自带的DateFieldListFilter只能处理单个日期范围,所以我们需要继承它创建两个过滤器,分别对应“日期范围1”和“日期范围2”,各自处理独立的筛选参数。
2. 在Admin类中注册过滤器
将自定义过滤器绑定到目标日期字段,这样Admin侧边栏会显示两个独立的日期范围筛选控件。
3. 添加自定义列表字段标记记录所属范围
为了直观区分两组结果,我们可以在列表中新增一个字段,判断每条记录属于哪个日期范围(范围1、范围2、都包含、都不包含),并用不同颜色标记。
完整代码示例
假设你的模型是Order,日期字段为create_time,代码如下:
from django.contrib import admin from django.contrib.admin.filters import DateFieldListFilter from django.utils.translation import ugettext_lazy as _ from django.utils.html import format_html from .models import Order # 自定义第一个日期范围过滤器 class DateRangeFilter1(DateFieldListFilter): title = _('日期范围1') parameter_name = 'date_range1' def __init__(self, field, request, params, model, model_admin, field_path): super().__init__(field, request, params, model, model_admin, field_path) self.start_param = f'{self.parameter_name}_start' self.end_param = f'{self.parameter_name}_end' self.start_date = params.get(self.start_param) self.end_date = params.get(self.end_param) def queryset(self, request, queryset): if self.start_date and self.end_date: return queryset.filter( create_time__gte=self.start_date, create_time__lte=self.end_date ) return queryset def expected_parameters(self): return [self.start_param, self.end_param] def choices(self, changelist): # 显示“所有日期”重置选项 yield { 'selected': self.start_date is None and self.end_date is None, 'query_string': changelist.get_query_string({}, [self.start_param, self.end_param]), 'display': _('所有日期'), } # 渲染日期范围输入框 display_text = _('日期范围1: %(start)s 至 %(end)s') % {'start': self.start_date or '', 'end': self.end_date or ''} if (self.start_date or self.end_date) else _('选择日期范围1') yield { 'selected': False, 'query_string': '', 'display': display_text, 'input_html': f''' <input type="text" name="{self.start_param}" placeholder="YYYY-MM-DD" value="{self.start_date or ''}" class="vDateField"> <span style="margin: 0 5px;">至</span> <input type="text" name="{self.end_param}" placeholder="YYYY-MM-DD" value="{self.end_date or ''}" class="vDateField"> <button type="submit" class="button" style="margin-left: 5px;">筛选</button> ''', } # 自定义第二个日期范围过滤器(仅修改标题和参数前缀) class DateRangeFilter2(DateFieldListFilter): title = _('日期范围2') parameter_name = 'date_range2' def __init__(self, field, request, params, model, model_admin, field_path): super().__init__(field, request, params, model, model_admin, field_path) self.start_param = f'{self.parameter_name}_start' self.end_param = f'{self.parameter_name}_end' self.start_date = params.get(self.start_param) self.end_date = params.get(self.end_param) def queryset(self, request, queryset): if self.start_date and self.end_date: return queryset.filter( create_time__gte=self.start_date, create_time__lte=self.end_date ) return queryset def expected_parameters(self): return [self.start_param, self.end_param] def choices(self, changelist): yield { 'selected': self.start_date is None and self.end_date is None, 'query_string': changelist.get_query_string({}, [self.start_param, self.end_param]), 'display': _('所有日期'), } display_text = _('日期范围2: %(start)s 至 %(end)s') % {'start': self.start_date or '', 'end': self.end_date or ''} if (self.start_date or self.end_date) else _('选择日期范围2') yield { 'selected': False, 'query_string': '', 'display': display_text, 'input_html': f''' <input type="text" name="{self.start_param}" placeholder="YYYY-MM-DD" value="{self.start_date or ''}" class="vDateField"> <span style="margin: 0 5px;">至</span> <input type="text" name="{self.end_param}" placeholder="YYYY-MM-DD" value="{self.end_date or ''}" class="vDateField"> <button type="submit" class="button" style="margin-left: 5px;">筛选</button> ''', } @admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display = ('id', 'order_no', 'create_time', 'date_range_group') list_filter = ( ('create_time', DateRangeFilter1), ('create_time', DateRangeFilter2), ) def changelist_view(self, request, extra_context=None): # 保存request到实例,供自定义字段方法调用 self.request = request return super().changelist_view(request, extra_context) def date_range_group(self, obj): # 获取两个范围的筛选参数 request = self.request start1 = request.GET.get('date_range1_start') end1 = request.GET.get('date_range1_end') start2 = request.GET.get('date_range2_start') end2 = request.GET.get('date_range2_end') in_range1 = False in_range2 = False # 判断是否在范围1内(仅比较日期部分,忽略时间) if start1 and end1: in_range1 = start1 <= str(obj.create_time.date()) <= end1 # 判断是否在范围2内 if start2 and end2: in_range2 = start2 <= str(obj.create_time.date()) <= end2 if in_range1 and in_range2: return format_html('<span style="color: purple;">两个范围都包含</span>') elif in_range1: return format_html('<span style="color: blue;">范围1</span>') elif in_range2: return format_html('<span style="color: green;">范围2</span>') else: return format_html('<span style="color: gray;">不在任何范围</span>') date_range_group.short_description = '所属范围'
使用说明
- 打开Admin列表页,侧边栏会出现两个日期范围筛选器,分别输入要对比的日期(比如
2024-01-01至2024-02-01和2024-05-01至2024-06-01),点击筛选按钮。 - 列表中的“所属范围”字段会用不同颜色标记每条记录的分组,直观区分两组结果。
- 如果需要同时查看两组结果,保留两个范围的筛选条件即可,无需重置任何过滤器。
注意事项
- 日期输入需遵循
YYYY-MM-DD格式,与Django默认日期控件一致。 - 代码中使用
format_html确保HTML渲染安全,避免XSS风险。 - 若你的日期字段包含时间,用
obj.create_time.date()仅比较日期部分,避免时间干扰判断。
内容的提问来源于stack exchange,提问作者tikael
相关产品推荐
相关产品推荐

