Skip to content
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

Nested serializers support #104

Merged
merged 6 commits into from
Jun 30, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@ drfdocs.egg-info/

site/

demo/node_modules/
rest_framework_docs/static/rest_framework_docs/js/dist.min.js

rest_framework_docs/static/node_modules/
rest_framework_docs/static/rest_framework_docs/js/dist.min.js.map
17 changes: 16 additions & 1 deletion demo/project/organisations/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@
from project.accounts.serializers import UserProfileSerializer


class MembershipSerializer(serializers.ModelSerializer):
class Meta:
model = Membership
fields = ('joined', 'is_owner', 'role')


class CreateOrganisationSerializer(serializers.ModelSerializer):
membership_set = MembershipSerializer(many=True)

class Meta:
model = Organisation
fields = ('name', 'slug',)
fields = ('name', 'slug', 'membership_set')


class OrganisationMembersSerializer(serializers.ModelSerializer):
Expand All @@ -27,3 +34,11 @@ class OrganisationDetailSerializer(serializers.ModelSerializer):
class Meta:
model = Organisation
fields = ('name', 'slug', 'is_active')


class RetrieveOrganisationSerializer(serializers.ModelSerializer):
membership_set = MembershipSerializer()

class Meta:
model = Organisation
fields = ('name', 'slug', 'is_active', 'membership_set')
1 change: 1 addition & 0 deletions demo/project/organisations/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
urlpatterns = [

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

Expand Down
7 changes: 6 additions & 1 deletion demo/project/organisations/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
from rest_framework.response import Response
from project.organisations.models import Organisation, Membership
from project.organisations.serializers import (
CreateOrganisationSerializer, OrganisationMembersSerializer
CreateOrganisationSerializer, OrganisationMembersSerializer, RetrieveOrganisationSerializer
)


class RetrieveOrganisationView(generics.RetrieveAPIView):

serializer_class = RetrieveOrganisationSerializer


class CreateOrganisationView(generics.CreateAPIView):

serializer_class = CreateOrganisationSerializer
Expand Down
49 changes: 33 additions & 16 deletions rest_framework_docs/api_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import inspect
from django.contrib.admindocs.views import simplify_regex
from django.utils.encoding import force_str
from rest_framework.serializers import BaseSerializer


class ApiEndpoint(object):
Expand All @@ -17,8 +18,12 @@ def __init__(self, pattern, parent_pattern=None, drf_router=None):
self.allowed_methods = self.__get_allowed_methods__()
# self.view_name = pattern.callback.__name__
self.errors = None
self.fields = self.__get_serializer_fields__()
self.fields_json = self.__get_serializer_fields_json__()
self.serializer_class = self.__get_serializer_class__()
if self.serializer_class:
self.serializer = self.__get_serializer__()
self.fields = self.__get_serializer_fields__(self.serializer)
self.fields_json = self.__get_serializer_fields_json__()

self.permissions = self.__get_permissions_class__()

def __get_path__(self, parent_pattern):
Expand Down Expand Up @@ -68,27 +73,39 @@ def __get_permissions_class__(self):
for perm_class in self.pattern.callback.cls.permission_classes:
return perm_class.__name__

def __get_serializer_fields__(self):
fields = []
serializer = None
def __get_serializer__(self):
try:
return self.serializer_class()
except KeyError as e:
self.errors = e

def __get_serializer_class__(self):
if hasattr(self.callback.cls, 'serializer_class'):
serializer = self.callback.cls.serializer_class
return self.callback.cls.serializer_class

elif hasattr(self.callback.cls, 'get_serializer_class'):
serializer = self.callback.cls.get_serializer_class(self.pattern.callback.cls())
if hasattr(self.callback.cls, 'get_serializer_class'):
return self.callback.cls.get_serializer_class(self.pattern.callback.cls())

def __get_serializer_fields__(self, serializer):
fields = []

if hasattr(serializer, 'get_fields'):
try:
fields = [{
for key, field in serializer.get_fields().items():
to_many_relation = True if hasattr(field, 'many') else False
sub_fields = []

if to_many_relation:
sub_fields = self.__get_serializer_fields__(field.child) if isinstance(field, BaseSerializer) else None
else:
sub_fields = self.__get_serializer_fields__(field) if isinstance(field, BaseSerializer) else None

fields.append({
"name": key,
"type": str(field.__class__.__name__),
"required": field.required
} for key, field in serializer().get_fields().items()]
except KeyError as e:
self.errors = e
fields = []

"sub_fields": sub_fields,
"required": field.required,
"to_many_relation": to_many_relation
})
# FIXME:
# Show more attibutes of `field`?

Expand Down
36,338 changes: 36,326 additions & 12 deletions rest_framework_docs/static/rest_framework_docs/js/dist.min.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<ul class="list fields">
{% for field in fields %}
<li class="field">
{{ field.name }}: {{ field.type }}
{% if field.required %}
<span class="label label-primary label-required" title="Required">R</span>
{% endif %}

{% if field.to_many_relation %}
<span class="label label-primary label-required" title="Support sending several objects of this type in a request ({{obj}, {obj}, ... })">Array of objects</span>
{% endif %}

{% if field.sub_fields %}
{%include "rest_framework_docs/components/fields_list.html" with fields=field.sub_fields %}
{% endif %}
</li>
{% endfor %}
</ul>
6 changes: 1 addition & 5 deletions rest_framework_docs/templates/rest_framework_docs/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,7 @@ <h4 class="panel-title title">

{% if endpoint.fields %}
<p class="fields-desc">Fields:</p>
<ul class="list fields">
{% for field in endpoint.fields %}
<li class="field">{{ field.name }}: {{ field.type }} {% if field.required %}<span class="label label-primary label-required" title="Required">R</span>{% endif %}</li>
{% endfor %}
</ul>
{%include "rest_framework_docs/components/fields_list.html" with fields=endpoint.fields %}
{% elif not endpoint.errors %}
<p>No fields.</p>
{% endif %}
Expand Down
17 changes: 16 additions & 1 deletion tests/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,26 @@ class Meta:
extra_kwargs = {'password': {'write_only': True}}


class MembershipSerializer(serializers.ModelSerializer):
class Meta:
model = Membership
fields = ('joined', 'is_owner', 'role')


class CreateOrganisationSerializer(serializers.ModelSerializer):
membership_set = MembershipSerializer(many=True)

class Meta:
model = Organisation
fields = ('name', 'slug', 'membership_set')


class RetrieveOrganisationSerializer(serializers.ModelSerializer):
membership_set = MembershipSerializer()

class Meta:
model = Organisation
fields = ('name', 'slug',)
fields = ('name', 'slug', 'is_active', 'membership_set')


class OrganisationMembersSerializer(serializers.ModelSerializer):
Expand Down
17 changes: 10 additions & 7 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def test_index_view_with_endpoints(self):
response = self.client.get(reverse('drfdocs'))

self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context["endpoints"]), 14)
self.assertEqual(len(response.context["endpoints"]), 15)

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

