diff options
| author | Tom Christie | 2013-05-07 05:08:28 -0700 | 
|---|---|---|
| committer | Tom Christie | 2013-05-07 05:08:28 -0700 | 
| commit | 5356af8651fccacf5524add33569dd84d9e78646 (patch) | |
| tree | 8f7145c0a4cfeab61e1e523409b82091f44dfdbb /rest_framework/serializers.py | |
| parent | 287ff43cdd85a5c2275205bf37e19dea3f69ad01 (diff) | |
| parent | 0f00da848d9a8d25a0049231e9794da71a96662b (diff) | |
| download | django-rest-framework-5356af8651fccacf5524add33569dd84d9e78646.tar.bz2 | |
Merge pull request #808 from tomchristie/2.3
2.3
Diffstat (limited to 'rest_framework/serializers.py')
| -rw-r--r-- | rest_framework/serializers.py | 145 | 
1 files changed, 111 insertions, 34 deletions
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index add46566..ea5175e2 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -1,3 +1,15 @@ +""" +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 primatives. +2. The process of marshalling between python primatives and request and +response content is handled by parsers and renderers. +"""  from __future__ import unicode_literals  import copy  import datetime @@ -412,9 +424,9 @@ class BaseSerializer(WritableField):              else:                  many = hasattr(data, '__iter__') and not isinstance(data, (Page, dict, six.text_type))                  if many: -                    warnings.warn('Implict list/queryset serialization is due to be deprecated. ' +                    warnings.warn('Implict list/queryset serialization is deprecated. '                                    'Use the `many=True` flag when instantiating the serializer.', -                                  PendingDeprecationWarning, stacklevel=3) +                                  DeprecationWarning, stacklevel=3)              if many:                  ret = [] @@ -474,9 +486,9 @@ class BaseSerializer(WritableField):              else:                  many = hasattr(obj, '__iter__') and not isinstance(obj, (Page, dict))                  if many: -                    warnings.warn('Implict list/queryset serialization is due to be deprecated. ' +                    warnings.warn('Implict list/queryset serialization is deprecated. '                                    'Use the `many=True` flag when instantiating the serializer.', -                                  PendingDeprecationWarning, stacklevel=2) +                                  DeprecationWarning, stacklevel=2)              if many:                  self._data = [self.to_native(item) for item in obj] @@ -536,6 +548,7 @@ class ModelSerializer(Serializer):          models.DateTimeField: DateTimeField,          models.DateField: DateField,          models.TimeField: TimeField, +        models.DecimalField: DecimalField,          models.EmailField: EmailField,          models.CharField: CharField,          models.URLField: URLField, @@ -556,36 +569,85 @@ class ModelSerializer(Serializer):          assert cls is not None, \                  "Serializer class '%s' is missing 'model' Meta option" % self.__class__.__name__          opts = get_concrete_model(cls)._meta -        pk_field = opts.pk +        ret = SortedDict() +        nested = bool(self.opts.depth) -        # If model is a child via multitable inheritance, use parent's pk +        # Deal with adding the primary key field +        pk_field = opts.pk          while pk_field.rel and pk_field.rel.parent_link: +            # If model is a child via multitable inheritance, use parent's pk              pk_field = pk_field.rel.to._meta.pk -        fields = [pk_field] -        fields += [field for field in opts.fields if field.serialize] -        fields += [field for field in opts.many_to_many if field.serialize] +        field = self.get_pk_field(pk_field) +        if field: +            ret[pk_field.name] = field -        ret = SortedDict() -        nested = bool(self.opts.depth) -        is_pk = True  # First field in the list is the pk - -        for model_field in fields: -            if is_pk: -                field = self.get_pk_field(model_field) -                is_pk = False -            elif model_field.rel and nested: -                field = self.get_nested_field(model_field) -            elif model_field.rel: +        # Deal with forward relationships +        forward_rels = [field for field in opts.fields if field.serialize] +        forward_rels += [field for field in opts.many_to_many if field.serialize] + +        for model_field in forward_rels: +            if model_field.rel:                  to_many = isinstance(model_field,                                       models.fields.related.ManyToManyField) -                field = self.get_related_field(model_field, to_many=to_many) +                related_model = model_field.rel.to + +            if model_field.rel and nested: +                if len(inspect.getargspec(self.get_nested_field).args) == 2: +                    warnings.warn( +                        'The `get_nested_field(model_field)` call signature ' +                        'is due to be deprecated. ' +                        'Use `get_nested_field(model_field, related_model, ' +                        'to_many) instead', +                        PendingDeprecationWarning +                    ) +                    field = self.get_nested_field(model_field) +                else: +                    field = self.get_nested_field(model_field, related_model, to_many) +            elif model_field.rel: +                if len(inspect.getargspec(self.get_nested_field).args) == 3: +                    warnings.warn( +                        'The `get_related_field(model_field, to_many)` call ' +                        'signature is due to be deprecated. ' +                        'Use `get_related_field(model_field, related_model, ' +                        'to_many) instead', +                        PendingDeprecationWarning +                    ) +                    field = self.get_related_field(model_field, to_many=to_many) +                else: +                    field = self.get_related_field(model_field, related_model, to_many)              else:                  field = self.get_field(model_field)              if field:                  ret[model_field.name] = field +        # Deal with reverse relationships +        if not self.opts.fields: +            reverse_rels = [] +        else: +            # Reverse relationships are only included if they are explicitly +            # present in the `fields` option on the serializer +            reverse_rels = opts.get_all_related_objects() +            reverse_rels += opts.get_all_related_many_to_many_objects() + +        for relation in reverse_rels: +            accessor_name = relation.get_accessor_name() +            if not self.opts.fields or accessor_name not in self.opts.fields: +                continue +            related_model = relation.model +            to_many = relation.field.rel.multiple + +            if nested: +                field = self.get_nested_field(None, related_model, to_many) +            else: +                field = self.get_related_field(None, related_model, to_many) + +            if field: +                ret[accessor_name] = field + +        # Add the `read_only` flag to any fields that have bee specified +        # in the `read_only_fields` option          for field_name in self.opts.read_only_fields:              assert field_name in ret, \                  "read_only_fields on '%s' included invalid item '%s'" % \ @@ -600,29 +662,36 @@ class ModelSerializer(Serializer):          """          return self.get_field(model_field) -    def get_nested_field(self, model_field): +    def get_nested_field(self, model_field, related_model, to_many):          """          Creates a default instance of a nested relational field. + +        Note that model_field will be `None` for reverse relationships.          """          class NestedModelSerializer(ModelSerializer):              class Meta: -                model = model_field.rel.to +                model = related_model                  depth = self.opts.depth - 1 -        return NestedModelSerializer() +        return NestedModelSerializer(many=to_many) -    def get_related_field(self, model_field, to_many=False): +    def get_related_field(self, model_field, related_model, to_many):          """          Creates a default instance of a flat relational field. + +        Note that model_field will be `None` for reverse relationships.          """          # TODO: filter queryset using:          # .using(db).complex_filter(self.rel.limit_choices_to) +          kwargs = { -            'required': not(model_field.null or model_field.blank), -            'queryset': model_field.rel.to._default_manager, +            'queryset': related_model._default_manager,              'many': to_many          } +        if model_field: +            kwargs['required'] = not(model_field.null or model_field.blank) +          return PrimaryKeyRelatedField(**kwargs)      def get_field(self, model_field): @@ -758,6 +827,7 @@ class HyperlinkedModelSerializerOptions(ModelSerializerOptions):      def __init__(self, meta):          super(HyperlinkedModelSerializerOptions, self).__init__(meta)          self.view_name = getattr(meta, 'view_name', None) +        self.lookup_field = getattr(meta, 'slug_field', None)  class HyperlinkedModelSerializer(ModelSerializer): @@ -767,6 +837,7 @@ class HyperlinkedModelSerializer(ModelSerializer):      """      _options_class = HyperlinkedModelSerializerOptions      _default_view_name = '%(model_name)s-detail' +    _hyperlink_field_class = HyperlinkedRelatedField      url = HyperlinkedIdentityField() @@ -787,22 +858,28 @@ class HyperlinkedModelSerializer(ModelSerializer):          return self._default_view_name % format_kwargs      def get_pk_field(self, model_field): -        return None +        if self.opts.fields and model_field.name in self.opts.fields: +            return self.get_field(model_field) -    def get_related_field(self, model_field, to_many): +    def get_related_field(self, model_field, related_model, to_many):          """          Creates a default instance of a flat relational field.          """          # TODO: filter queryset using:          # .using(db).complex_filter(self.rel.limit_choices_to) -        rel = model_field.rel.to          kwargs = { -            'required': not(model_field.null or model_field.blank), -            'queryset': rel._default_manager, -            'view_name': self._get_default_view_name(rel), +            'queryset': related_model._default_manager, +            'view_name': self._get_default_view_name(related_model),              'many': to_many          } -        return HyperlinkedRelatedField(**kwargs) + +        if model_field: +            kwargs['required'] = not(model_field.null or model_field.blank) + +        if self.opts.lookup_field: +            kwargs['lookup_field'] = self.opts.lookup_field + +        return self._hyperlink_field_class(**kwargs)      def get_identity(self, data):          """  | 
