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

Django REST Framework中SerializerMethodField POST请求为何抛500错误?

问题描述

我在模型中希望将testfield字段以列表形式返回,为此创建了SerializerMethodField:

class testserializer(serializers.ModelSerializer):
    testfield_as_list =  serializers.SerializerMethodField()

    def get_testfield_as_list(self,obj):
        return [obj.testfield]

该字段在GET请求时正常工作,但执行POST请求时,数据虽能正确存入数据库(管理后台可查),接口却返回500错误,抛出异常:

in get_testfield_as_list return [obj.tesfield]
'collections.OrderedDict' object has no attribute 'testfield'

我原本以为SerializerMethodField是只读字段,POST时不应生效,为何会报错?

补充:我使用了upsert功能实现仅更新变更字段,相关代码如下:

class TestViewSet(viewsets.ModelViewSet):
    queryset = Test.objects.all()
    serializer_class = testserializer
    permission_classes = [IsAdminUserOrReadOnly]

    def create(self, request, *args, **kwargs):
        serializer = testserializer(data=request.data,many=True)
        serializer.is_valid(raise_exception=True)

        test_object_list = [Test(**x) for x in serializer.validated_data]
 
        Test.objects.bulk_create(
            test_object_list, 
            update_conflicts=True,
            unique_fields=['unique_id'],
            update_fields=['category','description','testfield'])

        headers = self.get_success_headers(serializer.data)
        return Response(
            serializer.data,
            status=status.HTTP_201_CREATED,
            headers=headers
        )


class Test(models.Model):
    def __str__(self):
        return self.pk
    
    unique_id = models.CharField(max_length=100)
    category = models.CharField(max_length=400)
    description = models.CharField(max_length=400)
    testfield = models.CharField(max_length=400)

完整报错堆栈:

Internal Server Error: /api/v1/boards/ Traceback (most recent call last):
File "folder\django\core\handlers\exception.py", line 55, in inner
response = get_response(request)
File "folder\django\core\handlers\base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "folder\django\views\decorators\csrf.py", line 56, in wrapper_view
return view_func(*args, **kwargs)
File "folder\rest_framework\viewsets.py", line 125, in view
return self.dispatch(request, *args, **kwargs)
File "folder\rest_framework\views.py", line 509, in dispatch
response = self.handle_exception(exc)
File "folder\rest_framework\views.py", line 469, in handle_exception
self.raise_uncaught_exception(exc)
File "folder\rest_framework\views.py", line 480, in raise_uncaught_exception
raise exc
File "folder\rest_framework\views.py", line 506, in dispatch
response = handler(request, *args, **kwargs)
File "....\api\views.py", line 33, in create
headers = self.get_success_headers(serializer.data)
File "folder\rest_framework\serializers.py", line 768, in data
ret = super().data
File "folder\rest_framework\serializers.py", line 255, in data
self._data = self.to_representation(self.validated_data)
File "folder\rest_framework\serializers.py", line 686, in to_representation
return [
File "folder\rest_framework\serializers.py", line 687, in
self.child.to_representation(item) for item in iterable
File "folder\rest_framework\serializers.py", line 522, in to_representation
ret[field.field_name] = field.to_representation(attribute)
File "folder\rest_framework\fields.py", line 1838, in to_representation
return method(value)
File "....\api\serializers.py", line 12, in get_testfield_as_list
return [obj.testfield]
AttributeError: 'collections.OrderedDict' object has no attribute 'testfield'

原因分析

SerializerMethodField确实是只读字段,不会参与反序列化(即不会处理POST请求的输入数据),但问题出在返回响应时调用了serializer.data——此时DRF会执行序列化操作,把validated_data(OrderedDict类型)传入to_representation方法,而SerializerMethodField的get_testfield_as_list方法会尝试访问obj.testfield,但obj此时是validated_data里的OrderedDict,不是数据库返回的Test模型实例,自然没有testfield属性。

简单说:POST时你用序列化器处理了输入数据,但返回响应时直接用这个输入序列化器的data属性,导致序列化过程中触发了只读字段的处理逻辑,而此时传入的对象不是模型实例而是字典。

解决方案

有三种可行的解决办法:

方法一:重新查询数据库获取实例,序列化真实模型对象

在bulk_create之后,根据unique_id重新查询数据库中的Test实例,再用序列化器处理这些实例:

def create(self, request, *args, **kwargs):
    serializer = testserializer(data=request.data,many=True)
    serializer.is_valid(raise_exception=True)

    test_object_list = [Test(**x) for x in serializer.validated_data]
    unique_ids = [obj.unique_id for obj in test_object_list]
 
    Test.objects.bulk_create(
        test_object_list, 
        update_conflicts=True,
        unique_fields=['unique_id'],
        update_fields=['category','description','testfield'])

    # 重新查询数据库中的实例
    created_or_updated_instances = Test.objects.filter(unique_id__in=unique_ids)
    response_serializer = testserializer(created_or_updated_instances, many=True)

    headers = self.get_success_headers(response_serializer.data)
    return Response(
        response_serializer.data,
        status=status.HTTP_201_CREATED,
        headers=headers
    )

方法二:修改SerializerMethodField的get方法,兼容字典和模型实例

在get_testfield_as_list方法中判断传入的obj类型,如果是字典就用键取值,如果是模型实例就用属性访问:

def get_testfield_as_list(self, obj):
    # 兼容模型实例和字典(validated_data)
    testfield_value = obj.testfield if hasattr(obj, 'testfield') else obj.get('testfield')
    return [testfield_value]

方法三:拆分输入输出序列化器

创建两个序列化器,输入序列化器只处理需要的字段,输出序列化器包含testfield_as_list字段:

# 输入序列化器,仅处理需要的字段
class TestInputSerializer(serializers.ModelSerializer):
    class Meta:
        model = Test
        fields = ['unique_id', 'category', 'description', 'testfield']

# 输出序列化器,包含只读的列表字段
class TestOutputSerializer(serializers.ModelSerializer):
    testfield_as_list = serializers.SerializerMethodField()

    def get_testfield_as_list(self, obj):
        return [obj.testfield]

    class Meta:
        model = Test
        fields = ['unique_id', 'category', 'description', 'testfield', 'testfield_as_list']

然后在ViewSet的create方法中使用输入序列化器处理请求数据,输出时用输出序列化器处理模型实例:

def create(self, request, *args, **kwargs):
    serializer = TestInputSerializer(data=request.data,many=True)
    serializer.is_valid(raise_exception=True)

    test_object_list = [Test(**x) for x in serializer.validated_data]
    unique_ids = [obj.unique_id for obj in test_object_list]
 
    Test.objects.bulk_create(
        test_object_list, 
        update_conflicts=True,
        unique_fields=['unique_id'],
        update_fields=['category','description','testfield'])

    created_or_updated_instances = Test.objects.filter(unique_id__in=unique_ids)
    response_serializer = TestOutputSerializer(created_or_updated_instances, many=True)

    headers = self.get_success_headers(response_serializer.data)
    return Response(
        response_serializer.data,
        status=status.HTTP_201_CREATED,
        headers=headers
    )

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 14:20:39