diff --git a/rest_framework/compat.py b/rest_framework/compat.py index b0e076203..d2cfa05cf 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -180,6 +180,13 @@ except (ImportError, SyntaxError): uritemplate = None +# coreschema is optional +try: + import coreschema +except ImportError: + coreschema = None + + # django-filter is optional try: import django_filters diff --git a/rest_framework/filters.py b/rest_framework/filters.py index 00e753d42..db116efb0 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -16,7 +16,7 @@ from django.utils import six from django.utils.translation import ugettext_lazy as _ from rest_framework.compat import ( - coreapi, distinct, django_filters, guardian, template_render + coreapi, coreschema, distinct, django_filters, guardian, template_render ) from rest_framework.settings import api_settings @@ -34,6 +34,7 @@ class BaseFilterBackend(object): def get_schema_fields(self, view): assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' + assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`' return [] @@ -162,7 +163,18 @@ class SearchFilter(BaseFilterBackend): def get_schema_fields(self, view): assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' - return [coreapi.Field(name=self.search_param, required=False, location='query')] + assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`' + return [ + coreapi.Field( + name=self.search_param, + required=False, + location='query', + schema=coreschema.String( + title='Search', + description='...' + ) + ) + ] class OrderingFilter(BaseFilterBackend): @@ -280,7 +292,18 @@ class OrderingFilter(BaseFilterBackend): def get_schema_fields(self, view): assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' - return [coreapi.Field(name=self.ordering_param, required=False, location='query')] + assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`' + return [ + coreapi.Field( + name=self.ordering_param, + required=False, + location='query', + schema=coreschema.String( + title='Ordering', + description='...' + ) + ) + ] class DjangoObjectPermissionsFilter(BaseFilterBackend): diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 3bb3f7897..95ba1448d 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -16,7 +16,7 @@ from django.utils.encoding import force_text from django.utils.six.moves.urllib import parse as urlparse from django.utils.translation import ugettext_lazy as _ -from rest_framework.compat import coreapi, template_render +from rest_framework.compat import coreapi, coreschema, template_render from rest_framework.exceptions import NotFound from rest_framework.response import Response from rest_framework.settings import api_settings @@ -289,12 +289,16 @@ class PageNumberPagination(BasePagination): def get_schema_fields(self, view): assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' + assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`' fields = [ coreapi.Field( name=self.page_query_param, required=False, location='query', - #description=force_text(self.page_query_description) + schema=coreschema.Integer( + title='Page', + description=force_text(self.page_query_description) + ) ) ] if self.page_size_query_param is not None: @@ -439,18 +443,25 @@ class LimitOffsetPagination(BasePagination): def get_schema_fields(self, view): assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' + assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`' return [ coreapi.Field( name=self.limit_query_param, required=False, location='query', - #description=force_text(self.limit_query_description) + schema=coreschema.Integer( + title='Limit', + description=force_text(self.limit_query_description) + ) ), coreapi.Field( name=self.offset_query_param, required=False, location='query', - #description=force_text(self.offset_query_description) + schema=coreschema.Integer( + title='Offset', + description=force_text(self.offset_query_description) + ) ) ] @@ -764,11 +775,15 @@ class CursorPagination(BasePagination): def get_schema_fields(self, view): assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' + assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`' return [ coreapi.Field( name=self.cursor_query_param, required=False, location='query', - description=force_text(self.cursor_query_description) + schema=coreschema.String( + title='Cursor', + description=force_text(self.cursor_query_description) + ) ) ] diff --git a/rest_framework/schemas.py b/rest_framework/schemas.py index 3a35db2cc..cdfb4aff9 100644 --- a/rest_framework/schemas.py +++ b/rest_framework/schemas.py @@ -1,4 +1,5 @@ import re +import coreschema from collections import OrderedDict from importlib import import_module @@ -26,36 +27,62 @@ from rest_framework.views import APIView header_regex = re.compile('^[a-zA-Z][0-9A-Za-z_]*:') -types_lookup = ClassLookupDict({ - serializers.Field: 'string', - serializers.IntegerField: 'integer', - serializers.FloatField: 'number', - serializers.DecimalField: 'number', - serializers.BooleanField: 'boolean', - serializers.FileField: 'file', - serializers.MultipleChoiceField: 'array', - serializers.ManyRelatedField: 'array', - serializers.Serializer: 'object', - serializers.ListSerializer: 'array' -}) -input_lookup = ClassLookupDict({ - serializers.Field: 'text', - serializers.IntegerField: 'number', - serializers.FloatField: 'number', - serializers.DecimalField: 'number', - serializers.BooleanField: 'checkbox', - serializers.FileField: 'file', - serializers.ChoiceField: 'select' -}) +def field_to_schema(field): + title = force_text(field.label) if field.label else '' + description = force_text(field.help_text) if field.help_text else '' + if isinstance(field, serializers.ListSerializer): + child_schema = serializer_to_schema(field.child) + return coreschema.Array( + items=child_schema, + title=title, + description=description + ) + elif isinstance(field, serializers.Serializer): + return coreschema.Object( + properties={ + key: serializer_to_schema(value) + for key, value + in field.fields.items() + }, + title=title, + description=description + ) + elif isinstance(field, serializers.ManyRelatedField): + return coreschema.Array( + items=coreschema.String(), + title=title, + description=description + ) + elif isinstance(field, serializers.RelatedField): + return coreschema.String(title=title, description=description) + elif isinstance(field, serializers.MultipleChoiceField): + return coreschema.Array( + items=coreschema.Enum(enum=list(field.choices.values())), + title=title, + description=description + ) + elif isinstance(field, serializers.ChoiceField): + return coreschema.Enum( + enum=list(field.choices.values()), + title=title, + description=description + ) + elif isinstance(field, serializers.BooleanField): + return coreschema.Boolean(title=title, description=description) + elif isinstance(field, (serializers.DecimalField, serializers.FloatField)): + return coreschema.Number(title=title, description=description) + elif isinstance(field, serializers.IntegerField): + return coreschema.Integer(title=title, description=description) -def determine_input(field): - input_type = input_lookup[field] - base_template = field.style.get('base_template') - if base_template == 'textarea.html': - input_type = 'textarea' - return input_type + if field.style.get('base_template') == 'textarea.html': + return coreschema.String( + title=title, + description=description, + format='textarea' + ) + return coreschema.String(title=title, description=description) def common_path(paths): @@ -493,8 +520,8 @@ class SchemaGenerator(object): fields = [] for variable in uritemplate.variables(path): - title = None - description = None + title = '' + description = '' if model is not None: # Attempt to infer a field description if possible. try: @@ -514,8 +541,7 @@ class SchemaGenerator(object): name=variable, location='path', required=True, - #title='' if (title is None) else title, - #description='' if (description is None) else description + schema=coreschema.String(title=title, description=description) ) fields.append(field) @@ -540,7 +566,7 @@ class SchemaGenerator(object): name='data', location='body', required=True, - #type='array' + schema=coreschema.Array() ) ] @@ -553,17 +579,11 @@ class SchemaGenerator(object): continue required = field.required and method != 'PATCH' - title = force_text(field.label) if field.label else '' - description = force_text(field.help_text) if field.help_text else '' field = coreapi.Field( name=field.field_name, location='form', required=required, - #title=title, - #description=description, - #type=types_lookup[field], - #input=determine_input(field), - #choices=getattr(field, 'choices', None) + schema=field_to_schema(field) ) fields.append(field) diff --git a/rest_framework/templates/rest_framework/docs/link.html b/rest_framework/templates/rest_framework/docs/link.html index 2d0f1879a..4e6fac231 100644 --- a/rest_framework/templates/rest_framework/docs/link.html +++ b/rest_framework/templates/rest_framework/docs/link.html @@ -20,7 +20,7 @@
{% for field in link.fields|with_location:'path' %} -{{ field.name }}
{% if field.required %} required{% endif %}{{ field.name }}
{% if field.required %} required{% endif %}{{ field.name }}
{% if field.required %} required{% endif %}{{ field.name }}
{% if field.required %} required{% endif %}{{ field.name }}
{% if field.required %} required{% endif %}{{ field.name }}
{% if field.required %} required{% endif %}{{ field.name }}
{% if field.required %} required{% endif %}{{ field.name }}
{% if field.required %} required{% endif %}{{ field.name }}
{% if field.required %} required{% endif %}{{ field.name }}
{% if field.required %} required{% endif %}