Django中践行DRY原则:如何重构Views层重复的列表视图代码
Django多相似列表视图的优化实现方案
你当前的装饰器方案将公共逻辑完全封装在装饰器内部,阻断了视图函数的扩展空间,以下提供两种可实现个性化扩展的优化方案:
方案1:基于类视图实现(推荐,符合Django最佳实践)
将公共逻辑抽离到父类视图中,普通视图仅需指定模板路径即可,需要自定义逻辑的视图直接覆写对应方法即可。
基础父类实现
from django.views.generic import View from django.shortcuts import render from django.db.models import Avg from .models import Bacteria from .filters import BacteriaFilter class BaseBacteriaListView(View): # 子类仅需覆写该属性指定模板路径 template_name = "" def get(self, request, *args, **kwargs): # 所有列表视图公共逻辑 bacteria = Bacteria.objects.all() bacteria_count = bacteria.count() bacteriaFilter = BacteriaFilter(request.GET, queryset=bacteria) bacteria = bacteriaFilter.qs remaining = bacteria.count() common_tags = Bacteria.tags.most_common()[:8] # 上下文单独抽离为方法,方便子类扩展 context = self.get_extra_context({ "bacteria": bacteria, "common_tags": common_tags, "bacteria_count": bacteria_count, "bacteriaFilter": bacteriaFilter, "remaining": remaining }, request, *args, **kwargs) return render(request, self.template_name, context) def get_extra_context(self, base_context, request, *args, **kwargs): # 默认返回基础上下文,子类可重写该方法新增/修改上下文变量 return base_context
无自定义逻辑的普通视图使用示例
仅需继承父类指定模板路径即可,比原装饰器写法更简洁:
class GeneralPhysiologyView(BaseBacteriaListView): template_name = "bacteria/generalPhysiology.html" class EnzymeActivityTableView(BaseBacteriaListView): template_name = "bacteria/enzymeActiveTable.html"
有个性化逻辑的视图使用示例
直接重写对应方法即可,支持扩展查询逻辑、上下文变量等:
class EnzymeActivityTableView(BaseBacteriaListView): template_name = "bacteria/enzymeActiveTable.html" def get_extra_context(self, base_context, request, *args, **kwargs): # 继承原有基础上下文 context = super().get_extra_context(base_context, request, *args, **kwargs) # 新增个性化变量 context["enzyme_avg_activity"] = Bacteria.objects.aggregate(avg_activity=Avg("activity"))["avg_activity"] # 支持修改原有上下文变量 context["common_tags"] = Bacteria.tags.most_common()[:12] return context
URL配置示例
urlpatterns = [ path('general-physiology/', GeneralPhysiologyView.as_view(), name='general_physiology'), path('enzyme-activity/', EnzymeActivityTableView.as_view(), name='enzyme_activity'), ]
方案2:改造现有装饰器(兼容原有代码,无需修改现有URL配置)
如果你不想改动现有代码结构,可以对原装饰器进行改造,增加扩展入口,让被装饰的视图函数返回的字典自动合并到模板上下文中:
改造后的装饰器代码
def my_decorator(html=""): def decorator(view_func): def wrapper(request, *args, **kwargs): # 公共逻辑保持不变 bacteria = Bacteria.objects.all() bacteria_count = bacteria.count() bacteriaFilter = BacteriaFilter(request.GET, queryset=bacteria) bacteria = bacteriaFilter.qs remaining = bacteria.count() common_tags = Bacteria.tags.most_common()[:8] # 显式构造基础上下文,替代原locals()避免引入无关变量 base_context = { "bacteria": bacteria, "common_tags": common_tags, "bacteria_count": bacteria_count, "bacteriaFilter": bacteriaFilter, "remaining": remaining } # 执行原视图函数,若返回字典则合并到上下文中 extra_context = view_func(request, *args, **kwargs) if isinstance(extra_context, dict): base_context.update(extra_context) return render(request, html, base_context) return wrapper return decorator
使用示例
原有无自定义逻辑的视图无需任何修改,保持原样即可:
@my_decorator(html="bacteria/generalPhysiology.html") def general_physiology_view(request): pass
需要添加个性化逻辑的视图直接返回字典即可:
@my_decorator(html="bacteria/enzymeActiveTable.html") def enzyme_activity_table_view(request): # 个性化业务逻辑 avg_activity = Bacteria.objects.aggregate(avg_activity=Avg("activity"))["avg_activity"] # 返回的字典会自动合并到模板上下文 return { "enzyme_avg_activity": avg_activity, "common_tags": Bacteria.tags.most_common()[:12] }
方案对比
- 类视图方案:扩展性更强,逻辑分层清晰,适合后期有大量自定义需求的场景,是Django官方推荐的实现方式
- 装饰器改造方案:完全兼容现有代码,无需修改原有URL配置,适合不想大动现有项目结构的场景
内容的提问来源于stack exchange,提问作者Danhr
相关产品推荐
相关产品推荐

