API docs tweaks

This commit is contained in:
Tom Christie 2017-03-01 14:50:01 +00:00
parent c412de920e
commit 5c99e7e01e
18 changed files with 2111 additions and 53 deletions

View File

@ -0,0 +1,63 @@
<style>
.promo li a {
float: left;
width: 130px;
height: 20px;
text-align: center;
margin: 10px 30px;
padding: 150px 0 0 0;
background-position: 0 50%;
background-size: 130px auto;
background-repeat: no-repeat;
font-size: 120%;
color: black;
}
.promo li {
list-style: none;
}
</style>
# Django REST framework 3.6
---
## Funding
The 3.6 release would not have been possible without our [collaborative funding model][funding].
If you use REST framework commercially and would like to see this work continue,
we strongly encourage you to invest in its continued development by
**[signing up for a paid&nbsp;plan][funding]**.
<ul class="premium-promo promo">
<li><a href="http://jobs.rover.com/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://getsentry.com/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li>
<li><a href="https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
<li><a href="https://hello.machinalis.co.uk/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
<li><a href="https://rollbar.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rollbar.png)">Rollbar</a></li>
<li><a href="https://micropyramid.com/django-rest-framework-development-services/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/mp-text-logo.png)">MicroPyramid</a></li>
</ul>
<div style="clear: both; padding-bottom: 20px;"></div>
*Many thanks to all our [sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Machinalis](https://hello.machinalis.co.uk/), [Rollbar](https://rollbar.com), and [MicroPyramid](https://micropyramid.com/django-rest-framework-development-services/).*
---
## API documentation
...
## JavaScript Client
...
---
## Deprecations
...
---
[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
[funding]: funding.md

View File

@ -6,9 +6,13 @@
There are a variety of approaches to API documentation. This document introduces a few of the various tools and options you might choose from. The approaches should not be considered exclusive - you may want to provide more than one documentation style for you API, such as a self describing API that also includes static documentation of the various API endpoints. There are a variety of approaches to API documentation. This document introduces a few of the various tools and options you might choose from. The approaches should not be considered exclusive - you may want to provide more than one documentation style for you API, such as a self describing API that also includes static documentation of the various API endpoints.
## Endpoint documentation ##
The most common way to document Web APIs today is to produce documentation that lists the API endpoints verbatim, and describes the allowable operations on each. There are various tools that allow you to do this in an automated or semi-automated way. ... TODO ...
## Third party packages
There are a number of mature third-party packages for providing API documentation.
--- ---

View File

@ -3,7 +3,7 @@
Looking for a new Django REST Framework related role? On this site we provide a list of job resources that may be helpful. It's also worth checking out if any of [our sponsors are hiring][drf-funding]. Looking for a new Django REST Framework related role? On this site we provide a list of job resources that may be helpful. It's also worth checking out if any of [our sponsors are hiring][drf-funding].
## Places to Look for Django REST Framework Jobs ## Places to look for Django REST Framework Jobs
* [https://www.djangoproject.com/community/jobs/][djangoproject-website] * [https://www.djangoproject.com/community/jobs/][djangoproject-website]
* [https://www.python.org/jobs/][python-org-jobs] * [https://www.python.org/jobs/][python-org-jobs]

View File

@ -62,14 +62,15 @@ pages:
- 'Tutorials and Resources': 'topics/tutorials-and-resources.md' - 'Tutorials and Resources': 'topics/tutorials-and-resources.md'
- 'Contributing to REST framework': 'topics/contributing.md' - 'Contributing to REST framework': 'topics/contributing.md'
- 'Project management': 'topics/project-management.md' - 'Project management': 'topics/project-management.md'
- 'Jobs': 'topics/jobs.md'
- '3.0 Announcement': 'topics/3.0-announcement.md' - '3.0 Announcement': 'topics/3.0-announcement.md'
- '3.1 Announcement': 'topics/3.1-announcement.md' - '3.1 Announcement': 'topics/3.1-announcement.md'
- '3.2 Announcement': 'topics/3.2-announcement.md' - '3.2 Announcement': 'topics/3.2-announcement.md'
- '3.3 Announcement': 'topics/3.3-announcement.md' - '3.3 Announcement': 'topics/3.3-announcement.md'
- '3.4 Announcement': 'topics/3.4-announcement.md' - '3.4 Announcement': 'topics/3.4-announcement.md'
- '3.5 Announcement': 'topics/3.5-announcement.md' - '3.5 Announcement': 'topics/3.5-announcement.md'
- '3.6 Announcement': 'topics/3.6-announcement.md'
- 'Kickstarter Announcement': 'topics/kickstarter-announcement.md' - 'Kickstarter Announcement': 'topics/kickstarter-announcement.md'
- 'Mozilla Grant': 'topics/mozilla-grant.md' - 'Mozilla Grant': 'topics/mozilla-grant.md'
- 'Funding': 'topics/funding.md' - 'Funding': 'topics/funding.md'
- 'Release Notes': 'topics/release-notes.md' - 'Release Notes': 'topics/release-notes.md'
- 'Jobs': 'topics/jobs.md'

View File

@ -1,13 +1,36 @@
from rest_framework.renderers import CoreJSONRenderer, DocumentationRenderer from rest_framework.renderers import CoreJSONRenderer, DocumentationRenderer, SchemaJSRenderer
from rest_framework.schemas import get_schema_view from rest_framework.schemas import get_schema_view
from django.conf.urls import url, include
def get_docs_view(title=None, url=None, renderer_classes=None): def get_docs_view(title=None, url=None, renderer_classes=None, public=True):
if renderer_classes is None: if renderer_classes is None:
renderer_classes = [DocumentationRenderer, CoreJSONRenderer] renderer_classes = [DocumentationRenderer, CoreJSONRenderer]
return get_schema_view( return get_schema_view(
title=title, title=title,
url=url, url=url,
renderer_classes=renderer_classes renderer_classes=renderer_classes,
public=public
) )
def get_schemajs_view(title=None, url=None, public=True):
renderer_classes = [SchemaJSRenderer]
return get_schema_view(
title=title,
url=url,
renderer_classes=renderer_classes,
public=public
)
def include_docs_urls(title=None, schema_url=None, public=True):
docs_view = get_docs_view(title, schema_url, public=public)
schema_js_view = get_schemajs_view(title, schema_url, public=public)
urls = [
url(r'^$', docs_view, name='docs-index'),
url(r'^schema.js$', schema_js_view, name='schema-js')
]
return include(urls, namespace='api-docs')

View File

@ -8,6 +8,7 @@ REST framework also provides an HTML renderer that renders the browsable API.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
import base64
import json import json
from collections import OrderedDict from collections import OrderedDict
@ -19,6 +20,7 @@ from django.http.multipartparser import parse_header
from django.template import Template, loader from django.template import Template, loader
from django.test.client import encode_multipart from django.test.client import encode_multipart
from django.utils import six from django.utils import six
from django.utils.html import mark_safe
from rest_framework import VERSION, exceptions, serializers, status from rest_framework import VERSION, exceptions, serializers, status
from rest_framework.compat import ( from rest_framework.compat import (
@ -803,17 +805,14 @@ class DocumentationRenderer(BaseRenderer):
def get_context(self, data, request): def get_context(self, data, request):
from pygments.formatters import HtmlFormatter from pygments.formatters import HtmlFormatter
from django.utils.html import mark_safe
formatter = HtmlFormatter(style=self.code_style) formatter = HtmlFormatter(style=self.code_style)
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(json.dumps(codec.encode(data)))
return { return {
'document': data, 'document': data,
'langs': langs, 'langs': langs,
'code_style': code_style, 'code_style': code_style,
'schema': schema,
'request': request 'request': request
} }
@ -823,6 +822,22 @@ class DocumentationRenderer(BaseRenderer):
return template_render(template, context, request=renderer_context['request']) return template_render(template, context, request=renderer_context['request'])
class SchemaJSRenderer(BaseRenderer):
media_type = 'script/javascript'
format = 'javascript'
charset = 'utf-8'
template = 'rest_framework/schema.js'
def render(self, data, accepted_media_type=None, renderer_context=None):
codec = coreapi.codecs.CoreJSONCodec()
schema = base64.b64encode(codec.encode(data))
template = loader.get_template(self.template)
context = {'schema': mark_safe(schema)}
request = renderer_context['request']
return template_render(template, context, request=request)
class MultiPartRenderer(BaseRenderer): class MultiPartRenderer(BaseRenderer):
media_type = 'multipart/form-data; boundary=BoUnDaRyStRiNg' media_type = 'multipart/form-data; boundary=BoUnDaRyStRiNg'
format = 'multipart' format = 'multipart'

View File

@ -291,7 +291,7 @@ class SchemaGenerator(object):
self.url = url self.url = url
self.endpoints = None self.endpoints = None
def get_schema(self, request=None): def get_schema(self, request=None, public=False):
""" """
Generate a `coreapi.Document` representing the API schema. Generate a `coreapi.Document` representing the API schema.
""" """
@ -299,7 +299,7 @@ class SchemaGenerator(object):
inspector = self.endpoint_inspector_cls(self.patterns, self.urlconf) inspector = self.endpoint_inspector_cls(self.patterns, self.urlconf)
self.endpoints = inspector.get_api_endpoints() self.endpoints = inspector.get_api_endpoints()
links = self.get_links(request) links = self.get_links(None if public else request)
if not links: if not links:
return None return None
@ -661,7 +661,7 @@ class SchemaGenerator(object):
return named_path_components + [action] return named_path_components + [action]
def get_schema_view(title=None, url=None, urlconf=None, renderer_classes=None): def get_schema_view(title=None, url=None, urlconf=None, renderer_classes=None, public=False):
""" """
Return a schema view. Return a schema view.
""" """
@ -680,7 +680,7 @@ def get_schema_view(title=None, url=None, urlconf=None, renderer_classes=None):
renderer_classes = rclasses renderer_classes = rclasses
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
schema = generator.get_schema(request) schema = generator.get_schema(request, public)
if schema is None: if schema is None:
raise exceptions.PermissionDenied() raise exceptions.PermissionDenied()
return Response(schema) return Response(schema)

View File

@ -1,3 +1,23 @@
h1 {
font-size: 45px;
}
.intro-code {
margin-top: 20px;
}
pre.highlight code * {
white-space: nowrap; // this sets all children inside to nowrap
}
pre.highlight {
overflow-x: auto; // this sets the scrolling in x
}
pre.highlight code {
white-space: pre; // forces <code> to respect <pre> formatting
}
.main-container { .main-container {
padding-left: 30px; padding-left: 30px;
padding-right: 30px; padding-right: 30px;
@ -301,9 +321,23 @@ body {
.api-modal .modal-content .toggle-view { .api-modal .modal-content .toggle-view {
text-align: right; text-align: right;
float: right; float: right;
} }
.api-modal .modal-content .response .well { .api-modal .modal-content .response .well {
margin: 0; margin: 0;
max-height: 550px; max-height: 550px;
} }
.highlight {
background-color: #f7f7f9
}
.checkbox label.control-label {
font-weight: bold
}
@media (min-width: 768px) {
.navbar-nav.navbar-right:last-child {
margin-right: 0 !important;
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,16 @@
{% load rest_framework %} {% load rest_framework %}
<div class="row intro">
<div class="col-md-6 intro-title">
<h1>{{ document.title }}</h1>
</div>
<div class="col-md-6 intro-code">
{% if 'shell' in langs %}{% include "rest_framework/docs/langs/shell-intro.html" %}{% endif %}
{% if 'python' in langs %}{% include "rest_framework/docs/langs/python-intro.html" %}{% endif %}
{% if 'javascript' in langs %}{% include "rest_framework/docs/langs/javascript-intro.html" %}{% endif %}
</div>
</div>
{% for section_key, section in document.data.items %} {% for section_key, section in document.data.items %}
<h2 id="{{ section_key }}" class="coredocs-section-title">{{ section_key }} <a href="#{{ section_key }}"><i class="fa fa-link" aria-hidden="true"></i> <h2 id="{{ section_key }}" class="coredocs-section-title">{{ section_key }} <a href="#{{ section_key }}"><i class="fa fa-link" aria-hidden="true"></i>
</a></h2> </a></h2>

View File

@ -17,16 +17,8 @@
<link href="{% static 'rest_framework/docs/img/favicon.ico' %}" rel="shortcut icon"> <link href="{% static 'rest_framework/docs/img/favicon.ico' %}" rel="shortcut icon">
<style>{{ code_style }}</style> <style>{{ code_style }}</style>
<style> <script src="{% static 'rest_framework/js/coreapi-0.0.20.js' %}"></script>
.highlight {background-color: #f7f7f9} <script src="{% url 'api-docs:schema-js' %}"></script>
.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.20/dist/coreapi.js"></script>
</head> </head>
@ -76,9 +68,7 @@
let responseDisplay = 'data'; let responseDisplay = 'data';
const coreapi = window.coreapi const coreapi = window.coreapi
const codec = new coreapi.codecs.CoreJSONCodec() const schema = window.schema
const schema = {{ schema }}
const doc = codec.decode(schema)
{% if user.is_authenticated %} {% if user.is_authenticated %}
window.auth = { window.auth = {
@ -147,71 +137,70 @@
}) })
function requestCallback(request) { function requestCallback(request) {
// Fill in the "GET /foo/" display.
let parser = document.createElement('a'); let parser = document.createElement('a');
parser.href = request.url; parser.href = request.url;
const method = request.options.method const method = request.options.method
const path = parser.pathname + parser.hash + parser.search const path = parser.pathname + parser.hash + parser.search
// Filling the main response meta
form.closest(".modal-content").find(".toggle-view").removeClass("hide")
form.find(".request-method").text(method) form.find(".request-method").text(method)
form.find(".request-url").text(path) form.find(".request-url").text(path)
// Filling the raw panel
// form.find(".response-raw-request").text(method + ' ' + path + '\n\n')
} }
function responseCallback(response, responseText) { function responseCallback(response, responseText) {
// Display the 'Data'/'Raw' control.
form.closest(".modal-content").find(".toggle-view").removeClass("hide")
// Fill in the "200 OK" display.
form.find(".response-status-code").removeClass("label-success").removeClass("label-danger") form.find(".response-status-code").removeClass("label-success").removeClass("label-danger")
if (response.ok) { if (response.ok) {
form.find(".response-status-code").addClass("label-success") form.find(".response-status-code").addClass("label-success")
} else { } else {
form.find(".response-status-code").addClass("label-danger") form.find(".response-status-code").addClass("label-danger")
} }
form.find(".response-status-code").text(response.status) form.find(".response-status-code").text(response.status)
form.find(".meta").removeClass("hide") form.find(".meta").removeClass("hide")
// Filling the raw panel
var panelText = 'HTTP/1.1 ' + response.status + ' ' + response.statusText + '\n';
// Fill in the Raw HTTP response display.
var panelText = 'HTTP/1.1 ' + response.status + ' ' + response.statusText + '\n';
response.headers.forEach((header, key) => { response.headers.forEach((header, key) => {
panelText += normalizeHTTPHeader(key) + ': ' + header + '\n' panelText += normalizeHTTPHeader(key) + ': ' + header + '\n'
}) })
if (responseText) { if (responseText) {
panelText += '\n' + responseText panelText += '\n' + responseText
} }
form.find(".response-raw-response").text(panelText) form.find(".response-raw-response").text(panelText)
} }
// Instantiate a client to make the outgoing request.
let options = { let options = {
requestCallback: requestCallback, requestCallback: requestCallback,
responseCallback: responseCallback, responseCallback: responseCallback,
} }
// Setup authentication options.
if (window.auth && window.auth.type === 'token') { if (window.auth && window.auth.type === 'token') {
// Header authentication
options.headers = { options.headers = {
'Authorization': window.auth.value 'Authorization': window.auth.value
} }
} else if (window.auth && window.auth.type === 'basic') { } else if (window.auth && window.auth.type === 'basic') {
// Basic authentication
const token = window.auth.username + ':' + window.auth.password const token = window.auth.username + ':' + window.auth.password
const hash = window.btoa(token) const hash = window.btoa(token)
options.headers = { options.headers = {
'Authorization': 'Basic ' + hash 'Authorization': 'Basic ' + hash
} }
} else if (window.auth && window.auth.type === 'session') { } else if (window.auth && window.auth.type === 'session') {
// Session authentication
options.csrf = { options.csrf = {
'X-CSRFToken': getCookie('csrftoken') 'X-CSRFToken': getCookie('csrftoken')
} }
} }
const client = new coreapi.Client(options) const client = new coreapi.Client(options)
client.action(doc, key, params).then(function (data) { client.action(schema, key, params).then(function (data) {
var response = JSON.stringify(data, null, 2); var response = JSON.stringify(data, null, 2);
form.find(".request-awaiting").addClass("hide") form.find(".request-awaiting").addClass("hide")
form.find(".response-raw").addClass("hide") form.find(".response-raw").addClass("hide")
@ -240,6 +229,7 @@
}) })
}); });
// 'Data'/'Raw' control
$('.toggle-view button').click(function() { $('.toggle-view button').click(function() {
responseDisplay = $(this).data("display-toggle"); responseDisplay = $(this).data("display-toggle");
$(this).removeClass("btn-default").addClass('btn-info').siblings().removeClass('btn-info'); $(this).removeClass("btn-default").addClass('btn-info').siblings().removeClass('btn-info');

View File

@ -0,0 +1,5 @@
{% load rest_framework %}
{% load staticfiles %}
<pre class="highlight javascript hide" data-language="javascript"><code>{% code html %}<!-- Load the JavaScript client library -->
<script src="{% static 'rest_framework/js/coreapi-0.0.20.js' %}"></script>
<script src="{% url 'api-docs:schema-js' %}"></script>{% endcode %}</code></pre>

View File

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

View File

@ -0,0 +1,3 @@
{% load rest_framework %}
<pre class="highlight python hide" data-language="python"><code>{% code bash %}# Install the Python client library
$ pip install coreapi{% endcode %}</code></pre>

View File

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

View File

@ -0,0 +1,3 @@
{% load rest_framework %}
<pre class="highlight shell" data-language="shell"><code>{% code bash %}# Install the command line client
$ pip install coreapi-cli{% endcode %}</code></pre>

View File

@ -1,5 +1,5 @@
<div class="sidebar"> <div class="sidebar">
<h3 class="brand"><a href=".">{{ document.title }}</a></h3> <h3 class="brand"><a href="#">{{ document.title }}</a></h3>
<i class="fa fa-bars fa-2x toggle-btn" data-toggle="collapse" data-target="#menu-content"></i> <i class="fa fa-bars fa-2x toggle-btn" data-toggle="collapse" data-target="#menu-content"></i>
<div class="menu-list"> <div class="menu-list">

View File

@ -0,0 +1,3 @@
let codec = new window.coreapi.codecs.CoreJSONCodec()
let coreJSON = window.atob('{{ schema }}')
window.schema = codec.decode(coreJSON)