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

非外键关联主模型与其他模型及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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 06:50:27