如何优化Django中获取二级外键数据?以服务商技能为例
优化方案
核心问题分析
你当前的实现存在N+1数据库查询问题:遍历每个服务商时,模板标签都会单独发起多次查询(查ProviderProfile、查关联的ProviderSkill、再查Skill),服务商数量多的时候性能会明显下降,而且模板标签的写法也增加了冗余逻辑。下面是更简洁高效的优化方向:
1. 视图层预加载关联数据,消灭N+1查询
直接在视图里一次性把所有需要的关联数据预加载好,后续模板里直接用就行,不用再反复查库:
修改视图代码:
from django.db.models import Prefetch from .models import ProviderProfile, ProviderSkill, Skill @login_required def clientProviderListView(request): # 预加载ProviderSkill及其关联的Skill对象,一次查询搞定所有数据 providers = ProviderProfile.objects.all().prefetch_related( Prefetch('skills', queryset=ProviderSkill.objects.select_related('skill')) ) # 这里提前预留筛选逻辑的位置,后面加筛选直接拓展就行 # 比如按技能筛选: # selected_skill_id = request.GET.get('skill') # if selected_skill_id: # providers = providers.filter(skills__skill_id=selected_skill_id) context = {'providers': providers} return render(request,'accounts/client_provider_list.html', context)
2. 删掉自定义模板标签,简化模板逻辑
预加载完成后,模板里直接遍历关联数据就行,完全不需要额外的模板标签:
修改accounts/partials/provider_card.html里的技能展示部分:
<!-- 替换原来的 {% get_skills provider.user %} 调用 --> {% if provider.skills %} <ul class="skill-list"> {% for provider_skill in provider.skills %} <li>{{ provider_skill.skill.name }}</li> {% endfor %} </ul> {% else %} <p>暂无技能</p> {% endif %}
之后直接删掉skills.py里的自定义标签代码,减少冗余文件。
3. 为后续筛选功能快速拓展
如果要加按技能筛选的功能,直接在视图里处理请求参数就行,同时保持预加载逻辑:
更新后的完整视图:
from django.db.models import Prefetch from .models import ProviderProfile, ProviderSkill, Skill @login_required def clientProviderListView(request): providers = ProviderProfile.objects.all().prefetch_related( Prefetch('skills', queryset=ProviderSkill.objects.select_related('skill')) ) # 处理技能筛选 selected_skill_id = request.GET.get('skill') all_skills = Skill.objects.filter(is_active=True) if selected_skill_id: providers = providers.filter(skills__skill_id=selected_skill_id) context = { 'providers': providers, 'all_skills': all_skills, 'selected_skill_id': selected_skill_id } return render(request,'accounts/client_provider_list.html', context)
然后在主模板里加筛选表单:
{% extends 'core/base.html' %} {% block title %}Providers{% endblock %} {% block content %} <h2 class="me-1">Providers</h2> <!-- 新增筛选表单 --> <form method="get" class="mb-3"> <select name="skill" class="form-select w-auto d-inline"> <option value="">全部技能</option> {% for skill in all_skills %} <option value="{{ skill.id }}" {% if skill.id|stringformat:"i" == selected_skill_id %}selected{% endif %}> {{ skill.name }} </option> {% endfor %} </select> <button type="submit" class="btn btn-primary ms-2">筛选</button> </form> <!-- 原服务商列表代码 --> {% if providers %} <div id="client_p" class="d-flex"> <div class="row row-cols-1"> {% for provider in providers %} <div class="col text-center p-2"> {% include 'accounts/partials/provider_card.html' %} </div> {% endfor %} </div> </div> {% else %} <div class="container-fluid my-2"> <button class="btn btn-warning">系统中暂无服务商</button> </div> {% endif %} {% endblock %}
额外小优化
可以在ProviderProfile模型里加个属性方法,快速获取技能名称列表,模板里展示更方便:
class ProviderProfile(models.Model): # 现有字段... @property def skill_names(self): return [ps.skill.name for ps in self.skills.all()]
然后模板里直接用{{ provider.skill_names|join:", " }}就能展示逗号分隔的技能列表,不用写循环。
内容的提问来源于stack exchange,提问作者J Jennings
相关产品推荐
相关产品推荐

