Merge pull request #1 from tomchristie/docs

Docs
This commit is contained in:
Emmanouil Konstantinidis 2017-02-01 10:29:24 +00:00 committed by GitHub
commit 0b0a55c9a0
16 changed files with 492 additions and 16 deletions

View File

@ -1155,6 +1155,11 @@ The [html-json-forms][html-json-forms] package provides an algorithm and seriali
[DRF-Base64][drf-base64] provides a set of field and model serializers that handles the upload of base64-encoded files.
## QueryFields
[djangorestframework-queryfields][djangorestframework-queryfields] allows API clients to specify which fields will be sent in the response via inclusion or exclusion query paramaters.
[cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion
[relations]: relations.md
[model-managers]: https://docs.djangoproject.com/en/stable/topics/db/managers/
@ -1173,3 +1178,4 @@ The [html-json-forms][html-json-forms] package provides an algorithm and seriali
[drf-dynamic-fields]: https://github.com/dbrgn/drf-dynamic-fields
[drf-base64]: https://bitbucket.org/levit_scs/drf_base64
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions
[djangorestframework-queryfields]: http://djangorestframework-queryfields.readthedocs.io/

View File

@ -894,11 +894,11 @@ If the request is omitted from the context, the returned URLs will be of the for
The custom `X-Throttle-Wait-Second` header has now been dropped in favor of the standard `Retry-After` header. You can revert this behavior if needed by writing a custom exception handler for your application.
#### Date and time objects as ISO-8859-1 strings in serializer data.
#### Date and time objects as ISO-8601 strings in serializer data.
Date and Time objects are now coerced to strings by default in the serializer output. Previously they were returned as `Date`, `Time` and `DateTime` objects, and later coerced to strings by the renderer.
You can modify this behavior globally by settings the existing `DATE_FORMAT`, `DATETIME_FORMAT` and `TIME_FORMAT` settings keys. Setting these values to `None` instead of their default value of `'iso-8859-1'` will result in native objects being returned in serializer data.
You can modify this behavior globally by settings the existing `DATE_FORMAT`, `DATETIME_FORMAT` and `TIME_FORMAT` settings keys. Setting these values to `None` instead of their default value of `'iso-8601'` will result in native objects being returned in serializer data.
REST_FRAMEWORK = {
# Return native `Date` and `Time` objects in `serializer.data`

View File

@ -207,6 +207,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [html-json-forms][html-json-forms] - Provides an algorithm and serializer to process HTML JSON Form submissions per the (inactive) spec.
* [django-rest-framework-serializer-extensions][drf-serializer-extensions] -
Enables black/whitelisting fields, and conditionally expanding child serializers on a per-view/request basis.
* [djangorestframework-queryfields][djangorestframework-queryfields] - Serializer mixin allowing clients to control which fields will be sent in the API response.
### Serializer fields
@ -370,3 +371,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[drf_tweaks]: https://github.com/ArabellaTech/drf_tweaks
[drf-oidc-auth]: https://github.com/ByteInternet/drf-oidc-auth
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions
[djangorestframework-queryfields]: https://github.com/wimglenn/djangorestframework-queryfields

View File

@ -48,6 +48,8 @@ We'll need to add our new `snippets` app and the `rest_framework` app to `INSTAL
'snippets.apps.SnippetsConfig',
)
Please note that if you're using Django <1.9, you need to replace `snippets.apps.SnippetsConfig` with `snippets`.
Okay, we're ready to roll.
## Creating a model to work with

View File

@ -294,7 +294,7 @@ class PageNumberPagination(BasePagination):
name=self.page_query_param,
required=False,
location='query',
description=force_text(self.page_query_description)
#description=force_text(self.page_query_description)
)
]
if self.page_size_query_param is not None:
@ -303,7 +303,7 @@ class PageNumberPagination(BasePagination):
name=self.page_size_query_param,
required=False,
location='query',
description=force_text(self.page_size_query_description)
#description=force_text(self.page_size_query_description)
)
)
return fields
@ -444,13 +444,13 @@ class LimitOffsetPagination(BasePagination):
name=self.limit_query_param,
required=False,
location='query',
description=force_text(self.limit_query_description)
#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)
#description=force_text(self.offset_query_description)
)
]

