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 42e0554

Browse files
committed
Merge branch develop into feature/compound_documents
2 parents 73bc6d5 + ce945f2 commit 42e0554

File tree

12 files changed

+444
-349
lines changed

12 files changed

+444
-349
lines changed

‎.travis.yml‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,25 @@ script: tox
66
env:
77
- TOXENV=py27-django17-drf31
88
- TOXENV=py27-django17-drf32
9-
- TOXENV=py32-django17-drf31
10-
- TOXENV=py32-django17-drf32
119
- TOXENV=py33-django17-drf31
1210
- TOXENV=py33-django17-drf32
1311
- TOXENV=py34-django17-drf31
1412
- TOXENV=py34-django17-drf32
1513
- TOXENV=py27-django18-drf31
1614
- TOXENV=py27-django18-drf32
17-
- TOXENV=py32-django18-drf31
18-
- TOXENV=py32-django18-drf32
15+
- TOXENV=py27-django18-drf33
1916
- TOXENV=py33-django18-drf31
2017
- TOXENV=py33-django18-drf32
18+
- TOXENV=py33-django18-drf33
2119
- TOXENV=py34-django18-drf31
2220
- TOXENV=py34-django18-drf32
21+
- TOXENV=py34-django18-drf33
2322
- TOXENV=py27-django19-drf31
2423
- TOXENV=py27-django19-drf32
24+
- TOXENV=py27-django19-drf33
2525
- TOXENV=py34-django19-drf31
2626
- TOXENV=py34-django19-drf32
27+
- TOXENV=py34-django19-drf33
2728
- TOXENV=py35-django19-drf31
2829
- TOXENV=py35-django19-drf32
30+
- TOXENV=py35-django19-drf33

‎example/serializers.py‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,31 @@ class Meta:
1111

1212
class EntrySerializer(serializers.ModelSerializer):
1313

14+
def __init__(self, *args, **kwargs):
15+
# to make testing more concise we'll only output the
16+
# `suggested` field when it's requested via `include`
17+
request = kwargs.get('context', {}).get('request')
18+
if request and 'suggested' not in request.query_params.get('include', []):
19+
self.fields.pop('suggested')
20+
super(EntrySerializer, self).__init__(*args, **kwargs)
21+
1422
included_serializers = {
1523
'comments': 'example.serializers.CommentSerializer',
24+
'suggested': 'example.serializers.EntrySerializer',
1625
}
1726

1827
comments = relations.ResourceRelatedField(
1928
source='comment_set', many=True, read_only=True)
29+
suggested = relations.SerializerMethodResourceRelatedField(
30+
source='get_suggested', model=Entry, read_only=True)
31+
32+
def get_suggested(self, obj):
33+
return Entry.objects.exclude(pk=obj.pk).first()
2034

2135
class Meta:
2236
model = Entry
2337
fields = ('blog', 'headline', 'body_text', 'pub_date', 'mod_date',
24-
'authors', 'comments',)
38+
'authors', 'comments','suggested',)
2539

2640

2741
class AuthorSerializer(serializers.ModelSerializer):

