metadata.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. """
  2. The metadata API is used to allow customization of how `OPTIONS` requests
  3. are handled. We currently provide a single default implementation that returns
  4. some fairly ad-hoc information about the view.
  5. Future implementations might use JSON schema or other definitions in order
  6. to return this information in a more standardized way.
  7. """
  8. from collections import OrderedDict
  9. from django.core.exceptions import PermissionDenied
  10. from django.http import Http404
  11. from django.utils.encoding import force_str
  12. from rest_framework import exceptions, serializers
  13. from rest_framework.request import clone_request
  14. from rest_framework.utils.field_mapping import ClassLookupDict
  15. class BaseMetadata:
  16. def determine_metadata(self, request, view):
  17. """
  18. Return a dictionary of metadata about the view.
  19. Used to return responses for OPTIONS requests.
  20. """
  21. raise NotImplementedError(".determine_metadata() must be overridden.")
  22. class SimpleMetadata(BaseMetadata):
  23. """
  24. This is the default metadata implementation.
  25. It returns an ad-hoc set of information about the view.
  26. There are not any formalized standards for `OPTIONS` responses
  27. for us to base this on.
  28. """
  29. label_lookup = ClassLookupDict({
  30. serializers.Field: 'field',
  31. serializers.BooleanField: 'boolean',
  32. serializers.CharField: 'string',
  33. serializers.UUIDField: 'string',
  34. serializers.URLField: 'url',
  35. serializers.EmailField: 'email',
  36. serializers.RegexField: 'regex',
  37. serializers.SlugField: 'slug',
  38. serializers.IntegerField: 'integer',
  39. serializers.FloatField: 'float',
  40. serializers.DecimalField: 'decimal',
  41. serializers.DateField: 'date',
  42. serializers.DateTimeField: 'datetime',
  43. serializers.TimeField: 'time',
  44. serializers.ChoiceField: 'choice',
  45. serializers.MultipleChoiceField: 'multiple choice',
  46. serializers.FileField: 'file upload',
  47. serializers.ImageField: 'image upload',
  48. serializers.ListField: 'list',
  49. serializers.DictField: 'nested object',
  50. serializers.Serializer: 'nested object',
  51. })
  52. def determine_metadata(self, request, view):
  53. metadata = OrderedDict()
  54. metadata['name'] = view.get_view_name()
  55. metadata['description'] = view.get_view_description()
  56. metadata['renders'] = [renderer.media_type for renderer in view.renderer_classes]
  57. metadata['parses'] = [parser.media_type for parser in view.parser_classes]
  58. if hasattr(view, 'get_serializer'):
  59. actions = self.determine_actions(request, view)
  60. if actions:
  61. metadata['actions'] = actions
  62. return metadata
  63. def determine_actions(self, request, view):
  64. """
  65. For generic class based views we return information about
  66. the fields that are accepted for 'PUT' and 'POST' methods.
  67. """
  68. actions = {}
  69. for method in {'PUT', 'POST'} & set(view.allowed_methods):
  70. view.request = clone_request(request, method)
  71. try:
  72. # Test global permissions
  73. if hasattr(view, 'check_permissions'):
  74. view.check_permissions(view.request)
  75. # Test object permissions
  76. if method == 'PUT' and hasattr(view, 'get_object'):
  77. view.get_object()
  78. except (exceptions.APIException, PermissionDenied, Http404):
  79. pass
  80. else:
  81. # If user has appropriate permissions for the view, include
  82. # appropriate metadata about the fields that should be supplied.
  83. serializer = view.get_serializer()
  84. actions[method] = self.get_serializer_info(serializer)
  85. finally:
  86. view.request = request
  87. return actions
  88. def get_serializer_info(self, serializer):
  89. """
  90. Given an instance of a serializer, return a dictionary of metadata
  91. about its fields.
  92. """
  93. if hasattr(serializer, 'child'):
  94. # If this is a `ListSerializer` then we want to examine the
  95. # underlying child serializer instance instead.
  96. serializer = serializer.child
  97. return OrderedDict([
  98. (field_name, self.get_field_info(field))
  99. for field_name, field in serializer.fields.items()
  100. if not isinstance(field, serializers.HiddenField)
  101. ])
  102. def get_field_info(self, field):
  103. """
  104. Given an instance of a serializer field, return a dictionary
  105. of metadata about it.
  106. """
  107. field_info = OrderedDict()
  108. field_info['type'] = self.label_lookup[field]
  109. field_info['required'] = getattr(field, 'required', False)
  110. attrs = [
  111. 'read_only', 'label', 'help_text',
  112. 'min_length', 'max_length',
  113. 'min_value', 'max_value'
  114. ]
  115. for attr in attrs:
  116. value = getattr(field, attr, None)
  117. if value is not None and value != '':
  118. field_info[attr] = force_str(value, strings_only=True)
  119. if getattr(field, 'child', None):
  120. field_info['child'] = self.get_field_info(field.child)
  121. elif getattr(field, 'fields', None):
  122. field_info['children'] = self.get_serializer_info(field)
  123. if (not field_info.get('read_only') and
  124. not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) and
  125. hasattr(field, 'choices')):
  126. field_info['choices'] = [
  127. {
  128. 'value': choice_value,
  129. 'display_name': force_str(choice_name, strings_only=True)
  130. }
  131. for choice_value, choice_name in field.choices.items()
  132. ]
  133. return field_info