Skip to content

Commit 474cafb

Browse files
authored
Merge pull request #14 from ShipChain/feature/enhanced-json-renderer
Enhanced JSON renderer
2 parents 9ec10c1 + c0934b4 commit 474cafb

File tree

4 files changed

+179
-3
lines changed

4 files changed

+179
-3
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "shipchain-common"
3-
version = "1.0.9"
3+
version = "1.0.10"
44
description = "A PyPI package containing shared code for ShipChain's Python/Django projects."
55

66
license = "Apache-2.0"

src/shipchain_common/renderers.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from rest_framework_json_api import renderers, utils
2+
from rest_framework_json_api.serializers import PolymorphicModelSerializer
3+
4+
from .mixins import SerializationType
5+
6+
7+
def _get_resource_name(context, expand_polymorphic_types=False):
8+
"""
9+
Return the name of a resource with a special override for ConfigurableGenericViewSet's multiple serializers
10+
"""
11+
if CGVSJsonRenderer.original_get_resource_name is None:
12+
raise Exception('original_get_resource_name was not captured')
13+
14+
view = context.get('view')
15+
is_response = context.get('response', False)
16+
17+
try:
18+
original_view_resource_name = getattr(view, 'resource_name', None)
19+
20+
# If view.resource_name was set, respect it
21+
if original_view_resource_name:
22+
raise AttributeError
23+
24+
serializer = view.get_serializer_class(
25+
serialization_type=SerializationType.RESPONSE if is_response else SerializationType.REQUEST)
26+
27+
if expand_polymorphic_types and issubclass(serializer, PolymorphicModelSerializer):
28+
resource_name = serializer.get_polymorphic_types()
29+
else:
30+
resource_name = utils.get_resource_type_from_serializer(serializer)
31+
32+
# Force the default method to find our serializer at view.resource_name and reset when we're done
33+
setattr(view, 'resource_name', resource_name)
34+
# pylint: disable=not-callable
35+
resource_name = CGVSJsonRenderer.original_get_resource_name(context, expand_polymorphic_types)
36+
setattr(view, 'resource_name', original_view_resource_name)
37+
38+
return resource_name
39+
40+
# For any exceptions above, fallback to original method
41+
except AttributeError:
42+
# pylint: disable=not-callable
43+
return CGVSJsonRenderer.original_get_resource_name(context, expand_polymorphic_types)
44+
45+
46+
class CGVSJsonRenderer(renderers.JSONRenderer):
47+
"""JSONRenderer that is aware of the multiple serializers in ConfigurableGenericViewSet
48+
This also affects the JSONParser since we are patching method in the util package
49+
"""
50+
original_get_resource_name = None
51+
52+
if original_get_resource_name is None:
53+
original_get_resource_name = utils.get_resource_name
54+
utils.get_resource_name = _get_resource_name

tests/django_mocking/models.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from enumfields import Enum, EnumIntegerField
2-
from django.db.models import Model
2+
from django.db import models
3+
from shipchain_common import utils
34

45

56
class GenericEnum(Enum):
@@ -13,5 +14,10 @@ class Labels:
1314
THIRD = 'THIRD'
1415

1516

16-
class EnumObject(Model):
17+
class EnumObject(models.Model):
1718
enum_field = EnumIntegerField(enum=GenericEnum, default=GenericEnum.FIRST)
19+
20+
21+
class BasicModel(models.Model):
22+
id = models.CharField(primary_key=True, max_length=36, default=utils.random_id)
23+
my_field = models.CharField(max_length=1)

