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
相关产品推荐
相关产品推荐

