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

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

解决方案

方法一:拆分序列化器(推荐)

为创建和查询操作分别定义不同的序列化器,在视图中根据请求方法切换:

  1. 定义基础序列化器用于写入:
class MessageWriteSerializer(serializers.ModelSerializer):
    class Meta:
        model = Message
        fields = ['channel', 'content']
  1. 修改MessageSerializer保留嵌套用于读取:
class MessageSerializer(serializers.ModelSerializer):
    user = UserSerializer(read_only=True)
    channel = ChannelSerializer(read_only=True)
    
    class Meta:
        model = Message
        fields = '__all__'
  1. 在视图中重写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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 22:52:02