tests/test_renderers.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import json
2+
3+
import django
4+
# We run django.setup() in order to auto populate the base django's app models for testing purposes
5+
import pytest
6+
from rest_framework import status
7+
from rest_framework.response import Response
8+
from rest_framework_json_api import serializers
9+
10+
from tests.django_mocking.models import BasicModel
11+
12+
try:
13+
django.setup()
14+
except Exception as exc:
15+
raise exc
16+
17+
from shipchain_common.viewsets import ActionConfiguration, ConfigurableGenericViewSet
18+
from shipchain_common.renderers import CGVSJsonRenderer
19+
20+
21+
class DefaultSerializer(serializers.ModelSerializer):
22+
23+
class Meta:
24+
model = BasicModel
25+
fields = '__all__'
26+
27+
class JSONAPIMeta:
28+
resource_name = 'DefaultSerializer'
29+
30+
31+
class ListResponseSerializer(DefaultSerializer):
32+
class JSONAPIMeta:
33+
resource_name = 'ListResponseSerializer'
34+
35+
36+
class CreateRequestSerializer(DefaultSerializer):
37+
class JSONAPIMeta:
38+
resource_name = 'CreateRequestSerializer'
39+
40+
41+
class CreateResponseSerializer(DefaultSerializer):
42+
class JSONAPIMeta:
43+
resource_name = 'CreateResponseSerializer'
44+
45+
46+
class CustomActionSerializer(DefaultSerializer):
47+
class JSONAPIMeta:
48+
resource_name = 'CustomActionSerializer'
49+
50+
51+
class CustomActionCSVSerializer(DefaultSerializer):
52+
class JSONAPIMeta:
53+
resource_name = 'CustomActionCSVSerializer'
54+
55+
56+
class TestCGVSJsonRenderer:
57+
58+
@pytest.fixture
59+
def viewset(self):
60+
class TestViewSet(ConfigurableGenericViewSet):
61+
kwargs = {}
62+
request = {}
63+
response = Response(status=status.HTTP_200_OK)
64+
format_kwarg = ''
65+
serializer_class = DefaultSerializer
66+
67+
return TestViewSet()
68+
69+
def test_get_resource_from_configurable_serializer(self, viewset):
70+
viewset.configuration = {
71+
'list': ActionConfiguration(
72+
response_serializer=ListResponseSerializer,
73+
),
74+
'create': ActionConfiguration(
75+
request_serializer=CreateRequestSerializer,
76+
response_serializer=CreateResponseSerializer,
77+
),
78+
'custom_action': ActionConfiguration(
79+
request_serializer=CustomActionSerializer,
80+
response_serializer={
81+
'default': CustomActionSerializer,
82+
'csv': CustomActionCSVSerializer,
83+
},
84+
),
85+
}
86+
87+
renderer = CGVSJsonRenderer()
88+
response_data = DefaultSerializer(BasicModel(my_field='1')).data
89+
renderer_context = {'view': viewset, 'request': viewset.request, 'response': viewset.response}
90+
91+
viewset.action = 'update'
92+
response = renderer.render(response_data, renderer_context=renderer_context)
93+
response = json.loads(response)
94+
assert response['data']['type'] == 'DefaultSerializer'
95+
96+
viewset.action = 'list'
97+
response = renderer.render(response_data, renderer_context=renderer_context)
98+
response = json.loads(response)
99+
assert response['data']['type'] == 'ListResponseSerializer'
100+
101+
viewset.action = 'create'
102+
response = renderer.render(response_data, renderer_context=renderer_context)
103+
response = json.loads(response)
104+
assert response['data']['type'] == 'CreateResponseSerializer'
105+
106+
viewset.action = 'custom_action'
107+
response = renderer.render(response_data, renderer_context=renderer_context)
108+
response = json.loads(response)
109+
assert response['data']['type'] == 'CustomActionSerializer'
110+
111+
viewset.action = 'custom_action'
112+
viewset.kwargs['format'] = 'csv'
113+
response = renderer.render(response_data, renderer_context=renderer_context)
114+
response = json.loads(response)
115+
assert response['data']['type'] == 'CustomActionCSVSerializer'
116+
viewset.kwargs['format'] = None

0 commit comments

Comments
 (0)