非外键关联主模型与其他模型及Django CBV视图编写
一、Django模型编写的正确方式
绝对不要嵌套定义模型类,Django的ORM不支持在一个Model类内部嵌套另一个Model类,这种写法会导致模型无法被Django识别,也无法生成对应的数据库表。
正确的做法是将所有模型类平级定义,通过外键/一对一字段建立与Employees的关联:
- 如果每个员工对应唯一一份附加数据(比如员工额外信息),用
OneToOneField - 如果每个员工对应多条记录(比如多条观察日志),用
ForeignKey
示例models.py代码:
from django.db import models class Employees(models.Model): class Meta: db_table = "employees" # 主模型字段示例 name = models.CharField(max_length=100, verbose_name="员工姓名") hire_date = models.DateField(verbose_name="入职日期") class EmployeesAdditionalData(models.Model): class Meta: db_table = "employeesaditional" # 一对一关联主模型,on_delete=models.CASCADE表示删除员工时同步删除附加数据 employee = models.OneToOneField(Employees, on_delete=models.CASCADE, related_name="additional_data") # 附加数据字段示例 emergency_contact = models.CharField(max_length=100, verbose_name="紧急联系人") id_card = models.CharField(max_length=20, verbose_name="身份证号") class EmployeesObs(models.Model): class Meta: db_table = "employeesobs" # 一对多关联主模型,一个员工可以有多条观察记录 employee = models.ForeignKey(Employees, on_delete=models.CASCADE, related_name="observations") # 观察记录字段示例 observation_content = models.TextField(verbose_name="观察内容") create_time = models.DateTimeField(auto_now_add=True, verbose_name="记录时间")
二、处理多模型表单的CBV实现
默认的CreateView/UpdateView只能处理单个模型的表单,要实现多标签页提交多个模型数据,需要自定义视图逻辑,手动处理多个表单的初始化、验证和保存。
步骤1:创建各模型对应的表单(forms.py)
from django import forms from .models import Employees, EmployeesAdditionalData, EmployeesObs class EmployeesForm(forms.ModelForm): class Meta: model = Employees fields = ["name", "hire_date"] class EmployeesAdditionalDataForm(forms.ModelForm): class Meta: model = EmployeesAdditionalData fields = ["emergency_contact", "id_card"] # 观察记录是多条,用表单集处理 from django.forms import formset_factory EmployeesObsFormSet = formset_factory( form=forms.ModelForm, model=EmployeesObs, fields=["observation_content"], extra=1, # 默认显示1个空表单 can_delete=True # 允许删除已有的记录 )
步骤2:自定义新增员工的视图(views.py)
from django.views.generic.edit import FormMixin from django.urls import reverse_lazy from django.contrib.messages.views import SuccessMessageMixin from django.shortcuts import redirect from .models import Employees from .forms import EmployeesForm, EmployeesAdditionalDataForm, EmployeesObsFormSet class AddEmployeesView(SuccessMessageMixin, FormMixin): template_name = "../templates/employees/form_employees.html" success_url = reverse_lazy('list_Employees') success_message = "员工 %(name)s 添加成功!" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # 初始化所有表单,添加到上下文供模板使用 if self.request.method == "POST": context["employee_form"] = EmployeesForm(self.request.POST) context["additional_form"] = EmployeesAdditionalDataForm(self.request.POST) context["obs_formset"] = EmployeesObsFormSet(self.request.POST) else: context["employee_form"] = EmployeesForm() context["additional_form"] = EmployeesAdditionalDataForm() context["obs_formset"] = EmployeesObsFormSet() return context def post(self, request, *args, **kwargs): context = self.get_context_data() employee_form = context["employee_form"] additional_form = context["additional_form"] obs_formset = context["obs_formset"] # 验证所有表单是否有效 if employee_form.is_valid() and additional_form.is_valid() and obs_formset.is_valid(): # 先保存主员工模型 employee = employee_form.save() # 保存附加数据,关联到新创建的员工 additional_data = additional_form.save(commit=False) additional_data.employee = employee additional_data.save() # 保存所有观察记录 for form in obs_formset: if form.cleaned_data and not form.cleaned_data.get("DELETE", False): obs = form.save(commit=False) obs.employee = employee obs.save() # 触发成功消息 self.success_message = self.success_message % {"name": employee.name} return redirect(self.success_url) # 表单验证失败,返回原页面显示错误 return self.render_to_response(context)
步骤3:自定义编辑员工的视图(views.py)
class EditEmployeesView(SuccessMessageMixin, FormMixin): template_name = "../templates/employees/form_employees.html" success_url = reverse_lazy('list_Employees') success_message = "员工 %(name)s 编辑成功!" def get_object(self): # 获取要编辑的员工实例 return Employees.objects.get(pk=self.kwargs.get("pk")) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) employee = self.get_object() if self.request.method == "POST": context["employee_form"] = EmployeesForm(self.request.POST, instance=employee) # 编辑时附加数据如果已存在则用实例,否则新建空表单 try: additional_data = employee.additional_data context["additional_form"] = EmployeesAdditionalDataForm(self.request.POST, instance=additional_data) except EmployeesAdditionalData.DoesNotExist: context["additional_form"] = EmployeesAdditionalDataForm(self.request.POST) # 表单集初始化时关联员工的已有观察记录 context["obs_formset"] = EmployeesObsFormSet( self.request.POST, queryset=employee.observations.all() ) else: context["employee_form"] = EmployeesForm(instance=employee) try: additional_data = employee.additional_data context["additional_form"] = EmployeesAdditionalDataForm(instance=additional_data) except EmployeesAdditionalData.DoesNotExist: context["additional_form"] = EmployeesAdditionalDataForm() context["obs_formset"] = EmployeesObsFormSet(queryset=employee.observations.all()) return context def post(self, request, *args, **kwargs): employee = self.get_object() context = self.get_context_data() employee_form = context["employee_form"] additional_form = context["additional_form"] obs_formset = context["obs_formset"] if employee_form.is_valid() and additional_form.is_valid() and obs_formset.is_valid(): # 保存主员工信息 employee = employee_form.save() # 保存附加数据 additional_data = additional_form.save(commit=False) additional_data.employee = employee additional_data.save() # 处理观察记录的表单集 obs_formset.save(commit=False) # 删除标记为删除的记录 for form in obs_formset.deleted_forms: if form.instance.pk: form.instance.delete() # 保存新增或修改的记录 for form in obs_formset: if form.cleaned_data and not form.cleaned_data.get("DELETE", False): obs = form.save(commit=False) obs.employee = employee obs.save() self.success_message = self.success_message % {"name": employee.name} return redirect(self.success_url) return self.render_to_response(context)
步骤4:模板中的多标签页示例(form_employees.html)
用Bootstrap的标签页组件为例:
<form method="post"> {% csrf_token %} <div class="container"> <h2>员工信息管理</h2> <ul class="nav nav-tabs" id="employeeTabs" role="tablist"> <li class="nav-item" role="presentation"> <button class="nav-link active" id="main-tab" data-bs-toggle="tab" data-bs-target="#main" type="button" role="tab">基本信息</button> </li> <li class="nav-item" role="presentation"> <button class="nav-link" id="additional-tab" data-bs-toggle="tab" data-bs-target="#additional" type="button" role="tab">附加信息</button> </li> <li class="nav-item" role="presentation"> <button class="nav-link" id="obs-tab" data-bs-toggle="tab" data-bs-target="#obs" type="button" role="tab">观察记录</button> </li> </ul> <div class="tab-content" id="employeeTabContent"> <div class="tab-pane fade show active" id="main" role="tabpanel"> {{ employee_form.as_p }} </div> <div class="tab-pane fade" id="additional" role="tabpanel"> {{ additional_form.as_p }} </div> <div class="tab-pane fade" id="obs" role="tabpanel"> {{ obs_formset.management_form }} {% for form in obs_formset %} {{ form.as_p }} {% if form.instance.pk %} {{ form.DELETE }} 删除这条记录 {% endif %} {% endfor %} </div> </div> <button type="submit" class="btn btn-primary mt-3">保存</button> </div> </form>
关键注意事项
- 所有表单要放在同一个
<form>标签内,确保POST请求能提交所有数据 - 表单集必须渲染
management_form,否则Django无法识别表单集的数量和顺序 - 编辑视图中要处理关联模型不存在的情况(比如员工还没有附加数据)
内容的提问来源于stack exchange,提问作者Gustavo Cavasotto
相关产品推荐
相关产品推荐