View File

@ -798,10 +798,30 @@ class DocumentationRenderer(BaseRenderer):
media_type = 'text/html'
format = 'html'
charset = 'utf-8'
template = 'rest_framework/docs/index.html'
code_style = 'emacs'
def get_context(self, data):
from pygments.formatters import HtmlFormatter
from django.utils.html import mark_safe
formatter = HtmlFormatter(style=self.code_style)
code_style = formatter.get_style_defs('.highlight')
langs = ['shell', 'javascript', 'python']
codec = coreapi.codecs.CoreJSONCodec()
schema = mark_safe(codec.encode(data))
return {
'document': data,
'langs': langs,
'code_style': code_style,
'schema': schema
}
def render(self, data, accepted_media_type=None, renderer_context=None):
from coredocs.main import render as render_docs
return render_docs(data, theme='cerulean', highlight='emacs', static=lambda path: '/static/rest_framework/docs/' + path)
#from coredocs.main import render as render_docs
#return render_docs(data, theme='cerulean', highlight='emacs', static=lambda path: '/static/rest_framework/docs/' + path)
template = loader.get_template(self.template)
context = self.get_context(data)
return template_render(template, context, request=renderer_context['request'])
class MultiPartRenderer(BaseRenderer):

View File

@ -514,8 +514,8 @@ class SchemaGenerator(object):
name=variable,
location='path',
required=True,
title='' if (title is None) else title,
description='' if (description is None) else description
#title='' if (title is None) else title,
#description='' if (description is None) else description
)
fields.append(field)
@ -540,7 +540,7 @@ class SchemaGenerator(object):
name='data',
location='body',
required=True,
type='array'
#type='array'
)
]
@ -559,11 +559,11 @@ class SchemaGenerator(object):
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)
#title=title,
#description=description,
#type=types_lookup[field],
#input=determine_input(field),
#choices=getattr(field, 'choices', None)
)
fields.append(field)

View File

@ -0,0 +1,19 @@
{% load rest_framework %}
<div class="row">
<div class="col-md-6">
<h1 id="document-title">{{ document.title }}</h1>
<p>{% render_markdown document.description %}</p>
</div>
<div class="col-md-6"></div>
</div>
{% for section_key, section in document.data.items %}
<h1 id="{{ section_key }}" class="coredocs-section">{{ section_key }}</h1>
{% for link_key, link in section.links.items %}
{% include "rest_framework/docs/link.html" %}
{% endfor %}
{% endfor %}
{% for link_key, link in document.links.items %}
{% include "rest_framework/docs/llink.html" %}
{% endfor %}

View File

