diff options
Diffstat (limited to 'rest_framework/metadata.py')
| -rw-r--r-- | rest_framework/metadata.py | 138 | 
1 files changed, 138 insertions, 0 deletions
| diff --git a/rest_framework/metadata.py b/rest_framework/metadata.py new file mode 100644 index 00000000..bf3611aa --- /dev/null +++ b/rest_framework/metadata.py @@ -0,0 +1,138 @@ +""" +The metadata API is used to allow customization of how `OPTIONS` requests +are handled. We currently provide a single default implementation that returns +some fairly ad-hoc information about the view. + +Future implementations might use JSON schema or other definitions in order +to return this information in a more standardized way. +""" +from __future__ import unicode_literals + +from django.core.exceptions import PermissionDenied +from django.http import Http404 +from django.utils.encoding import force_text +from rest_framework import exceptions, serializers +from rest_framework.compat import OrderedDict +from rest_framework.request import clone_request +from rest_framework.utils.field_mapping import ClassLookupDict + + +class BaseMetadata(object): +    def determine_metadata(self, request, view): +        """ +        Return a dictionary of metadata about the view. +        Used to return responses for OPTIONS requests. +        """ +        raise NotImplementedError(".determine_metadata() must be overridden.") + + +class SimpleMetadata(BaseMetadata): +    """ +    This is the default metadata implementation. +    It returns an ad-hoc set of information about the view. +    There are not any formalized standards for `OPTIONS` responses +    for us to base this on. +    """ +    label_lookup = ClassLookupDict({ +        serializers.Field: 'field', +        serializers.BooleanField: 'boolean', +        serializers.CharField: 'string', +        serializers.URLField: 'url', +        serializers.EmailField: 'email', +        serializers.RegexField: 'regex', +        serializers.SlugField: 'slug', +        serializers.IntegerField: 'integer', +        serializers.FloatField: 'float', +        serializers.DecimalField: 'decimal', +        serializers.DateField: 'date', +        serializers.DateTimeField: 'datetime', +        serializers.TimeField: 'time', +        serializers.ChoiceField: 'choice', +        serializers.MultipleChoiceField: 'multiple choice', +        serializers.FileField: 'file upload', +        serializers.ImageField: 'image upload', +    }) + +    def determine_metadata(self, request, view): +        metadata = OrderedDict() +        metadata['name'] = view.get_view_name() +        metadata['description'] = view.get_view_description() +        metadata['renders'] = [renderer.media_type for renderer in view.renderer_classes] +        metadata['parses'] = [parser.media_type for parser in view.parser_classes] +        if hasattr(view, 'get_serializer'): +            actions = self.determine_actions(request, view) +            if actions: +                metadata['actions'] = actions +        return metadata + +    def determine_actions(self, request, view): +        """ +        For generic class based views we return information about +        the fields that are accepted for 'PUT' and 'POST' methods. +        """ +        actions = {} +        for method in set(['PUT', 'POST']) & set(view.allowed_methods): +            view.request = clone_request(request, method) +            try: +                # Test global permissions +                if hasattr(view, 'check_permissions'): +                    view.check_permissions(view.request) +                # Test object permissions +                if method == 'PUT' and hasattr(view, 'get_object'): +                    view.get_object() +            except (exceptions.APIException, PermissionDenied, Http404): +                pass +            else: +                # If user has appropriate permissions for the view, include +                # appropriate metadata about the fields that should be supplied. +                serializer = view.get_serializer() +                actions[method] = self.get_serializer_info(serializer) +            finally: +                view.request = request + +        return actions + +    def get_serializer_info(self, serializer): +        """ +        Given an instance of a serializer, return a dictionary of metadata +        about its fields. +        """ +        if hasattr(serializer, 'child'): +            # If this is a `ListSerializer` then we want to examine the +            # underlying child serializer instance instead. +            serializer = serializer.child +        return OrderedDict([ +            (field_name, self.get_field_info(field)) +            for field_name, field in serializer.fields.items() +        ]) + +    def get_field_info(self, field): +        """ +        Given an instance of a serializer field, return a dictionary +        of metadata about it. +        """ +        field_info = OrderedDict() +        field_info['type'] = self.label_lookup[field] +        field_info['required'] = getattr(field, 'required', False) + +        attrs = [ +            'read_only', 'label', 'help_text', +            'min_length', 'max_length', +            'min_value', 'max_value' +        ] + +        for attr in attrs: +            value = getattr(field, attr, None) +            if value is not None and value != '': +                field_info[attr] = force_text(value, strings_only=True) + +        if hasattr(field, 'choices'): +            field_info['choices'] = [ +                { +                    'value': choice_value, +                    'display_name': force_text(choice_name, strings_only=True) +                } +                for choice_value, choice_name in field.choices.items() +            ] + +        return field_info | 
