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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 15:48:15