Skip to content

Commit 9aa259c

Browse files
committed
Merge branch 'develop' of github.com:django-json-api/django-rest-framework-json-api
2 parents 3471569 + cebecf5 commit 9aa259c

12 files changed

+127
-25
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ var/
2020
*.egg-info/
2121
.installed.cfg
2222
*.egg
23+
.eggs/
2324

2425
# Installer logs
2526
pip-log.txt

.travis.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ env:
2626

2727
- DJANGO=">=1.10,<1.11" DRF=">=3.4,<3.5"
2828
before_install:
29-
# Force an upgrade of py to avoid VersionConflict
29+
# Force an upgrade of py & pytest to avoid VersionConflict
3030
- pip install --upgrade py
31+
- pip install "pytest>=2.8,<3"
3132
- pip install codecov
3233
install:
3334
- pip install Django${DJANGO} djangorestframework${DRF}

docs/usage.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ class OrderSerializer(serializers.ModelSerializer):
318318

319319
customer = ResourceRelatedField(
320320
queryset=Customer.objects,
321-
related_link_view-name='order-customer-detail',
321+
related_link_view_name='order-customer-detail',
322322
related_link_url_kwarg='order_pk',
323323
self_link_view_name='order-relationships'
324324
)
@@ -399,7 +399,7 @@ The urlconf would need to contain a route like the following:
399399

