Skip to content

Fixed the parser #60

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 8 commits into from
Aug 14, 2015
Merged
7 changes: 3 additions & 4 deletions example/tests/test_model_viewsets.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import json

from example.tests import TestBase

from django.contrib.auth import get_user_model
from django.utils import encoding
from django.core.urlresolvers import reverse
from django.conf import settings

from example.tests import TestBase


class ModelViewSetTests(TestBase):
Expand Down Expand Up @@ -167,7 +166,7 @@ def test_key_in_post(self):
}
}
data_attributes = data['data']['attributes']
response = self.client.put(self.detail_url, data=data, format='json')
response = self.client.put(self.detail_url, content_type='application/vnd.api+json', data=json.dumps(data))

result = json.loads(response.content.decode('utf8'))
result_attributes = result['data']['attributes']
Expand Down
12 changes: 10 additions & 2 deletions rest_framework_json_api/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@

from django.utils import six, encoding
from django.utils import encoding
from django.utils.translation import ugettext_lazy as _
from rest_framework import status
from rest_framework.exceptions import APIException
from rest_framework.views import exception_handler as drf_exception_handler

from rest_framework_json_api.utils import format_value


Expand Down Expand Up @@ -39,3 +42,8 @@ def exception_handler(exc, context):
context['view'].resource_name = 'errors'
response.data = errors
return response


class Conflict(APIException):
status_code = status.HTTP_409_CONFLICT
default_detail = _('Conflict.')
47 changes: 40 additions & 7 deletions rest_framework_json_api/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
from rest_framework import parsers

from . import utils
from . import utils, renderers, exceptions


class JSONParser(parsers.JSONParser):
Expand All @@ -23,14 +23,47 @@ class JSONParser(parsers.JSONParser):

We extract the attributes so that DRF serializers can work as normal.
"""
media_type = 'application/vnd.api+json'
renderer_class = renderers.JSONRenderer

def parse(self, stream, media_type=None, parser_context=None):
"""
Parses the incoming bytestream as JSON and returns the resulting data
"""
result = super(JSONParser, self).parse(stream, media_type=media_type,
parser_context=parser_context)
result = super(JSONParser, self).parse(stream, media_type=media_type, parser_context=parser_context)
data = result.get('data', {})
attributes = data.get('attributes')
if attributes:
attributes['id'] = data.get('id')
return utils.format_keys(attributes, 'underscore')

if data:
# Check for inconsistencies
resource_name = utils.get_resource_name(parser_context)
if data.get('type') != resource_name:
raise exceptions.Conflict(
"The resource object's type ({data_type}) is not the type "
"that constitute the collection represented by the endpoint ({resource_type}).".format(
data_type=data.get('type'),
resource_type=resource_name
)
)
# Get the ID
data_id = data.get('id')
# Get the attributes
attributes = utils.format_keys(data.get('attributes'), 'underscore') if data.get(
'attributes') else dict()
# Get the relationships
relationships = utils.format_keys(data.get('relationships'), 'underscore') if data.get(
'relationships') else dict()

# Parse the relationships
parsed_relationships = dict()
for field_name, field_data in relationships.items():
field_data = field_data.get('data')
if isinstance(field_data, dict):
parsed_relationships[field_name] = field_data.get('id')
elif isinstance(field_data, list):
parsed_relationships[field_name] = list(relation.get('id') for relation in field_data)

# Construct the return data
parsed_data = {'id': data_id}
parsed_data.update(attributes)
parsed_data.update(parsed_relationships)
return parsed_data
6 changes: 3 additions & 3 deletions rest_framework_json_api/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
Renderers
"""
from collections import OrderedDict

from rest_framework import renderers

from . import utils
from rest_framework.relations import RelatedField
from rest_framework.settings import api_settings


class JSONRenderer(renderers.JSONRenderer):
Expand All @@ -30,6 +29,7 @@ class JSONRenderer(renderers.JSONRenderer):
"""

media_type = 'application/vnd.api+json'
format = 'vnd.api+json'

def render(self, data, accepted_media_type=None, renderer_context=None):
# Get the resource name.
Expand All @@ -47,7 +47,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None):

# If this is an error response, skip the rest.
if resource_name == 'errors':
if len(data) > 1:
if len(data) > 1 and isinstance(data, list):
data.sort(key=lambda x: x.get('source', {}).get('pointer', ''))
return super(JSONRenderer, self).render(
{resource_name: data}, accepted_media_type, renderer_context
Expand Down
31 changes: 15 additions & 16 deletions rest_framework_json_api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@
Utils.
"""
import inflection

