DRF发送POST请求时NOT NULL约束失败错误求助
DRF创建Message时NOT NULL约束错误及嵌套序列化器冲突问题
使用Django Rest Framework开发API时,发送POST请求创建新Message时触发错误:django.db.utils.IntegrityError: NOT NULL constraint failed: chat_message.channel_id。
需求是GET请求返回完整的关联对象而非仅主键,因此在MessageSerializer中设置了嵌套的ChannelSerializer和UserSerializer(均为read_only=True),但此时POST请求失效;移除channel = ChannelSerializer(read_only=True)后POST请求正常,但不符合GET返回格式要求。
模型代码
class Channel(models.Model): name = models.CharField(max_length=100) users = models.ManyToManyField(Account) created_date = models.DateTimeField(auto_now_add=True) class Message(models.Model): user = models.ForeignKey(Account, on_delete=models.CASCADE) channel = models.ForeignKey(Channel, on_delete=models.CASCADE) content = models.TextField(blank=False) created_date = models.DateTimeField(auto_now_add=True)
序列化器代码
class ChannelSerializer(serializers.ModelSerializer): users = UserSerializer(many=True, read_only=True) class Meta: model = Channel fields = '__all__' class MessageSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) channel = ChannelSerializer(read_only=True) class Meta: model = Message fields = '__all__' class UserSerializer(serializers.ModelSerializer): """ Serializers For The User Object""" class Meta: model = Account fields = ["email","password","username","id"] extra_kwargs = {'password': {'write_only': True, 'min_length': 8 }} def create(self, validated_data): """ Create the user with encrypted password""" return Account.objects.create_user(**validated_data) def update(self, instance, validated_data): """Update The user""" user = super().update(instance, validated_data) return user
视图代码
class ChannelView(viewsets.ModelViewSet): """View for the channels""" serializer_class = ChannelSerializer queryset = Channel.objects.all() permission_classes = [IsAuthenticated] def get_queryset(self): """Retrieve channels linked to the authenticated user""" return self.queryset.filter(users=self.request.user) def perform_create(self, serializer): """ Create a new channel with users set in the request as users""" serializer.save(users=self.request.data.get('users')) class MessageView(viewsets.ModelViewSet): """View for the messages""" serializer_class = MessageSerializer queryset = Message.objects.all() permission_classes = [IsAuthenticated] def perform_create(self, serializer): """ Create a new message with the current user set as user """ serializer.save(user=self.request.user)
尝试过的请求Payload
{ "user": { "email": "test@test.test", "username": "test", "id": 2 }, "channel": { "id": 2, "users": [ { "email": "vincent@yahoo.fr", "username": "username", "id": 1 }, { "email": "test@test.test", "username": "test", "id": 2 } ], "name": "Channel 2", "created_date": "2023-03-03T09:45:24.184499Z" }, "content" : "hello" }
或
{ "user": 1, "channel": 1, "content" : "hello" }
或
{ "user": [1], "channel": [1], "content" : "hello" }
完整错误堆栈
[03/Mar/2023 15:43:15] "POST /chat/message/ HTTP/1.1" 500 201970 Internal Server Error: /chat/message/ Traceback (most recent call last): File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\db\backends\utils.py", line 89, in _execute return self.cursor.execute(sql, params) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\db\backends\sqlite3\base.py", line 357, in execute return Database.Cursor.execute(self, query, params) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ sqlite3.IntegrityError: NOT NULL constraint failed: chat_message.channel_id The above exception was the direct cause of the following exception: Traceback (most recent call last): File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\core\handlers\exception.py", line 56, in inner response = get_response(request) ^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\core\handlers\base.py", line 197, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\views\decorators\csrf.py", line 55, in wrapped_view return view_func(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\rest_framework\viewsets.py", line 125, in view return self.dispatch(request, *args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\rest_framework\views.py", line 509, in dispatch response = self.handle_exception(exc) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\rest_framework\views.py", line 469, in handle_exception self.raise_uncaught_exception(exc) File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception raise exc File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\rest_framework\views.py", line 506, in dispatch response = handler(request, *args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\rest_framework\mixins.py", line 19, in create self.perform_create(serializer) File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\chat\views.py", line 34, in perform_create serializer.save(user=self.request.user) File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\rest_framework\serializers.py", line 212, in save self.instance = self.create(validated_data) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\rest_framework\serializers.py", line 962, in create instance = ModelClass._default_manager.create(**validated_data) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\db\models\manager.py", line 85, in manager_method return getattr(self.get_queryset(), name)(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\db\models\query.py", line 671, in create obj.save(force_insert=True, using=self.db) File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\db\models\base.py", line 812, in save self.save_base( File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\db\models\base.py", line 863, in save_base updated = self._save_table( ^^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\db\models\base.py", line 1006, in _save_table results = self._do_insert( ^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\db\models\base.py", line 1047, in _do_insert return manager._insert( ^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\db\models\manager.py", line 85, in manager_method return getattr(self.get_queryset(), name)(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\db\models\query.py", line 1791, in _insert return query.get_compiler(using=using).execute_sql(returning_fields) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\db\models\sql\compiler.py", line 1660, in execute_sql cursor.execute(sql, params) File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\db\backends\utils.py", line 102, in execute return super().execute(sql, params) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\db\backends\utils.py", line 67, in execute return self._execute_with_wrappers( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\db\backends\utils.py", line 80, in _execute_with_wrappers return executor(sql, params, many, context) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ return self.cursor.execute(sql, params) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\vince\Desktop\Code\Rep\ChatProject\ChatProject---API\env\Lib\site-packages\django\db\backends\sqlite3\base.py", line 357, in execute return Database.Cursor.execute(self, query, params) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ django.db.utils.IntegrityError: NOT NULL constraint failed: chat_message.channel_id
解决方案
方法一:拆分序列化器(推荐)
为创建和查询操作分别定义不同的序列化器,在视图中根据请求方法切换:
- 定义基础序列化器用于写入:
class MessageWriteSerializer(serializers.ModelSerializer): class Meta: model = Message fields = ['channel', 'content']
- 修改
MessageSerializer保留嵌套用于读取:
class MessageSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) channel = ChannelSerializer(read_only=True) class Meta: model = Message fields = '__all__'
- 在视图中重写
get_serializer_class方法:
class MessageView(viewsets.ModelViewSet): queryset = Message.objects.all() permission_classes = [IsAuthenticated] def get_serializer_class(self): if self.action in ['create', 'update', 'partial_update']: return MessageWriteSerializer return MessageSerializer def perform_create(self, serializer): serializer.save(user=self.request.user)
此时POST请求只需传channel主键和content:
{ "channel": 1, "content": "hello" }
方法二:使用SerializerMethodField
在MessageSerializer中保留默认的外键字段用于写入,同时添加嵌套字段用于读取:
class MessageSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) channel_details = ChannelSerializer(source='channel', read_only=True) class Meta: model = Message fields = ['id', 'user', 'channel', 'channel_details', 'content', 'created_date']
这种方式下,POST请求传channel主键,GET返回时会同时包含channel(主键)和channel_details(完整对象)。
方法三:自定义create方法
在MessageSerializer中手动处理嵌套数据,但这种方式较繁琐,仅适用于特殊场景:
class MessageSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) channel = ChannelSerializer(read_only=True) class Meta: model = Message fields = '__all__' def create(self, validated_data): # 从request.data中获取channel_id channel_id = self.context['request'].data.get('channel') if not channel_id: raise serializers.ValidationError("channel is required") channel = Channel.objects.get(id=channel_id) return Message.objects.create( user=self.context['request'].user, channel=channel, content=validated_data['content'] )
POST请求传channel主键即可:
{ "channel": 1, "content": "hello" }
内容的提问来源于stack exchange,提问作者VincentAb
相关产品推荐
相关产品推荐

