diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 389680517..de1b2cbc4 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -77,6 +77,13 @@ LIST_SERIALIZER_KWARGS = ( ALL_FIELDS = '__all__' +READ_ONLY_FIELDS = 'read_only_fields' +WRITE_ONLY_FIELDS = 'write_only_fields' +EXTRA_KWARGS_FIELDS = { + READ_ONLY_FIELDS: 'read_only', + WRITE_ONLY_FIELDS: 'write_only', +} + # BaseSerializer # -------------- @@ -1373,26 +1380,27 @@ class ModelSerializer(Serializer): """ extra_kwargs = copy.deepcopy(getattr(self.Meta, 'extra_kwargs', {})) - read_only_fields = getattr(self.Meta, 'read_only_fields', None) - if read_only_fields is not None: - if not isinstance(read_only_fields, (list, tuple)): - raise TypeError( - 'The `read_only_fields` option must be a list or tuple. ' - 'Got %s.' % type(read_only_fields).__name__ - ) - for field_name in read_only_fields: - kwargs = extra_kwargs.get(field_name, {}) - kwargs['read_only'] = True - extra_kwargs[field_name] = kwargs + for option, limit in EXTRA_KWARGS_FIELDS.items(): + fields = getattr(self.Meta, option, None) - else: - # Guard against the possible misspelling `readonly_fields` (used - # by the Django admin and others). - assert not hasattr(self.Meta, 'readonly_fields'), ( - 'Serializer `%s.%s` has field `readonly_fields`; ' - 'the correct spelling for the option is `read_only_fields`.' % - (self.__class__.__module__, self.__class__.__name__) - ) + if fields is not None: + if not isinstance(fields, (list, tuple)): + raise TypeError( + f'The `{option}` option must be a list or tuple. ' + f'Got {type(fields).__name__}.' + ) + for field_name in fields: + kwargs = extra_kwargs.get(field_name, {}) + kwargs[limit] = True + extra_kwargs[field_name] = kwargs + else: + # Guard against the possible misspelling `readonly_fields` (used + # by the Django admin and others). + assert not hasattr(self.Meta, 'readonly_fields'), ( + 'Serializer `%s.%s` has field `readonly_fields`; ' + 'the correct spelling for the option is `read_only_fields`.' % + (self.__class__.__module__, self.__class__.__name__) + ) return extra_kwargs diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 7da1b41ae..ca03145cf 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -47,6 +47,11 @@ class OneFieldModel(models.Model): char_field = models.CharField(max_length=100) +class TwoFieldModel(models.Model): + char_field = models.CharField(max_length=100) + datetime_field = models.DateTimeField() + + class RegularFieldsModel(models.Model): """ A model class for testing regular flat fields. @@ -1078,25 +1083,29 @@ class TestMetaInheritance(TestCase): non_model_field = serializers.CharField() class Meta: - model = OneFieldModel + model = TwoFieldModel read_only_fields = ('char_field', 'non_model_field') - fields = read_only_fields + write_only_fields = ('datetime_field',) + fields = read_only_fields + write_only_fields extra_kwargs = {} class ChildSerializer(TestSerializer): class Meta(TestSerializer.Meta): read_only_fields = () + write_only_fields = () test_expected = dedent(""" TestSerializer(): char_field = CharField(read_only=True) non_model_field = CharField() + datetime_field = DateTimeField(write_only=True) """) child_expected = dedent(""" ChildSerializer(): char_field = CharField(max_length=100) non_model_field = CharField() + datetime_field = DateTimeField() """) self.assertEqual(repr(ChildSerializer()), child_expected) self.assertEqual(repr(TestSerializer()), test_expected) @@ -1124,6 +1133,27 @@ class TestModelFieldValues(TestCase): self.assertEqual(serializer.data, {'target': 1}) +class TestExtraKwargs(TestCase): + def test_write_only_fields(self): + class TestSerializer(serializers.ModelSerializer): + + class Meta: + model = OneFieldModel + write_only_fields = ('char_field',) + fields = write_only_fields + + test_expected = dedent(""" + TestSerializer(): + char_field = CharField(max_length=100, write_only=True) + """) + + self.assertEqual(repr(TestSerializer()), test_expected) + self.assertEqual( + TestSerializer().get_extra_kwargs().get('char_field', {}).get('write_only'), + True, + ) + + class TestUniquenessOverride(TestCase): def test_required_not_overwritten(self): class TestModel(models.Model):