Skip to content

Added the ability to specify a resource_name on Models #152

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

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