Skip to content

Commit 875a600

Browse files
Merge pull request #104 from HackSoftware/nested_serializers_support
Nested serializers support
2 parents a001d9f + ec5784a commit 875a600

File tree

12 files changed

+36437
-44
lines changed

12 files changed

+36437
-44
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,8 @@ drfdocs.egg-info/
1111

1212
site/
1313

14+
demo/node_modules/
15+
rest_framework_docs/static/rest_framework_docs/js/dist.min.js
16+
1417
rest_framework_docs/static/node_modules/
1518
rest_framework_docs/static/rest_framework_docs/js/dist.min.js.map

demo/project/organisations/serializers.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,18 @@
33
from project.accounts.serializers import UserProfileSerializer
44

55

6+
class MembershipSerializer(serializers.ModelSerializer):
7+
class Meta:
8+
model = Membership
9+
fields = ('joined', 'is_owner', 'role')
10+
11+
612
class CreateOrganisationSerializer(serializers.ModelSerializer):
13+
membership_set = MembershipSerializer(many=True)
714

815
class Meta:
916
model = Organisation
10-
fields = ('name', 'slug',)
17+
fields = ('name', 'slug', 'membership_set')
1118

1219

1320
class OrganisationMembersSerializer(serializers.ModelSerializer):
@@ -27,3 +34,11 @@ class OrganisationDetailSerializer(serializers.ModelSerializer):
2734
class Meta:
2835
model = Organisation
2936
fields = ('name', 'slug', 'is_active')
37+
38+
39+
class RetrieveOrganisationSerializer(serializers.ModelSerializer):
40+
membership_set = MembershipSerializer()
41+
42+
class Meta:
43+
model = Organisation
44+
fields = ('name', 'slug', 'is_active', 'membership_set')

