Django DRF:read_only_fields 无法正常工作

San*_*idi 5 django django-serializer django-rest-framework

我有以下型号

class Breed(models.Model)::
    name = models.CharField(max_length=200)

class Pet(models.Model):
    owner = models.ForeignKey(
        "User",
        on_delete=models.CASCADE,
    )
    name = models.CharField(max_length=200)
    breed = models.ForeignKey(
        "Breed",
        on_delete=models.CASCADE,
    )
Run Code Online (Sandbox Code Playgroud)

我正在尝试添加一些文件representation。我不希望他们被包括create在内update

class PetSerializer(serializers.ModelSerializer):
    owner_email = serializers.CharField(source='owner.email')
    breed_name = serializers.CharField(source='breed.str')
    class Meta:
        model = Pet
        fields = "__all__"
        read_only_fields = ["breed_name","owner_email"]
Run Code Online (Sandbox Code Playgroud)

这是行不通的。我在 HTMLform(DRF api 页面)中看到了owner_emailbreed_name

然而

class PetSerializer(serializers.ModelSerializer):
    owner_email = serializers.CharField(source='owner.email',read_only=True)
    breed_name = serializers.CharField(source='breed.str',read_only=True)
    class Meta:
        model = Pet
        fields = "__all__"
Run Code Online (Sandbox Code Playgroud)

这是有效的。我在 HTML 表单中没有看到它们

我还观察到,如果我直接在 read_only_fields 中使用模型字段,那么它就可以工作。

class PetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Pet
        fields = "__all__"
        read_only_fields = ["name"]
Run Code Online (Sandbox Code Playgroud)

这将使所有内容name在更新或创建时不显示

为什么read_only_fields不能正常工作

Nie*_*ano 2

这很有趣。我查看了代码并找到了根本原因,特别是ModelSerializer实现中的这一

for field_name in field_names:
    # If the field is explicitly declared on the class then use that.
    if field_name in declared_fields:
        fields[field_name] = declared_fields[field_name]
        continue

    ....
Run Code Online (Sandbox Code Playgroud)

这是我的调查脚本

from django.db import models
from rest_framework import serializers


class MyModel(models.Model):
    xero_contact_id = models.UUIDField(unique=True)
    name = models.CharField(max_length=255, default="Some name")
    class Meta:
        db_table = "my_model"


class MySerializer(serializers.ModelSerializer):
    owner_email = serializers.CharField()
    breed_name = serializers.CharField(max_length=255)
    class Meta:
        model = MyModel
        fields = '__all__'
        read_only_fields = ["breed_name", "owner_email", "xero_contact_id"]


serializer = MySerializer()
print(repr(serializer))
Run Code Online (Sandbox Code Playgroud)

我添加了一些印刷品,这是我所看到的:

>>> print(repr(serializer))
field_names ['id', 'owner_email', 'breed_name', 'xero_contact_id', 'name']
declared_fields OrderedDict([('owner_email', CharField()), ('breed_name', CharField(max_length=255))])
extra_kwargs {'breed_name': {'read_only': True}, 'owner_email': {'read_only': True}, 'xero_contact_id': {'read_only': True}}
MySerializer():
    id = IntegerField(label='ID', read_only=True)
    owner_email = CharField()
    breed_name = CharField(max_length=255)
    xero_contact_id = UUIDField(read_only=True)
    name = CharField(max_length=255, required=False)
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,read_only参数位于extra_kwargs. 问题是,对于仅在自身中声明ModelSerializer(从 可见declared_fields)而不是在模型类中声明的所有字段,它们不会从 中读取extra_kwargs,它们只是读取字段本身中设置的内容(如代码片段中可见)上面fields[field_name] = declared_fields[field_name]然后执行一个continue. 因此,选项 forread_only被忽略。

我通过修改 的实现来修复它ModelSerializer,同时考虑extra_kwargs非模型字段的偶数

for field_name in field_names:
    # If the field is explicitly declared on the class then use that.
    if field_name in declared_fields:
        field_class = type(declared_fields[field_name])
        declared_field_args = declared_fields[field_name].__dict__['_args']
        declared_field_kwargs = declared_fields[field_name].__dict__['_kwargs']
        extra_field_kwargs = extra_kwargs.get(field_name, {})

        # Old implementation doesn't take into account the extra_kwargs
        # fields[field_name] = declared_fields[field_name]

        # New implementation takes into account the extra_kwargs
        fields[field_name] = field_class(*declared_field_args, **declared_field_kwargs, **extra_field_kwargs)
        continue

    ....
Run Code Online (Sandbox Code Playgroud)

现在,read_only已正确设置为目标字段,包括非模型字段:

>>> print(repr(serializer))
field_names ['id', 'owner_email', 'breed_name', 'xero_contact_id', 'name']
declared_fields OrderedDict([('owner_email', CharField()), ('breed_name', CharField(max_length=255))])
extra_kwargs {'breed_name': {'read_only': True}, 'owner_email': {'read_only': True}, 'xero_contact_id': {'read_only': True}}
MySerializer():
    id = IntegerField(label='ID', read_only=True)
    owner_email = CharField(read_only=True)
    breed_name = CharField(max_length=255, read_only=True)
    xero_contact_id = UUIDField(read_only=True)
    name = CharField(max_length=255, required=False)
Run Code Online (Sandbox Code Playgroud)

DRF 文档中似乎没有这个内容。听起来像是我们可以向 DRF 请求的功能:) 因此,同时的解决方案就像 @JPG 指出的那样,read_only=True在额外的非模型字段中显式使用。