from django.core import urlresolvers
from django.conf import settings
from django.utils import six, encoding
from django.utils.six.moves.urllib.parse import urlparse, urlunparse
from django.utils.translation import ugettext_lazy as _

from rest_framework.serializers import BaseSerializer, ListSerializer, ModelSerializer
from rest_framework.relations import RelatedField, HyperlinkedRelatedField, PrimaryKeyRelatedField
from rest_framework.settings import api_settings
from rest_framework.exceptions import APIException

from django.utils.six.moves.urllib.parse import urlparse

try:
from rest_framework.compat import OrderedDict
except ImportError:
Expand Down Expand Up @@ -178,9 +177,9 @@ def extract_id_from_url(url):
def extract_id(fields, resource):
for field_name, field in six.iteritems(fields):
if field_name == 'id':
return encoding.force_text(resource[field_name])
return encoding.force_text(resource.get(field_name))
if field_name == api_settings.URL_FIELD_NAME:
return extract_id_from_url(resource[field_name])
return extract_id_from_url(resource.get(field_name))


def extract_attributes(fields, resource):
Expand All @@ -193,7 +192,7 @@ def extract_attributes(fields, resource):
if isinstance(field, (RelatedField, BaseSerializer, ManyRelatedField)):
continue
data.update({
field_name: resource[field_name]
field_name: resource.get(field_name)
})

return format_keys(data)
Expand All @@ -213,11 +212,11 @@ def extract_relationships(fields, resource):
if isinstance(field, (PrimaryKeyRelatedField, HyperlinkedRelatedField)):
relation_type = get_related_resource_type(field)

if resource[field_name] is not None:
if resource.get(field_name) is not None:
if isinstance(field, PrimaryKeyRelatedField):
relation_id = encoding.force_text(resource[field_name])
relation_id = encoding.force_text(resource.get(field_name))
elif isinstance(field, HyperlinkedRelatedField):
relation_id = extract_id_from_url(resource[field_name])
relation_id = extract_id_from_url(resource.get(field_name))
else:
relation_id = None

Expand All @@ -240,14 +239,14 @@ def extract_relationships(fields, resource):
relation_type = get_related_resource_type(relation)

if isinstance(relation, HyperlinkedRelatedField):
for link in resource[field_name]:
for link in resource.get(field_name, list()):
relation_data.append(OrderedDict([('type', relation_type), ('id', extract_id_from_url(link))]))

data.update({field_name: {'data': relation_data}})
continue

if isinstance(relation, PrimaryKeyRelatedField):
for pk in resource[field_name]:
for pk in resource.get(field_name, list()):
relation_data.append(OrderedDict([('type', relation_type), ('id', encoding.force_text(pk))]))

data.update({field_name: {'data': relation_data}})
Expand All @@ -262,7 +261,7 @@ def extract_relationships(fields, resource):

# Get the serializer fields
serializer_fields = get_serializer_fields(serializer)
serializer_data = resource[field_name]
serializer_data = resource.get(field_name)
if isinstance(serializer_data, list):
for serializer_resource in serializer_data:
relation_data.append(
Expand All @@ -279,14 +278,14 @@ def extract_relationships(fields, resource):

# Get the serializer fields
serializer_fields = get_serializer_fields(field)
serializer_data = resource[field_name]
serializer_data = resource.get(field_name)
data.update({
field_name: {
'data': (
OrderedDict([
('type', relation_type),
('id', extract_id(serializer_fields, serializer_data))
]) if resource[field_name] else None)
]) if resource.get(field_name) else None)
}
})
continue
Expand All @@ -313,7 +312,7 @@ def extract_included(fields, resource):

# Get the serializer fields
serializer_fields = get_serializer_fields(serializer)
serializer_data = resource[field_name]
serializer_data = resource.get(field_name)
if isinstance(serializer_data, list):
for serializer_resource in serializer_data:
included_data.append(build_json_resource_obj(serializer_fields, serializer_resource, relation_type))
Expand All @@ -325,7 +324,7 @@ def extract_included(fields, resource):

# Get the serializer fields
serializer_fields = get_serializer_fields(field)
serializer_data = resource[field_name]
serializer_data = resource.get(field_name)
if serializer_data:
included_data.append(build_json_resource_obj(serializer_fields, serializer_data, relation_type))

Expand Down