‎example/tests/integration/test_includes.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,12 @@ def test_included_data_on_detail(single_entry, client):
2727
comment_count = len([resource for resource in included if resource["type"] == "comments"])
2828
expected_comment_count = single_entry.comment_set.count()
2929
assert comment_count == expected_comment_count, 'Detail comment count is incorrect'
30+
31+
def test_dynamic_related_data_is_included(single_entry, entry_factory, client):
32+
entry_factory()
33+
response = client.get(reverse("entry-detail", kwargs={'pk': single_entry.pk}) + '?include=suggested')
34+
included = load_json(response.content).get('included')
35+
36+
assert [x.get('type') for x in included] == ['entries'], 'Dynamic included types are incorrect'
37+
assert len(included) == 1, 'The dynamically included blog entries are of an incorrect count'
38+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from django.core.urlresolvers import reverse
2+
3+
import pytest
4+
5+
6+
pytestmark = pytest.mark.django_db
7+
8+
9+
def test_sparse_fieldset_ordered_dict_error(multiple_entries, client):
10+
base_url = reverse('entry-list')
11+
querystring = '?fields[entries]=blog,headline'
12+
response = client.get(base_url + querystring) # RuntimeError: OrderedDict mutated during iteration
13+
assert response.status_code == 200 # succeed if we didn't fail due to the above RuntimeError
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import pytest
2+
from django.contrib.auth import get_user_model
3+
4+
from rest_framework_json_api import serializers
5+
from rest_framework_json_api.renderers import JSONRenderer
6+
7+
pytestmark = pytest.mark.django_db
8+
9+
class ResourceSerializer(serializers.ModelSerializer):
10+
class Meta:
11+
fields = ('username',)
12+
model = get_user_model()
13+
14+
15+
def test_build_json_resource_obj():
16+
resource = {
17+
'pk': 1,
18+
'username': 'Alice',
19+
}
20+
21+
serializer = ResourceSerializer(data={'username': 'Alice'})
22+
serializer.is_valid()
23+
resource_instance = serializer.save()
24+
25+
output = {
26+
'type': 'user',
27+
'id': '1',
28+
'attributes': {
29+
'username': 'Alice'
30+
},
31+
}
32+
33+
assert JSONRenderer.build_json_resource_obj(
34+
serializer.fields, resource, resource_instance, 'user') == output
35+
36+
37+
def test_extract_attributes():
38+
fields = {
39+
'id': serializers.Field(),
40+
'username': serializers.Field(),
41+
'deleted': serializers.ReadOnlyField(),
42+
}
43+
resource = {'id': 1, 'deleted': None, 'username': 'jerel'}
44+
expected = {
45+
'username': 'jerel',
46+
'deleted': None
47+
}
48+
assert sorted(JSONRenderer.extract_attributes(fields, resource)) == sorted(expected), 'Regular fields should be extracted'
49+
assert sorted(JSONRenderer.extract_attributes(fields, {})) == sorted(
50+
{'username': ''}), 'Should not extract read_only fields on empty serializer'

‎example/tests/unit/test_utils.py‎

Lines changed: 9 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,35 @@
11
import pytest
2-
from django.utils import six
32
from django.conf import settings
43
from django.contrib.auth import get_user_model
4+
from django.utils import six
55
from rest_framework import serializers
6+
from rest_framework.generics import GenericAPIView
67
from rest_framework.response import Response
78
from rest_framework.views import APIView
89

9-
from rest_framework_json_api import utils
1010
from example.serializers import (EntrySerializer, BlogSerializer,
1111
AuthorSerializer, CommentSerializer)
12+
from rest_framework_json_api import utils
1213
from rest_framework_json_api.utils import get_included_serializers
1314

1415
pytestmark = pytest.mark.django_db
1516

1617

17-
class ResourceView(APIView):
18-
pass
19-
20-
2118
class ResourceSerializer(serializers.ModelSerializer):
22-
class Meta():
19+
class Meta:
2320
fields = ('username',)
2421
model = get_user_model()
2522

2623

2724
def test_get_resource_name():
28-
view = ResourceView()
25+
view = APIView()
2926
context = {'view': view}
3027
setattr(settings, 'JSON_API_FORMAT_RELATION_KEYS', None)
31-
assert 'ResourceViews' == utils.get_resource_name(context), 'not formatted'
28+
assert 'APIViews' == utils.get_resource_name(context), 'not formatted'
3229

33-
view = ResourceView()
3430
context = {'view': view}
3531
setattr(settings, 'JSON_API_FORMAT_RELATION_KEYS', 'dasherize')
36-
assert 'resource-views' == utils.get_resource_name(context), 'derived from view'
32+
assert 'api-views' == utils.get_resource_name(context), 'derived from view'
3733

