Django ORM按天数字段+datetime字段过滤过期记录并适配Admin筛选
Django Admin过期筛选项与ORM过滤实现方案
你的原有@property属性仅支持单实例调用,无法直接用于ORM过滤和Admin筛选,因为该属性是Python实例层逻辑,数据库层面没有对应字段,这也是你之前自定义Filter报错的核心原因。可按以下方案实现需求:
步骤1:扩展模型QuerySet,实现ORM层面直接过滤
首先给模型自定义查询集方法,把过期判断逻辑转换为数据库可识别的表达式,既支持批量过滤,也可以给Admin筛选项复用:
from django.db import models, timezone from django.db.models import F, ExpressionWrapper, DateTimeField, Q from datetime import timedelta # 自定义查询集 class AccessDurationQuerySet(models.QuerySet): def with_expiration(self): """给查询集预注解到期时间字段""" return self.annotate( db_expiration_date=ExpressionWrapper( F('lab_start_date') + ExpressionWrapper( timedelta(days=1) * F('duration'), output_field=DateTimeField() ), output_field=DateTimeField() ) ) def filter_by_expired_status(self, is_expired: bool): """按是否过期过滤数据集 这里默认lab_start_date为空时视为未过期,可根据业务调整判断逻辑 """ qs = self.with_expiration() if is_expired: return qs.filter( Q(lab_start_date__isnull=False) & Q(db_expiration_date__lt=timezone.now()) ) else: return qs.filter( Q(lab_start_date__isnull=True) | Q(db_expiration_date__gte=timezone.now()) ) # 你的原有模型 class AccessDuration(models.Model): duration = models.PositiveIntegerField(default=30, verbose_name="有效天数") lab_start_date = models.DateTimeField(null=True, blank=True, verbose_name="启用时间") # 绑定自定义查询集到模型Manager objects = AccessDurationQuerySet.as_manager() # 保留原有@property给单实例使用 @property def expiration_date(self): if not self.lab_start_date: return None return self.lab_start_date + timedelta(days=self.duration) @property def is_expired(self): exp_date = self.expiration_date return exp_date < timezone.now() if exp_date else False
配置完成后你就可以直接用ORM语句按过期状态过滤数据集:
- 查询所有已过期记录:
AccessDuration.objects.filter_by_expired_status(is_expired=True) - 查询所有未过期记录:
AccessDuration.objects.filter_by_expired_status(is_expired=False)
步骤2:实现Django Admin列表页筛选项
在admin.py中自定义SimpleListFilter,直接复用上面已经写好的查询集方法即可:
from django.contrib import admin from django.contrib.admin import SimpleListFilter from django.utils.translation import gettext_lazy as _ from .models import AccessDuration class ExpiredStatusFilter(SimpleListFilter): title = _("是否过期") parameter_name = "expired_status" def lookups(self, request, model_admin): # 筛选项的下拉选项 return ( ("1", _("已过期")), ("0", _("未过期")), ) def queryset(self, request, queryset): # 按选择的筛选项过滤查询集 if self.value() == "1": return queryset.filter_by_expired_status(is_expired=True) elif self.value() == "0": return queryset.filter_by_expired_status(is_expired=False) return queryset @admin.register(AccessDuration) class AccessDurationAdmin(admin.ModelAdmin): # 把自定义筛选项加入列表页过滤栏 list_filter = (ExpiredStatusFilter,) # 其他原有Admin配置保持不变
注意事项
- 时间运算必须使用Django ORM表达式(
F、ExpressionWrapper),不能直接用Python的datetime计算,否则逻辑会在Python端执行,批量查询时结果错误 - 若业务中
lab_start_date为空的判断逻辑不同,只需修改filter_by_expired_status方法中的过滤条件即可
内容的提问来源于stack exchange,提问作者binpy
相关产品推荐
相关产品推荐

