aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Christie2014-12-19 15:35:52 +0000
committerTom Christie2014-12-19 15:35:52 +0000
commit75e81b82545704bac8afdf3270ba9f6c8da09c27 (patch)
tree1d622b48023dbf574c60b302d5a5f3a062e7a7b4
parentf72928ea982cfe2127288dd6dc52f8006638b0c3 (diff)
downloaddjango-rest-framework-75e81b82545704bac8afdf3270ba9f6c8da09c27.tar.bz2
build_*_field methods
-rw-r--r--rest_framework/serializers.py611
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