Merge remote-tracking branch 'tomchristie/docs' into docs

This commit is contained in:
Emmanouil Konstantinidis 2017-02-01 16:00:07 +00:00
commit 05066b60da
8 changed files with 128 additions and 61 deletions

View File

@ -180,6 +180,13 @@ except (ImportError, SyntaxError):
uritemplate = None uritemplate = None
# coreschema is optional
try:
import coreschema
except ImportError:
coreschema = None
# django-filter is optional # django-filter is optional
try: try:
import django_filters import django_filters

View File

@ -16,7 +16,7 @@ from django.utils import six
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework.compat import ( 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 from rest_framework.settings import api_settings
@ -34,6 +34,7 @@ class BaseFilterBackend(object):
def get_schema_fields(self, view): def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' 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 [] return []
@ -162,7 +163,18 @@ class SearchFilter(BaseFilterBackend):
def get_schema_fields(self, view): def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' 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): class OrderingFilter(BaseFilterBackend):
@ -280,7 +292,18 @@ class OrderingFilter(BaseFilterBackend):
def get_schema_fields(self, view): def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' 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): class DjangoObjectPermissionsFilter(BaseFilterBackend):

View File

@ -16,7 +16,7 @@ from django.utils.encoding import force_text
from django.utils.six.moves.urllib import parse as urlparse from django.utils.six.moves.urllib import parse as urlparse
from django.utils.translation import ugettext_lazy as _ 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.exceptions import NotFound
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
@ -289,12 +289,16 @@ class PageNumberPagination(BasePagination):
def get_schema_fields(self, view): def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' 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 = [ fields = [
coreapi.Field( coreapi.Field(
name=self.page_query_param, name=self.page_query_param,
required=False, required=False,
location='query', 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: if self.page_size_query_param is not None:
@ -439,18 +443,25 @@ class LimitOffsetPagination(BasePagination):
def get_schema_fields(self, view): def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' 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 [ return [
coreapi.Field( coreapi.Field(
name=self.limit_query_param, name=self.limit_query_param,
required=False, required=False,
location='query', location='query',
#description=force_text(self.limit_query_description) schema=coreschema.Integer(
title='Limit',
description=force_text(self.limit_query_description)
)
), ),
coreapi.Field( coreapi.Field(
name=self.offset_query_param, name=self.offset_query_param,
required=False, required=False,
location='query', 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): def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' 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 [ return [
coreapi.Field( coreapi.Field(
name=self.cursor_query_param, name=self.cursor_query_param,
required=False, required=False,
location='query', location='query',
description=force_text(self.cursor_query_description) schema=coreschema.String(
title='Cursor',
description=force_text(self.cursor_query_description)
)
) )
] ]

View File

@ -808,7 +808,7 @@ class DocumentationRenderer(BaseRenderer):
code_style = formatter.get_style_defs('.highlight') code_style = formatter.get_style_defs('.highlight')
langs = ['shell', 'javascript', 'python'] langs = ['shell', 'javascript', 'python']
codec = coreapi.codecs.CoreJSONCodec() codec = coreapi.codecs.CoreJSONCodec()
schema = mark_safe(codec.encode(data)) schema = mark_safe(json.dumps(codec.encode(data)))
return { return {
'document': data, 'document': data,
'langs': langs, 'langs': langs,

View File

@ -13,7 +13,7 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework import exceptions, renderers, serializers from rest_framework import exceptions, renderers, serializers
from rest_framework.compat import ( from rest_framework.compat import (
RegexURLPattern, RegexURLResolver, coreapi, uritemplate, urlparse RegexURLPattern, RegexURLResolver, coreapi, coreschema, uritemplate, urlparse
) )
from rest_framework.request import clone_request from rest_framework.request import clone_request
from rest_framework.response import Response from rest_framework.response import Response
@ -26,36 +26,62 @@ from rest_framework.views import APIView
header_regex = re.compile('^[a-zA-Z][0-9A-Za-z_]*:') 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({ def field_to_schema(field):
serializers.Field: 'text', title = force_text(field.label) if field.label else ''
serializers.IntegerField: 'number', description = force_text(field.help_text) if field.help_text else ''
serializers.FloatField: 'number',
serializers.DecimalField: 'number',
serializers.BooleanField: 'checkbox',
serializers.FileField: 'file',
serializers.ChoiceField: 'select'
})
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): if field.style.get('base_template') == 'textarea.html':
input_type = input_lookup[field] return coreschema.String(
base_template = field.style.get('base_template') title=title,
if base_template == 'textarea.html': description=description,
input_type = 'textarea' format='textarea'
return input_type )
return coreschema.String(title=title, description=description)
def common_path(paths): def common_path(paths):
@ -252,6 +278,7 @@ class SchemaGenerator(object):
def __init__(self, title=None, url=None, patterns=None, urlconf=None): def __init__(self, title=None, url=None, patterns=None, urlconf=None):
assert coreapi, '`coreapi` must be installed for schema support.' assert coreapi, '`coreapi` must be installed for schema support.'
assert coreschema, '`coreschema` must be installed for schema support.'
if url and not url.endswith('/'): if url and not url.endswith('/'):
url += '/' url += '/'
@ -493,8 +520,9 @@ class SchemaGenerator(object):
fields = [] fields = []
for variable in uritemplate.variables(path): for variable in uritemplate.variables(path):
title = None title = ''
description = None description = ''
schema_cls = coreschema.String
if model is not None: if model is not None:
# Attempt to infer a field description if possible. # Attempt to infer a field description if possible.
try: try:
@ -510,12 +538,14 @@ class SchemaGenerator(object):
elif model_field is not None and model_field.primary_key: elif model_field is not None and model_field.primary_key:
description = get_pk_description(model, model_field) description = get_pk_description(model, model_field)
if isinstance(model_field, models.AutoField):
schema_cls = coreschema.Integer
field = coreapi.Field( field = coreapi.Field(
name=variable, name=variable,
location='path', location='path',
required=True, required=True,
#title='' if (title is None) else title, schema=schema_cls(title=title, description=description)
#description='' if (description is None) else description
) )
fields.append(field) fields.append(field)
@ -540,7 +570,7 @@ class SchemaGenerator(object):
name='data', name='data',
location='body', location='body',
required=True, required=True,
#type='array' schema=coreschema.Array()
) )
] ]
@ -553,17 +583,11 @@ class SchemaGenerator(object):
continue continue
required = field.required and method != 'PATCH' 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( field = coreapi.Field(
name=field.field_name, name=field.field_name,
location='form', location='form',
required=required, required=required,
#title=title, schema=field_to_schema(field)
#description=description,
#type=types_lookup[field],
#input=determine_input(field),
#choices=getattr(field, 'choices', None)
) )
fields.append(field) fields.append(field)