400400
```python
401401
url(
402-
regex=r'^orders/(?P<pk>[^/.]+/relationships/(?P<related_field>[^/.]+)$',
402+
regex=r'^orders/(?P<pk>[^/.]+)/relationships/(?P<related_field>[^/.]+)$',
403403
view=OrderRelationshipView.as_view(),
404404
name='order-relationships'
405405
)

example/tests/test_utils.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""
2+
Test rest_framework_json_api's utils functions.
3+
"""
4+
from rest_framework_json_api import utils
5+
6+
from ..serializers import EntrySerializer
7+
from ..tests import TestBase
8+
9+
10+
class GetRelatedResourceTests(TestBase):
11+
"""
12+
Ensure the `get_related_resource_type` function returns correct types.
13+
"""
14+
15+
def test_reverse_relation(self):
16+
"""
17+
Ensure reverse foreign keys have their types identified correctly.
18+
"""
19+
serializer = EntrySerializer()
20+
field = serializer.fields['comments']
21+
22+
self.assertEqual(utils.get_related_resource_type(field), 'comments')
23+
24+
def test_m2m_relation(self):
25+
"""
26+
Ensure m2ms have their types identified correctly.
27+
"""
28+
serializer = EntrySerializer()
29+
field = serializer.fields['authors']
30+
31+
self.assertEqual(utils.get_related_resource_type(field), 'authors')

example/tests/unit/test_renderers.py

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from example.models import Entry, Comment
2+
from rest_framework_json_api import serializers, views
3+
from rest_framework_json_api.renderers import JSONRenderer
4+
5+
6+
# serializers
7+
class RelatedModelSerializer(serializers.ModelSerializer):
8+
class Meta:
9+
model = Comment
10+
fields = ('id',)
11+
12+
13+
class DummyTestSerializer(serializers.ModelSerializer):
14+
'''
15+
This serializer is a simple compound document serializer which includes only
16+
a single embedded relation
17+
'''
18+
related_models = RelatedModelSerializer(
19+
source='comment_set', many=True, read_only=True)
20+
21+
class Meta:
22+
model = Entry
23+
fields = ('related_models',)
24+
25+
class JSONAPIMeta:
26+
included_resources = ('related_models',)
27+
28+
29+
# views
30+
class DummyTestViewSet(views.ModelViewSet):
31+
queryset = Entry.objects.all()
32+
serializer_class = DummyTestSerializer
33+
34+
35+
def test_simple_reverse_relation_included_renderer():
36+
'''
37+
Test renderer when a single reverse fk relation is passed.
38+
'''
39+
serializer = DummyTestSerializer(instance=Entry())
40+
renderer = JSONRenderer()
41+
rendered = renderer.render(
42+
serializer.data,
43+
renderer_context={'view': DummyTestViewSet()})
44+
45+
assert rendered

requirements-development.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
-e .
2-
pytest==2.8.2
2+
pytest>=2.9.0,<3.0
33
pytest-django
44
pytest-factoryboy
55
fake-factory

rest_framework_json_api/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: utf-8 -*-
22

33
__title__ = 'djangorestframework-jsonapi'
4-
__version__ = '2.1.0'
4+
__version__ = '2.1.1'
55
__author__ = ''
66
__license__ = 'MIT'
77
__copyright__ = ''

rest_framework_json_api/parsers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def parse(self, stream, media_type=None, parser_context=None):
9292
raise ParseError("The resource identifier object must contain an 'id' member")
9393

9494
# Construct the return data
95-
parsed_data = {'id': data.get('id')}
95+
parsed_data = {'id': data.get('id')} if 'id' in data else {}
9696
parsed_data.update(self.parse_attributes(data))
9797
parsed_data.update(self.parse_relationships(data))
9898
parsed_data.update(self.parse_metadata(result))

rest_framework_json_api/renderers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ def extract_relationships(fields, resource, resource_instance):
199199
})
200200
continue
201201

202-
if isinstance(field, ListSerializer) and relation_instance is not None:
202+
if isinstance(field, ListSerializer):
203203
resolved, relation_instance = utils.get_relation_instance(resource_instance, source, field.parent)
204204
if not resolved:
205205
continue

rest_framework_json_api/utils.py

+27-17
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@
22
Utils.
33
"""
44
import copy
5+
import inspect
56
import warnings
67
from collections import OrderedDict
7-
import inspect
88

99
import inflection
10+
from rest_framework import exceptions
11+
from rest_framework.exceptions import APIException
12+
13+
import django
1014
from django.conf import settings
11-
from django.utils import encoding
12-
from django.utils import six
15+
from django.db.models import Manager
16+
from django.utils import encoding, six
1317
from django.utils.module_loading import import_string as import_class_from_dotted_path
1418
from django.utils.translation import ugettext_lazy as _
15-
from django.db.models import Manager
16-
from rest_framework.exceptions import APIException
17-
from rest_framework import exceptions
1819

1920
try:
2021
from rest_framework.serializers import ManyRelatedField
@@ -26,6 +27,14 @@
2627
except ImportError:
2728
HyperlinkedRouterField = type(None)
2829

30+
if django.VERSION >= (1, 9):
31+
from django.db.models.fields.related_descriptors import ManyToManyDescriptor, ReverseManyToOneDescriptor
32+
ReverseManyRelatedObjectsDescriptor = type(None)
33+
else:
34+
from django.db.models.fields.related import ManyRelatedObjectsDescriptor as ManyToManyDescriptor
35+
from django.db.models.fields.related import ForeignRelatedObjectsDescriptor as ReverseManyToOneDescriptor
36+
from django.db.models.fields.related import ReverseManyRelatedObjectsDescriptor
37+
2938

3039
def get_resource_name(context):
3140
"""
@@ -87,6 +96,7 @@ def get_serializer_fields(serializer):
8796
pass
8897
return fields
8998

99+
90100
def format_keys(obj, format_type=None):
91101
"""
92102
Takes either a dict or list and returns it with camelized keys only if
@@ -148,6 +158,7 @@ def format_relation_name(value, format_type=None):
148158
pluralize = getattr(settings, 'JSON_API_PLURALIZE_RELATION_TYPE', None)
149159
return format_resource_type(value, format_type, pluralize)
150160

161+
151162
def format_resource_type(value, format_type=None, pluralize=None):
152163
if format_type is None:
153164
format_type = getattr(settings, 'JSON_API_FORMAT_TYPES', False)
@@ -167,7 +178,6 @@ def get_related_resource_type(relation):
167178
return get_resource_type_from_serializer(relation)
168179
except AttributeError:
169180
pass
170-
171181
relation_model = None
172182
if hasattr(relation, '_meta'):
173183
relation_model = relation._meta.model
@@ -184,7 +194,7 @@ def get_related_resource_type(relation):
184194
elif hasattr(parent_serializer, 'parent') and hasattr(parent_serializer.parent, 'Meta'):
185195
parent_model = getattr(parent_serializer.parent.Meta, 'model', None)
186196

187-
if parent_model is not None:
197+
if parent_model is not None:
188198
if relation.source:
189199
if relation.source != '*':
190200
parent_model_relation = getattr(parent_model, relation.source)
@@ -193,17 +203,17 @@ def get_related_resource_type(relation):
193203
else:
194204
parent_model_relation = getattr(parent_model, parent_serializer.field_name)
195205

196-
if hasattr(parent_model_relation, 'related'):
197-
try:
206+
if type(parent_model_relation) is ReverseManyToOneDescriptor:
207+
if django.VERSION >= (1, 9):
208+
relation_model = parent_model_relation.rel.related_model
209+
elif django.VERSION >= (1, 8):
198210
relation_model = parent_model_relation.related.related_model
199-
except AttributeError:
200-
# Django 1.7
211+
else:
201212
relation_model = parent_model_relation.related.model
202-
elif hasattr(parent_model_relation, 'field'):
203-
try:
204-
relation_model = parent_model_relation.field.remote_field.model
205-
except AttributeError:
206-
relation_model = parent_model_relation.field.related.model
213+
elif type(parent_model_relation) is ManyToManyDescriptor:
214+
relation_model = parent_model_relation.field.remote_field.model
215+
elif type(parent_model_relation) is ReverseManyRelatedObjectsDescriptor:
216+
relation_model = parent_model_relation.field.related.model
207217
else:
208218
return get_related_resource_type(parent_model_relation)
209219

setup.cfg

+14
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,17 @@ test = pytest
33

44
[wheel]
55
universal = 1
6+
7+
[flake8]
8+
ignore = E501
9+
max-line-length = 100
10+
11+
[isort]
12+
known_django = django
13+
sections = FUTURE,STDLIB,THIRDPARTY,DJANGO,FIRSTPARTY,LOCALFOLDER
14+
default_section = THIRDPARTY
15+
known_standard_library = factory,mock,requests
16+
known_first_party = rest_framework_json_api
17+
multi_line_output = 3
18+
line_length = 100
19+
indent = 4

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def get_package_data(package):
105105
tests_require=[
106106
'pytest-factoryboy',
107107
'pytest-django',
108-
'pytest>=2.8',
108+
'pytest>=2.8,<3',
109109
] + mock,
110110
zip_safe=False,
111111
)

0 commit comments

Comments
 (0)