mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-06 05:20:12 +03:00
commit
0b0a55c9a0
|
@ -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.
|
[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
|
[cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion
|
||||||
[relations]: relations.md
|
[relations]: relations.md
|
||||||
[model-managers]: https://docs.djangoproject.com/en/stable/topics/db/managers/
|
[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-dynamic-fields]: https://github.com/dbrgn/drf-dynamic-fields
|
||||||
[drf-base64]: https://bitbucket.org/levit_scs/drf_base64
|
[drf-base64]: https://bitbucket.org/levit_scs/drf_base64
|
||||||
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions
|
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions
|
||||||
|
[djangorestframework-queryfields]: http://djangorestframework-queryfields.readthedocs.io/
|
||||||
|
|
|
@ -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.
|
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.
|
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 = {
|
REST_FRAMEWORK = {
|
||||||
# Return native `Date` and `Time` objects in `serializer.data`
|
# Return native `Date` and `Time` objects in `serializer.data`
|
||||||
|
|
|
@ -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.
|
* [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] -
|
* [django-rest-framework-serializer-extensions][drf-serializer-extensions] -
|
||||||
Enables black/whitelisting fields, and conditionally expanding child serializers on a per-view/request basis.
|
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
|
### 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_tweaks]: https://github.com/ArabellaTech/drf_tweaks
|
||||||
[drf-oidc-auth]: https://github.com/ByteInternet/drf-oidc-auth
|
[drf-oidc-auth]: https://github.com/ByteInternet/drf-oidc-auth
|
||||||
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions
|
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions
|
||||||
|
[djangorestframework-queryfields]: https://github.com/wimglenn/djangorestframework-queryfields
|
||||||
|
|
|
@ -48,6 +48,8 @@ We'll need to add our new `snippets` app and the `rest_framework` app to `INSTAL
|
||||||
'snippets.apps.SnippetsConfig',
|
'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.
|
Okay, we're ready to roll.
|
||||||
|
|
||||||
## Creating a model to work with
|
## Creating a model to work with
|
||||||
|
|
|
@ -294,7 +294,7 @@ class PageNumberPagination(BasePagination):
|
||||||
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)
|
#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:
|
||||||
|
@ -303,7 +303,7 @@ class PageNumberPagination(BasePagination):
|
||||||
name=self.page_size_query_param,
|
name=self.page_size_query_param,
|
||||||
required=False,
|
required=False,
|
||||||
location='query',
|
location='query',
|
||||||
description=force_text(self.page_size_query_description)
|
#description=force_text(self.page_size_query_description)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return fields
|
return fields
|
||||||
|
@ -444,13 +444,13 @@ class LimitOffsetPagination(BasePagination):
|
||||||
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)
|
#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)
|
#description=force_text(self.offset_query_description)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -798,10 +798,30 @@ class DocumentationRenderer(BaseRenderer):
|
||||||
media_type = 'text/html'
|
media_type = 'text/html'
|
||||||
format = 'html'
|
format = 'html'
|
||||||
charset = 'utf-8'
|
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):
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||||
from coredocs.main import render as render_docs
|
#from coredocs.main import render as render_docs
|
||||||
return render_docs(data, theme='cerulean', highlight='emacs', static=lambda path: '/static/rest_framework/docs/' + path)
|
#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):
|
class MultiPartRenderer(BaseRenderer):
|
||||||
|
|
|
@ -514,8 +514,8 @@ class SchemaGenerator(object):
|
||||||
name=variable,
|
name=variable,
|
||||||
location='path',
|
location='path',
|
||||||
required=True,
|
required=True,
|
||||||
title='' if (title is None) else title,
|
#title='' if (title is None) else title,
|
||||||
description='' if (description is None) else description
|
#description='' if (description is None) else description
|
||||||
)
|
)
|
||||||
fields.append(field)
|
fields.append(field)
|
||||||
|
|
||||||
|
@ -540,7 +540,7 @@ class SchemaGenerator(object):
|
||||||
name='data',
|
name='data',
|
||||||
location='body',
|
location='body',
|
||||||
required=True,
|
required=True,
|
||||||
type='array'
|
#type='array'
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -559,11 +559,11 @@ class SchemaGenerator(object):
|
||||||
name=field.field_name,
|
name=field.field_name,
|
||||||
location='form',
|
location='form',
|
||||||
required=required,
|
required=required,
|
||||||
title=title,
|
#title=title,
|
||||||
description=description,
|
#description=description,
|
||||||
type=types_lookup[field],
|
#type=types_lookup[field],
|
||||||
input=determine_input(field),
|
#input=determine_input(field),
|
||||||
choices=getattr(field, 'choices', None)
|
#choices=getattr(field, 'choices', None)
|
||||||
)
|
)
|
||||||
fields.append(field)
|
fields.append(field)
|
||||||
|
|
||||||
|
|
19
rest_framework/templates/rest_framework/docs/document.html
Normal file
19
rest_framework/templates/rest_framework/docs/document.html
Normal 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 %}
|
143
rest_framework/templates/rest_framework/docs/index.html
Normal file
143
rest_framework/templates/rest_framework/docs/index.html
Normal 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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
119
rest_framework/templates/rest_framework/docs/link.html
Normal file
119
rest_framework/templates/rest_framework/docs/link.html
Normal 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 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">×</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>
|
34
rest_framework/templates/rest_framework/docs/nav.html
Normal file
34
rest_framework/templates/rest_framework/docs/nav.html
Normal 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>
|
||||||
|
|
17
rest_framework/templates/rest_framework/docs/toc.html
Normal file
17
rest_framework/templates/rest_framework/docs/toc.html
Normal 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>
|
|
@ -13,12 +13,89 @@ from rest_framework.compat import NoReverseMatch, reverse, template_render
|
||||||
from rest_framework.renderers import HTMLFormRenderer
|
from rest_framework.renderers import HTMLFormRenderer
|
||||||
from rest_framework.utils.urls import replace_query_param
|
from rest_framework.utils.urls import replace_query_param
|
||||||
|
|
||||||
|
from markdown.extensions.fenced_code import FencedBlockPreprocessor
|
||||||
|
import markdown
|
||||||
|
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
# Regex for adding classes to html snippets
|
# Regex for adding classes to html snippets
|
||||||
class_re = re.compile(r'(?<=class=["\'])(.*)(?=["\'])')
|
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
|
@register.simple_tag
|
||||||
def get_pagination_html(pager):
|
def get_pagination_html(pager):
|
||||||
return pager.to_html()
|
return pager.to_html()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user