demo/project/organisations/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
urlpatterns = [
66

77
url(r'^create/$', view=views.CreateOrganisationView.as_view(), name="create"),
8+
url(r'^(?P<slug>[\w-]+)/$', view=views.RetrieveOrganisationView.as_view(), name="organisation"),
89
url(r'^(?P<slug>[\w-]+)/members/$', view=views.OrganisationMembersView.as_view(), name="members"),
910
url(r'^(?P<slug>[\w-]+)/leave/$', view=views.LeaveOrganisationView.as_view(), name="leave")
1011

demo/project/organisations/views.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22
from rest_framework.response import Response
33
from project.organisations.models import Organisation, Membership
44
from project.organisations.serializers import (
5-
CreateOrganisationSerializer, OrganisationMembersSerializer
5+
CreateOrganisationSerializer, OrganisationMembersSerializer, RetrieveOrganisationSerializer
66
)
77

88

9+
class RetrieveOrganisationView(generics.RetrieveAPIView):
10+
11+
serializer_class = RetrieveOrganisationSerializer
12+
13+
914
class CreateOrganisationView(generics.CreateAPIView):
1015

1116
serializer_class = CreateOrganisationSerializer

rest_framework_docs/api_endpoint.py

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import inspect
33
from django.contrib.admindocs.views import simplify_regex
44
from django.utils.encoding import force_str
5+
from rest_framework.serializers import BaseSerializer
56

67

78
class ApiEndpoint(object):
@@ -17,8 +18,12 @@ def __init__(self, pattern, parent_pattern=None, drf_router=None):
1718
self.allowed_methods = self.__get_allowed_methods__()
1819
# self.view_name = pattern.callback.__name__
1920
self.errors = None
20-
self.fields = self.__get_serializer_fields__()
21-
self.fields_json = self.__get_serializer_fields_json__()
21+
self.serializer_class = self.__get_serializer_class__()
22+
if self.serializer_class:
23+
self.serializer = self.__get_serializer__()
24+
self.fields = self.__get_serializer_fields__(self.serializer)
25+
self.fields_json = self.__get_serializer_fields_json__()
26+
2227
self.permissions = self.__get_permissions_class__()
2328

2429
def __get_path__(self, parent_pattern):
@@ -68,27 +73,39 @@ def __get_permissions_class__(self):
6873
for perm_class in self.pattern.callback.cls.permission_classes:
6974
return perm_class.__name__
7075

71-
def __get_serializer_fields__(self):
72-
fields = []
73-
serializer = None
76+
def __get_serializer__(self):
77+
try:
78+
return self.serializer_class()
79+
except KeyError as e:
80+
self.errors = e
7481

82+
def __get_serializer_class__(self):
7583
if hasattr(self.callback.cls, 'serializer_class'):
76-
serializer = self.callback.cls.serializer_class
84+
return self.callback.cls.serializer_class
7785

78-
elif hasattr(self.callback.cls, 'get_serializer_class'):
79-
serializer = self.callback.cls.get_serializer_class(self.pattern.callback.cls())
86+
if hasattr(self.callback.cls, 'get_serializer_class'):
87+
return self.callback.cls.get_serializer_class(self.pattern.callback.cls())
88+
89+
def __get_serializer_fields__(self, serializer):
90+
fields = []
8091

8192
if hasattr(serializer, 'get_fields'):
82-
try:
83-
fields = [{
93+
for key, field in serializer.get_fields().items():
94+
to_many_relation = True if hasattr(field, 'many') else False
95+
sub_fields = []
96+
97+
if to_many_relation:
98+
sub_fields = self.__get_serializer_fields__(field.child) if isinstance(field, BaseSerializer) else None
99+
else:
100+
sub_fields = self.__get_serializer_fields__(field) if isinstance(field, BaseSerializer) else None
101+
102+
fields.append({
84103
"name": key,
85104
"type": str(field.__class__.__name__),
86-
"required": field.required
87-
} for key, field in serializer().get_fields().items()]
88-
except KeyError as e:
89-
self.errors = e
90-
fields = []
91-
105+
"sub_fields": sub_fields,
106+
"required": field.required,
107+
"to_many_relation": to_many_relation
108+
})
92109
# FIXME:
93110
# Show more attibutes of `field`?
94111

rest_framework_docs/static/rest_framework_docs/js/dist.min.js

Lines changed: 36326 additions & 12 deletions
Large diffs are not rendered by default.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<ul class="list fields">
2+
{% for field in fields %}
3+
<li class="field">
4+
{{ field.name }}: {{ field.type }}
5+
{% if field.required %}
6+
<span class="label label-primary label-required" title="Required">R</span>
7+
{% endif %}
8+
9+
{% if field.to_many_relation %}
10+
<span class="label label-primary label-required" title="Support sending several objects of this type in a request ({{obj}, {obj}, ... })">Array of objects</span>
11+
{% endif %}
12+
13+
{% if field.sub_fields %}
14+
{%include "rest_framework_docs/components/fields_list.html" with fields=field.sub_fields %}
15+
{% endif %}
16+
</li>
17+
{% endfor %}
18+
</ul>

rest_framework_docs/templates/rest_framework_docs/home.html

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,7 @@ <h4 class="panel-title title">
6565

6666
{% if endpoint.fields %}
6767
<p class="fields-desc">Fields:</p>
68-
<ul class="list fields">
69-
{% for field in endpoint.fields %}
70-
<li class="field">{{ field.name }}: {{ field.type }} {% if field.required %}<span class="label label-primary label-required" title="Required">R</span>{% endif %}</li>
71-
{% endfor %}
72-
</ul>
68+
{%include "rest_framework_docs/components/fields_list.html" with fields=endpoint.fields %}
7369
{% elif not endpoint.errors %}
7470
<p>No fields.</p>
7571
{% endif %}

tests/serializers.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,26 @@ class Meta:
3434
extra_kwargs = {'password': {'write_only': True}}
3535

3636

37+
class MembershipSerializer(serializers.ModelSerializer):
38+
class Meta:
39+
model = Membership
40+
fields = ('joined', 'is_owner', 'role')
41+
42+
3743
class CreateOrganisationSerializer(serializers.ModelSerializer):
44+
membership_set = MembershipSerializer(many=True)
45+
46+
class Meta:
47+
model = Organisation
48+
fields = ('name', 'slug', 'membership_set')
49+
50+
51+
class RetrieveOrganisationSerializer(serializers.ModelSerializer):
52+
membership_set = MembershipSerializer()
3853

3954
class Meta:
4055
model = Organisation
41-
fields = ('name', 'slug',)
56+
fields = ('name', 'slug', 'is_active', 'membership_set')
4257

4358

4459
class OrganisationMembersSerializer(serializers.ModelSerializer):

tests/tests.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def test_index_view_with_endpoints(self):
2727
response = self.client.get(reverse('drfdocs'))
2828

2929
self.assertEqual(response.status_code, 200)
30-
self.assertEqual(len(response.context["endpoints"]), 14)
30+
self.assertEqual(len(response.context["endpoints"]), 15)
3131

3232
# Test the login view
3333
self.assertEqual(response.context["endpoints"][0].name_parent, "accounts")
@@ -72,9 +72,12 @@ def test_model_viewset(self):
7272
response = self.client.get(reverse('drfdocs'))
7373

7474
self.assertEqual(response.status_code, 200)
75-
self.assertEqual(response.context["endpoints"][10].path, '/organisation-model-viewsets/')
76-
self.assertEqual(response.context["endpoints"][11].path, '/organisation-model-viewsets/<pk>/')
77-
self.assertEqual(response.context["endpoints"][10].allowed_methods, ['GET', 'POST', 'OPTIONS'])
78-
self.assertEqual(response.context["endpoints"][11].allowed_methods, ['GET', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'])
79-
self.assertEqual(response.context["endpoints"][12].allowed_methods, ['POST', 'OPTIONS'])
80-
self.assertEqual(response.context["endpoints"][12].docstring, 'This is a test.')
75+
76+
self.assertEqual(response.context["endpoints"][10].path, '/organisations/<slug>/')
77+
self.assertEqual(response.context['endpoints'][6].fields[2]['to_many_relation'], True)
78+
self.assertEqual(response.context["endpoints"][11].path, '/organisation-model-viewsets/')
79+
self.assertEqual(response.context["endpoints"][12].path, '/organisation-model-viewsets/<pk>/')
80+
self.assertEqual(response.context["endpoints"][11].allowed_methods, ['GET', 'POST', 'OPTIONS'])
81+
self.assertEqual(response.context["endpoints"][12].allowed_methods, ['GET', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'])
82+
self.assertEqual(response.context["endpoints"][13].allowed_methods, ['POST', 'OPTIONS'])
83+
self.assertEqual(response.context["endpoints"][13].docstring, 'This is a test.')

0 commit comments

Comments
 (0)