You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django如何合并差异极小的视图函数并改写为类视图

重复视图合并与类视图改造方案

核心思路

两个视图95%以上逻辑完全一致,差异仅为4处可配置项:模板路径、关联的业务Info模型、User反向关联该模型的查询前缀、对应筛选字段名。只要把这几个差异点抽成可传入的参数,公共逻辑只写一次即可,后续新增同类型视图也不需要重复写代码。

一、函数视图合并实现

先把公共逻辑抽成一个内部复用的工具函数,差异部分通过参数传入,原来的两个视图只需要传对应配置就能用。

公共逻辑封装

from django.db.models import Q
from django.core.paginator import Paginator
from django.http import HttpResponseForbidden
from collections import Counter

def _gen_manager_dashboard_data(request, template_path, info_model, rel_key):
    """
    生成经理仪表盘所需的模板和上下文
    :param rel_key: User关联Info模型的反向查询名,即scientificinfo/religiousinfo
    """
    context = {}
    # 补全原代码缺失的权限拦截:非经理角色直接返回403
    if request.user.role != 'manager':
        return HttpResponseForbidden(), None

    # 基础用户查询
    users = User.objects.filter(
        role='applicant',
        isPreRegistered=True,
        **{f'{rel_key}__is_interviewed': True}
    ).order_by('-date_joined')
    approved_query = info_model.objects.filter(is_approved__exact='0')

    context['all_users'] = users.count() # 替换原len(users),避免全量加载数据提升性能
    context['approved'] = approved_query.count()
    context['survey_choices'] = SURVEY_CHOICES

    # 新用户筛选
    if request.GET.get('user', 'all') == 'new':
        users = users.filter(**{f'{rel_key}__is_approved__exact': '0'})

    # 以下筛选逻辑两个视图完全一致,直接复用
    # 研究领域筛选
    field_list = request.GET.getlist('field')
    if field_list:
        if 'None' in field_list:
            users = users.filter(fields__title=None) | users.filter(fields__title__in=field_list)
        else:
            users = users.filter(fields__title__in=field_list)
    # 性别筛选
    gender_list = request.GET.getlist('gender')
    if gender_list:
        users = users.filter(gender__in=gender_list)
    # 学历筛选
    education_list = request.GET.getlist('education')
    if education_list:
        users = users.filter(educationalinfo__grade__in=education_list)
    # 工作职位筛选
    work_list = request.GET.getlist('work')
    if work_list:
        users = users.filter(workinfo__position__in=work_list)
    # 所在省份筛选
    province_list = request.GET.getlist('province')
    if province_list:
        if 'None' in province_list:
            users = users.filter(prevaddress__address_province__in=province_list) | users.filter(
                prevaddress__address_province=None)
        else:
            users = users.filter(prevaddress__address_province__in=province_list)
    # 关键词搜索
    query_string = request.GET.get('query_string')
    if query_string:
        name_query = None
        for term in query_string.split():
            if name_query:
                name_query = name_query & (Q(first_name__contains=term) | Q(last_name__contains=term))
            else:
                name_query = Q(first_name__contains=term) | Q(last_name__contains=term)
        users = users.filter(name_query |
                             Q(educationalinfo__field__contains=query_string) |
                             Q(educationalinfo__tendency__contains=query_string) |
                             Q(educationalinfo__university_name__contains=query_string) |
                             Q(workinfo__organization__contains=query_string) |
                             Q(ngoinfo__ngo_name__contains=query_string) |
                             Q(melli_code__contains=query_string))

    users = users.distinct()

    # 筛选项统计
    context['grade_choices'] = []
    for g, grade in EDUCATIONAL_GRADE_CHOICES:
        count = EducationalInfo.objects.filter(user__in=users, grade=g).count()
        context['grade_choices'].append((g, grade, count))

    context['work_position_choices'] = []
    for p, position in WORK_POSITION_CHOICES:
        count = WorkInfo.objects.filter(user__in=users, position=p).count()
        context['work_position_choices'].append((p, position, count))

    provinces = users.values_list('prevaddress__address_province')
    provinces = Counter([d[0] for d in provinces])
    context['provinces'] = provinces.items()

    # 分页处理
    paginator = Paginator(users, 25)
    page_number = request.GET.get('page', 1)
    page_obj = paginator.get_page(page_number)
    context['users'] = page_obj

    # 顶部统计卡片数据
    context['is_interviewed'] = info_model.objects.filter(is_approved__exact='0', user__is_staff=False).count()
    context['not_interviewed'] = info_model.objects.filter(is_interviewed=True, user__is_staff=False).count()
    context['men'] = users.filter(gender="male").count()
    context['women'] = users.filter(gender="female").count()
    context['query_string'] = query_string

    return loader.get_template(template_path), context

简化后的原视图

原来两个几十行的视图现在只需要3行配置即可,后续改公共逻辑只需要改上面的工具函数,两个视图同步生效:

def manager_scientific(request, *args, **kwargs):
    template, context = _gen_manager_dashboard_data(
        request,
        template_path='reg/scientific-manager-dashboard.html',
        info_model=ScientificInfo,
        rel_key='scientificinfo'
    )
    return template if context is None else HttpResponse(template.render(request=request, context=context))

def manager_religious(request, *args, **kwargs):
    template, context = _gen_manager_dashboard_data(
        request,
        template_path='reg/religious-manager-dashboard.html',
        info_model=ReligiousInfo,
        rel_key='religiousinfo'
    )
    return template if context is None else HttpResponse(template.render(request=request, context=context))

