Skip to content

Avoid AttributeError for PUT and PATCH methods when using APIView #778

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Apr 18, 2020
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Note that in line with [Django REST Framework policy](http://www.django-rest-framework.org/topics/release-notes/),
any parts of the framework not mentioned in the documentation should generally be considered private API, and may be subject to change.

## [Unreleased]

### Fixed

* Avoid `AttributeError` for PUT and PATCH methods when using `APIView`

## [3.1.0] - 2020-02-08

### Added
Expand Down
82 changes: 82 additions & 0 deletions example/tests/test_parsers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import json
from io import BytesIO

from django.conf.urls import url
from django.test import TestCase, override_settings
from django.urls import reverse
from rest_framework import views, status
from rest_framework.exceptions import ParseError
from rest_framework.response import Response
from rest_framework.test import APITestCase

from rest_framework_json_api import serializers
from rest_framework_json_api.parsers import JSONParser
from rest_framework_json_api.renderers import JSONRenderer


class TestJSONParser(TestCase):
Expand Down Expand Up @@ -69,3 +76,78 @@ def test_parse_invalid_data_key(self):

with self.assertRaises(ParseError):
parser.parse(stream, None, self.parser_context)


class DummyDTO:
def __init__(self, response_dict):
for k, v in response_dict.items():
setattr(self, k, v)

@property
def pk(self):
return self.id if hasattr(self, 'id') else None


class DummySerializer(serializers.Serializer):
body = serializers.CharField()
id = serializers.IntegerField()


class DummyAPIView(views.APIView):
parser_classes = [JSONParser]
renderer_classes = [JSONRenderer]
resource_name = 'dummy'

def patch(self, request, *args, **kwargs):
serializer = DummySerializer(DummyDTO(request.data))
return Response(status=status.HTTP_200_OK, data=serializer.data)


urlpatterns = [
url(r'repeater$', DummyAPIView.as_view(), name='repeater'),
]


class TestParserOnAPIView(APITestCase):

def setUp(self):
class MockRequest(object):
def __init__(self):
self.method = 'PATCH'

request = MockRequest()
# To be honest view string isn't resolved into actual view
self.parser_context = {'request': request, 'kwargs': {}, 'view': 'DummyAPIView'}

self.data = {
'data': {
'id': 123,
'type': 'strs',
'attributes': {
'body': 'hello'
},
}
}

self.string = json.dumps(self.data)

def test_patch_doesnt_raise_attribute_error(self):
parser = JSONParser()

stream = BytesIO(self.string.encode('utf-8'))

data = parser.parse(stream, None, self.parser_context)

assert data['id'] == 123
assert data['body'] == 'hello'

@override_settings(ROOT_URLCONF=__name__)
def test_patch_request(self):
url = reverse('repeater')
data = self.data
data['data']['type'] = 'dummy'
response = self.client.patch(url, data=data)
data = response.json()

assert data['data']['id'] == str(123)
assert data['data']['attributes']['body'] == 'hello'
7 changes: 4 additions & 3 deletions rest_framework_json_api/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,14 @@ def parse(self, stream, media_type=None, parser_context=None):
raise ParseError("The resource identifier object must contain an 'id' member")

if request.method in ('PATCH', 'PUT'):
lookup_url_kwarg = view.lookup_url_kwarg or view.lookup_field
if str(data.get('id')) != str(view.kwargs[lookup_url_kwarg]):
lookup_url_kwarg = getattr(view, 'lookup_url_kwarg', None) or \
getattr(view, 'lookup_field', None)
if lookup_url_kwarg and str(data.get('id')) != str(view.kwargs[lookup_url_kwarg]):
raise exceptions.Conflict(
"The resource object's id ({data_id}) does not match url's "
"lookup id ({url_id})".format(
data_id=data.get('id'),
url_id=view.kwargs[view.lookup_field]
url_id=view.kwargs[lookup_url_kwarg]
)
)

Expand Down