为什么Django出现“违反非空约束”错误?

3 投票
4 回答
5548 浏览
提问于 2025-04-16 19:21

错误信息:

列 "postal_code_id" 中的空值违反了非空约束

表单内容:

def add(request):
    if request.method == 'POST':
        address_form = AddressForm(request.POST)
        company_form = CompanyForm(request.POST)
        if address_form.is_valid() and company_form.is_valid():
            print address_form.cleaned_data['postal_code'] # <-- prints (<PostalCode: V4N 1K6>, False)
            address_form.save() # <------------------------------- occurs here
        else:
            print 'Address errors',address_form.errors
            print 'Company errors', company_form.errors
    else:
        address_form = AddressForm()
        company_form = CompanyForm()
    return render(request, 'company/add.html', locals())

很明显,表单里确实有一个有效的 PostalCode 对象,所以我不明白为什么会说违反了非空约束。当然,我在处理表单时做了一些奇怪的事情:

class AddressForm(ModelForm):
    postal_code = CharField(max_length=10, validators=[validate_postal_code])
    city = CharField(max_length=50, validators=[validate_non_whitespace])
    province = CharField(max_length=50, validators=[validate_non_whitespace])
    country = CharField(max_length=50, initial='Canada', validators=[validate_non_whitespace])

    def clean_postal_code(self):
        code = self.cleaned_data['postal_code']
        code = code.upper()
        code = re.sub('[^A-Z0-9]', '', code)
        code = code[:3] + ' ' + code[-3:]
        return code

    def clean_country(self):
        country = self.cleaned_data['country']

        try:
            country = Country.objects.get(name__iexact=country)
        except Country.DoesNotExist:
            raise ValidationError('Country does not exist')

        return country

    def clean_province(self):
        province = self.cleaned_data['province']

        if not Province.objects.filter(name__iexact=province).exists():
            raise ValidationError('Province does not exist')

        return province

    def clean(self):
        data = self.cleaned_data

        if 'country' in data and 'province' in data:
            try:
                data['province'] = Province.objects.get(country=data['country'], name__iexact=data['province'])
                if 'city' in data:
                    data['city'] = City.objects.get_or_create(name__iexact=data['city'], province=data['province'], defaults={'name':data['city']})[0]
                    if 'postal_code' in data:
                        data['postal_code'] = PostalCode.objects.get_or_create(code=data['postal_code'], city=data['city'])
            except Province.DoesNotExist:
                self._errors['province'] = self.error_class(['Province does not exist in that Country'])
                del data['province']

        return data

    class Meta:
        exclude = ['postal_code']
        model = Address

具体来说,我把 postal_code 字段替换成了一个文本字段,然后在 "clean" 方法里查找或创建这个对象。为什么这样会让 Django 感到困惑呢?最后它不是得到了需要的对象吗?

4 个回答

1

我之所以关注这个问题,是因为我想在表单保存之前,自动给表单数据添加一个 uploaded_by 字段。我使用的是 ModelForm,并且把 uploaded_by 字段排除了。后来我发现,关于这个问题的一个相关问题的答案,给出了一个简洁的解决方案。

1

我不太懂Django,但也许你需要确保邮政编码在保存地址之前已经保存好?

2

你现在把 postal_code 这个字段给排除了,这样在保存的时候,模型表单就会跳过这个字段。我之前也遇到过类似的问题,最后不得不去查 django 的代码才能搞明白这个行为。顺便说一下,这个过程是值得的。

你应该做的是,直接设置 postal_code 字段使用的控件,而不是先排除再重新包含。

class AddressForm(ModelForm):
    class Meta:
        model = Address
        widgets = {
            'postal_code': CharField(max_length=10),
        }

这样做可以让模型表单正确验证这个字段并保存。我为了简洁省略了你表单的其他部分。

补充:

在模型表单中用 CharField 来处理外键是非常麻烦的。相反,建议你把它转换成一个普通的表单。看起来你已经在定义大部分字段了。这样的话,就需要你自己来验证这些字段是否有效,并且是否已经在数据库中存在。你可以创建一个保存方法,让它的行为像模型表单的保存方法一样,这样就可以了。

撰写回答