self.assertEqual(response.status_code, 200)
self.assertEqual(response.context["endpoints"][10].path, '/organisation-model-viewsets/')
self.assertEqual(response.context["endpoints"][11].path, '/organisation-model-viewsets/<pk>/')
self.assertEqual(response.context["endpoints"][10].allowed_methods, ['GET', 'POST', 'OPTIONS'])
self.assertEqual(response.context["endpoints"][11].allowed_methods, ['GET', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'])
self.assertEqual(response.context["endpoints"][12].allowed_methods, ['POST', 'OPTIONS'])
self.assertEqual(response.context["endpoints"][12].docstring, 'This is a test.')

self.assertEqual(response.context["endpoints"][10].path, '/organisations/<slug>/')
self.assertEqual(response.context['endpoints'][6].fields[2]['to_many_relation'], True)
self.assertEqual(response.context["endpoints"][11].path, '/organisation-model-viewsets/')
self.assertEqual(response.context["endpoints"][12].path, '/organisation-model-viewsets/<pk>/')
self.assertEqual(response.context["endpoints"][11].allowed_methods, ['GET', 'POST', 'OPTIONS'])
self.assertEqual(response.context["endpoints"][12].allowed_methods, ['GET', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'])
self.assertEqual(response.context["endpoints"][13].allowed_methods, ['POST', 'OPTIONS'])
self.assertEqual(response.context["endpoints"][13].docstring, 'This is a test.')
3 changes: 2 additions & 1 deletion tests/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
url(r'^create/$', view=views.CreateOrganisationView.as_view(), name="create"),
url(r'^(?P<slug>[\w-]+)/members/$', view=views.OrganisationMembersView.as_view(), name="members"),
url(r'^(?P<slug>[\w-]+)/leave/$', view=views.LeaveOrganisationView.as_view(), name="leave"),
url(r'^(?P<slug>[\w-]+)/errored/$', view=views.OrganisationErroredView.as_view(), name="errored")
url(r'^(?P<slug>[\w-]+)/errored/$', view=views.OrganisationErroredView.as_view(), name="errored"),
url(r'^(?P<slug>[\w-]+)/$', view=views.RetrieveOrganisationView.as_view(), name="organisation"),
]

router = SimpleRouter()
Expand Down
5 changes: 5 additions & 0 deletions tests/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,8 @@ class TestModelViewSet(ModelViewSet):
def test_route(self, request):
"""This is a test."""
return Response()


class RetrieveOrganisationView(generics.RetrieveAPIView):

serializer_class = serializers.RetrieveOrganisationSerializer