diff --git a/example/serializers.py b/example/serializers.py index c6b243a1..cc7e624b 100644 --- a/example/serializers.py +++ b/example/serializers.py @@ -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): diff --git a/example/tests/integration/test_model_resource_name.py b/example/tests/integration/test_model_resource_name.py new file mode 100644 index 00000000..979b55b5 --- /dev/null +++ b/example/tests/integration/test_model_resource_name.py @@ -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 diff --git a/example/urls_test.py b/example/urls_test.py index 96f415fd..208334ad 100644 --- a/example/urls_test.py +++ b/example/urls_test.py @@ -2,7 +2,7 @@ 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) @@ -10,6 +10,7 @@ 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) diff --git a/example/views.py b/example/views.py index 59ca1a05..6a3fb505 100644 --- a/example/views.py +++ b/example/views.py @@ -41,4 +41,3 @@ class CommentRelationshipView(RelationshipView): class AuthorRelationshipView(RelationshipView): queryset = Author.objects.all() self_link_view_name = 'author-relationships' - diff --git a/rest_framework_json_api/relations.py b/rest_framework_json_api/relations.py index 61b9ecd7..bc44e4c1 100644 --- a/rest_framework_json_api/relations.py +++ b/rest_framework_json_api/relations.py @@ -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): @@ -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): diff --git a/rest_framework_json_api/renderers.py b/rest_framework_json_api/renderers.py index b0f8b0ec..60e55f96 100644 --- a/rest_framework_json_api/renderers.py +++ b/rest_framework_json_api/renderers.py @@ -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 @@ -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( diff --git a/rest_framework_json_api/serializers.py b/rest_framework_json_api/serializers.py index 8fd78292..6e8d377c 100644 --- a/rest_framework_json_api/serializers.py +++ b/rest_framework_json_api/serializers.py @@ -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): @@ -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: diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py index ed01b739..a4093718 100644 --- a/rest_framework_json_api/utils.py +++ b/rest_framework_json_api/utils.py @@ -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__ @@ -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): @@ -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): diff --git a/rest_framework_json_api/views.py b/rest_framework_json_api/views.py index 36c89687..a43fad89 100644 --- a/rest_framework_json_api/views.py +++ b/rest_framework_json_api/views.py @@ -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): @@ -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):