diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 9cd5c313f..d32303151 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -278,6 +278,7 @@ class Field(object): self.help_text = help_text self.style = {} if style is None else style self.allow_null = allow_null + self.is_bound = False if self.default_empty_html is not empty: if default is not empty: @@ -303,6 +304,9 @@ class Field(object): Called when a field is added to the parent serializer instance. """ + if self.is_bound: + return + # In order to enforce a consistent style, we error if a redundant # 'source' argument has been used. For example: # my_field = serializer.CharField(source='my_field') @@ -331,6 +335,8 @@ class Field(object): else: self.source_attrs = self.source.split('.') + self.is_bound = True + # .validators is a lazily loaded property, that gets its default # value from `get_validators`. @property diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index c7d4405c5..65072c466 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -493,6 +493,11 @@ class Serializer(BaseSerializer): return ReturnDict(ret, serializer=self) +class ImmutableSerializer(Serializer): + def get_fields(self): + return self._declared_fields + + # There's some replication of `ListField` here, # but that's probably better than obfuscating the call hierarchy. @@ -787,6 +792,12 @@ class ModelSerializer(Serializer): # views, to correctly handle the 'Location' response header for # "HTTP 201 Created" responses. url_field_name = api_settings.URL_FIELD_NAME + + @cached_property + def _depth(self): + return getattr(self.Meta, 'depth', 0) + + # Default `create` and `update` behavior... # Default `create` and `update` behavior... def create(self, validated_data): @@ -864,11 +875,7 @@ class ModelSerializer(Serializer): # Determine the fields to apply... - def get_fields(self): - """ - Return the dict of field names -> field instances that should be - used for `self.fields` when instantiating the serializer. - """ + def _validate_serializer_meta(self): assert hasattr(self, 'Meta'), ( 'Class {serializer_class} missing "Meta" attribute'.format( serializer_class=self.__class__.__name__ @@ -884,14 +891,14 @@ class ModelSerializer(Serializer): 'Cannot use ModelSerializer with Abstract Models.' ) - declared_fields = copy.deepcopy(self._declared_fields) - model = getattr(self.Meta, 'model') - depth = getattr(self.Meta, 'depth', 0) - + depth = self._depth if depth is not None: assert depth >= 0, "'depth' may not be negative." assert depth <= 10, "'depth' may not be greater than 10." + def _get_serializer_fields_from_declared_fields(self, declared_fields, depth): + model = getattr(self.Meta, 'model') + # Retrieve metadata about fields & relationships on the model class. info = model_meta.get_field_info(model) field_names = self.get_field_names(declared_fields, info) @@ -928,6 +935,16 @@ class ModelSerializer(Serializer): # Add in any hidden fields. fields.update(hidden_fields) + return fields + + def get_fields(self): + """ + Return the dict of field names -> field instances that should be + used for `self.fields` when instantiating the serializer. + """ + self._validate_serializer_meta() + declared_fields = copy.deepcopy(self._declared_fields) + fields = self._get_serializer_fields_from_declared_fields(declared_fields, self._depth) return fields @@ -1382,6 +1399,14 @@ class ModelSerializer(Serializer): return validators +class ImmutableModelSerializer(ModelSerializer): + def get_fields(self): + self._validate_serializer_meta() + fields = self._get_serializer_fields_from_declared_fields(self._declared_fields, self._depth) + + return fields + + if hasattr(models, 'UUIDField'): ModelSerializer.serializer_field_mapping[models.UUIDField] = UUIDField