Django DRF: read_only_fields не работает должным образом

У меня есть следующие модели

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,
    )

Я пытаюсь добавить несколько файлов для 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"]

Это не работает. Я вижу owner_email и breed_name в HTML-форме (страница DRF api)

Где как

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__"

Это работает. Я не вижу их в HTML-форме

Также я заметил, что если я использую поле модели непосредственно в read_only_fields, то оно работает.

class PetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Pet
        fields = "__all__"
        read_only_fields = ["name"]

Это сделает так, что все name не будут отображаться при обновлении или создании

Почему read_only_fields не работает должным образом

Опция мета read_only_fields будет работать для полей, которые явно не определены в сериализаторе .

Итак, в вашем случае вам нужно добавить read_only=True к этим явно определенным полям, как

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__"

Это очень интересно. Я заглянул в код и нашел первопричину, а именно это lines в реализации для 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

    ....

Вот мой сценарий для расследования

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))

Я добавил несколько отпечатков и вот что я увидел:

>>> 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)

Как видите, аргумент read_only находится в extra_kwargs. Проблема в том, что для всех полей, которые объявлены только в самом ModelSerializer (видимом из declared_fields), а не в классе модели, они не читаются из extra_kwargs, они просто читают то, что было установлено в самом поле, как видно из приведенного выше фрагмента кода fields[field_name] = declared_fields[field_name], а затем выполняют continue. Таким образом, опция для read_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

    ....

Теперь, 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)

Похоже, что этого нет в документации DRF. Звучит как возможность, которую мы можем запросить в DRF :) Так что решение на данный момент таково, как указал @JPG, использовать read_only=True явно в дополнительных немодельных полях.

Вернуться на верх