二、基于类的视图改造

如果要改成类视图,推荐继承Django内置的TemplateView实现,把公共逻辑写到基类,差异项定义为类属性,扩展新视图比函数版更方便。

基类实现

from django.views.generic import TemplateView

class BaseManagerDashboard(TemplateView):
    # 公共默认配置
    paginate_by = 25
    # 子类必须覆盖的配置项,默认留空
    info_model = None
    rel_key = None

    def dispatch(self, request, *args, **kwargs):
        # 权限校验
        if request.user.role != 'manager':
            return HttpResponseForbidden()
        # 校验子类是否配置了必填参数
        assert self.info_model is not None, "必须配置info_model属性"
        assert self.rel_key is not None, "必须配置rel_key属性"
        return super().dispatch(request, *args, **kwargs)

    def _get_filtered_users(self):
        """封装用户筛选逻辑"""
        users = User.objects.filter(
            role='applicant',
            isPreRegistered=True,
            **{f'{self.rel_key}__is_interviewed': True}
        ).order_by('-date_joined')

        # 新用户筛选
        if self.request.GET.get('user', 'all') == 'new':
            users = users.filter(**{f'{self.rel_key}__is_approved__exact': '0'})

        # 通用筛选逻辑和函数版一致
        field_list = self.request.GET.getlist('field')
        if field_list:
            if 'None' in field_list:
                users = users.filter(fields__title=None) | users.filter(fields__title__in=field_list)
            else:
                users = users.filter(fields__title__in=field_list)
        
        gender_list = self.request.GET.getlist('gender')
        if gender_list:
            users = users.filter(gender__in=gender_list)
        
        education_list = self.request.GET.getlist('education')
        if education_list:
            users = users.filter(educationalinfo__grade__in=education_list)
        
        work_list = self.request.GET.getlist('work')
        if work_list:
            users = users.filter(workinfo__position__in=work_list)
        
        province_list = self.request.GET.getlist('province')
        if province_list:
            if 'None' in province_list:
                users = users.filter(prevaddress__address_province__in=province_list) | users.filter(
                    prevaddress__address_province=None)
            else:
                users = users.filter(prevaddress__address_province__in=province_list)
        
        query_string = self.request.GET.get('query_string')
        if query_string:
            name_query = None
            for term in query_string.split():
                if name_query:
                    name_query = name_query & (Q(first_name__contains=term) | Q(last_name__contains=term))
                else:
                    name_query = Q(first_name__contains=term) | Q(last_name__contains=term)
            users = users.filter(name_query |
                                Q(educationalinfo__field__contains=query_string) |
                                Q(educationalinfo__tendency__contains=query_string) |
                                Q(educationalinfo__university_name__contains=query_string) |
                                Q(workinfo__organization__contains=query_string) |
                                Q(ngoinfo__ngo_name__contains=query_string) |
                                Q(melli_code__contains=query_string))
        return users.distinct(), query_string

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        users, query_string = self._get_filtered_users()

        context['all_users'] = users.count()
        context['approved'] = self.info_model.objects.filter(is_approved__exact='0').count()
        context['survey_choices'] = SURVEY_CHOICES

        # 筛选项统计
        context['grade_choices'] = []
        for g, grade in EDUCATIONAL_GRADE_CHOICES:
            count = EducationalInfo.objects.filter(user__in=users, grade=g).count()
            context['grade_choices'].append((g, grade, count))
        
        context['work_position_choices'] = []
        for p, position in WORK_POSITION_CHOICES:
            count = WorkInfo.objects.filter(user__in=users, position=p).count()
            context['work_position_choices'].append((p, position, count))
        
        provinces = users.values_list('prevaddress__address_province')
        provinces = Counter([d[0] for d in provinces])
        context['provinces'] = provinces.items()

        # 分页
        paginator = Paginator(users, self.paginate_by)
        page_obj = paginator.get_page(self.request.GET.get('page', 1))
        context['users'] = page_obj

        # 统计数据
        context['is_interviewed'] = self.info_model.objects.filter(is_approved__exact='0', user__is_staff=False).count()
        context['not_interviewed'] = self.info_model.objects.filter(is_interviewed=True, user__is_staff=False).count()
        context['men'] = users.filter(gender="male").count()
        context['women'] = users.filter(gender="female").count()
        context['query_string'] = query_string
        return context

具体业务视图实现

两个业务视图只需要配置3个类属性即可,不需要写任何业务逻辑:

class ScientificManagerDashboard(BaseManagerDashboard):
    template_name = 'reg/scientific-manager-dashboard.html'
    info_model = ScientificInfo
    rel_key = 'scientificinfo'

class ReligiousManagerDashboard(BaseManagerDashboard):
    template_name = 'reg/religious-manager-dashboard.html'
    info_model = ReligiousInfo
    rel_key = 'religiousinfo'

路由配置的时候直接调用as_view()即可:

urlpatterns = [
    path('manager/scientific/', ScientificManagerDashboard.as_view(), name='manager_scientific'),
    path('manager/religious/', ReligiousManagerDashboard.as_view(), name='manager_religious'),
]

优化点说明

  • 修复了原代码非经理角色访问时返回空模板的问题,统一返回403
  • 把原代码中len(users)的统计方式替换为users.count(),避免全量加载用户数据到内存,大幅提升大数量下的接口性能
  • 所有动态关联字段用字典解包的方式传入查询,避免硬编码

内容的提问来源于stack exchange,提问作者Sam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 18:18:53