metadata.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  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.NullBooleanField: 'boolean',
  33. serializers.CharField: 'string',
  34. serializers.UUIDField: 'string',
  35. serializers.URLField: 'url',
  36. serializers.EmailField: 'email',
  37. serializers.RegexField: 'regex',
  38. serializers.SlugField: 'slug',
  39. serializers.IntegerField: 'integer',
  40. serializers.FloatField: 'float',
  41. serializers.DecimalField: 'decimal',
  42. serializers.DateField: 'date',
  43. serializers.DateTimeField: 'datetime',
  44. serializers.TimeField: 'time',
  45. serializers.ChoiceField: 'choice',
  46. serializers.MultipleChoiceField: 'multiple choice',
  47. serializers.FileField: 'file upload',
  48. serializers.ImageField: 'image upload',
  49. serializers.ListField: 'list',
  50. serializers.DictField: 'nested object',
  51. serializers.Serializer: 'nested object',
  52. })
  53. def determine_metadata(self, request, view):
  54. metadata = OrderedDict()
  55. metadata['name'] = view.get_view_name()
  56. metadata['description'] = view.get_view_description()
  57. metadata['renders'] = [renderer.media_type for renderer in view.renderer_classes]
  58. metadata['parses'] = [parser.media_type for parser in view.parser_classes]
  59. if hasattr(view, 'get_serializer'):
  60. actions = self.determine_actions(request, view)
  61. if actions:
  62. metadata['actions'] = actions
  63. return metadata
  64. def determine_actions(self, request, view):
  65. """
  66. For generic class based views we return information about
  67. the fields that are accepted for 'PUT' and 'POST' methods.
  68. """
  69. actions = {}
  70. for method in {'PUT', 'POST'} & set(view.allowed_methods):
  71. view.request = clone_request(request, method)
  72. try:
  73. # Test global permissions
  74. if hasattr(view, 'check_permissions'):
  75. view.check_permissions(view.request)
  76. # Test object permissions
  77. if method == 'PUT' and hasattr(view, 'get_object'):
  78. view.get_object()
  79. except (exceptions.APIException, PermissionDenied, Http404):
  80. pass
  81. else:
  82. # If user has appropriate permissions for the view, include
  83. # appropriate metadata about the fields that should be supplied.
  84. serializer = view.get_serializer()
  85. actions[method] = self.get_serializer_info(serializer)
  86. finally:
  87. view.request = request
  88. return actions
  89. def get_serializer_info(self, serializer):
  90. """
  91. Given an instance of a serializer, return a dictionary of metadata
  92. about its fields.
  93. """
  94. if hasattr(serializer, 'child'):
  95. # If this is a `ListSerializer` then we want to examine the
  96. # underlying child serializer instance instead.
  97. serializer = serializer.child
  98. return OrderedDict([
  99. (field_name, self.get_field_info(field))
  100. for field_name, field in serializer.fields.items()
  101. if not isinstance(field, serializers.HiddenField)
  102. ])
  103. def get_field_info(self, field):
  104. """
  105. Given an instance of a serializer field, return a dictionary
  106. of metadata about it.
  107. """
  108. field_info = OrderedDict()
  109. field_info['type'] = self.label_lookup[field]
  110. field_info['required'] = getattr(field, 'required', False)
  111. attrs = [
  112. 'read_only', 'label', 'help_text',
  113. 'min_length', 'max_length',
  114. 'min_value', 'max_value'
  115. ]
  116. for attr in attrs:
  117. value = getattr(field, attr, None)
  118. if value is not None and value != '':
  119. field_info[attr] = force_str(value, strings_only=True)
  120. if getattr(field, 'child', None):
  121. field_info['child'] = self.get_field_info(field.child)
  122. elif getattr(field, 'fields', None):
  123. field_info['children'] = self.get_serializer_info(field)
  124. if (not field_info.get('read_only') and
  125. not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) and
  126. hasattr(field, 'choices')):
  127. field_info['choices'] = [
  128. {
  129. 'value': choice_value,
  130. 'display_name': force_str(choice_name, strings_only=True)
  131. }
  132. for choice_value, choice_name in field.choices.items()
  133. ]
  134. return field_info