Skip to content

Commit c0dace2

Browse files
committed
Merge pull request #90 from django-json-api/feature/Metadata
Added JSON:API OPTIONS metadata handler
2 parents d016f1a + 4d87815 commit c0dace2

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

README.rst

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ override ``settings.REST_FRAMEWORK``::
130130
'rest_framework_json_api.renderers.JSONRenderer',
131131
'rest_framework.renderers.BrowsableAPIRenderer',
132132
),
133+
'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata',
133134
}
134135

135136
If ``PAGINATE_BY`` is set the renderer will return a ``meta`` object with

rest_framework_json_api/metadata.py

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from collections import OrderedDict
2+
from django.db.models.fields import related
3+
from django.http import Http404
4+
from django.utils.encoding import force_text
5+
from rest_framework import serializers
6+
from rest_framework.exceptions import PermissionDenied
7+
from rest_framework.metadata import BaseMetadata, SimpleMetadata
8+
from rest_framework.request import clone_request
9+
from rest_framework.utils.field_mapping import ClassLookupDict
10+
from rest_framework_json_api import exceptions
11+
from rest_framework_json_api.utils import format_relation_name, get_related_resource_type
12+
13+
14+
class JSONAPIMetadata(SimpleMetadata):
15+
"""
16+
This is the JSON:API metadata implementation.
17+
It returns an ad-hoc set of information about the view.
18+
There are not any formalized standards for `OPTIONS` responses
19+
for us to base this on.
20+
"""
21+
type_lookup = ClassLookupDict({
22+
serializers.HyperlinkedIdentityField: 'Relationship',
23+
serializers.HyperlinkedRelatedField: 'Relationship',
24+
serializers.BooleanField: 'Boolean',
25+
serializers.NullBooleanField: 'Boolean',
26+
serializers.CharField: 'String',
27+
serializers.URLField: 'URL',
28+
serializers.EmailField: 'Email',
29+
serializers.RegexField: 'Regex',
30+
serializers.SlugField: 'Slug',
31+
serializers.IntegerField: 'Integer',
32+
serializers.FloatField: 'Float',
33+
serializers.DecimalField: 'Decimal',
34+
serializers.DateField: 'Date',
35+
serializers.DateTimeField: 'DateTime',
36+
serializers.TimeField: 'Time',
37+
serializers.ChoiceField: 'Choice',
38+
serializers.MultipleChoiceField: 'MultipleChoice',
39+
serializers.FileField: 'File',
40+
serializers.ImageField: 'Image',
41+
serializers.ListField: 'List',
42+
serializers.DictField: 'Dict',
43+
serializers.Serializer: 'Serializer',
44+
})
45+
46+
relation_type_lookup = ClassLookupDict({
47+
related.ReverseManyRelatedObjectsDescriptor: 'ManyToMany',
48+
related.ForeignRelatedObjectsDescriptor: 'OneToMany',
49+
related.ReverseSingleRelatedObjectDescriptor: 'ManyToOne',
50+
})
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+
metadata['allowed_methods'] = view.allowed_methods
59+
if hasattr(view, 'get_serializer'):
60+
actions = self.determine_actions(request, view)
61+
if actions:
62+
metadata['actions'] = actions
63+
return metadata
64+
65+
def get_serializer_info(self, serializer):
66+
"""
67+
Given an instance of a serializer, return a dictionary of metadata
68+
about its fields.
69+
"""
70+
if hasattr(serializer, 'child'):
71+
# If this is a `ListSerializer` then we want to examine the
72+
# underlying child serializer instance instead.
73+
serializer = serializer.child
74+
return OrderedDict([
75+
(field_name, self.get_field_info(field, serializer))
76+
for field_name, field in serializer.fields.items()
77+
])
78+
79+
def get_field_info(self, field, serializer):
80+
"""
81+
Given an instance of a serializer field, return a dictionary
82+
of metadata about it.
83+
"""
84+
field_info = OrderedDict()
85+
86+
if isinstance(field, serializers.ManyRelatedField):
87+
field_info['type'] = self.type_lookup[field.child_relation]
88+
else:
89+
field_info['type'] = self.type_lookup[field]
90+
91+
serializer_model = getattr(serializer.Meta, 'model')
92+
try:
93+
field_info['relationship_type'] = self.relation_type_lookup[getattr(serializer_model, field.field_name)]
94+
except KeyError:
95+
pass
96+
except AttributeError:
97+
pass
98+
else:
99+
field_info['relationship_resource'] = get_related_resource_type(field)
100+
101+
field_info['required'] = getattr(field, 'required', False)
102+
103+
attrs = [
104+
'read_only', 'write_only', 'label', 'help_text',
105+
'min_length', 'max_length',
106+
'min_value', 'max_value', 'initial'
107+
]
108+
109+
for attr in attrs:
110+
value = getattr(field, attr, None)
111+
if value is not None and value != '':
112+
field_info[attr] = force_text(value, strings_only=True)
113+
114+
if getattr(field, 'child', None):
115+
field_info['child'] = self.get_field_info(field.child, field.child.serializer)
116+
elif getattr(field, 'fields', None):
117+
field_info['children'] = self.get_serializer_info(field)
118+
119+
return field_info

0 commit comments

Comments
 (0)