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

如何优化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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 21:40:29