diff options
Diffstat (limited to 'rest_framework/serializers.py')
| -rw-r--r-- | rest_framework/serializers.py | 611 |
1 files changed, 337 insertions, 274 deletions
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 80ad10f0..a983d3fc 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -696,7 +696,7 @@ class ModelSerializer(Serializer): you need you should either declare the extra/differing fields explicitly on the serializer class, or simply use a `Serializer` class. """ - _field_mapping = ClassLookupDict({ + serializer_field_mapping = { models.AutoField: IntegerField, models.BigIntegerField: IntegerField, models.BooleanField: BooleanField, @@ -719,8 +719,8 @@ class ModelSerializer(Serializer): models.TextField: CharField, models.TimeField: TimeField, models.URLField: URLField, - }) - _related_class = PrimaryKeyRelatedField + } + serializer_related_class = PrimaryKeyRelatedField # Default `create` and `update` behavior... @@ -793,98 +793,13 @@ class ModelSerializer(Serializer): return instance - # Determine the validators to apply... - - def get_validators(self): - """ - Determine the set of validators to use when instantiating serializer. - """ - # If the validators have been declared explicitly then use that. - validators = getattr(getattr(self, 'Meta', None), 'validators', None) - if validators is not None: - return validators - - # Otherwise use the default set of validators. - return ( - self.get_unique_together_validators() + - self.get_unique_for_date_validators() - ) - - def get_unique_together_validators(self): - """ - Determine a default set of validators for any unique_together contraints. - """ - model_class_inheritance_tree = ( - [self.Meta.model] + - list(self.Meta.model._meta.parents.keys()) - ) - - # The field names we're passing though here only include fields - # which may map onto a model field. Any dotted field name lookups - # cannot map to a field, and must be a traversal, so we're not - # including those. - field_names = set([ - field.source for field in self.fields.values() - if (field.source != '*') and ('.' not in field.source) - ]) - - # Note that we make sure to check `unique_together` both on the - # base model class, but also on any parent classes. - validators = [] - for parent_class in model_class_inheritance_tree: - for unique_together in parent_class._meta.unique_together: - if field_names.issuperset(set(unique_together)): - validator = UniqueTogetherValidator( - queryset=parent_class._default_manager, - fields=unique_together - ) - validators.append(validator) - return validators - - def get_unique_for_date_validators(self): - """ - Determine a default set of validators for the following contraints: - - * unique_for_date - * unique_for_month - * unique_for_year - """ - info = model_meta.get_field_info(self.Meta.model) - default_manager = self.Meta.model._default_manager - field_names = [field.source for field in self.fields.values()] - - validators = [] - - for field_name, field in info.fields_and_pk.items(): - if field.unique_for_date and field_name in field_names: - validator = UniqueForDateValidator( - queryset=default_manager, - field=field_name, - date_field=field.unique_for_date - ) - validators.append(validator) - - if field.unique_for_month and field_name in field_names: - validator = UniqueForMonthValidator( - queryset=default_manager, - field=field_name, - date_field=field.unique_for_month - ) - validators.append(validator) - - if field.unique_for_year and field_name in field_names: - validator = UniqueForYearValidator( - queryset=default_manager, - field=field_name, - date_field=field.unique_for_year - ) - validators.append(validator) - - return validators - # 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. + """ declared_fields = copy.deepcopy(self._declared_fields) model = getattr(self.Meta, 'model') depth = getattr(self.Meta, 'depth', 0) @@ -912,7 +827,7 @@ class ModelSerializer(Serializer): field_cls, kwargs = self.build_field(field_name, info, model, depth) # Populate any kwargs defined in `Meta.extra_kwargs` - kwargs = self.build_final_kwargs(kwargs, extra_kwargs, field_name) + kwargs = self.build_field_kwargs(kwargs, extra_kwargs, field_name) # Create the serializer field. ret[field_name] = field_cls(**kwargs) @@ -922,57 +837,188 @@ class ModelSerializer(Serializer): return ret - def build_field(self, field_name, info, model, depth): + # Methods for determining the set of field names to include... + + def get_field_names(self, declared_fields, info): + """ + Returns the list of all field names that should be created when + instantiating this serializer class. This is based on the default + set of fields, but also takes into account the `Meta.fields` or + `Meta.exclude` options if they have been specified. + """ + fields = getattr(self.Meta, 'fields', None) + exclude = getattr(self.Meta, 'exclude', None) + + if fields and not isinstance(fields, (list, tuple)): + raise TypeError( + 'The `fields` option must be a list or tuple. Got %s.' % + type(fields).__name__ + ) + + if exclude and not isinstance(exclude, (list, tuple)): + raise TypeError( + 'The `exclude` option must be a list or tuple. Got %s.' % + type(exclude).__name__ + ) + + assert not (fields and exclude), ( + "Cannot set both 'fields' and 'exclude' options on " + "serializer {serializer_class}.".format( + serializer_class=self.__class__.__name__ + ) + ) + + if fields is not None: + # Ensure that all declared fields have also been included in the + # `Meta.fields` option. + for field_name in declared_fields: + assert field_name in fields, ( + "The field '{field_name}' was declared on serializer " + "{serializer_class}, but has not been included in the " + "'fields' option.".format( + field_name=field_name, + serializer_class=self.__class__.__name__ + ) + ) + return fields + + # Use the default set of field names if `Meta.fields` is not specified. + fields = self.get_default_field_names(declared_fields, info) + + if exclude is not None: + # If `Meta.exclude` is included, then remove those fields. + for field_name in exclude: + assert field_name in fields, ( + "The field '{field_name}' was include on serializer " + "{serializer_class} in the 'exclude' option, but does " + "not match any model field.".format( + field_name=field_name, + serializer_class=self.__class__.__name__ + ) + ) + fields.remove(field_name) + + return fields + + def get_default_field_names(self, declared_fields, model_info): + """ + Return the default list of field names that will be used if the + `Meta.fields` option is not specified. + """ + return ( + [model_info.pk.name] + + list(declared_fields.keys()) + + list(model_info.fields.keys()) + + list(model_info.forward_relations.keys()) + ) + + # Methods for constructing serializer fields... + + def build_field(self, field_name, info, model, nested_depth): + """ + Return a two tuple of (cls, kwargs) to build a serializer field with. + """ if field_name in info.fields_and_pk: - # Create regular model fields. - model_field = info.fields_and_pk[field_name] - field_cls = self._field_mapping[model_field] - kwargs = get_field_kwargs(field_name, model_field) - if 'choices' in kwargs: - # Fields with choices get coerced into `ChoiceField` - # instead of using their regular typed field. - field_cls = ChoiceField - if not issubclass(field_cls, ModelField): - # `model_field` is only valid for the fallback case of - # `ModelField`, which is used when no other typed field - # matched to the model field. - kwargs.pop('model_field', None) - if not issubclass(field_cls, CharField) and not issubclass(field_cls, ChoiceField): - # `allow_blank` is only valid for textual fields. - kwargs.pop('allow_blank', None) + return self.build_standard_field(field_name, info, model) elif field_name in info.relations: - # Create forward and reverse relationships. - relation_info = info.relations[field_name] - if depth: - field_cls = self._get_nested_class(depth, relation_info) - kwargs = get_nested_relation_kwargs(relation_info) + if not nested_depth: + return self.build_relational_field(field_name, info, model) else: - field_cls = self._related_class - kwargs = get_relation_kwargs(field_name, relation_info) - # `view_name` is only valid for hyperlinked relationships. - if not issubclass(field_cls, HyperlinkedRelatedField): - kwargs.pop('view_name', None) + return self.build_nested_field(field_name, info, model, nested_depth) elif hasattr(model, field_name): - # Create a read only field for model methods and properties. - field_cls = ReadOnlyField - kwargs = {} + return self.build_property_field(field_name, info, model) elif field_name == api_settings.URL_FIELD_NAME: - # Create the URL field. - field_cls = HyperlinkedIdentityField - kwargs = get_url_kwargs(model) + return self.build_url_field(field_name, info, model) - else: - raise ImproperlyConfigured( - 'Field name `%s` is not valid for model `%s`.' % - (field_name, model.__class__.__name__) - ) + return self.build_unknown_field(field_name, info, model) + + def build_standard_field(self, field_name, info, model): + """ + Create regular model fields. + """ + field_mapping = ClassLookupDict(self.serializer_field_mapping) + model_field = info.fields_and_pk[field_name] + + field_cls = field_mapping[model_field] + kwargs = get_field_kwargs(field_name, model_field) + + if 'choices' in kwargs: + # Fields with choices get coerced into `ChoiceField` + # instead of using their regular typed field. + field_cls = ChoiceField + if not issubclass(field_cls, ModelField): + # `model_field` is only valid for the fallback case of + # `ModelField`, which is used when no other typed field + # matched to the model field. + kwargs.pop('model_field', None) + if not issubclass(field_cls, CharField) and not issubclass(field_cls, ChoiceField): + # `allow_blank` is only valid for textual fields. + kwargs.pop('allow_blank', None) + + return field_cls, kwargs + + def build_relational_field(self, field_name, info, model): + """ + Create fields for forward and reverse relationships. + """ + relation_info = info.relations[field_name] + + field_cls = self.serializer_related_class + kwargs = get_relation_kwargs(field_name, relation_info) + + # `view_name` is only valid for hyperlinked relationships. + if not issubclass(field_cls, HyperlinkedRelatedField): + kwargs.pop('view_name', None) + + return field_cls, kwargs + + def build_nested_field(self, field_name, info, model, nested_depth): + """ + Create nested fields for forward and reverse relationships. + """ + relation_info = info.relations[field_name] + + class NestedSerializer(ModelSerializer): + class Meta: + model = relation_info.related + depth = nested_depth - 1 + + field_cls = NestedSerializer + kwargs = get_nested_relation_kwargs(relation_info) + + return field_cls, kwargs + + def build_property_field(self, field_name, info, model): + """ + Create a read only field for model methods and properties. + """ + field_cls = ReadOnlyField + kwargs = {} + + return field_cls, kwargs + + def build_url_field(self, field_name, info, model): + """ + Create a field representing the object's own URL. + """ + field_cls = HyperlinkedIdentityField + kwargs = get_url_kwargs(model) return field_cls, kwargs - def build_final_kwargs(self, kwargs, extra_kwargs, field_name): + def build_unknown_field(self, field_name, info, model): + """ + Raise an error on any unknown fields. + """ + raise ImproperlyConfigured( + 'Field name `%s` is not valid for model `%s`.' % + (field_name, model.__class__.__name__) + ) + + def build_field_kwargs(self, kwargs, extra_kwargs, field_name): """ Include an 'extra_kwargs' that have been included for this field, possibly removing any incompatible existing keyword arguments. @@ -994,38 +1040,61 @@ class ModelSerializer(Serializer): return kwargs - def _get_model_fields(self, field_names, declared_fields, extra_kwargs): + # Methods for determining additional keyword arguments to apply... + + def get_extra_kwargs(self): """ - Returns all the model fields that are being mapped to by fields - on the serializer class. - Returned as a dict of 'model field name' -> 'model field'. - Used internally by `get_uniqueness_field_options`. + Return a dictionary mapping field names to a dictionary of + additional keyword arguments. """ - model = getattr(self.Meta, 'model') - model_fields = {} + extra_kwargs = getattr(self.Meta, 'extra_kwargs', {}) - for field_name in field_names: - if field_name in declared_fields: - # If the field is declared on the serializer - field = declared_fields[field_name] - source = field.source or field_name - else: - try: - source = extra_kwargs[field_name]['source'] - except KeyError: - source = field_name + read_only_fields = getattr(self.Meta, 'read_only_fields', None) + if read_only_fields is not None: + for field_name in read_only_fields: + kwargs = extra_kwargs.get(field_name, {}) + kwargs['read_only'] = True + extra_kwargs[field_name] = kwargs - if '.' in source or source == '*': - # Model fields will always have a simple source mapping, - # they can't be nested attribute lookups. - continue + # These are all pending deprecation. + write_only_fields = getattr(self.Meta, 'write_only_fields', None) + if write_only_fields is not None: + warnings.warn( + "The `Meta.write_only_fields` option is pending deprecation. " + "Use `Meta.extra_kwargs={<field_name>: {'write_only': True}}` instead.", + PendingDeprecationWarning, + stacklevel=3 + ) + for field_name in write_only_fields: + kwargs = extra_kwargs.get(field_name, {}) + kwargs['write_only'] = True + extra_kwargs[field_name] = kwargs - try: - model_fields[source] = model._meta.get_field(source) - except FieldDoesNotExist: - pass + view_name = getattr(self.Meta, 'view_name', None) + if view_name is not None: + warnings.warn( + "The `Meta.view_name` option is pending deprecation. " + "Use `Meta.extra_kwargs={'url': {'view_name': ...}}` instead.", + PendingDeprecationWarning, + stacklevel=3 + ) + kwargs = extra_kwargs.get(api_settings.URL_FIELD_NAME, {}) + kwargs['view_name'] = view_name + extra_kwargs[api_settings.URL_FIELD_NAME] = kwargs - return model_fields + lookup_field = getattr(self.Meta, 'lookup_field', None) + if lookup_field is not None: + warnings.warn( + "The `Meta.lookup_field` option is pending deprecation. " + "Use `Meta.extra_kwargs={'url': {'lookup_field': ...}}` instead.", + PendingDeprecationWarning, + stacklevel=3 + ) + kwargs = extra_kwargs.get(api_settings.URL_FIELD_NAME, {}) + kwargs['lookup_field'] = lookup_field + extra_kwargs[api_settings.URL_FIELD_NAME] = kwargs + + return extra_kwargs def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs): """ @@ -1102,140 +1171,127 @@ class ModelSerializer(Serializer): return extra_kwargs, hidden_fields - def get_extra_kwargs(self): + def _get_model_fields(self, field_names, declared_fields, extra_kwargs): """ - Return a dictionary mapping field names to a dictionary of - additional keyword arguments. + Returns all the model fields that are being mapped to by fields + on the serializer class. + Returned as a dict of 'model field name' -> 'model field'. + Used internally by `get_uniqueness_field_options`. """ - extra_kwargs = getattr(self.Meta, 'extra_kwargs', {}) + model = getattr(self.Meta, 'model') + model_fields = {} - read_only_fields = getattr(self.Meta, 'read_only_fields', None) - if read_only_fields is not None: - for field_name in read_only_fields: - kwargs = extra_kwargs.get(field_name, {}) - kwargs['read_only'] = True - extra_kwargs[field_name] = kwargs + for field_name in field_names: + if field_name in declared_fields: + # If the field is declared on the serializer + field = declared_fields[field_name] + source = field.source or field_name + else: + try: + source = extra_kwargs[field_name]['source'] + except KeyError: + source = field_name - # These are all pending deprecation. - write_only_fields = getattr(self.Meta, 'write_only_fields', None) - if write_only_fields is not None: - warnings.warn( - "The `Meta.write_only_fields` option is pending deprecation. " - "Use `Meta.extra_kwargs={<field_name>: {'write_only': True}}` instead.", - PendingDeprecationWarning, - stacklevel=3 - ) - for field_name in write_only_fields: - kwargs = extra_kwargs.get(field_name, {}) - kwargs['write_only'] = True - extra_kwargs[field_name] = kwargs + if '.' in source or source == '*': + # Model fields will always have a simple source mapping, + # they can't be nested attribute lookups. + continue - view_name = getattr(self.Meta, 'view_name', None) - if view_name is not None: - warnings.warn( - "The `Meta.view_name` option is pending deprecation. " - "Use `Meta.extra_kwargs={'url': {'view_name': ...}}` instead.", - PendingDeprecationWarning, - stacklevel=3 - ) - kwargs = extra_kwargs.get(api_settings.URL_FIELD_NAME, {}) - kwargs['view_name'] = view_name - extra_kwargs[api_settings.URL_FIELD_NAME] = kwargs + try: + model_fields[source] = model._meta.get_field(source) + except FieldDoesNotExist: + pass - lookup_field = getattr(self.Meta, 'lookup_field', None) - if lookup_field is not None: - warnings.warn( - "The `Meta.lookup_field` option is pending deprecation. " - "Use `Meta.extra_kwargs={'url': {'lookup_field': ...}}` instead.", - PendingDeprecationWarning, - stacklevel=3 - ) - kwargs = extra_kwargs.get(api_settings.URL_FIELD_NAME, {}) - kwargs['lookup_field'] = lookup_field - extra_kwargs[api_settings.URL_FIELD_NAME] = kwargs + return model_fields - return extra_kwargs + # Determine the validators to apply... - def get_field_names(self, declared_fields, info): + def get_validators(self): """ - Returns the list of all field names that should be created when - instantiating this serializer class. This is based on the default - set of fields, but also takes into account the `Meta.fields` or - `Meta.exclude` options if they have been specified. + Determine the set of validators to use when instantiating serializer. """ - fields = getattr(self.Meta, 'fields', None) - exclude = getattr(self.Meta, 'exclude', None) - - if fields and not isinstance(fields, (list, tuple)): - raise TypeError( - 'The `fields` option must be a list or tuple. Got %s.' % - type(fields).__name__ - ) - - if exclude and not isinstance(exclude, (list, tuple)): - raise TypeError( - 'The `exclude` option must be a list or tuple. Got %s.' % - type(exclude).__name__ - ) + # If the validators have been declared explicitly then use that. + validators = getattr(getattr(self, 'Meta', None), 'validators', None) + if validators is not None: + return validators - assert not (fields and exclude), ( - "Cannot set both 'fields' and 'exclude' options on " - "serializer {serializer_class}.".format( - serializer_class=self.__class__.__name__ - ) + # Otherwise use the default set of validators. + return ( + self.get_unique_together_validators() + + self.get_unique_for_date_validators() ) - if fields is not None: - # Ensure that all declared fields have also been included in the - # `Meta.fields` option. - for field_name in declared_fields: - assert field_name in fields, ( - "The field '{field_name}' was declared on serializer " - "{serializer_class}, but has not been included in the " - "'fields' option.".format( - field_name=field_name, - serializer_class=self.__class__.__name__ - ) - ) - return fields + def get_unique_together_validators(self): + """ + Determine a default set of validators for any unique_together contraints. + """ + model_class_inheritance_tree = ( + [self.Meta.model] + + list(self.Meta.model._meta.parents.keys()) + ) - # Use the default set of field names if `Meta.fields` is not specified. - fields = self.get_default_field_names(declared_fields, info) + # The field names we're passing though here only include fields + # which may map onto a model field. Any dotted field name lookups + # cannot map to a field, and must be a traversal, so we're not + # including those. + field_names = set([ + field.source for field in self.fields.values() + if (field.source != '*') and ('.' not in field.source) + ]) - if exclude is not None: - # If `Meta.exclude` is included, then remove those fields. - for field_name in exclude: - assert field_name in fields, ( - "The field '{field_name}' was include on serializer " - "{serializer_class} in the 'exclude' option, but does " - "not match any model field.".format( - field_name=field_name, - serializer_class=self.__class__.__name__ + # Note that we make sure to check `unique_together` both on the + # base model class, but also on any parent classes. + validators = [] + for parent_class in model_class_inheritance_tree: + for unique_together in parent_class._meta.unique_together: + if field_names.issuperset(set(unique_together)): + validator = UniqueTogetherValidator( + queryset=parent_class._default_manager, + fields=unique_together ) - ) - fields.remove(field_name) - - return fields + validators.append(validator) + return validators - def get_default_field_names(self, declared_fields, model_info): + def get_unique_for_date_validators(self): """ - Return the default list of field names that will be used if the - `Meta.fields` option is not specified. + Determine a default set of validators for the following contraints: + + * unique_for_date + * unique_for_month + * unique_for_year """ - return ( - [model_info.pk.name] + - list(declared_fields.keys()) + - list(model_info.fields.keys()) + - list(model_info.forward_relations.keys()) - ) + info = model_meta.get_field_info(self.Meta.model) + default_manager = self.Meta.model._default_manager + field_names = [field.source for field in self.fields.values()] - def _get_nested_class(self, nested_depth, relation_info): - class NestedSerializer(ModelSerializer): - class Meta: - model = relation_info.related - depth = nested_depth - 1 + validators = [] - return NestedSerializer + for field_name, field in info.fields_and_pk.items(): + if field.unique_for_date and field_name in field_names: + validator = UniqueForDateValidator( + queryset=default_manager, + field=field_name, + date_field=field.unique_for_date + ) + validators.append(validator) + + if field.unique_for_month and field_name in field_names: + validator = UniqueForMonthValidator( + queryset=default_manager, + field=field_name, + date_field=field.unique_for_month + ) + validators.append(validator) + + if field.unique_for_year and field_name in field_names: + validator = UniqueForYearValidator( + queryset=default_manager, + field=field_name, + date_field=field.unique_for_year + ) + validators.append(validator) + + return validators class HyperlinkedModelSerializer(ModelSerializer): @@ -1246,7 +1302,7 @@ class HyperlinkedModelSerializer(ModelSerializer): * A 'url' field is included instead of the 'id' field. * Relationships to other instances are hyperlinks, instead of primary keys. """ - _related_class = HyperlinkedRelatedField + serializer_related_class = HyperlinkedRelatedField def get_default_field_names(self, declared_fields, model_info): """ @@ -1260,10 +1316,17 @@ class HyperlinkedModelSerializer(ModelSerializer): list(model_info.forward_relations.keys()) ) - def _get_nested_class(self, nested_depth, relation_info): + def build_nested_field(self, field_name, info, model, nested_depth): + """ + Create nested fields for forward and reverse relationships. + """ + relation_info = info.relations[field_name] + class NestedSerializer(HyperlinkedModelSerializer): class Meta: model = relation_info.related depth = nested_depth - 1 - return NestedSerializer + field_cls = NestedSerializer + kwargs = get_nested_relation_kwargs(relation_info) + return field_cls, kwargs |
