123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- """
- 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 collections import OrderedDict
- from django.core.exceptions import PermissionDenied
- from django.http import Http404
- from django.utils.encoding import force_str
- from rest_framework import exceptions, serializers
- from rest_framework.request import clone_request
- from rest_framework.utils.field_mapping import ClassLookupDict
- class BaseMetadata:
- 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.NullBooleanField: 'boolean',
- serializers.CharField: 'string',
- serializers.UUIDField: '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',
- serializers.ListField: 'list',
- serializers.DictField: 'nested object',
- serializers.Serializer: 'nested object',
- })
- 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 {'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()
- if not isinstance(field, serializers.HiddenField)
- ])
- 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_str(value, strings_only=True)
- if getattr(field, 'child', None):
- field_info['child'] = self.get_field_info(field.child)
- elif getattr(field, 'fields', None):
- field_info['children'] = self.get_serializer_info(field)
- if (not field_info.get('read_only') and
- not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) and
- hasattr(field, 'choices')):
- field_info['choices'] = [
- {
- 'value': choice_value,
- 'display_name': force_str(choice_name, strings_only=True)
- }
- for choice_value, choice_name in field.choices.items()
- ]
- return field_info
|