3834
view.model = get_user_model()
3935
assert 'users' == utils.get_resource_name(context), 'derived from view model'
@@ -47,9 +43,9 @@ def test_get_resource_name():
4743
view.response = Response(status=500)
4844
assert 'errors' == utils.get_resource_name(context), 'handles 500 error'
4945

50-
view = ResourceView()
51-
context = {'view': view}
46+
view = GenericAPIView()
5247
view.serializer_class = ResourceSerializer
48+
context = {'view': view}
5349
assert 'users' == utils.get_resource_name(context), 'derived from serializer'
5450

5551
view.serializer_class.Meta.resource_name = 'rcustom'
@@ -90,28 +86,6 @@ def test_format_relation_name():
9086
assert utils.format_relation_name('first_name', 'camelize') == 'firstNames'
9187

9288

93-
def test_build_json_resource_obj():
94-
resource = {
95-
'pk': 1,
96-
'username': 'Alice',
97-
}
98-
99-
serializer = ResourceSerializer(data={'username': 'Alice'})
100-
serializer.is_valid()
101-
resource_instance = serializer.save()
102-
103-
output = {
104-
'type': 'user',
105-
'id': '1',
106-
'attributes': {
107-
'username': 'Alice'
108-
},
109-
}
110-
111-
assert utils.build_json_resource_obj(
112-
serializer.fields, resource, resource_instance, 'user') == output
113-
114-
11589
class SerializerWithIncludedSerializers(EntrySerializer):
11690
included_serializers = {
11791
'blog': BlogSerializer,

‎rest_framework_json_api/pagination.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""
22
Pagination fields
33
"""
4+
from collections import OrderedDict
45
from rest_framework import serializers
56
from rest_framework.views import Response
6-
from rest_framework.compat import OrderedDict
77
from rest_framework.pagination import PageNumberPagination
88
from rest_framework.templatetags.rest_framework import replace_query_param
99

‎rest_framework_json_api/relations.py‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ def __init__(self, self_link_view_name=None, related_link_view_name=None, **kwar
3131
self.related_link_lookup_field = kwargs.pop('related_link_lookup_field', self.related_link_lookup_field)
3232
self.related_link_url_kwarg = kwargs.pop('related_link_url_kwarg', self.related_link_lookup_field)
3333

34+
# check for a model class that was passed in for the relation type
35+
model = kwargs.pop('model', None)
36+
if model:
37+
self.model = model
38+
3439
# We include this simply for dependency injection in tests.
3540
# We can't add it as a class attributes or it would expect an
3641
# implicit `self` argument to be passed.
@@ -104,7 +109,11 @@ def get_links(self, obj=None, lookup_field='pk'):
104109

105110
def to_internal_value(self, data):
106111
if isinstance(data, six.text_type):
107-
data = json.loads(data)
112+
try:
113+
data = json.loads(data)
114+
except ValueError:
115+
# show a useful error if they send a `pk` instead of resource object
116+
self.fail('incorrect_type', data_type=type(data).__name__)
108117
if not isinstance(data, dict):
109118
self.fail('incorrect_type', data_type=type(data).__name__)
110119
expected_relation_type = get_resource_type_from_queryset(self.queryset)
@@ -136,3 +145,12 @@ def choices(self):
136145
for item in queryset
137146
])
138147

148+
149+
class SerializerMethodResourceRelatedField(ResourceRelatedField):
150+
def get_attribute(self, instance):
151+
# check for a source fn defined on the serializer instead of the model
152+
if self.source and hasattr(self.parent, self.source):
153+
serializer_method = getattr(self.parent, self.source)
154+
if hasattr(serializer_method, '__call__'):
155+
return serializer_method(instance)
156+
return super(ResourceRelatedField, self).get_attribute(instance)

0 commit comments

Comments
(0)

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