aboutsummaryrefslogtreecommitdiffstats
path: root/rest_framework/metadata.py
diff options
context:
space:
mode:
Diffstat (limited to 'rest_framework/metadata.py')
-rw-r--r--rest_framework/metadata.py138
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