在 Django 项目开发里,主表与子表的一对多关联场景十分常见,比如一个用户主表对应多个地址子表,一个文章主表对应多个评论子表。当我们需要基于一个已有的主表 ID,批量保存多个子表表单数据时,需要合理处理数据关联和批量写入逻辑,才能保证数据的一致性和操作效率。

基础模型定义
首先我们需要定义主表和子表的模型,明确两者的关联关系,这里以文章和评论的场景为例,主表为文章表,子表为评论表,一个文章可以对应多条评论。
from django.db import models
# 主表:文章表
class Article(models.Model):
title = models.CharField(max_length=100, verbose_name="文章标题")
content = models.TextField(verbose_name="文章内容")
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
def __str__(self):
return self.title
# 子表:评论表,通过外键关联主表
class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, verbose_name="关联文章")
content = models.CharField(max_length=200, verbose_name="评论内容")
commenter = models.CharField(max_length=50, verbose_name="评论人")
create_time = models.DateTimeField(auto_now_add=True, verbose_name="评论时间")
def __str__(self):
return f"{self.commenter}的评论"
子表表单定义
接下来定义子表的表单类,用于校验前端提交的单个子表数据,这里我们使用 Django 的 ModelForm 来简化表单定义。
from django import forms
from .models import Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
# 子表表单只需要提交评论内容和评论人,关联的文章ID由主表ID提供
fields = ["content", "commenter"]
视图层批量保存逻辑实现
视图层是处理批量保存的核心,我们需要先根据主表 ID 获取对应的主表实例,然后校验所有提交的子表表单数据,最后批量关联主表实例并保存所有子表数据。
方式一:使用表单集(Formset)实现
Django 内置的表单集功能非常适合处理批量表单的场景,我们可以基于子表表单创建表单集,统一处理多个子表数据的校验和保存。
from django.shortcuts import render, get_object_or_404, redirect
from .models import Article
from .forms import CommentForm
from django.forms import formset_factory
def batch_save_comments(request, article_id):
# 根据主表ID获取文章实例,不存在则返回404
article = get_object_or_404(Article, id=article_id)
# 创建评论表单集,额外允许提交3个空表单
CommentFormSet = formset_factory(CommentForm, extra=3)
if request.method == "POST":
formset = CommentFormSet(request.POST)
if formset.is_valid():
# 遍历所有有效的表单,关联主表实例后批量保存
for form in formset:
# 跳过空表单(用户未填写的表单)
if form.cleaned_data.get("content") and form.cleaned_data.get("commenter"):
comment = form.save(commit=False)
# 关联主表ID对应的文章实例
comment.article = article
comment.save()
return redirect("article_detail", article_id=article_id)
else:
formset = CommentFormSet()
return render(request, "batch_save_comments.html", {"article": article, "formset": formset})
方式二:手动处理批量数据实现
如果不想使用表单集,也可以手动接收前端提交的批量数据,逐个校验后批量保存,这种方式更灵活,适合前端数据结构不固定的场景。
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from .models import Article, Comment
import json
def batch_save_comments_manual(request, article_id):
article = get_object_or_404(Article, id=article_id)
if request.method != "POST":
return JsonResponse({"code": 400, "msg": "仅支持POST请求"})
try:
# 假设前端提交的是JSON格式的数据,comments是子表数据列表
data = json.loads(request.body)
comments_data = data.get("comments", [])
except json.JSONDecodeError:
return JsonResponse({"code": 400, "msg": "请求数据格式错误"})
# 存储所有待保存的子表实例
comment_objs = []
for item in comments_data:
content = item.get("content")
commenter = item.get("commenter")
# 简单校验数据
if not content or not commenter:
continue
# 创建子表实例,关联主表
comment_objs.append(Comment(
article=article,
content=content,
commenter=commenter
))
# 批量保存,减少数据库查询次数
if comment_objs:
Comment.objects.bulk_create(comment_objs)
return JsonResponse({"code": 200, "msg": "批量保存成功", "count": len(comment_objs)})
前端提交示例
如果使用表单集的方式,前端页面可以这样编写,用于提交多个子表表单数据。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>批量添加评论</title>
</head>
<body>
<h3>为文章《{{ article.title }}》批量添加评论</h3>
<form method="post">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<div style="margin-bottom: 15px; padding: 10px; border: 1px solid #eee;">
<p>评论人:{{ form.commenter }}</p>
<p>评论内容:{{ form.content }}</p>
</div>
{% endfor %}
<button type="submit">批量保存</button>
</form>
</body>
</html>
注意事项
- 批量保存前一定要先校验主表 ID 对应的主表实例是否存在,避免出现外键关联错误。
- 使用
bulk_create方法批量保存子表数据时,不会触发模型的save方法和信号,如果有相关逻辑需要额外处理。 - 前端提交的批量数据需要做基本的合法性校验,避免存入无效数据。
- 如果子表数据量较大,建议分批次批量保存,避免单次数据库操作压力过大。