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

Django IntegrityError排查:新增MenuItem时NOT NULL约束失败

问题描述

我正在完成Coursera的API课程项目,需要创建模型序列化器及相关代码。当前遇到错误:

Error: django.db.utils.IntegrityError: NOT NULL constraint failed: LittleLemonAPI_menuitem.category_id

这个错误在MenuItemSerializer中设置depth = 1并新增菜品时触发;如果移除depth=1和category = CategorySerializer就不会报错,但这样无法返回Category表的关联数据。


相关代码

serializers.py

from rest_framework import serializers, validators
from .models import Category, MenuItem, Cart, Order, OrderItem
from django.contrib.auth.models import User, Group, Permission

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ['id', 'slug', 'title']

        validators = [
            validators.UniqueTogetherValidator(
                queryset=Category.objects.all(),
                fields=('title',),
                message="Category must be unique"
            )
        ]

class MenuItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = MenuItem
        category = CategorySerializer()
        fields = ['id', 'title', 'price',
                  'featured', 'inventory', 'category',]
        depth = 1

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'email', 'groups',]
        depth = 1

class CartSerializer(serializers.ModelSerializer):
    class Meta:
        model = Cart
        user = UserSerializer()
        menuitem = MenuItemSerializer()
        fields = ['id', 'user', 'menuitem', 'quantity', 'unit_price', 'price']
        depth = 1
        validators = [
            validators.UniqueTogetherValidator(
                queryset=MenuItem.objects.all(),
                fields=('id',),
                message="Menuitem should be unique in this curt"
            ),
            validators.UniqueTogetherValidator(
                queryset=User.objects.all(), fields=('id'), message="User should be unique")
        ]

class OrderSerilizer(serializers.ModelSerializer):
    class Meta:
        model = Order
        fields = ['id', 'user', 'delivery_crew', 'status', 'total', 'date']
        depth = 1

class OrderItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = OrderItem
        fields = ['id', 'order', 'menuitem', 'quantity', 'unit_price', 'price']
        depth = 1
        validators = [
            validators.UniqueTogetherValidator(
                queryset=Order.objects.all(),
                fields=('id',)
            ),
            validators.UniqueTogetherValidator(
                queryset=MenuItem.objects.all(),
                fields=('id',)
            )
        ]

models.py

from django.db import models
from django.contrib.auth.models import User
# Create your models here.

class Category(models.Model):
    slug = models.SlugField()
    title = models.CharField(max_length=255, db_index=True)

class MenuItem(models.Model):
    title = models.CharField(max_length=255, db_index=True)
    price = models.DecimalField(max_digits=6, decimal_places=2, db_index=True)
    featured = models.BooleanField(db_index=True)
    category = models.ForeignKey(Category, on_delete=models.PROTECT)
    inventory = models.IntegerField()

class Cart(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    menuitem = models.ForeignKey(MenuItem, on_delete=models.CASCADE)
    quantity = models.SmallIntegerField(),
    unit_price = models.DecimalField(max_digits=6, decimal_places=2)
    price = models.DecimalField(max_digits=6, decimal_places=2)

    class Meta:
        unique_together = ('menuitem', 'user')

class Order(models.Model):
    user = models.ForeignKey(User, models.CASCADE)
    delivery_crew = models.ForeignKey(
        User, on_delete=models.SET_NULL, related_name='delivery_crew', null=True)
    status = models.BooleanField(db_index=True, default=0)
    total = models.DecimalField(max_digits=6, decimal_places=2)
    date = models.DateField(db_index=True)

class OrderItem(models.Model):
    order = models.ForeignKey(User, on_delete=models.CASCADE)
    menuitem = models.ForeignKey(MenuItem, on_delete=models.CASCADE)
    quantity = models.SmallIntegerField()
    unit_price = models.DecimalField(max_digits=6, decimal_places=2)
    price = models.DecimalField(max_digits=6, decimal_places=2)

    class Meta:
        unique_together = ('order', 'menuitem')

解决方案

问题核心是序列化器读写逻辑冲突:当你在MenuItemSerializer中直接声明category = CategorySerializer()并设置depth=1时,序列化器在创建菜品时会要求传入完整的Category对象数据(比如title、slug),但实际你可能只传了category_id,导致category_id字段为空,触发NOT NULL约束错误。

要同时实现「读取时返回关联Category详情」和「创建时仅需传入category_id」,按以下方式修改:

1. 修复MenuItemSerializer

class MenuItemSerializer(serializers.ModelSerializer):
    # 仅用于读取时返回完整Category信息,不参与写入
    category = CategorySerializer(read_only=True)
    # 新增可写入的category_id字段,映射到模型的category外键
    category_id = serializers.PrimaryKeyRelatedField(
        queryset=Category.objects.all(),
        source='category'
    )

    class Meta:
        model = MenuItem
        fields = ['id', 'title', 'price', 'featured', 'inventory', 'category', 'category_id']
        # 删除多余的depth=1,显式嵌套序列化器后无需该参数

逻辑说明

  • category = CategorySerializer(read_only=True):指定该字段仅负责序列化输出(返回完整分类数据),创建/更新时忽略它。
  • category_id = serializers.PrimaryKeyRelatedField(...):专门用于写入操作,接收Category的ID,通过source='category'关联到模型的外键字段,这样创建菜品时传category_id就能正确关联分类,不会触发空约束。

2. 其他序列化器和模型的额外修复

修复CartSerializer的验证逻辑

原CartSerializer中的UniqueTogetherValidator用错了查询集,应该基于Cart模型而非MenuItem,同时区分读写字段:

class CartSerializer(serializers.ModelSerializer):
    user = UserSerializer(read_only=True)
    menuitem = MenuItemSerializer(read_only=True)
    user_id = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), source='user')
    menuitem_id = serializers.PrimaryKeyRelatedField(queryset=MenuItem.objects.all(), source='menuitem')

    class Meta:
        model = Cart
        fields = ['id', 'user', 'menuitem', 'user_id', 'menuitem_id', 'quantity', 'unit_price', 'price']
        validators = [
            validators.UniqueTogetherValidator(
                queryset=Cart.objects.all(),
                fields=('user', 'menuitem'),
                message="该菜品已在购物车中"
            )
        ]

修复OrderItem模型的外键错误

原OrderItem模型中order外键错误指向了User,应该改为Order:

class OrderItem(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE)  # 替换User为Order
    menuitem = models.ForeignKey(MenuItem, on_delete=models.CASCADE)
    quantity = models.SmallIntegerField()
    unit_price = models.DecimalField(max_digits=6, decimal_places=2)
    price = models.DecimalField(max_digits=6, decimal_places=2)

    class Meta:
        unique_together = ('order', 'menuitem')

内容的提问来源于stack exchange,提问作者Rony Ahmmod

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 22:14:53