""" Serializers and ModelSerializers are similar to Forms and ModelForms. Unlike forms, they are not constrained to dealing with HTML output, and form encoded input. Serialization in REST framework is a two-phase process: 1. Serializers marshal between complex types like model instances, and python primitives. 2. The process of marshalling between python primitives and request and response content is handled by parsers and renderers. """ from __future__ import unicode_literals from django.db import models from django.db.models.fields import FieldDoesNotExist from django.utils.translation import ugettext_lazy as _ from rest_framework.compat import unicode_to_repr from rest_framework.utils import model_meta from rest_framework.utils.field_mapping import ( get_url_kwargs, get_field_kwargs, get_relation_kwargs, get_nested_relation_kwargs, ClassLookupDict ) from rest_framework.utils.serializer_helpers import ( ReturnDict, ReturnList, BoundField, NestedBoundField, BindingDict ) from rest_framework.validators import ( UniqueForDateValidator, UniqueForMonthValidator, UniqueForYearValidator, UniqueTogetherValidator ) import warnings # Note: We do the following so that users of the framework can use this style: # # example_field = serializers.CharField(...) # # This helps keep the separation between model fields, form fields, and # serializer fields more explicit. from rest_framework.relations import * # NOQA from rest_framework.fields import * # NOQA # We assume that 'validators' are intended for the child serializer, # rather than the parent serializer. LIST_SERIALIZER_KWARGS = ( 'read_only', 'write_only', 'required', 'default', 'initial', 'source', 'label', 'help_text', 'style', 'error_messages', 'instance', 'data', 'partial', 'context' ) # BaseSerializer # -------------- class BaseSerializer(Field): """ The BaseSerializer class provides a minimal class which may be used for writing custom serializer implementations. Note that we strongly restrict the ordering of operations/properties that may be used on the serializer in order to enforce correct usage. In particular, if a `data=` argument is passed then: .is_valid() - Available. .initial_data - Available. .validated_data - Only available after calling `is_valid()` .errors - Only available after calling `is_valid()` .data - Only available after calling `is_valid()` If a `data=` argument is not passed then: .is_valid() - Not available. .initial_data - Not available. .validated_data - Not available. .errors - Not available. .data - Available. """ def __init__(self, instance=None, data=empty, **kwargs): self.instance = instance if data is not empty: self.initial_data = data self.partial = kwargs.pop('partial', False) self._context = kwargs.pop('context', {}) kwargs.pop('many', None) super(BaseSerializer, self).__init__(**kwargs) 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): return cls.many_init(*args, **kwargs) return super(BaseSerializer, cls).__new__(cls, *args, **kwargs) @classmethod def many_init(cls, *args, **kwargs): """ This method implements the creation of a `ListSerializer` parent class when `many=True` is used. You can customize it if you need to control which keyword arguments are passed to the parent, and which are passed to the child. Note that we're over-cautious in passing most arguments to both parent and child classes in order to try to cover the general case. If you're overriding this method you'll probably want something much simpler, eg: @classmethod def many_init(cls, *args, **kwargs): kwargs['child'] = cls() return CustomListSerializer(*args, **kwargs) """ child_serializer = cls(*args, **kwargs) list_kwargs = {'child': child_serializer} list_kwargs.update(dict([ (key, value) for key, value in kwargs.items() if key in LIST_SERIALIZER_KWARGS ])) meta = getattr(cls, 'Meta', None) list_serializer_class = getattr(meta, 'list_serializer_class', ListSerializer) return list_serializer_class(*args, **list_kwargs) def to_internal_value(self, data): raise NotImplementedError('`to_internal_value()` must be implemented.') def to_representation(self, instance): raise NotImplementedError('`to_representation()` must be implemented.') def update(self, instance, validated_data): raise NotImplementedError('`update()` must be implemented.') def create(self, validated_data): raise NotImplementedError('`create()` must be implemented.') def save(self, **kwargs): assert not hasattr(self, 'save_object'), ( 'Serializer `%s.%s` has old-style version 2 `.save_object()` ' 'that is no longer compatible with REST framework 3. ' 'Use the new-style `.create()` and `.update()` methods instead.' % (self.__class__.__module__, self.__class__.__name__) ) assert hasattr(self, '_errors'), ( 'You must call `.is_valid()` before calling `.save()`.' ) assert not self.errors, ( 'You cannot call `.save()` on a serializer with invalid data.' ) validated_data = dict( list(self.validated_data.items()) + list(kwargs.items()) ) if self.instance is not None: self.instance = self.update(self.instance, validated_data) assert self.instance is not None, ( '`update()` did not return an object instance.' ) else: self.instance = self.create(validated_data) assert self.instance is not None, ( '`create()` did not return an object instance.' ) return self.instance def is_valid(self, raise_exception=False): assert not hasattr(self, 'restore_object'), ( 'Serializer `%s.%s` has old-style version 2 `.restore_object()` ' 'that is no longer compatible with REST framework 3. ' 'Use the new-style `.create()` and `.update()` methods instead.' % (self.__class__.__module__, self.__class__.__name__) ) assert hasattr(self, 'initial_data'), ( 'Cannot call `.is_valid()` as no `data=` keyword argument was' 'passed when instantiating the serializer instance.' ) if not hasattr(self, '_validated_data'): try: self._validated_data = self.run_validation(self.initial_data) except ValidationError as exc: self._validated_data = {} self._errors = exc.detail else: self._errors = {} if self._errors and raise_exception: raise ValidationError(self._errors) return not bool(self._errors) @property def data(self): if hasattr(self, 'initial_data') and not hasattr(self, '_validated_data'): msg = ( 'When a serializer is passed a `data` keyword argument you ' 'must call `.is_valid()` before attempting to access the ' 'serialized `.data` representation.\n' 'You should either call `.is_valid()` first, ' 'or access `.initial_data` instead.' ) raise AssertionError(msg) if not hasattr(self, '_data'): if self.instance is not None and not getattr(self, '_errors', None): self._data = self.to_representation(self.instance) elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None): self._data = self.to_representation(self.validated_data) else: self._data = self.get_initial() return self._data @property def errors(self): if not hasattr(self, '_errors'): msg = 'You must call `.is_valid()` before accessing `.errors`.' raise AssertionError(msg) return self._errors @property def validated_data(self): if not hasattr(self, '_validated_data'): msg = 'You must call `.is_valid()` before accessing `.validated_data`.' raise AssertionError(msg) return self._validated_data # Serializer & ListSerializer classes # ----------------------------------- class SerializerMetaclass(type): """ This metaclass sets a dictionary named `base_fields` on the class. Any instances of `Field` included as attributes on either the class or on any of its superclasses will be include in the `base_fields` dictionary. """ @classmethod 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)] fields.sort(key=lambda x: x[1]._creation_counter) # If this class is subclassing another Serializer, add that Serializer's # 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, '_declared_fields'): fields = list(base._declared_fields.items()) + fields return OrderedDict(fields) def __new__(cls, name, bases, attrs): attrs['_declared_fields'] = cls._get_declared_fields(bases, attrs) return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs) def get_validation_error_detail(exc): assert isinstance(exc, (ValidationError, DjangoValidationError)) if isinstance(exc, DjangoValidationError): # Normally you should raise `serializers.ValidationError` # inside your codebase, but we handle Django's validation # exception class as well for simpler compat. # Eg. Calling Model.clean() explicitly inside Serializer.validate() return { api_settings.NON_FIELD_ERRORS_KEY: list(exc.messages) } elif isinstance(exc.detail, dict): # If errors may be a dict we use the standard {key: list of values}. # Here we ensure that all the values are *lists* of errors. return dict([ (key, value if isinstance(value, list) else [value]) for key, value in exc.detail.items() ]) elif isinstance(exc.detail, list): # Errors raised as a list are non-field errors. return { api_settings.NON_FIELD_ERRORS_KEY: exc.detail } # Errors raised as a string are non-field errors. return { api_settings.NON_FIELD_ERRORS_KEY: [exc.detail] } @six.add_metaclass(SerializerMetaclass) class Serializer(BaseSerializer): default_error_messages = { 'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.') } @property def fields(self): """ A dictionary of {field_name: field_instance}. """ # `fields` is evaluated lazily. We do this to ensure that we don't # have issues importing modules that use ModelSerializers as fields, # even if Django's app-loading stage has not yet run. if not hasattr(self, '_fields'): self._fields = BindingDict(self) for key, value in self.get_fields().items(): self._fields[key] = value return self._fields def get_fields(self): """ Returns a dictionary of {field_name: field_instance}. """ # 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. return copy.deepcopy(self._declared_fields) def get_validators(self): """ Returns a list of validator callables. """ # Used by the lazily-evaluated `validators` property. meta = getattr(self, 'Meta', None) validators = getattr(meta, 'validators', None) return validators[:] if validators else [] def get_initial(self): if hasattr(self, 'initial_data'): return OrderedDict([ (field_name, field.get_value(self.initial_data)) for field_name, field in self.fields.items() if field.get_value(self.initial_data) is not empty and not field.read_only ]) return OrderedDict([ (field.field_name, field.get_initial()) for field in self.fields.values() if not field.read_only ]) def get_value(self, dictionary): # We override the default field access in order to support # nested HTML forms. if html.is_html_input(dictionary): return html.parse_html_dict(dictionary, prefix=self.field_name) return dictionary.get(self.field_name, empty) def run_validation(self, data=empty): """ We override the default `run_validation`, because the validation performed by validators and the `.validate()` method should be coerced into an error dictionary with a 'non_fields_error' key. """ (is_empty_value, data) = self.validate_empty_values(data) if is_empty_value: return data value = self.to_internal_value(data) try: self.run_validators(value) value = self.validate(value) assert value is not None, '.validate() should return the validated data' except (ValidationError, DjangoValidationError) as exc: raise ValidationError(detail=get_validation_error_detail(exc)) return value def to_internal_value(self, data): """ Dict of native values <- Dict of primitive datatypes. """ if not isinstance(data, dict): message = self.error_messages['invalid'].format( datatype=type(data).__name__ ) raise ValidationError({ api_settings.NON_FIELD_ERRORS_KEY: [message] }) ret = OrderedDict() errors = OrderedDict() fields = [ field for field in self.fields.values() if (not field.read_only) or (field.default is not empty) ] for field in fields: validate_method = getattr(self, 'validate_' + field.field_name, None) primitive_value = field.get_value(data) try: validated_value = field.run_validation(primitive_value) if validate_method is not None: validated_value = validate_method(validated_value) except ValidationError as exc: errors[field.field_name] = exc.detail except DjangoValidationError as exc: errors[field.field_name] = list(exc.messages) except SkipField: pass else: set_value(ret, field.source_attrs, validated_value) if errors: raise ValidationError(errors) return ret def to_representation(self, instance): """ Object instance -> Dict of primitive datatypes. """ ret = OrderedDict() fields = [field for field in self.fields.values() if not field.write_only] for field in fields: attribute = field.get_attribute(instance) if attribute is None: ret[field.field_name] = None else: ret[field.field_name] = field.to_representation(attribute) return ret def validate(self, attrs): return attrs def __repr__(self): return unicode_to_repr(representation.serializer_repr(self, indent=1)) # The following are used for accessing `BoundField` instances on the # serializer, for the purposes of presenting a form-like API onto the # field values and field errors. def __iter__(self): for field in self.fields.values(): yield self[field.field_name] def __getitem__(self, key): field = self.fields[key] value = self.data.get(key) error = self.errors.get(key) if hasattr(self, '_errors') else None if isinstance(field, Serializer): return NestedBoundField(field, value, error) return BoundField(field, value, error) # Include a backlink to the serializer class on return objects. # Allows renderers such as HTMLFormRenderer to get the full field info. @property def data(self): ret = super(Serializer, self).data return ReturnDict(ret, serializer=self) @property def errors(self): ret = super(Serializer, self).errors return ReturnDict(ret, serializer=self) # There's some replication of `ListField` here, # but that's probably better than obfuscating the call hierarchy. class ListSerializer(BaseSerializer): child = None many = True default_error_messages = { 'not_a_list': _('Expected a list of items but got type "{input_type}".') } def __init__(self, *args, **kwargs): self.child = kwargs.pop('child', copy.deepcopy(self.child)) assert self.child is not None, '`child` is a required argument.' assert not inspect.isclass(self.child), '`child` has not been instantiated.' super(ListSerializer, self).__init__(*args, **kwargs) self.child.bind(field_name='', parent=self) def get_initial(self): if hasattr(self, 'initial_data'): return self.to_representation(self.initial_data) return [] def get_value(self, dictionary): """ Given the input dictionary, return the field value. """ # We override the default field access in order to support # lists in HTML forms. if html.is_html_input(dictionary): return html.parse_html_list(dictionary, prefix=self.field_name) return dictionary.get(self.field_name, empty) def run_validation(self, data=empty): """ We override the default `run_validation`, because the validation performed by validators and the `.validate()` method should be coerced into an error dictionary with a 'non_fields_error' key. """ (is_empty_value, data) = self.validate_empty_values(data) if is_empty_value: return data value = self.to_internal_value(data) try: self.run_validators(value) value = self.validate(value) assert value is not None, '.validate() should return the validated data' except (ValidationError, DjangoValidationError) as exc: raise ValidationError(detail=get_validation_error_detail(exc)) return value def to_internal_value(self, data): """ List of dicts of native values <- List of dicts of primitive datatypes. """ if html.is_html_input(data): data = html.parse_html_list(data) if not isinstance(data, list): message = self.error_messages['not_a_list'].format( input_type=type(data).__name__ ) raise ValidationError({ api_settings.NON_FIELD_ERRORS_KEY: [message] }) ret = [] errors = [] for item in data: try: validated = self.child.run_validation(item) except ValidationError as exc: errors.append(exc.detail) else: ret.append(validated) errors.append({}) if any(errors): raise ValidationError(errors) return ret def to_representation(self, data): """ List of object instances -> List of dicts of primitive datatypes. """ # Dealing with nested relationships, data can be a Manager, # so, first get a queryset from the Manager if needed iterable = data.all() if isinstance(data, models.Manager) else data return [ self.child.to_representation(item) for item in iterable ] def validate(self, attrs): return attrs def update(self, instance, validated_data): raise NotImplementedError( "Serializers with many=True do not support multiple update by " "default, only multiple create. For updates it is unclear how to " "deal with insertions and deletions. If you need to support " "multiple update, use a `ListSerializer` class and override " "`.update()` so you can specify the behavior exactly." ) def create(self, validated_data): return [ self.child.create(attrs) for attrs in validated_data ] def save(self, **kwargs): """ Save and return a list of object instances. """ validated_data = [ dict(list(attrs.items()) + list(kwargs.items())) for attrs in self.validated_data ] if self.instance is not None: self.instance = self.update(self.instance, validated_data) assert self.instance is not None, ( '`update()` did not return an object instance.' ) else: self.instance = self.create(validated_data) assert self.instance is not None, ( '`create()` did not return an object instance.' ) return self.instance def __repr__(self): return unicode_to_repr(representation.list_repr(self, indent=1)) # Include a backlink to the serializer class on return objects. # Allows renderers such as HTMLFormRenderer to get the full field info. @property def data(self): ret = super(ListSerializer, self).data return ReturnList(ret, serializer=self) @property def errors(self): ret = super(ListSerializer, self).errors if isinstance(ret, dict): return ReturnDict(ret, serializer=self) return ReturnList(ret, serializer=self) # ModelSerializer & HyperlinkedModelSerializer # -------------------------------------------- def raise_errors_on_nested_writes(method_name, serializer, validated_data): """ Give explicit errors when users attempt to pass writable nested data. If we don't do this explicitly they'd get a less helpful error when calling `.save()` on the serializer. We don't *automatically* support these sorts of nested writes brecause there are too many ambiguities to define a default behavior. Eg. Suppose we have a `UserSerializer` with a nested profile. How should we handle the case of an update, where the `profile` realtionship does not exist? Any of the following might be valid: * Raise an application error. * Silently ignore the nested part of the update. * Automatically create a profile instance. """ # Ensure we don't have a writable nested field. For example: # # class UserSerializer(ModelSerializer): # ... # profile = ProfileSerializer() assert not any( isinstance(field, BaseSerializer) and (key in validated_data) and isinstance(validated_data[key], (list, dict)) for key, field in serializer.fields.items() ), ( 'The `.{method_name}()` method does not support writable nested' 'fields by default.\nWrite an explicit `.{method_name}()` method for ' 'serializer `{module}.{class_name}`, or set `read_only=True` on ' 'nested serializer fields.'.format( method_name=method_name, module=serializer.__class__.__module__, class_name=serializer.__class__.__name__ ) ) # Ensure we don't have a writable dotted-source field. For example: # # class UserSerializer(ModelSerializer): # ... # address = serializer.CharField('profile.address') assert not any( '.' in field.source and (key in validated_data) and isinstance(validated_data[key], (list, dict)) for key, field in serializer.fields.items() ), ( 'The `.{method_name}()` method does not support writable dotted-source ' 'fields by default.\nWrite an explicit `.{method_name}()` method for ' 'serializer `{module}.{class_name}`, or set `read_only=True` on ' 'dotted-source serializer fields.'.format( method_name=method_name, module=serializer.__class__.__module__, class_name=serializer.__class__.__name__ ) ) class ModelSerializer(Serializer): """ A `ModelSerializer` is just a regular `Serializer`, except that: * A set of default fields are automatically populated. * A set of default validators are automatically populated. * Default `.create()` and `.update()` implementations are provided. The process of automatically determining a set of serializer fields based on the model fields is reasonably complex, but you almost certainly don't need to dig into the implementation. If the `ModelSerializer` class *doesn't* generate the set of fields that you need you should either declare the extra/differing fields explicitly on the serializer class, or simply use a `Serializer` class. """ serializer_field_mapping = { models.AutoField: IntegerField, models.BigIntegerField: IntegerField, models.BooleanField: BooleanField, models.CharField: CharField, models.CommaSeparatedIntegerField: CharField, models.DateField: DateField, models.DateTimeField: DateTimeField, models.DecimalField: DecimalField, models.EmailField: EmailField, models.Field: ModelField, models.FileField: FileField, models.FloatField: FloatField, models.ImageField: ImageField, models.IntegerField: IntegerField, models.NullBooleanField: NullBooleanField, models.PositiveIntegerField: IntegerField, models.PositiveSmallIntegerField: IntegerField, models.SlugField: SlugField, models.SmallIntegerField: IntegerField, models.TextField: CharField, models.TimeField: TimeField, models.URLField: URLField, } serializer_related_class = PrimaryKeyRelatedField # Default `create` and `update` behavior... def create(self, validated_data): """ We have a bit of extra checking around this in order to provide descriptive messages when something goes wrong, but this method is essentially just: return ExampleModel.objects.create(**validated_data) If there are many to many fields present on the instance then they cannot be set until the model is instantiated, in which case the implementation is like so: example_relationship = validated_data.pop('example_relationship') instance = ExampleModel.objects.create(**validated_data) instance.example_relationship = example_relationship return instance The default implementation also does not handle nested relationships. If you want to support writable nested relationships you'll need to write an explicit `.create()` method. """ raise_errors_on_nested_writes('create', self, validated_data) ModelClass = self.Meta.model # Remove many-to-many relationships from validated_data. # They are not valid arguments to the default `.create()` method, # as they require that the instance has already been saved. info = model_meta.get_field_info(ModelClass) many_to_many = {} for field_name, relation_info in info.relations.items(): if relation_info.to_many and (field_name in validated_data): many_to_many[field_name] = validated_data.pop(field_name) try: instance = ModelClass.objects.create(**validated_data) except TypeError as exc: msg = ( 'Got a `TypeError` when calling `%s.objects.create()`. ' 'This may be because you have a writable field on the ' 'serializer class that is not a valid argument to ' '`%s.objects.create()`. You may need to make the field ' 'read-only, or override the %s.create() method to handle ' 'this correctly.\nOriginal exception text was: %s.' % ( ModelClass.__name__, ModelClass.__name__, self.__class__.__name__, exc ) ) raise TypeError(msg) # Save many-to-many relationships after the instance is created. if many_to_many: for field_name, value in many_to_many.items(): setattr(instance, field_name, value) return instance def update(self, instance, validated_data): raise_errors_on_nested_writes('update', self, validated_data) for attr, value in validated_data.items(): setattr(instance, attr, value) instance.save() return instance # 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. """ assert hasattr(self, 'Meta'), ( 'Class {serializer_class} missing "Meta" attribute'.format( serializer_class=self.__class__.__name__ ) ) assert hasattr(self.Meta, 'model'), ( 'Class {serializer_class} missing "Meta.model" attribute'.format( serializer_class=self.__class__.__name__ ) ) declared_fields = copy.deepcopy(self._declared_fields) model = getattr(self.Meta, 'model') depth = getattr(self.Meta, 'depth', 0) if depth is not None: assert depth >= 0, "'depth' may not be negative." assert depth <= 10, "'depth' may not be greater than 10." # 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) # Determine any extra field arguments and hidden fields that # should be included extra_kwargs = self.get_extra_kwargs() extra_kwargs, hidden_fields = self.get_uniqueness_extra_kwargs( field_names, declared_fields, extra_kwargs ) # Determine the fields that should be included on the serializer. fields = OrderedDict() for field_name in field_names: # If the field is explicitly declared on the class then use that. if field_name in declared_fields: fields[field_name] = declared_fields[field_name] continue # Determine the serializer field class and keyword arguments. field_class, field_kwargs = self.build_field( field_name, info, model, depth ) # Include any kwargs defined in `Meta.extra_kwargs` field_kwargs = self.build_field_kwargs( field_kwargs, extra_kwargs, field_name ) # Create the serializer field. fields[field_name] = field_class(**field_kwargs) # Add in any hidden fields. fields.update(hidden_fields) return fields # 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_class, nested_depth): """ Return a two tuple of (cls, kwargs) to build a serializer field with. """ if field_name in info.fields_and_pk: model_field = info.fields_and_pk[field_name] return self.build_standard_field(field_name, model_field) elif field_name in info.relations: relation_info = info.relations[field_name] if not nested_depth: return self.build_relational_field(field_name, relation_info) else: return self.build_nested_field(field_name, relation_info, nested_depth) elif hasattr(model_class, field_name): return self.build_property_field(field_name, model_class) elif field_name == api_settings.URL_FIELD_NAME: return self.build_url_field(field_name, model_class) return self.build_unknown_field(field_name, model_class) def build_standard_field(self, field_name, model_field): """ Create regular model fields. """ field_mapping = ClassLookupDict(self.serializer_field_mapping) field_class = field_mapping[model_field] field_kwargs = get_field_kwargs(field_name, model_field) if 'choices' in field_kwargs: # Fields with choices get coerced into `ChoiceField` # instead of using their regular typed field. field_class = ChoiceField if not issubclass(field_class, 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. field_kwargs.pop('model_field', None) if not issubclass(field_class, CharField) and not issubclass(field_class, ChoiceField): # `allow_blank` is only valid for textual fields. field_kwargs.pop('allow_blank', None) return field_class, field_kwargs def build_relational_field(self, field_name, relation_info): """ Create fields for forward and reverse relationships. """ field_class = self.serializer_related_class field_kwargs = get_relation_kwargs(field_name, relation_info) # `view_name` is only valid for hyperlinked relationships. if not issubclass(field_class, HyperlinkedRelatedField): field_kwargs.pop('view_name', None) return field_class, field_kwargs def build_nested_field(self, field_name, relation_info, nested_depth): """ Create nested fields for forward and reverse relationships. """ class NestedSerializer(ModelSerializer): class Meta: model = relation_info.related_model depth = nested_depth field_class = NestedSerializer field_kwargs = get_nested_relation_kwargs(relation_info) return field_class, field_kwargs def build_property_field(self, field_name, model_class): """ Create a read only field for model methods and properties. """ field_class = ReadOnlyField field_kwargs = {} return field_class, field_kwargs def build_url_field(self, field_name, model_class): """ Create a field representing the object's own URL. """ field_class = HyperlinkedIdentityField field_kwargs = get_url_kwargs(model_class) return field_class, field_kwargs def build_unknown_field(self, field_name, model_class): """ 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. """ extras = extra_kwargs.get(field_name, {}) if extras.get('read_only', False): for attr in [ 'required', 'default', 'allow_blank', 'allow_null', 'min_length', 'max_length', 'min_value', 'max_value', 'validators', 'queryset' ]: kwargs.pop(attr, None) if extras.get('default') and kwargs.get('required') is False: kwargs.pop('required') kwargs.update(extras) return kwargs # Methods for determining additional keyword arguments to apply... def get_extra_kwargs(self): """ Return a dictionary mapping field names to a dictionary of additional keyword arguments. """ extra_kwargs = getattr(self.Meta, 'extra_kwargs', {}) 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 # 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={: {'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 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 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): """ Return any additional field options that need to be included as a result of uniqueness constraints on the model. This is returned as a two-tuple of: ('dict of updated extra kwargs', 'mapping of hidden fields') """ model = getattr(self.Meta, 'model') model_fields = self._get_model_fields( field_names, declared_fields, extra_kwargs ) # Determine if we need any additional `HiddenField` or extra keyword # arguments to deal with `unique_for` dates that are required to # be in the input data in order to validate it. unique_constraint_names = set() for model_field in model_fields.values(): # Include each of the `unique_for_*` field names. unique_constraint_names |= set([ model_field.unique_for_date, model_field.unique_for_month, model_field.unique_for_year ]) unique_constraint_names -= set([None]) # Include each of the `unique_together` field names, # so long as all the field names are included on the serializer. for parent_class in [model] + list(model._meta.parents.keys()): for unique_together_list in parent_class._meta.unique_together: if set(field_names).issuperset(set(unique_together_list)): unique_constraint_names |= set(unique_together_list) # Now we have all the field names that have uniqueness constraints # applied, we can add the extra 'required=...' or 'default=...' # arguments that are appropriate to these fields, or add a `HiddenField` for it. hidden_fields = {} uniqueness_extra_kwargs = {} for unique_constraint_name in unique_constraint_names: # Get the model field that is referred too. unique_constraint_field = model._meta.get_field(unique_constraint_name) if getattr(unique_constraint_field, 'auto_now_add', None): default = CreateOnlyDefault(timezone.now) elif getattr(unique_constraint_field, 'auto_now', None): default = timezone.now elif unique_constraint_field.has_default(): default = unique_constraint_field.default else: default = empty if unique_constraint_name in model_fields: # The corresponding field is present in the serializer if default is empty: uniqueness_extra_kwargs[unique_constraint_name] = {'required': True} else: uniqueness_extra_kwargs[unique_constraint_name] = {'default': default} elif default is not empty: # The corresponding field is not present in the, # serializer. We have a default to use for it, so # add in a hidden field that populates it. hidden_fields[unique_constraint_name] = HiddenField(default=default) # Update `extra_kwargs` with any new options. for key, value in uniqueness_extra_kwargs.items(): if key in extra_kwargs: extra_kwargs[key].update(value) else: extra_kwargs[key] = value return extra_kwargs, hidden_fields def _get_model_fields(self, field_names, declared_fields, extra_kwargs): """ 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`. """ model = getattr(self.Meta, 'model') model_fields = {} 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 if '.' in source or source == '*': # Model fields will always have a simple source mapping, # they can't be nested attribute lookups. continue try: model_fields[source] = model._meta.get_field(source) except FieldDoesNotExist: pass return model_fields # 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 class HyperlinkedModelSerializer(ModelSerializer): """ A type of `ModelSerializer` that uses hyperlinked relationships instead of primary key relationships. Specifically: * A 'url' field is included instead of the 'id' field. * Relationships to other instances are hyperlinks, instead of primary keys. """ serializer_related_class = HyperlinkedRelatedField 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 ( [api_settings.URL_FIELD_NAME] + list(declared_fields.keys()) + list(model_info.fields.keys()) + list(model_info.forward_relations.keys()) ) def build_nested_field(self, field_name, relation_info, nested_depth): """ Create nested fields for forward and reverse relationships. """ class NestedSerializer(HyperlinkedModelSerializer): class Meta: model = relation_info.related_model depth = nested_depth - 1 field_class = NestedSerializer field_kwargs = get_nested_relation_kwargs(relation_info) return field_class, field_kwargs