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

Django表单提交后表间关联失效、外键约束报错问题排查

问题核心原因

你遇到的所有报错、关联失效问题,核心都是保存各表数据时没有手动绑定外键/多对多关联,Django不会自动把同一次请求里的三个模型数据关联起来,必须手动指定关联关系。


1. 修复models.py错误与不合理设计

原代码存在字段前缀遗漏、命名不匹配问题,且用ManyToManyField不符合你单条关联仅存2个号码的需求,直接用双外键天然限制数量:

from django.db import models 

class Customer(models.Model):
    customer_name = models.CharField(max_length=100)
    customer_email = models.CharField(max_length=100)
    # 方便后台显示
    def __str__(self):
        return self.customer_name

class Phonenumber(models.Model):
    customer_number = models.CharField(max_length=100)
    # 补全models前缀,后续保存时会赋值,不需要加null=True
    belongs_to = models.ForeignKey(Customer, on_delete=models.DO_NOTHING)
    def __str__(self):
        return self.customer_number

class Connection(models.Model): # 模型名建议单数,Django自动生成复数标识
    registered_at = models.DateTimeField(auto_now=True)
    got_number_at = models.DateTimeField(blank=True, null=True)
    registered_by = models.ForeignKey(Customer, on_delete=models.DO_NOTHING)
    # 双外键直接绑定两个号码,天然限制单条关联最多2个号码
    number1 = models.ForeignKey(Phonenumber, on_delete=models.DO_NOTHING, related_name="connection_as_number1")
    number2 = models.ForeignKey(Phonenumber, on_delete=models.DO_NOTHING, related_name="connection_as_number2")
    def __str__(self):
        return f"{self.number1} <-> {self.number2}"

2. 修复phonebook_form.py错误

原代码存在字段名不匹配、语法括号错误,修改后支持两个号码同时提交:

from django.forms import ModelForm, TextInput, forms
from .models import *

class CustomerForm(ModelForm):
    class Meta:
        model = Customer
        # 字段名必须和模型定义一致
        fields = ['customer_name', 'customer_email']
        widgets = {
            'customer_name': TextInput(attrs={'class':'form-control', 'id': 'cus_name'}),
            'customer_email': TextInput(attrs={'class':'form-control', 'id': 'cus_email'})
        }

# 双号码提交表单,符合单会话注册2个号码的需求
class NumberPairForm(forms.Form):
    number1 = forms.CharField(max_length=100, widget=TextInput(attrs={'class':'form-control', 'id': 'cus_number1'}), label="号码1")
    number2 = forms.CharField(max_length=100, widget=TextInput(attrs={'class':'form-control', 'id': 'cus_number2'}), label="号码2")

class ConnectionForm(ModelForm):
    class Meta:
        model = Connection
        fields = ['got_number_at']
        widgets = {
            'got_number_at': TextInput(attrs={'class':'form-control', 'id': 'connection_date'})
        }

3. 修复views.py逻辑,手动绑定关联

原代码存在语法错误、没有关联绑定、未传上下文给模板的问题:

from django.shortcuts import render
from .phonebook_form import *

# 所有视图逻辑必须放在函数内
def phonebook_view(request):
    form1 = CustomerForm()
    form2 = NumberPairForm()
    form3 = ConnectionForm()

    if request.method == "POST": # 双等号才是判断逻辑
        form_customer = CustomerForm(request.POST)
        form_number = NumberPairForm(request.POST)
        form_connection = ConnectionForm(request.POST)
        # 三个表单全部校验通过再处理
        if form_customer.is_valid() and form_number.is_valid() and form_connection.is_valid():
            # 1. 先存客户,拿到客户实例
            customer = form_customer.save()
            # 2. 存两个号码,绑定所属客户
            num1 = Phonenumber.objects.create(
                customer_number = form_number.cleaned_data['number1'],
                belongs_to = customer
            )
            num2 = Phonenumber.objects.create(
                customer_number = form_number.cleaned_data['number2'],
                belongs_to = customer
            )
            # 3. 存关联记录,先不提交到库,绑定所有关联字段后再保存
            connection = form_connection.save(commit=False)
            connection.registered_by = customer
            connection.number1 = num1
            connection.number2 = num2
            connection.save()

    context = {'form1': form1,
               'form2': form2,
               'form3': form3
               } 
    # 必须把context传给模板
    return render(request, 'phoneBook/phonebook.html', context)

4. 后台显示配置(解决后台看不到关联的问题)

在app下的admin.py中添加如下配置,即可在后台直接查看所有关联关系:

from django.contrib import admin
from .models import *

@admin.register(Customer)
class CustomerAdmin(admin.ModelAdmin):
    list_display = ('id', 'customer_name', 'customer_email')

@admin.register(Phonenumber)
class PhonenumberAdmin(admin.ModelAdmin):
    list_display = ('id', 'customer_number', 'belongs_to')

@admin.register(Connection)
class ConnectionAdmin(admin.ModelAdmin):
    list_display = ('id', 'registered_at', 'got_number_at', 'registered_by', 'number1', 'number2')

最后操作

修改完代码后执行迁移命令更新数据库结构即可:

python manage.py makemigrations
python manage.py migrate

如果迁移时提示需要给现有历史数据设置默认值,直接选择1输入0临时处理即可,或者清空之前的测试数据再执行迁移。

内容的提问来源于stack exchange,提问作者Sunny Garden

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.12 14:42:02