如何优雅重构Django中重复的QuerySet过滤逻辑?
解决Django中重复QuerySet过滤逻辑的通用化方案
针对你遇到的singer/writer模型过滤逻辑高度重复的问题,核心思路是把**可变的部分(主模型、关联周时间表)**抽离出来,将通用过滤逻辑封装成可复用单元,下面给出两种实用的实现方式:
方式一:封装通用私有方法
把重复的过滤逻辑写到一个私有方法中,通过传入不同的模型参数实现复用:
原重复代码示例(模拟)
from .models import singer, writer, weeklyTimeSinger, weeklyTimeWriter class Filters: def queryFilter_singer(self, request): queryset = singer.objects.all() # 通用过滤逻辑1:按起始日期过滤 start_date = request.GET.get('start_date') if start_date: queryset = queryset.filter(weeklytimesinger__date__gte=start_date) # 通用过滤逻辑2:按结束日期过滤 end_date = request.GET.get('end_date') if end_date: queryset = queryset.filter(weeklytimesinger__date__lte=end_date) # 更多重复的过滤逻辑... return queryset def queryFilter_writer(self, request): queryset = writer.objects.all() # 和上面完全一致的过滤逻辑,仅关联表名不同 start_date = request.GET.get('start_date') if start_date: queryset = queryset.filter(weeklytimewriter__date__gte=start_date) end_date = request.GET.get('end_date') if end_date: queryset = queryset.filter(weeklytimewriter__date__lte=end_date) # 更多重复的过滤逻辑... return queryset
改写后的代码
from .models import singer, writer, weeklyTimeSinger, weeklyTimeWriter class Filters: def _generic_query_filter(self, main_model, weekly_time_model, request, related_field=None): queryset = main_model.objects.all() # 自动生成关联字段名,若字段名不同可手动传参覆盖 related_field = related_field or weekly_time_model.__name__.lower() # 通用过滤逻辑:统一维护,修改只需改这里 start_date = request.GET.get('start_date') if start_date: queryset = queryset.filter(**{f"{related_field}__date__gte": start_date}) end_date = request.GET.get('end_date') if end_date: queryset = queryset.filter(**{f"{related_field}__date__lte": end_date}) # 其他所有通用过滤逻辑... return queryset # 对外暴露的方法,只需传入对应模型 def queryFilter_singer(self, request): return self._generic_query_filter(singer, weeklyTimeSinger, request) def queryFilter_writer(self, request): return self._generic_query_filter(writer, weeklyTimeWriter, request)
方式二:使用Mixin基类(适合后续扩展更多同类模型)
如果后续可能新增类似的模型(比如actor+weeklyTimeActor),用Mixin基类的方式更便于扩展:
from .models import singer, writer, weeklyTimeSinger, weeklyTimeWriter class BaseWeeklyFilter: # 子类需重写这两个属性 main_model = None weekly_time_model = None # 若关联字段名和模型类名小写不一致,可手动指定 related_field = None def queryFilter(self, request): if not self.main_model or not self.weekly_time_model: raise ValueError("必须设置main_model和weekly_time_model属性") queryset = self.main_model.objects.all() related_field = self.related_field or self.weekly_time_model.__name__.lower() # 通用过滤逻辑 start_date = request.GET.get('start_date') if start_date: queryset = queryset.filter(**{f"{related_field}__date__gte": start_date}) end_date = request.GET.get('end_date') if end_date: queryset = queryset.filter(**{f"{related_field}__date__lte": end_date}) # 其他通用过滤逻辑... return queryset # 针对不同模型创建子类 class SingerFilter(BaseWeeklyFilter): main_model = singer weekly_time_model = weeklyTimeSinger # 若关联字段不是weeklytimesinger,可手动设置:related_field = "custom_related_name" class WriterFilter(BaseWeeklyFilter): main_model = writer weekly_time_model = weeklyTimeWriter
使用示例
在视图中直接实例化子类调用即可:
# 处理歌手过滤 singer_filter = SingerFilter() singer_queryset = singer_filter.queryFilter(request) # 处理作者过滤 writer_filter = WriterFilter() writer_queryset = writer_filter.queryFilter(request)
注意事项
- 如果关联字段名称和模型类名小写不一致(比如模型是
weeklyTimeSinger,但关联字段定义为weekly_time_singer),可以在调用通用方法时手动传入related_field参数,或者在Mixin子类中设置related_field属性。 - 所有通用过滤逻辑只需在一处维护,后续修改需求时无需重复修改多个方法,完全符合DRY(Don't Repeat Yourself)原则。
内容的提问来源于stack exchange,提问作者sablam
相关产品推荐
相关产品推荐

