Skip to content

Commit 874bd9c

Browse files
author
Boris Pleshakov
committed
Merge master
2 parents 72df66f + ff0f93a commit 874bd9c

File tree

3 files changed

+87
-3
lines changed

3 files changed

+87
-3
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ any parts of the framework not mentioned in the documentation should generally b
1313
### Added
1414

1515
* Added support for serializiing complex structures as attributes. For details please reffer to #769
16+
* Avoid `AttributeError` for PUT and PATCH methods when using `APIView`
1617

1718
## [3.1.0] - 2020-02-08
1819

example/tests/test_parsers.py

+82
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import json
22
from io import BytesIO
33

4+
from django.conf.urls import url
45
from django.test import TestCase, override_settings
6+
from django.urls import reverse
7+
from rest_framework import views, status
58
from rest_framework.exceptions import ParseError
9+
from rest_framework.response import Response
10+
from rest_framework.test import APITestCase
611

12+
from rest_framework_json_api import serializers
713
from rest_framework_json_api.parsers import JSONParser
14+
from rest_framework_json_api.renderers import JSONRenderer
815

916

1017
class TestJSONParser(TestCase):
@@ -69,3 +76,78 @@ def test_parse_invalid_data_key(self):
6976

7077
with self.assertRaises(ParseError):
7178
parser.parse(stream, None, self.parser_context)
79+
80+
81+
class DummyDTO:
82+
def __init__(self, response_dict):
83+
for k, v in response_dict.items():
84+
setattr(self, k, v)
85+
86+
@property
87+
def pk(self):
88+
return self.id if hasattr(self, 'id') else None
89+
90+
91+
class DummySerializer(serializers.Serializer):
92+
body = serializers.CharField()
93+
id = serializers.IntegerField()
94+
95+
96+
class DummyAPIView(views.APIView):
97+
parser_classes = [JSONParser]
98+
renderer_classes = [JSONRenderer]
99+
resource_name = 'dummy'
100+
101+
def patch(self, request, *args, **kwargs):
102+
serializer = DummySerializer(DummyDTO(request.data))
103+
return Response(status=status.HTTP_200_OK, data=serializer.data)
104+
105+
106+
urlpatterns = [
107+
url(r'repeater$', DummyAPIView.as_view(), name='repeater'),
108+
]
109+
110+
111+
class TestParserOnAPIView(APITestCase):
112+
113+
def setUp(self):
114+
class MockRequest(object):
115+
def __init__(self):
116+
self.method = 'PATCH'
117+
118+
request = MockRequest()
119+
# To be honest view string isn't resolved into actual view
120+
self.parser_context = {'request': request, 'kwargs': {}, 'view': 'DummyAPIView'}
121+
122+
self.data = {
123+
'data': {
124+
'id': 123,
125+
'type': 'strs',
126+
'attributes': {
127+
'body': 'hello'
128+
},
129+
}
130+
}
131+
132+
self.string = json.dumps(self.data)
133+
134+
def test_patch_doesnt_raise_attribute_error(self):
135+
parser = JSONParser()
136+
137+
stream = BytesIO(self.string.encode('utf-8'))
138+
139+
data = parser.parse(stream, None, self.parser_context)
140+
141+
assert data['id'] == 123
142+
assert data['body'] == 'hello'
143+
144+
@override_settings(ROOT_URLCONF=__name__)
145+
def test_patch_request(self):
146+
url = reverse('repeater')
147+
data = self.data
148+
data['data']['type'] = 'dummy'
149+
response = self.client.patch(url, data=data)
150+
data = response.json()
151+
152+
assert data['data']['id'] == str(123)
153+
assert data['data']['attributes']['body'] == 'hello'

rest_framework_json_api/parsers.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,14 @@ def parse(self, stream, media_type=None, parser_context=None):
144144
raise ParseError("The resource identifier object must contain an 'id' member")
145145

146146
if request.method in ('PATCH', 'PUT'):
147-
lookup_url_kwarg = view.lookup_url_kwarg or view.lookup_field
148-
if str(data.get('id')) != str(view.kwargs[lookup_url_kwarg]):
147+
lookup_url_kwarg = getattr(view, 'lookup_url_kwarg', None) or \
148+
getattr(view, 'lookup_field', None)
149+
if lookup_url_kwarg and str(data.get('id')) != str(view.kwargs[lookup_url_kwarg]):
149150
raise exceptions.Conflict(
150151
"The resource object's id ({data_id}) does not match url's "
151152
"lookup id ({url_id})".format(
152153
data_id=data.get('id'),
153-
url_id=view.kwargs[view.lookup_field]
154+
url_id=view.kwargs[lookup_url_kwarg]
154155
)
155156
)
156157

0 commit comments

Comments
 (0)