Skip to content
2 changes: 1 addition & 1 deletion example/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def get_suggested(self, obj):
class Meta:
model = Entry
fields = ('blog', 'headline', 'body_text', 'pub_date', 'mod_date',
'authors', 'comments', 'suggested',)
'authors', 'comments', 'suggested',)


class AuthorSerializer(serializers.ModelSerializer):
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: 2 additions & 1 deletion example/urls_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
from rest_framework import routers

from example.views import BlogViewSet, EntryViewSet, AuthorViewSet, EntryRelationshipView, BlogRelationshipView, \
CommentRelationshipView, AuthorRelationshipView
CommentRelationshipView, AuthorRelationshipView, CommentViewSet
from .api.resources.identity import Identity, GenericIdentity

router = routers.DefaultRouter(trailing_slash=False)

router.register(r'blogs', BlogViewSet)
router.register(r'entries', EntryViewSet)
router.register(r'authors', AuthorViewSet)
router.register(r'comments', CommentViewSet)

# for the old tests
router.register(r'identities', Identity)
Expand Down
1 change: 0 additions & 1 deletion example/views.py
Original file line number Diff line number Diff line change
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 @@ -127,7 +128,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 @@ -276,8 +276,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 @@ -298,15 +297,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 @@ -171,7 +171,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 @@ -182,25 +182,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