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