View File

@ -85,7 +85,7 @@
const coreapi = window.coreapi const coreapi = window.coreapi
const codec = new coreapi.codecs.CoreJSONCodec() const codec = new coreapi.codecs.CoreJSONCodec()
const schema = {{ schema }} const schema = {{ schema }}
const doc = codec.decode(schema, {preloaded: true}) const doc = codec.decode(schema)
const client = new coreapi.Client(null, null, csrf) const client = new coreapi.Client(null, null, csrf)
// Language Control // Language Control

View File

@ -20,7 +20,7 @@
</thead> </thead>
<tbody> <tbody>
{% for field in link.fields|with_location:'path' %} {% for field in link.fields|with_location:'path' %}
<tr><td class="parameter-name"><code>{{ field.name }}</code>{% if field.required %} <span class="label label-warning">required</span>{% endif %}</td><td>{% if field.description %}{{ field.description }}{% endif %}</td></tr> <tr><td class="parameter-name"><code>{{ field.name }}</code>{% if field.required %} <span class="label label-warning">required</span>{% endif %}</td><td>{% if field.schema.description %}{{ field.schema.description }}{% endif %}</td></tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
@ -34,7 +34,7 @@
</thead> </thead>
<tbody> <tbody>
{% for field in link.fields|with_location:'query' %} {% for field in link.fields|with_location:'query' %}
<tr><td class="parameter-name"><code>{{ field.name }}</code>{% if field.required %} <span class="label label-warning">required</span>{% endif %}</td><td>{% if field.description %}{{ field.description }}{% endif %}</td></tr> <tr><td class="parameter-name"><code>{{ field.name }}</code>{% if field.required %} <span class="label label-warning">required</span>{% endif %}</td><td>{% if field.schema.description %}{{ field.schema.description }}{% endif %}</td></tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
@ -48,7 +48,7 @@
</thead> </thead>
<tbody> <tbody>
{% for field in link.fields|with_location:'header' %} {% for field in link.fields|with_location:'header' %}
<tr><td class="parameter-name"><code>{{ field.name }}</code>{% if field.required %} <span class="label label-warning">required</span>{% endif %}</td><td>{% if field.description %}{{ field.description }}{% endif %}</td></tr> <tr><td class="parameter-name"><code>{{ field.name }}</code>{% if field.required %} <span class="label label-warning">required</span>{% endif %}</td><td>{% if field.schema.description %}{{ field.schema.description }}{% endif %}</td></tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
@ -62,7 +62,7 @@
</thead> </thead>
<tbody> <tbody>
{% for field in link.fields|with_location:'body' %} {% for field in link.fields|with_location:'body' %}
<tr><td class="parameter-name"><code>{{ field.name }}</code>{% if field.required %} <span class="label label-warning">required</span>{% endif %}</td><td>{% if field.description %}{{ field.description }}{% endif %}</td></tr> <tr><td class="parameter-name"><code>{{ field.name }}</code>{% if field.required %} <span class="label label-warning">required</span>{% endif %}</td><td>{% if field.schema.description %}{{ field.schema.description }}{% endif %}</td></tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
@ -75,7 +75,7 @@
</thead> </thead>
<tbody> <tbody>
{% for field in link.fields|with_location:'form' %} {% for field in link.fields|with_location:'form' %}
<tr><td class="parameter-name"><code>{{ field.name }}</code>{% if field.required %} <span class="label label-warning">required</span>{% endif %}</td><td>{% if field.description %}{{ field.description }}{% endif %}</td></tr> <tr><td class="parameter-name"><code>{{ field.name }}</code>{% if field.required %} <span class="label label-warning">required</span>{% endif %}</td><td>{% if field.schema.description %}{{ field.schema.description }}{% endif %}</td></tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>

View File

@ -39,13 +39,8 @@ class FencedCodeExtension(markdown.Extension):
">normalize_whitespace") ">normalize_whitespace")
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import HtmlFormatter
@register.tag(name='code') @register.tag(name='code')
def do_code(parser,token): def highlight_code(parser,token):
code = token.split_contents()[-1] code = token.split_contents()[-1]
nodelist = parser.parse(('endcode',)) nodelist = parser.parse(('endcode',))
parser.delete_first_token() parser.delete_first_token()
@ -60,6 +55,9 @@ class CodeNode(template.Node):
self.nodelist = code self.nodelist = code
def render(self, context): def render(self, context):
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import HtmlFormatter
body = self.nodelist.render(context) body = self.nodelist.render(context)
lexer = get_lexer_by_name(self.lang, stripall=False) lexer = get_lexer_by_name(self.lang, stripall=False)
formatter = HtmlFormatter(nowrap=True, style=self.style) formatter = HtmlFormatter(nowrap=True, style=self.style)