From 5e3169b1c2718c2a76a6e6a6dc5b67b20f651ba9 Mon Sep 17 00:00:00 2001 From: Oliver Sauder Date: Wed, 6 Oct 2021 22:54:22 +0400 Subject: [PATCH] Verified in all fields whether reserved field names are used In meta class only specifically declared fields in serializer are accessible. In case of `ModelSerializer` fields may be just on the model or if a user has overwritten what fields are being returned during runtime DJA won't notice. So there does not seem to be another way then simply hooking into the `get_fields` method to check for reserved field names. --- rest_framework_json_api/serializers.py | 43 ++++++++++++++------------ tests/test_serializers.py | 2 ++ 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/rest_framework_json_api/serializers.py b/rest_framework_json_api/serializers.py index ee6d15b6..00cd3b38 100644 --- a/rest_framework_json_api/serializers.py +++ b/rest_framework_json_api/serializers.py @@ -153,6 +153,27 @@ def validate_path(serializer_class, field_path, path): super().__init__(*args, **kwargs) +class ReservedFieldNamesMixin: + """Ensures that reserved field names are not used and an error raised instead.""" + + _reserved_field_names = {"meta", "results"} + + def get_fields(self): + fields = super().get_fields() + + found_reserved_field_names = self._reserved_field_names.intersection( + fields.keys() + ) + if found_reserved_field_names: + raise AttributeError( + f"Serializer class {self.__class__.__module__}.{self.__class__.__qualname__} " + f"uses following reserved field name(s) which is not allowed: " + f"{', '.join(sorted(found_reserved_field_names))}" + ) + + return fields + + class LazySerializersDict(Mapping): """ A dictionary of serializers which lazily import dotted class path and self. @@ -184,25 +205,6 @@ def __repr__(self): class SerializerMetaclass(SerializerMetaclass): - - _reserved_field_names = {"meta", "results"} - - @classmethod - def _get_declared_fields(cls, bases, attrs): - fields = super()._get_declared_fields(bases, attrs) - - found_reserved_field_names = cls._reserved_field_names.intersection( - fields.keys() - ) - if found_reserved_field_names: - raise AttributeError( - f"Serializer class {attrs['__module__']}.{attrs['__qualname__']} uses " - f"following reserved field name(s) which is not allowed: " - f"{', '.join(sorted(found_reserved_field_names))}" - ) - - return fields - def __new__(cls, name, bases, attrs): serializer = super().__new__(cls, name, bases, attrs) @@ -228,6 +230,7 @@ def __new__(cls, name, bases, attrs): class Serializer( IncludedResourcesValidationMixin, SparseFieldsetsMixin, + ReservedFieldNamesMixin, Serializer, metaclass=SerializerMetaclass, ): @@ -251,6 +254,7 @@ class Serializer( class HyperlinkedModelSerializer( IncludedResourcesValidationMixin, SparseFieldsetsMixin, + ReservedFieldNamesMixin, HyperlinkedModelSerializer, metaclass=SerializerMetaclass, ): @@ -271,6 +275,7 @@ class HyperlinkedModelSerializer( class ModelSerializer( IncludedResourcesValidationMixin, SparseFieldsetsMixin, + ReservedFieldNamesMixin, ModelSerializer, metaclass=SerializerMetaclass, ): diff --git a/tests/test_serializers.py b/tests/test_serializers.py index 505111ca..2c433c2a 100644 --- a/tests/test_serializers.py +++ b/tests/test_serializers.py @@ -43,6 +43,8 @@ class ReservedFieldNamesSerializer(serializers.Serializer): meta = serializers.CharField() results = serializers.CharField() + ReservedFieldNamesSerializer().fields + assert str(e.value) == ( "Serializer class tests.test_serializers.test_reserved_field_names.." "ReservedFieldNamesSerializer uses following reserved field name(s) which is "