Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,15 @@ per request via the `PAGINATE_BY_PARAM` query parameter (`page_size` by default)

### Setting the resource_name

You may manually set the `resource_name` property on views or serializers to
specify the `type` key in the json output. It is automatically set for you as the
plural of the view or model name except on resources that do not subclass
You may manually set the `resource_name` property on views, serializers, or
models to specify the `type` key in the json output. In the case of setting the
`resource_name` property for models you must include the property inside a
`JSONAPIMeta` class on the model. It is automatically set for you as the plural
of the view or model name except on resources that do not subclass
`rest_framework.viewsets.ModelViewSet`:


Example - `resource_name` on View:
``` python
class Me(generics.GenericAPIView):
"""
Expand All @@ -56,6 +61,23 @@ If you set the `resource_name` property on the object to `False` the data
will be returned without modification.


Example - `resource_name` on Model:
``` python
class Me(models.Model):
"""
A simple model
"""
name = models.CharField(max_length=100)

class JSONAPIMeta:
resource_name = "users"
```
If you set the `resource_name` on a combination of model, serializer, or view
in the same hierarchy, the name will be resolved as following: view >
serializer > model. (Ex: A view `resource_name` will always override a
`resource_name` specified on a serializer or model)


### Inflecting object and relation keys

This package includes the ability (off by default) to automatically convert json
Expand Down
2 changes: 1 addition & 1 deletion example/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def get_body_format(self, obj):
class Meta:
model = Entry
fields = ('blog', 'headline', 'body_text', 'pub_date', 'mod_date',
'authors', 'comments', 'suggested',)
'authors', 'comments', 'suggested',)
meta_fields = ('body_format',)


Expand Down
137 changes: 137 additions & 0 deletions example/tests/integration/test_model_resource_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import pytest
from django.core.urlresolvers import reverse

from example.tests.utils import load_json

from example import models, serializers, views
pytestmark = pytest.mark.django_db


class _PatchedModel:
class JSONAPIMeta:
resource_name = "resource_name_from_JSONAPIMeta"


def _check_resource_and_relationship_comment_type_match(django_client):
entry_response = django_client.get(reverse("entry-list"))
comment_response = django_client.get(reverse("comment-list"))

comment_resource_type = load_json(comment_response.content).get('data')[0].get('type')
comment_relationship_type = load_json(entry_response.content).get(
'data')[0].get('relationships').get('comments').get('data')[0].get('type')

assert comment_resource_type == comment_relationship_type, "The resource type seen in the relationships and head resource do not match"


def _check_relationship_and_included_comment_type_are_the_same(django_client, url):
response = django_client.get(url + "?include=comments")
data = load_json(response.content).get('data')[0]
comment = load_json(response.content).get('included')[0]

comment_relationship_type = data.get('relationships').get('comments').get('data')[0].get('type')
comment_included_type = comment.get('type')

assert comment_relationship_type == comment_included_type, "The resource type seen in the relationships and included do not match"


@pytest.mark.usefixtures("single_entry")
class TestModelResourceName:

def test_model_resource_name_on_list(self, client):
models.Comment.__bases__ += (_PatchedModel,)
response = client.get(reverse("comment-list"))
data = load_json(response.content)['data'][0]
# name should be super-author instead of model name RenamedAuthor
assert (data.get('type') == 'resource_name_from_JSONAPIMeta'), (
'resource_name from model incorrect on list')

# Precedence tests
def test_resource_name_precendence(self, client):
# default
response = client.get(reverse("comment-list"))
data = load_json(response.content)['data'][0]
assert (data.get('type') == 'comments'), (
'resource_name from model incorrect on list')

# model > default
models.Comment.__bases__ += (_PatchedModel,)
response = client.get(reverse("comment-list"))
data = load_json(response.content)['data'][0]
assert (data.get('type') == 'resource_name_from_JSONAPIMeta'), (
'resource_name from model incorrect on list')

# serializer > model
serializers.CommentSerializer.Meta.resource_name = "resource_name_from_serializer"
response = client.get(reverse("comment-list"))
data = load_json(response.content)['data'][0]
assert (data.get('type') == 'resource_name_from_serializer'), (
'resource_name from serializer incorrect on list')

# view > serializer > model
views.CommentViewSet.resource_name = 'resource_name_from_view'
response = client.get(reverse("comment-list"))
data = load_json(response.content)['data'][0]
assert (data.get('type') == 'resource_name_from_view'), (
'resource_name from view incorrect on list')

def teardown_method(self, method):
models.Comment.__bases__ = (models.Comment.__bases__[0],)
try:
delattr(serializers.CommentSerializer.Meta, "resource_name")
except AttributeError:
pass
try:
delattr(views.CommentViewSet, "resource_name")
except AttributeError:
pass


@pytest.mark.usefixtures("single_entry")
class TestResourceNameConsistency:

# Included rename tests
def test_type_match_on_included_and_inline_base(self, client):
_check_relationship_and_included_comment_type_are_the_same(client, reverse("entry-list"))

def test_type_match_on_included_and_inline_with_JSONAPIMeta(self, client):
models.Comment.__bases__ += (_PatchedModel,)

_check_relationship_and_included_comment_type_are_the_same(client, reverse("entry-list"))

def test_type_match_on_included_and_inline_with_serializer_resource_name(self, client):
serializers.CommentSerializer.Meta.resource_name = "resource_name_from_serializer"

_check_relationship_and_included_comment_type_are_the_same(client, reverse("entry-list"))

def test_type_match_on_included_and_inline_with_serializer_resource_name_and_JSONAPIMeta(self, client):
models.Comment.__bases__ += (_PatchedModel,)
serializers.CommentSerializer.Meta.resource_name = "resource_name_from_serializer"

_check_relationship_and_included_comment_type_are_the_same(client, reverse("entry-list"))

# Relation rename tests
def test_resource_and_relationship_type_match(self, client):
_check_resource_and_relationship_comment_type_match(client)

def test_resource_and_relationship_type_match_with_serializer_resource_name(self, client):
serializers.CommentSerializer.Meta.resource_name = "resource_name_from_serializer"

_check_resource_and_relationship_comment_type_match(client)

def test_resource_and_relationship_type_match_with_JSONAPIMeta(self, client):
models.Comment.__bases__ += (_PatchedModel,)

_check_resource_and_relationship_comment_type_match(client)

def test_resource_and_relationship_type_match_with_serializer_resource_name_and_JSONAPIMeta(self, client):
models.Comment.__bases__ += (_PatchedModel,)
serializers.CommentSerializer.Meta.resource_name = "resource_name_from_serializer"

_check_resource_and_relationship_comment_type_match(client)

def teardown_method(self, method):
models.Comment.__bases__ = (models.Comment.__bases__[0],)
try:
delattr(serializers.CommentSerializer.Meta, "resource_name")
except AttributeError:
pass
3 changes: 1 addition & 2 deletions example/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from rest_framework_json_api.views import RelationshipView
from example.models import Blog, Entry, Author, Comment
from example.serializers import (
BlogSerializer, EntrySerializer, AuthorSerializer, CommentSerializer)
BlogSerializer, EntrySerializer, AuthorSerializer, CommentSerializer)


class BlogViewSet(viewsets.ModelViewSet):
Expand Down Expand Up @@ -41,4 +41,3 @@ class CommentRelationshipView(RelationshipView):
class AuthorRelationshipView(RelationshipView):
queryset = Author.objects.all()
self_link_view_name = 'author-relationships'

18 changes: 15 additions & 3 deletions rest_framework_json_api/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
from django.utils.translation import ugettext_lazy as _

from rest_framework_json_api.exceptions import Conflict
from rest_framework_json_api.utils import format_relation_name, Hyperlink, \
get_resource_type_from_queryset, get_resource_type_from_instance
from rest_framework_json_api.utils import Hyperlink, \
get_resource_type_from_queryset, get_resource_type_from_instance, \
get_included_serializers, get_resource_type_from_serializer


class ResourceRelatedField(PrimaryKeyRelatedField):
Expand Down Expand Up @@ -137,7 +138,18 @@ def to_representation(self, value):
else:
pk = value.pk

return OrderedDict([('type', format_relation_name(get_resource_type_from_instance(value))), ('id', str(pk))])
# check to see if this resource has a different resource_name when
# included and use that name
resource_type = None
root = getattr(self.parent, 'parent', self.parent)
field_name = self.field_name if self.field_name else self.parent.field_name
if getattr(root, 'included_serializers', None) is not None:
includes = get_included_serializers(root)
if field_name in includes.keys():
resource_type = get_resource_type_from_serializer(includes[field_name])

resource_type = resource_type if resource_type else get_resource_type_from_instance(value)
return OrderedDict([('type', resource_type), ('id', str(pk))])

@property
def choices(self):
Expand Down
12 changes: 6 additions & 6 deletions rest_framework_json_api/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,7 @@ def extract_included(fields, resource, resource_instance, included_resources):

if isinstance(field, ListSerializer):
serializer = field.child
model = serializer.Meta.model
relation_type = utils.format_relation_name(model.__name__)
relation_type = utils.get_resource_type_from_serializer(serializer)
relation_queryset = list(relation_instance_or_manager.all())

# Get the serializer fields
Expand All @@ -303,15 +302,16 @@ def extract_included(fields, resource, resource_instance, included_resources):
)

if isinstance(field, ModelSerializer):
model = field.Meta.model
relation_type = utils.format_relation_name(model.__name__)

relation_type = utils.get_resource_type_from_serializer(field)

# Get the serializer fields
serializer_fields = utils.get_serializer_fields(field)
if serializer_data:
included_data.append(
JSONRenderer.build_json_resource_obj(serializer_fields, serializer_data, relation_instance_or_manager,
relation_type)
JSONRenderer.build_json_resource_obj(
serializer_fields, serializer_data,
relation_instance_or_manager, relation_type)
)
included_data.extend(
JSONRenderer.extract_included(
Expand Down
9 changes: 5 additions & 4 deletions rest_framework_json_api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
from rest_framework.serializers import *

from rest_framework_json_api.relations import ResourceRelatedField
from rest_framework_json_api.utils import format_relation_name, get_resource_type_from_instance, \
get_resource_type_from_serializer, get_included_serializers
from rest_framework_json_api.utils import (
get_resource_type_from_model, get_resource_type_from_instance,
get_resource_type_from_serializer, get_included_serializers)


class ResourceIdentifierObjectSerializer(BaseSerializer):
Expand All @@ -24,12 +25,12 @@ def __init__(self, *args, **kwargs):

def to_representation(self, instance):
return {
'type': format_relation_name(get_resource_type_from_instance(instance)),
'type': get_resource_type_from_instance(instance),
'id': str(instance.pk)
}

def to_internal_value(self, data):
if data['type'] != format_relation_name(self.model_class.__name__):
if data['type'] != get_resource_type_from_model(self.model_class):
self.fail('incorrect_model_type', model_type=self.model_class, received_type=data['type'])
pk = data['id']
try:
Expand Down
28 changes: 17 additions & 11 deletions rest_framework_json_api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def get_resource_name(context):
return get_resource_type_from_serializer(serializer)
except AttributeError:
try:
resource_name = view.model.__name__
resource_name = get_resource_type_from_model(view.model)
except AttributeError:
resource_name = view.__class__.__name__

Expand Down Expand Up @@ -182,7 +182,7 @@ def get_related_resource_type(relation):
relation_model = parent_model_relation.field.related.model
else:
return get_related_resource_type(parent_model_relation)
return format_relation_name(relation_model.__name__)
return get_resource_type_from_model(relation_model)


def get_instance_or_manager_resource_type(resource_instance_or_manager):
Expand All @@ -193,25 +193,31 @@ def get_instance_or_manager_resource_type(resource_instance_or_manager):
pass


def get_resource_type_from_model(model):
json_api_meta = getattr(model, 'JSONAPIMeta', None)
return getattr(
json_api_meta,
'resource_name',
format_relation_name(model.__name__))


def get_resource_type_from_queryset(qs):
return format_relation_name(qs.model._meta.model.__name__)
return get_resource_type_from_model(qs.model)


def get_resource_type_from_instance(instance):
return format_relation_name(instance._meta.model.__name__)
return get_resource_type_from_model(instance._meta.model)


def get_resource_type_from_manager(manager):
return format_relation_name(manager.model.__name__)
return get_resource_type_from_model(manager.model)


def get_resource_type_from_serializer(serializer):
try:
# Check the meta class for resource_name
return serializer.Meta.resource_name
except AttributeError:
# Use the serializer model then pluralize and format
return format_relation_name(serializer.Meta.model.__name__)
return getattr(
serializer.Meta,
'resource_name',
get_resource_type_from_model(serializer.Meta.model))


def get_included_serializers(serializer):
Expand Down
4 changes: 2 additions & 2 deletions rest_framework_json_api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from rest_framework_json_api.exceptions import Conflict
from rest_framework_json_api.serializers import ResourceIdentifierObjectSerializer
from rest_framework_json_api.utils import format_relation_name, get_resource_type_from_instance, OrderedDict, Hyperlink
from rest_framework_json_api.utils import get_resource_type_from_instance, OrderedDict, Hyperlink


class RelationshipView(generics.GenericAPIView):
Expand Down Expand Up @@ -154,7 +154,7 @@ def _instantiate_serializer(self, instance):
def get_resource_name(self):
if not hasattr(self, '_resource_name'):
instance = getattr(self.get_object(), self.kwargs['related_field'])
self._resource_name = format_relation_name(get_resource_type_from_instance(instance))
self._resource_name = get_resource_type_from_instance(instance)
return self._resource_name

def set_resource_name(self, value):
Expand Down