@ -0,0 +1,143 @@
{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ document.title }}</title>
<link href="{% static 'rest_framework/docs/css/bootstrap-custom.min.css' %}" rel="stylesheet">
<link href="{% static 'rest_framework/docs/css/font-awesome-4.0.3.css' %}" rel="stylesheet">
<link href="{% static 'rest_framework/docs/css/base.css' %}" rel="stylesheet">
<style>{{ code_style }}</style>
<style>
.highlight {background-color: #f7f7f9}
.coredocs-link {border-top: 1px solid lightgrey; margin-top: 20px}
.coredocs-section {border-top: 2px solid lightgrey; margin-top: 20px; padding-top: 20px}
.checkbox label.control-label {font-weight: bold}
@media (min-width: 768px) {
.navbar-nav.navbar-right:last-child {
margin-right: 0 !important;
}
}
</style>
<script src="https://unpkg.com/coreapi@0.0.17/dist/coreapi.js"></script>
</head>
<body>
{% include "rest_framework/docs/nav.html" %}
<div class="container-fluid">
<div class="col-md-2">{% include "rest_framework/docs/toc.html" %}</div>
<div class="col-md-10" role="main">{% include "rest_framework/docs/document.html" %}</div>
</div>
<script src="{% static 'rest_framework/docs/js/jquery-1.10.2.min.js' %}"></script>
<script src="{% static 'rest_framework/docs/js/bootstrap-3.0.3.min.js' %}"></script>
<script>
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrf = {'X-CSRFToken': getCookie('csrftoken')}
const coreapi = window.coreapi
const codec = new coreapi.codecs.CoreJSONCodec()
const schema = {{ schema }}
const doc = codec.decode(schema, {preloaded: true})
const client = new coreapi.Client(null, null, csrf)
$('body').scrollspy({ target: '#toc' })
// Language Control
$('.language-control li a').click(function (event) {
event.preventDefault();
var button = $(this)
var language = button.data("language")
var languageControls = $('.language-control li a')
languageControls.not('[data-language="' + language +'"]').parent().removeClass("active")
languageControls.filter('[data-language="' + language +'"]').parent().addClass("active")
button.closest("li.dropdown").find('.dropdown-toggle span').text(language)
var codeBlocks = $('pre.highlight')
codeBlocks.not('[data-language="' + language +'"]').addClass("hide")
codeBlocks.filter('[data-language="' + language +'"]').removeClass("hide")
})
// API Explorer
$('form').submit(function(event) {
event.preventDefault();
const form = $(this).closest("form");
const key = form.data("key");
var params = {};
const formData = new FormData(form.get()[0]);
for (var [paramKey, paramValue] of formData.entries()) {
var elem = form.find("[name=" + paramKey + "]")
var dataType = elem.data('type') || 'string'
var dataLocation = elem.data('location')
if (dataType === 'integer' && paramValue) {
paramValue = parseInt(paramValue)
} else if (dataType === 'number' && paramValue) {
paramValue = parseFloat(paramValue)
} else if (dataType === 'boolean' && paramValue) {
paramValue = {
'true': true,
'false': false
}[paramValue.toLowerCase()]
} else if (dataType === 'array' && paramValue) {
paramValue = JSON.parse(paramValue)
}
if (dataLocation === 'query' && !paramValue) {
continue
}
params[paramKey] = paramValue
}
form.find(":checkbox").each(function( index ) {
var name = $(this).attr("name");
if (!params.hasOwnProperty(name)) {
params[name] = false
}
})
console.log(params)
client.action(doc, key, params).then(function (data) {
var response = JSON.stringify(data, null, 2);
form.find(".response-data").text(response)
form.find(".response-data").removeClass("hide")
form.find(".response-error").addClass("hide")
}).catch(function (error) {
var response = JSON.stringify(error.content, null, 2);
form.find(".response-error").text(error.message)
form.find(".response-data").text(response)
form.find(".response-error").removeClass("hide")
form.find(".response-data").removeClass("hide")
/*form.find(".response-data").addClass("hide")*/
})
});
</script>
</body>
</html>

View File

@ -0,0 +1,18 @@
{% load rest_framework %}
<pre class="highlight javascript hide" data-language="javascript"><code>{% code javascript %}var coreapi = window.coreapi
// Initialize a client & load the schema document
var client = new coreapi.Client()
var document = null
client.get("{{ document.url }}").then(function(result) {
document = result
})
// Interact with the API endpoint
var action = [{% if section_key %}"{{ section_key }}", {% endif %}"{{ link_key }}"]
{% if link.fields %}var params = {
{% for field in link.fields %} {{ field.name }}: ...{% if not loop.last %},{% endif %}
{% endfor %}}
{% endif %}client.action(document, action, params=params).then(function(result) {
// Return value is in 'result'
}){% endcode %}</code></pre>

View File

@ -0,0 +1,13 @@
{% load rest_framework %}
<pre class="highlight python hide" data-language="python"><code>{% code python %}import coreapi
# Initialize a client & load the schema document
client = coreapi.Client()
document = client.get("{{ document.url }}"{% if schema_format %}, format="{{ schema_format }}"{% endif %})
# Interact with the API endpoint
action = [{% if section_key %}"{{ section_key }}", {% endif %}"{{ link_key }}"]
{% if link.fields %}params = {
{% for field in link.fields %} "{{ field.name }}": ...{% if not loop.last %},{% endif %}
{% endfor %}}
{% endif %}result = client.action(document, action{% if link.fields %}, params=params{% endif %}){% endcode %}</code></pre>

View File

@ -0,0 +1,6 @@
{% load rest_framework %}
<pre class="highlight shell" data-language="shell"><code>{% code bash %}# Load the schema document
$ coreapi get {{ document.url }}{% if schema_format %} --format {{ schema_format }}{% endif %}
# Interact with the API endpoint
$ coreapi action {% if section_key %}{{ section_key }} {% endif %}{{ link_key }}{% for field in link.fields %} -p {{ field.name }}=...{% endfor %}{% endcode %}</code></pre>

View File

@ -0,0 +1,119 @@
{% load rest_framework %}
<div class="row coredocs-link">
<div class="col-md-6 docs-content">
<button class="btn btn-success" style="float: right; margin-top: 20px" data-toggle="modal" data-target="#{{ section_key }}_{{ link_key }}_modal">Interact</button>
<h2 id="{{ section_key }}-{{ link_key }}">{{ link.title|default:link_key }}</h2>
<p>
<span class="label label-primary">{{ link.action|upper }}</span> <code>{{ link.url }}</code>
</p>
<p>{% render_markdown link.description %}</p>
{% if link.fields|with_location:'path' %}
<h4>Path Parameters</h4>
<p>The following parameters should be included in the URL path.</p>
<table class="parameters table table-bordered table-striped">
<thead>
<tr><th>Parameter</th><th>Description</th></tr>
</thead>
<tbody>
{% 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>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if link.fields|with_location:'query' %}
<h4>Query Parameters</h4>
<p>The following parameters should be included as part of a URL query string.</p>
<table class="parameters table table-bordered table-striped">
<thead>
<tr><th>Parameter</th><th>Description</th></tr>
</thead>
<tbody>
{% 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>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if link.fields|with_location:'header' %}
<h4>Header Parameters</h4>
<p>The following parameters should be included as HTTP headers.</p>
<table class="parameters table table-bordered table-striped">
<thead>
<tr><th>Parameter</th><th>Description</th></tr>
</thead>
<tbody>
{% 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>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if link.fields|with_location:'body' %}
<h4>Request Body</h4>
<p>The request body should be <code>"{{ link.encoding }}"</code> encoded, and should contain a single item.</p>
<table class="parameters table table-bordered table-striped">
<thead>
<tr><th>Parameter</th><th>Description</th></tr>
</thead>
<tbody>
{% 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>
{% endfor %}
</tbody>
</table>
{% elif link.fields|with_location:'form' %}
<h4>Request Body</h4>
<p>The request body should be a <code>"{{ link.encoding }}"</code> encoded object, containing the following&nbsp;items.</p>
<table class="parameters table table-bordered table-striped">
<thead>
<tr><th>Parameter</th><th>Description</th></tr>
</thead>
<tbody>
{% 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>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
<div class="col-md-6 code-samples">
{% if 'shell' in langs %}{% include "rest_framework/docs/langs/shell.html" %}{% endif %}
{% if 'python' in langs %}{% include "rest_framework/docs/langs/python.html" %}{% endif %}
{% if 'javascript' in langs %}{% include "rest_framework/docs/langs/javascript.html" %}{% endif %}
</div>
</div>
<!-- Modal -->
<div class="modal fade api-modal" id="{{ section_key }}_{{ link_key }}_modal" tabindex="-1" role="dialog" aria-labelledby="api explorer modal">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">{{ link.title|default:link_key }}</h4>
</div>
<div class="modal-body">
<form data-key='["{{ section_key }}", "{{ link_key }}"]'>
{% form_for_link link %}
<div id="response">
<div class="response-error alert alert-danger hide"></div>
<pre class="response-data hide"></pre>
</div>
<div class="text-right">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,34 @@
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container-fluid">
<!-- Collapsed navigation -->
<div class="navbar-header">
<!-- Expander button -->
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Main title -->
<a class="navbar-brand" href="{{ homepage_url }}">{{ document.title }}</a>
</div>
<div class="navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span>shell</span> <b class="caret"></b></a>
<ul class="dropdown-menu language-control">
<li class="active"><a href="#" data-language="shell">shell</a></li>
<li><a href="#" data-language="javascript">javascript</a></li>
<li><a href="#" data-language="python">python</a></li>
</ul>
</li>
</ul>
</div>
</div>
</div>

View File

@ -0,0 +1,17 @@
<div id="toc" class="bs-sidebar hidden-print affix" role="complementary">
<ul class="nav bs-sidenav">
<li class="active">
<a href="#document-title">{{ document.title }}</a>
</li>
{% for section_key, section in document.data.items %}
<li>
<a href="#{{ section_key }}">{{ section_key }}</a>
<ul class="nav">
{% for link_key, link in section.links.items %}
<li><a href="#{{ section_key }}-{{ link_key }}">{{ link.title|default:link_key }}</a></li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
</div>

View File

@ -13,12 +13,89 @@ from rest_framework.compat import NoReverseMatch, reverse, template_render
from rest_framework.renderers import HTMLFormRenderer
from rest_framework.utils.urls import replace_query_param
from markdown.extensions.fenced_code import FencedBlockPreprocessor
import markdown
register = template.Library()
# Regex for adding classes to html snippets
class_re = re.compile(r'(?<=class=["\'])(.*)(?=["\'])')
class CustomFencedBlockPreprocessor(FencedBlockPreprocessor):
CODE_WRAP = '<pre%s><code>%s</code></pre>'
LANG_TAG = ' class="highlight %s"'
class FencedCodeExtension(markdown.Extension):
def extendMarkdown(self, md, md_globals):
""" Add FencedBlockPreprocessor to the Markdown instance. """
md.registerExtension(self)
md.preprocessors.add('fenced_code_block',
CustomFencedBlockPreprocessor(md),
">normalize_whitespace")
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import HtmlFormatter
@register.tag(name='code')
def do_code(parser,token):
code = token.split_contents()[-1]
nodelist = parser.parse(('endcode',))
parser.delete_first_token()
return CodeNode(code, nodelist)
class CodeNode(template.Node):
style = 'emacs'
def __init__(self, lang, code):
self.lang = lang
self.nodelist = code
def render(self, context):
body = self.nodelist.render(context)
lexer = get_lexer_by_name(self.lang, stripall=False)
formatter = HtmlFormatter(nowrap=True, style=self.style)
code = highlight(body, lexer, formatter)
return code
@register.filter()
def with_location(fields, location):
return [
field for field in fields
if field.location == location
]
@register.simple_tag
def form_for_link(link):
import coreschema
properties = {
field.name: field.schema or coreschema.String()
for field in link.fields
}
required = [
field.name
for field in link.fields
if field.required
]
schema = coreschema.Object(properties=properties, required=required)
return coreschema.render_to_form(schema)
@register.simple_tag
def render_markdown(markdown_text):
return markdown.markdown(markdown_text, extensions=[FencedCodeExtension(), "tables"])
@register.simple_tag
def get_pagination_html(pager):
return pager.to_html()