Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit cf8a930

Browse files
committed
Merge pull request #110 from django-json-api/feature/compound_documents
Complete support for compound documents
2 parents c1970c1 + 6460edb commit cf8a930

File tree

4 files changed

+122
-11
lines changed

4 files changed

+122
-11
lines changed

‎docs/usage.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
The DJA package implements a custom renderer, parser, exception handler, and
55
pagination. To get started enable the pieces in `settings.py` that you want to use.
66

7+
Many features of the JSON:API format standard have been implemented using Mixin classes in `serializers.py`.
8+
The easiest way to make use of those features is to import ModelSerializer variants
9+
from `rest_framework_json_api` instead of the usual `rest_framework`
10+
711
### Configuration
812
We suggest that you simply copy the settings block below and modify it if necessary.
913
``` python

‎rest_framework_json_api/renderers.py‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
6767
{resource_name: data}, accepted_media_type, renderer_context
6868
)
6969

70+
include_resources_param = request.query_params.get('include') if request else None
71+
if include_resources_param:
72+
included_resources = include_resources_param.split(',')
73+
else:
74+
included_resources = list()
75+
7076
json_api_included = list()
7177

7278
if view and hasattr(view, 'action') and view.action == 'list' and \
@@ -88,7 +94,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
8894
resource_instance = resource_serializer.instance[position] # Get current instance
8995
json_api_data.append(
9096
utils.build_json_resource_obj(fields, resource, resource_instance, resource_name))
91-
included = utils.extract_included(fields, resource, resource_instance)
97+
included = utils.extract_included(fields, resource, resource_instance, included_resources)
9298
if included:
9399
json_api_included.extend(included)
94100
else:
@@ -97,7 +103,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
97103
fields = utils.get_serializer_fields(data.serializer)
98104
resource_instance = data.serializer.instance
99105
json_api_data = utils.build_json_resource_obj(fields, data, resource_instance, resource_name)
100-
included = utils.extract_included(fields, data, resource_instance)
106+
included = utils.extract_included(fields, data, resource_instance, included_resources)
101107
if included:
102108
json_api_included.extend(included)
103109
else:

‎rest_framework_json_api/serializers.py‎

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from django.utils.translation import ugettext_lazy as _
2+
from rest_framework.exceptions import ParseError
23
from rest_framework.serializers import *
34

45
from rest_framework_json_api.utils import format_relation_name, get_resource_type_from_instance, \
@@ -58,13 +59,78 @@ def __init__(self, *args, **kwargs):
5859
super(SparseFieldsetsMixin, self).__init__(*args, **kwargs)
5960

6061

61-
class HyperlinkedModelSerializer(SparseFieldsetsMixin, HyperlinkedModelSerializer):
62+
class IncludedResourcesValidationMixin(object):
63+
def __init__(self, *args, **kwargs):
64+
context = kwargs.get('context')
65+
request = context.get('request') if context else None
66+
view = context.get('view') if context else None
67+
68+
def validate_path(serializer_class, field_path, serializers, path):
69+
serializers = {
70+
key: serializer_class if serializer == 'self' else serializer
71+
for key, serializer in serializers.items()
72+
} if serializers else dict()
73+
if serializers is None:
74+
raise ParseError('This endpoint does not support the include parameter')
75+
this_field_name = field_path[0]
76+
this_included_serializer = serializers.get(this_field_name)
77+
if this_included_serializer is None:
78+
raise ParseError(
79+
'This endpoint does not support the include parameter for path {}'.format(
80+
path
81+
)
82+
)
83+
if len(field_path) > 1:
84+
new_included_field_path = field_path[-1:]
85+
# We go down one level in the path
86+
validate_path(this_included_serializer, new_included_field_path, serializers, path)
87+
88+
if request and view:
89+
include_resources_param = request.query_params.get('include') if request else None
90+
if include_resources_param:
91+
included_resources = include_resources_param.split(',')
92+
for included_field_name in included_resources:
93+
included_field_path = included_field_name.split('.')
94+
this_serializer_class = view.serializer_class
95+
included_serializers = getattr(this_serializer_class, 'included_serializers', None)
96+
# lets validate the current path
97+
validate_path(this_serializer_class, included_field_path, included_serializers, included_field_name)
98+
99+
super(IncludedResourcesValidationMixin, self).__init__(*args, **kwargs)
100+
101+
102+
class HyperlinkedModelSerializer(IncludedResourcesValidationMixin, SparseFieldsetsMixin, HyperlinkedModelSerializer):
62103
"""
63104
A type of `ModelSerializer` that uses hyperlinked relationships instead
64105
of primary key relationships. Specifically:
65106
66107
* A 'url' field is included instead of the 'id' field.
67108
* Relationships to other instances are hyperlinks, instead of primary keys.
68109
110+
Included Mixins:
111+
* A mixin class to enable sparse fieldsets is included
112+
* A mixin class to enable validation of included resources is included
113+
"""
114+
115+
116+
class ModelSerializer(IncludedResourcesValidationMixin, SparseFieldsetsMixin, ModelSerializer):
117+
"""
118+
A `ModelSerializer` is just a regular `Serializer`, except that:
119+
120+
* A set of default fields are automatically populated.
121+
* A set of default validators are automatically populated.
122+
* Default `.create()` and `.update()` implementations are provided.
123+
124+
The process of automatically determining a set of serializer fields
125+
based on the model fields is reasonably complex, but you almost certainly
126+
don't need to dig into the implementation.
127+
128+
If the `ModelSerializer` class *doesn't* generate the set of fields that
129+
you need you should either declare the extra/differing fields explicitly on
130+
the serializer class, or simply use a `Serializer` class.
131+
132+
133+
Included Mixins:
69134
* A mixin class to enable sparse fieldsets is included
135+
* A mixin class to enable validation of included resources is included
70136
"""

‎rest_framework_json_api/utils.py‎

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from rest_framework.settings import api_settings
1212
from rest_framework.exceptions import APIException
1313

14-
1514
try:
1615
from rest_framework.compat import OrderedDict
1716
except ImportError:
@@ -190,7 +189,6 @@ def get_related_resource_type(relation):
190189

191190

192191
def get_instance_or_manager_resource_type(resource_instance_or_manager):
193-
194192
if hasattr(resource_instance_or_manager, 'model'):
195193
return get_resource_type_from_manager(resource_instance_or_manager)
196194
if hasattr(resource_instance_or_manager, '_meta'):
@@ -396,19 +394,45 @@ def extract_relationships(fields, resource, resource_instance):
396394
return format_keys(data)
397395

398396

399-
def extract_included(fields, resource, resource_instance):
397+
def extract_included(fields, resource, resource_instance, included_resources):
400398
included_data = list()
399+
400+
current_serializer = fields.serializer
401+
context = current_serializer.context
402+
included_serializers = getattr(fields.serializer, 'included_serializers', None)
403+
404+
included_serializers = {
405+
key: current_serializer.__class__ if serializer == 'self' else serializer
406+
for key, serializer in included_serializers.items()
407+
} if included_serializers else dict()
408+
401409
for field_name, field in six.iteritems(fields):
402410
# Skip URL field
403411
if field_name == api_settings.URL_FIELD_NAME:
404412
continue
405413

406-
# Skip fields without serialized data
407-
if not isinstance(field, BaseSerializer):
414+
# Skip fields without relations or serialized data
415+
if not isinstance(field, (RelatedField, ManyRelatedField, BaseSerializer)):
416+
continue
417+
418+
try:
419+
included_resources.remove(field_name)
420+
relation_instance_or_manager = getattr(resource_instance, field_name)
421+
serializer_data = resource.get(field_name)
422+
new_included_resources = [key.replace('%s.' % field_name, '', 1) for key in included_resources]
423+
except ValueError:
424+
# Skip fields not in requested included resources
408425
continue
409426

410-
relation_instance_or_manager = getattr(resource_instance, field_name)
411-
serializer_data = resource.get(field_name)
427+
if isinstance(field, ManyRelatedField):
428+
serializer_class = included_serializers.get(field_name)
429+
field = serializer_class(relation_instance_or_manager.all(), many=True, context=context)
430+
serializer_data = field.data
431+
432+
if isinstance(field, RelatedField):
433+
serializer_class = included_serializers.get(field_name)
434+
field = serializer_class(relation_instance_or_manager, context=context)
435+
serializer_data = field.data
412436

413437
if isinstance(field, ListSerializer):
414438
serializer = field.child
@@ -427,6 +451,11 @@ def extract_included(fields, resource, resource_instance):
427451
serializer_fields, serializer_resource, nested_resource_instance, relation_type
428452
)
429453
)
454+
included_data.extend(
455+
extract_included(
456+
serializer_fields, serializer_resource, nested_resource_instance, new_included_resources
457+
)
458+
)
430459

431460
if isinstance(field, ModelSerializer):
432461
model = field.Meta.model
@@ -436,7 +465,13 @@ def extract_included(fields, resource, resource_instance):
436465
serializer_fields = get_serializer_fields(field)
437466
if serializer_data:
438467
included_data.append(
439-
build_json_resource_obj(serializer_fields, serializer_data, relation_instance_or_manager, relation_type)
468+
build_json_resource_obj(serializer_fields, serializer_data, relation_instance_or_manager,
469+
relation_type)
470+
)
471+
included_data.extend(
472+
extract_included(
473+
serializer_fields, serializer_data, relation_instance_or_manager, new_included_resources
474+
)
440475
)
441476

442477
return format_keys(included_data)

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /