from collections import OrderedDict from django.db.models.fields import related from django.utils.encoding import force_str from rest_framework import serializers from rest_framework.metadata import SimpleMetadata from rest_framework.settings import api_settings from rest_framework.utils.field_mapping import ClassLookupDict from rest_framework_json_api.utils import format_value, get_related_resource_type class JSONAPIMetadata(SimpleMetadata): """ This is the JSON:API 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. """ type_lookup = ClassLookupDict({ serializers.Field: 'GenericField', serializers.RelatedField: 'Relationship', serializers.BooleanField: 'Boolean', serializers.NullBooleanField: '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: 'MultipleChoice', serializers.FileField: 'File', serializers.ImageField: 'Image', serializers.ListField: 'List', serializers.DictField: 'Dict', serializers.Serializer: 'Serializer', }) try: relation_type_lookup = ClassLookupDict({ related.ManyToManyDescriptor: 'ManyToMany', related.ReverseManyToOneDescriptor: 'OneToMany', related.ForwardManyToOneDescriptor: 'ManyToOne', }) except AttributeError: relation_type_lookup = ClassLookupDict({ related.ManyRelatedObjectsDescriptor: 'ManyToMany', related.ReverseManyRelatedObjectsDescriptor: 'ManyToMany', related.ForeignRelatedObjectsDescriptor: 'OneToMany', related.ReverseSingleRelatedObjectDescriptor: 'ManyToOne', }) 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] metadata['allowed_methods'] = view.allowed_methods if hasattr(view, 'get_serializer'): actions = self.determine_actions(request, view) if actions: metadata['actions'] = actions return metadata 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 # Remove the URL field if present serializer.fields.pop(api_settings.URL_FIELD_NAME, None) return OrderedDict([ (format_value(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() serializer = field.parent if isinstance(field, serializers.ManyRelatedField): field_info['type'] = self.type_lookup[field.child_relation] else: field_info['type'] = self.type_lookup[field] try: serializer_model = getattr(serializer.Meta, 'model') field_info['relationship_type'] = self.relation_type_lookup[ getattr(serializer_model, field.field_name) ] except KeyError: pass except AttributeError: pass else: field_info['relationship_resource'] = get_related_resource_type(field) field_info['required'] = getattr(field, 'required', False) attrs = [ 'read_only', 'write_only', 'label', 'help_text', 'min_length', 'max_length', 'min_value', 'max_value', 'initial' ] 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 field_info.get('relationship_resource') 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() ] if hasattr(serializer, 'included_serializers') and 'relationship_resource' in field_info: field_info['allows_include'] = field.field_name in serializer.included_serializers return field_info