aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/serializers.py
diff options
context:
space:
mode:
Diffstat (limited to 'rest_framework/serializers.py')
-rw-r--r--rest_framework/serializers.py464
1 files changed, 104 insertions, 360 deletions
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
index 1fea1380..99dcc349 100644
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -10,17 +10,19 @@ python primitives.
2. The process of marshalling between python primitives and request and
response content is handled by parsers and renderers.
"""
-from django.core import validators
from django.core.exceptions import ValidationError
from django.db import models
from django.utils import six
from django.utils.datastructures import SortedDict
-from django.utils.text import capfirst
from collections import namedtuple
-from rest_framework.compat import clean_manytomany_helptext
from rest_framework.fields import empty, set_value, Field, SkipField
from rest_framework.settings import api_settings
from rest_framework.utils import html, model_meta, representation
+from rest_framework.utils.field_mapping import (
+ get_url_kwargs, get_field_kwargs,
+ get_relation_kwargs, get_nested_relation_kwargs,
+ lookup_class
+)
import copy
# Note: We do the following so that users of the framework can use this style:
@@ -126,7 +128,7 @@ class SerializerMetaclass(type):
"""
@classmethod
- def _get_fields(cls, bases, attrs):
+ def _get_declared_fields(cls, bases, attrs):
fields = [(field_name, attrs.pop(field_name))
for field_name, obj in list(attrs.items())
if isinstance(obj, Field)]
@@ -136,25 +138,18 @@ class SerializerMetaclass(type):
# fields. Note that we loop over the bases in *reverse*. This is necessary
# in order to maintain the correct order of fields.
for base in bases[::-1]:
- if hasattr(base, 'base_fields'):
- fields = list(base.base_fields.items()) + fields
+ if hasattr(base, '_declared_fields'):
+ fields = list(base._declared_fields.items()) + fields
return SortedDict(fields)
def __new__(cls, name, bases, attrs):
- attrs['base_fields'] = cls._get_fields(bases, attrs)
+ attrs['_declared_fields'] = cls._get_declared_fields(bases, attrs)
return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs)
@six.add_metaclass(SerializerMetaclass)
class Serializer(BaseSerializer):
-
- def __new__(cls, *args, **kwargs):
- if kwargs.pop('many', False):
- kwargs['child'] = cls()
- return ListSerializer(*args, **kwargs)
- return super(Serializer, cls).__new__(cls, *args, **kwargs)
-
def __init__(self, *args, **kwargs):
self.context = kwargs.pop('context', {})
kwargs.pop('partial', None)
@@ -165,14 +160,22 @@ class Serializer(BaseSerializer):
# Every new serializer is created with a clone of the field instances.
# This allows users to dynamically modify the fields on a serializer
# instance without affecting every other serializer class.
- self.fields = self.get_fields()
+ self.fields = self._get_base_fields()
# Setup all the child fields, to provide them with the current context.
for field_name, field in self.fields.items():
field.bind(field_name, self, self)
- def get_fields(self):
- return copy.deepcopy(self.base_fields)
+ def __new__(cls, *args, **kwargs):
+ # We override this method in order to automagically create
+ # `ListSerializer` classes instead when `many=True` is set.
+ if kwargs.pop('many', False):
+ kwargs['child'] = cls()
+ return ListSerializer(*args, **kwargs)
+ return super(Serializer, cls).__new__(cls, *args, **kwargs)
+
+ def _get_base_fields(self):
+ return copy.deepcopy(self._declared_fields)
def bind(self, field_name, parent, root):
# If the serializer is used as a field then when it becomes bound
@@ -312,39 +315,8 @@ class ListSerializer(BaseSerializer):
return representation.list_repr(self, indent=1)
-class ModelSerializerOptions(object):
- """
- Meta class options for ModelSerializer
- """
- def __init__(self, meta):
- self.model = getattr(meta, 'model')
- self.fields = getattr(meta, 'fields', ())
- self.depth = getattr(meta, 'depth', 0)
-
-
-def lookup_class(mapping, instance):
- """
- Takes a dictionary with classes as keys, and an object.
- Traverses the object's inheritance hierarchy in method
- resolution order, and returns the first matching value
- from the dictionary or raises a KeyError if nothing matches.
- """
- for cls in inspect.getmro(instance.__class__):
- if cls in mapping:
- return mapping[cls]
- raise KeyError('Class %s not found in lookup.', cls.__name__)
-
-
-def needs_label(model_field, field_name):
- """
- Returns `True` if the label based on the model's verbose name
- is not equal to the default label it would have based on it's field name.
- """
- return capfirst(model_field.verbose_name) != field_name_to_label(field_name)
-
-
class ModelSerializer(Serializer):
- field_mapping = {
+ _field_mapping = {
models.AutoField: IntegerField,
models.BigIntegerField: IntegerField,
models.BooleanField: BooleanField,
@@ -368,16 +340,10 @@ class ModelSerializer(Serializer):
models.TimeField: TimeField,
models.URLField: URLField,
}
- nested_class = None # We fill this in at the end of this module.
-
- _options_class = ModelSerializerOptions
-
- def __init__(self, *args, **kwargs):
- self.opts = self._options_class(self.Meta)
- super(ModelSerializer, self).__init__(*args, **kwargs)
+ _related_class = PrimaryKeyRelatedField
def create(self, attrs):
- ModelClass = self.opts.model
+ ModelClass = self.Meta.model
return ModelClass.objects.create(**attrs)
def update(self, obj, attrs):
@@ -385,319 +351,97 @@ class ModelSerializer(Serializer):
setattr(obj, attr, value)
obj.save()
- def get_fields(self):
- # Get the explicitly declared fields.
- fields = copy.deepcopy(self.base_fields)
+ def _get_base_fields(self):
+ declared_fields = copy.deepcopy(self._declared_fields)
- # Add in the default fields.
- for key, val in self.get_default_fields().items():
- if key not in fields:
- fields[key] = val
-
- # If `fields` is set on the `Meta` class,
- # then use only those fields, and in that order.
- if self.opts.fields:
- fields = SortedDict([
- (key, fields[key]) for key in self.opts.fields
- ])
-
- return fields
-
- def get_default_fields(self):
- """
- Return all the fields that should be serialized for the model.
- """
- info = model_meta.get_field_info(self.opts.model)
ret = SortedDict()
+ model = getattr(self.Meta, 'model')
+ fields = getattr(self.Meta, 'fields', None)
+ depth = getattr(self.Meta, 'depth', 0)
+
+ # Retrieve metadata about fields & relationships on the model class.
+ info = model_meta.get_field_info(model)
+
+ # Use the default set of fields if none is supplied explicitly.
+ if fields is None:
+ fields = self._get_default_field_names(declared_fields, info)
+
+ for field_name in fields:
+ if field_name in declared_fields:
+ # Field is explicitly declared on the class, use that.
+ ret[field_name] = declared_fields[field_name]
+ continue
+
+ elif field_name == api_settings.URL_FIELD_NAME:
+ # Create the URL field.
+ field_cls = HyperlinkedIdentityField
+ kwargs = get_url_kwargs(model)
+
+ elif field_name in info.fields_and_pk:
+ # Create regular model fields.
+ model_field = info.fields_and_pk[field_name]
+ field_cls = lookup_class(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)
+
+ 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)
+ 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)
- # URL field
- serializer_url_field = self.get_url_field()
- if serializer_url_field:
- ret[api_settings.URL_FIELD_NAME] = serializer_url_field
-
- # Primary key field
- field_name = info.pk.name
- serializer_pk_field = self.get_pk_field(field_name, info.pk)
- if serializer_pk_field:
- ret[field_name] = serializer_pk_field
-
- # Regular fields
- for field_name, field in info.fields.items():
- ret[field_name] = self.get_field(field_name, field)
-
- # Forward relations
- for field_name, relation_info in info.forward_relations.items():
- if self.opts.depth:
- ret[field_name] = self.get_nested_field(field_name, *relation_info)
else:
- ret[field_name] = self.get_related_field(field_name, *relation_info)
+ assert False, 'Field name `%s` is not valid.' % field_name
- # Reverse relations
- for accessor_name, relation_info in info.reverse_relations.items():
- if accessor_name in self.opts.fields:
- if self.opts.depth:
- ret[accessor_name] = self.get_nested_field(accessor_name, *relation_info)
- else:
- ret[accessor_name] = self.get_related_field(accessor_name, *relation_info)
+ ret[field_name] = field_cls(**kwargs)
return ret
- def get_url_field(self):
- return None
-
- def get_pk_field(self, field_name, model_field):
- """
- Returns a default instance of the pk field.
- """
- return self.get_field(field_name, model_field)
-
- def get_nested_field(self, field_name, model_field, related_model, to_many, has_through_model):
- """
- Creates a default instance of a nested relational field.
+ def _get_default_field_names(self, declared_fields, model_info):
+ return (
+ [model_info.pk.name] +
+ list(declared_fields.keys()) +
+ list(model_info.fields.keys()) +
+ list(model_info.forward_relations.keys())
+ )
- Note that model_field will be `None` for reverse relationships.
- """
- class NestedModelSerializer(self.nested_class):
+ def _get_nested_class(self, nested_depth, relation_info):
+ class NestedSerializer(ModelSerializer):
class Meta:
- model = related_model
- depth = self.opts.depth - 1
-
- kwargs = {'read_only': True}
- if to_many:
- kwargs['many'] = True
- return NestedModelSerializer(**kwargs)
-
- def get_related_field(self, field_name, model_field, related_model, to_many, has_through_model):
- """
- Creates a default instance of a flat relational field.
-
- Note that model_field will be `None` for reverse relationships.
- """
- kwargs = {
- 'queryset': related_model._default_manager,
- }
-
- if to_many:
- kwargs['many'] = True
-
- if has_through_model:
- kwargs['read_only'] = True
- kwargs.pop('queryset', None)
-
- if model_field:
- if model_field.null or model_field.blank:
- kwargs['required'] = False
- if model_field.verbose_name and needs_label(model_field, field_name):
- kwargs['label'] = capfirst(model_field.verbose_name)
- if not model_field.editable:
- kwargs['read_only'] = True
- kwargs.pop('queryset', None)
- help_text = clean_manytomany_helptext(model_field.help_text)
- if help_text:
- kwargs['help_text'] = help_text
-
- return PrimaryKeyRelatedField(**kwargs)
-
- def get_field(self, field_name, model_field):
- """
- Creates a default instance of a basic non-relational field.
- """
- serializer_cls = lookup_class(self.field_mapping, model_field)
- kwargs = {}
- validator_kwarg = model_field.validators
-
- if model_field.null or model_field.blank:
- kwargs['required'] = False
-
- if model_field.verbose_name and needs_label(model_field, field_name):
- kwargs['label'] = capfirst(model_field.verbose_name)
-
- if model_field.help_text:
- kwargs['help_text'] = model_field.help_text
-
- if isinstance(model_field, models.AutoField) or not model_field.editable:
- kwargs['read_only'] = True
- # Read only implies that the field is not required.
- # We have a cleaner repr on the instance if we don't set it.
- kwargs.pop('required', None)
-
- if model_field.has_default():
- kwargs['default'] = model_field.get_default()
- # Having a default implies that the field is not required.
- # We have a cleaner repr on the instance if we don't set it.
- kwargs.pop('required', None)
-
- if model_field.flatchoices:
- # If this model field contains choices, then use a ChoiceField,
- # rather than the standard serializer field for this type.
- # Note that we return this prior to setting any validation type
- # keyword arguments, as those are not valid initializers.
- kwargs['choices'] = model_field.flatchoices
- return ChoiceField(**kwargs)
-
- # Ensure that max_length is passed explicitly as a keyword arg,
- # rather than as a validator.
- max_length = getattr(model_field, 'max_length', None)
- if max_length is not None:
- kwargs['max_length'] = max_length
- validator_kwarg = [
- validator for validator in validator_kwarg
- if not isinstance(validator, validators.MaxLengthValidator)
- ]
-
- # Ensure that min_length is passed explicitly as a keyword arg,
- # rather than as a validator.
- min_length = getattr(model_field, 'min_length', None)
- if min_length is not None:
- kwargs['min_length'] = min_length
- validator_kwarg = [
- validator for validator in validator_kwarg
- if not isinstance(validator, validators.MinLengthValidator)
- ]
-
- # Ensure that max_value is passed explicitly as a keyword arg,
- # rather than as a validator.
- max_value = next((
- validator.limit_value for validator in validator_kwarg
- if isinstance(validator, validators.MaxValueValidator)
- ), None)
- if max_value is not None:
- kwargs['max_value'] = max_value
- validator_kwarg = [
- validator for validator in validator_kwarg
- if not isinstance(validator, validators.MaxValueValidator)
- ]
-
- # Ensure that max_value is passed explicitly as a keyword arg,
- # rather than as a validator.
- min_value = next((
- validator.limit_value for validator in validator_kwarg
- if isinstance(validator, validators.MinValueValidator)
- ), None)
- if min_value is not None:
- kwargs['min_value'] = min_value
- validator_kwarg = [
- validator for validator in validator_kwarg
- if not isinstance(validator, validators.MinValueValidator)
- ]
-
- # URLField does not need to include the URLValidator argument,
- # as it is explicitly added in.
- if isinstance(model_field, models.URLField):
- validator_kwarg = [
- validator for validator in validator_kwarg
- if not isinstance(validator, validators.URLValidator)
- ]
-
- # EmailField does not need to include the validate_email argument,
- # as it is explicitly added in.
- if isinstance(model_field, models.EmailField):
- validator_kwarg = [
- validator for validator in validator_kwarg
- if validator is not validators.validate_email
- ]
-
- # SlugField do not need to include the 'validate_slug' argument,
- if isinstance(model_field, models.SlugField):
- validator_kwarg = [
- validator for validator in validator_kwarg
- if validator is not validators.validate_slug
- ]
-
- max_digits = getattr(model_field, 'max_digits', None)
- if max_digits is not None:
- kwargs['max_digits'] = max_digits
-
- decimal_places = getattr(model_field, 'decimal_places', None)
- if decimal_places is not None:
- kwargs['decimal_places'] = decimal_places
-
- if isinstance(model_field, models.BooleanField):
- # models.BooleanField has `blank=True`, but *is* actually
- # required *unless* a default is provided.
- # Also note that Django<1.6 uses `default=False` for
- # models.BooleanField, but Django>=1.6 uses `default=None`.
- kwargs.pop('required', None)
-
- if validator_kwarg:
- kwargs['validators'] = validator_kwarg
-
- if issubclass(serializer_cls, ModelField):
- kwargs['model_field'] = model_field
-
- return serializer_cls(**kwargs)
-
-
-class HyperlinkedModelSerializerOptions(ModelSerializerOptions):
- """
- Options for HyperlinkedModelSerializer
- """
- def __init__(self, meta):
- super(HyperlinkedModelSerializerOptions, self).__init__(meta)
- self.view_name = getattr(meta, 'view_name', None)
- self.lookup_field = getattr(meta, 'lookup_field', None)
+ model = relation_info.related
+ depth = nested_depth
+ return NestedSerializer
class HyperlinkedModelSerializer(ModelSerializer):
- _options_class = HyperlinkedModelSerializerOptions
-
- def get_url_field(self):
- if self.opts.view_name is not None:
- view_name = self.opts.view_name
- else:
- view_name = self.get_default_view_name(self.opts.model)
-
- kwargs = {
- 'view_name': view_name
- }
- if self.opts.lookup_field:
- kwargs['lookup_field'] = self.opts.lookup_field
-
- return HyperlinkedIdentityField(**kwargs)
-
- def get_pk_field(self, field_name, model_field):
- if self.opts.fields and model_field.name in self.opts.fields:
- return self.get_field(model_field)
-
- def get_related_field(self, field_name, model_field, related_model, to_many, has_through_model):
- """
- Creates a default instance of a flat relational field.
- """
- kwargs = {
- 'queryset': related_model._default_manager,
- 'view_name': self.get_default_view_name(related_model),
- }
-
- if to_many:
- kwargs['many'] = True
-
- if has_through_model:
- kwargs['read_only'] = True
- kwargs.pop('queryset', None)
-
- if model_field:
- if model_field.null or model_field.blank:
- kwargs['required'] = False
- if model_field.verbose_name and needs_label(model_field, field_name):
- kwargs['label'] = capfirst(model_field.verbose_name)
- if not model_field.editable:
- kwargs['read_only'] = True
- kwargs.pop('queryset', None)
- help_text = clean_manytomany_helptext(model_field.help_text)
- if help_text:
- kwargs['help_text'] = help_text
-
- return HyperlinkedRelatedField(**kwargs)
-
- def get_default_view_name(self, model):
- """
- Return the view name to use for related models.
- """
- return '%(model_name)s-detail' % {
- 'app_label': model._meta.app_label,
- 'model_name': model._meta.object_name.lower()
- }
-
-
-ModelSerializer.nested_class = ModelSerializer
-HyperlinkedModelSerializer.nested_class = HyperlinkedModelSerializer
+ _related_class = HyperlinkedRelatedField
+
+ def _get_default_field_names(self, declared_fields, model_info):
+ return (
+ [api_settings.URL_FIELD_NAME] +
+ list(declared_fields.keys()) +
+ list(model_info.fields.keys()) +
+ list(model_info.forward_relations.keys())
+ )
+
+ def _get_nested_class(self, nested_depth, relation_info):
+ class NestedSerializer(HyperlinkedModelSerializer):
+ class Meta:
+ model = relation_info.related
+ depth = nested_depth
+ return NestedSerializer