Django如何通过外键查询实现点击项目引用展示关联报告列表
Django项目点击Reference字段展示关联报告实现方案
核心逻辑说明
你的模型是标准一对多关系:Project(项目)为1端,Report(报告)为多端,报告通过外键绑定所属项目。实现点击展示关联列表有两种成熟方案,可根据数据量选择:
- 预加载方案:首屏直接把所有项目的关联报告查出来,前端点击切换显示,适合总数据量小于1000条的场景,无额外请求
- 异步加载方案:点击时才请求对应项目的报告数据,适合报告量大的场景,首屏加载更快
步骤1:后端代码配置
先确认模型配置
确保Report模型的外键字段配置正确,建议加related_name方便反向查询:
# models.py 示例配置,可根据你实际的字段名调整 from django.db import models class Project(models.Model): reference = models.CharField(max_length=100, verbose_name="Reference") project_type = models.CharField(max_length=50, verbose_name="项目类型") project_name = models.CharField(max_length=200, verbose_name="项目名称") date = models.DateField(verbose_name="日期") class Report(models.Model): # 外键关联项目,related_name定义反向查询的字段名 project = models.ForeignKey( Project, on_delete=models.CASCADE, related_name="reports", verbose_name="所属项目" ) # 自定义报告字段,比如标题、内容、创建时间等 title = models.CharField(max_length=200, verbose_name="报告标题") create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
如果你没配置
related_name,反向取项目关联报告的写法为project.report_set.all(),后续代码里对应替换即可。
视图配置
预加载方案视图
用prefetch_related预加载关联数据,避免N+1查询问题:
# views.py from django.shortcuts import render from .models import Project def project_home(request): projects = Project.objects.prefetch_related('reports').all() return render(request, 'project_home.html', {'projects': projects})
异步加载方案额外加接口
除了上面的主页视图,再加一个返回报告JSON数据的接口:
# views.py 追加 from django.http import JsonResponse from .models import Report def project_reports_api(request, project_id): # 过滤指定项目的报告,只返回需要展示的字段 reports = Report.objects.filter(project_id=project_id).values('id', 'title', 'create_time') return JsonResponse(list(reports), safe=False)
路由配置
# urls.py from django.urls import path from . import views urlpatterns = [ path('', views.project_home, name='project_home'), # 异步方案需要加下面这行路由 path('api/project/<int:project_id>/reports/', views.project_reports_api, name='project_reports_api'), ]
步骤2:前端模板实现
页面结构代码
两种方案的页面基础结构一致,在项目表格每一行下面加默认隐藏的报告展示行:
<!-- templates/project_home.html --> <style> .reference-clickable { color: #0b5ed7; cursor: pointer; text-decoration: underline; } .reference-clickable.active { color: #be2633; } .report-block { background: #f8f9fa; padding: 16px; border-radius: 4px; } </style> <table class="table"> <thead> <tr> <th>Reference</th> <th>Project Type</th> <th>Project Name</th> <th>Date</th> </tr> </thead> <tbody> {% for project in projects %} <tr> <td> <span class="reference-clickable" data-project-id="{{ project.id }}"> {{ project.reference }} </span> </td> <td>{{ project.get_project_type_display }}</td> <td>{{ project.project_name }}</td> <td>{{ project.date|date:"Y-m-d" }}</td> </tr> <tr class="report-row" id="report-row-{{ project.id }}" style="display: none;"> <td colspan="4"> <div class="report-block"> <!-- 预加载方案直接在这里渲染报告,异步方案这里留空等JS填充 --> <strong>关联报告列表:</strong> <ul class="mt-2"> {% for report in project.reports.all %} <li>{{ report.title }} | {{ report.create_time|date:"Y-m-d H:i" }}</li> {% empty %} <li>该项目暂无关联报告</li> {% endfor %} </ul> </div> </td> </tr> {% endfor %} </tbody> </table>
JS交互代码
预加载方案JS
逻辑简单,点击直接切换显示隐藏:
document.querySelectorAll('.reference-clickable').forEach(el => { el.addEventListener('click', function() { const pid = this.dataset.projectId const row = document.getElementById(`report-row-${pid}`) // 切换显示状态 const isShow = row.style.display === 'table-row' row.style.display = isShow ? 'none' : 'table-row' this.classList.toggle('active', !isShow) }) })
异步加载方案JS
点击时拉取数据,已经加载过的内容不会重复请求:
document.querySelectorAll('.reference-clickable').forEach(el => { el.addEventListener('click', async function() { const pid = this.dataset.projectId const row = document.getElementById(`report-row-${pid}`) const block = row.querySelector('.report-block') // 已经加载过直接切换显示 if (row.dataset.loaded) { const isShow = row.style.display === 'table-row' row.style.display = isShow ? 'none' : 'table-row' this.classList.toggle('active', !isShow) return } // 请求数据 const res = await fetch(`/api/project/${pid}/reports/`) const reports = await res.json() // 渲染内容 let html = '<strong>关联报告列表:</strong><ul class="mt-2">' if (reports.length === 0) { html += '<li>该项目暂无关联报告</li>' } else { reports.forEach(report => { const time = new Date(report.create_time).toLocaleString('zh-CN') html += `<li>${report.title} | ${time}</li>` }) } html += '</ul>' block.innerHTML = html row.dataset.loaded = 'true' row.style.display = 'table-row' this.classList.add('active') }) })
可选优化
- 可以加点击其他区域自动收起已展开报告的逻辑
- 报告列表需要分页的话,异步接口加分页参数即可
- 给加载过程加loading动画,提升交互体验
内容的提问来源于stack exchange,提问作者vita
相关产品推荐
相关产品推荐

