Commit 52db57a6 by Tom Christie Committed by GitHub

Version 3.6 (#4943)

parent 537df7a6
...@@ -279,7 +279,7 @@ Send a description of the issue via email to [rest-framework-security@googlegrou ...@@ -279,7 +279,7 @@ Send a description of the issue via email to [rest-framework-security@googlegrou
## License ## License
Copyright (c) 2011-2016, Tom Christie Copyright (c) 2011-2017, Tom Christie
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
......
...@@ -19,11 +19,21 @@ ...@@ -19,11 +19,21 @@
# Django REST framework 3.6 # Django REST framework 3.6
The 3.6 release adds two major new features to REST framework.
1. Built-in interactive API documentation support.
2. A new JavaScript client library.
![API Documentation](/img/api-docs.gif)
*Above: The interactive API documentation.*
--- ---
## Funding ## Funding
The 3.6 release would not have been possible without our [collaborative funding model][funding]. The 3.6 release would not have been possible without our [backing from Mozilla](mozilla-grant.md) to the project, and our [collaborative funding model][funding].
If you use REST framework commercially and would like to see this work continue, 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 we strongly encourage you to invest in its continued development by
**[signing up for a paid plan][funding]**. **[signing up for a paid plan][funding]**.
...@@ -40,24 +50,141 @@ we strongly encourage you to invest in its continued development by ...@@ -40,24 +50,141 @@ we strongly encourage you to invest in its continued development by
*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/).* *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/).*
---
## Interactive API documentation
REST framework's new API documentation supports a number of features:
* Live API interaction.
* Support for various authentication schemes.
* Code snippets for the Python, JavaScript, and Command Line clients.
To install the API documentation, you'll need to include it in your projects URLconf:
from rest_framework.documentation import include_docs_urls
API_TITLE = 'API title'
API_DESCRIPTION = '...'
urlpatterns = [
...
url(r'^docs/', include_docs_urls(title=API_TITLE, description=API_DESCRIPTION))
]
Once installed you should see something a little like this:
![API Documentation](/img/api-docs.png)
We'll likely be making further refinements to the API documentation over the
coming weeks. Keep in mind that this is a new feature, and please do give
us feedback if you run into any issues or limitations.
For more information on documenting your API endpoints see the ["Documenting your API"][api-docs] section.
--- ---
## API documentation ## JavaScript client library
The JavaScript client library allows you to load an API schema, and then interact
with that API at an application layer interface, rather than constructing fetch
requests explicitly.
Here's a brief example that demonstrates:
* Loading the client library and schema.
* Instantiating an authenticated client.
* Making an API request using the client.
... **index.html**
## JavaScript Client <html>
<head>
<script src="/static/rest_framework/js/coreapi-0.1.0.js"></script>
<script src="/docs/schema.js' %}"></script>
<script>
const coreapi = window.coreapi
const schema = window.schema
... // Instantiate a client...
let auth = coreapi.auth.TokenAuthentication({scheme: 'JWT', token: 'xxx'})
let client = coreapi.Client({auth: auth})
// Make an API request...
client.action(schema, ['projects', 'list']).then(function(result) {
alert(result)
})
</script>
</head>
</html>
The JavaScript client library supports various authentication schemes, and can be
used by your project itself, or as an external client interacting with your API.
The client is not limited to usage with REST framework APIs, although it does
currently only support loading CoreJSON API schemas. Support for Swagger and
other API schemas is planned.
For more details see the [JavaScript client library documentation][js-docs].
## Authentication classes for the Python client library
Previous authentication support in the Python client library was limited to
allowing users to provide explicit header values.
We now have better support for handling the details of authentication, with
the introduction of the `BasicAuthentication`, `TokenAuthentication`, and
`SessionAuthentication` schemes.
You can include the authentication scheme when instantiating a new client.
auth = coreapi.auth.TokenAuthentication(scheme='JWT', token='xxx-xxx-xxx')
client = coreapi.Client(auth=auth)
For more information see the [Python client library documentation][py-docs].
--- ---
## Deprecations ## Deprecations
... ### Generating schemas from Router
The 3.5 "pending deprecation" of router arguments for generating a schema view, such as `schema_title`, `schema_url` and `schema_renderers`, have now been escalated to a
"deprecated" warning.
Instead of using `DefaultRouter(schema_title='Example API')`, you should use the `get_schema_view()` function, and include the view explicitly in your URL conf.
### DjangoFilterBackend
The 3.5 "pending deprecation" warning of the built-in `DjangoFilterBackend` has now
been escalated to a "deprecated" warning.
You should change your imports and REST framework filter settings as follows:
* `rest_framework.filters.DjangoFilterBackend` becomes `django_filters.rest_framework.DjangoFilterBackend`.
* `rest_framework.filters.FilterSet` becomes `django_filters.rest_framework.FilterSet`.
--- ---
## What's next
There are likely to be a number of refinements to the API documentation and
JavaScript client library over the coming weeks, which could include some of the following:
* Support for private API docs, requiring login.
* File upload and download support in the JavaScript client & API docs.
* Comprehensive documentation for the JavaScript client library.
* Automatically including authentication details in the API doc code snippets.
* Adding authentication support in the command line client.
* Support for loading Swagger and other schemas in the JavaScript client.
* Improved support for documenting parameter schemas and response schemas.
* Refining the API documentation interaction modal.
Once work on those refinements is complete, we'll be starting feature work
on realtime support, for the 3.7 release.
[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors [sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors
[funding]: funding.md [funding]: funding.md
[api-docs]: documenting-your-api.md
[js-docs]: api-clients.md#javascript-client-library
[py-docs]: api-clients.md#python-client-library
...@@ -240,9 +240,59 @@ Once we have a `Client` instance, we can fetch an API schema from the network. ...@@ -240,9 +240,59 @@ Once we have a `Client` instance, we can fetch an API schema from the network.
schema = client.get('https://api.example.org/') schema = client.get('https://api.example.org/')
The object returned from this call will be a `Document` instance, which is The object returned from this call will be a `Document` instance, which is
the internal representation of the interface that we are interacting with. a representation of the API schema.
Now that we have our schema `Document`, we can now start to interact with the API: ## Authentication
Typically you'll also want to provide some authentication credentials when
instantiating the client.
#### Token authentication
The `TokenAuthentication` class can be used to support REST framework's built-in
`TokenAuthentication`, as well as OAuth and JWT schemes.
auth = coreapi.auth.TokenAuthentication(
scheme='JWT'
token='<token>'
)
client = coreapi.Client(auth=auth)
When using TokenAuthentication you'll probably need to implement a login flow
using the CoreAPI client.
A suggested pattern for this would be to initially make an unauthenticated client
request to an "obtain token" endpoint
For example, using the "Django REST framework JWT" package
client = coreapi.Client()
schema = client.get('https://api.example.org/')
action = ['api-token-auth', 'obtain-token']
params = {username: "example", email: "example@example.com"}
result = client.action(schema, action, params)
auth = coreapi.auth.TokenAuthentication(
scheme='JWT',
token=result['token']
)
client = coreapi.Client(auth=auth)
#### Basic authentication
The `BasicAuthentication` class can be used to support HTTP Basic Authentication.
auth = coreapi.auth.BasicAuthentication(
username='<username>',
password='<password>'
)
client = coreapi.Client(auth=auth)
## Interacting with the API
Now that we have a client and have fetched our schema `Document`, we can now
start to interact with the API:
users = client.action(schema, ['users', 'list']) users = client.action(schema, ['users', 'list'])
...@@ -330,12 +380,23 @@ There are two separate JavaScript resources that you need to include in your HTM ...@@ -330,12 +380,23 @@ There are two separate JavaScript resources that you need to include in your HTM
First, install the API documentation views. These will include the schema resource that'll allow you to load the schema directly from an HTML page, without having to make an asynchronous AJAX call. First, install the API documentation views. These will include the schema resource that'll allow you to load the schema directly from an HTML page, without having to make an asynchronous AJAX call.
url(r'^docs/', include_docs_urls(title='My API service')) from rest_framework.documentation import include_docs_urls
urlpatterns = [
...
url(r'^docs/', include_docs_urls(title='My API service'))
]
Once the API documentation URLs are installed, you'll be able to include both the required JavaScript resources. Note that the ordering of these two lines is important, as the schema loading requires CoreAPI to already be installed. Once the API documentation URLs are installed, you'll be able to include both the required JavaScript resources. Note that the ordering of these two lines is important, as the schema loading requires CoreAPI to already be installed.
<!--
Load the CoreAPI library and the API schema.
/static/rest_framework/js/coreapi-0.1.0.js
/docs/schema.js
-->
{% load staticfiles %} {% load staticfiles %}
<script src="{% static 'rest_framework/js/coreapi.js' %}"></script> <script src="{% static 'rest_framework/js/coreapi-0.1.0.js' %}"></script>
<script src="{% url 'api-docs:schema-js' %}"></script> <script src="{% url 'api-docs:schema-js' %}"></script>
The `coreapi` library, and the `schema` object will now both be available on the `window` instance. The `coreapi` library, and the `schema` object will now both be available on the `window` instance.
...@@ -345,42 +406,87 @@ The `coreapi` library, and the `schema` object will now both be available on the ...@@ -345,42 +406,87 @@ The `coreapi` library, and the `schema` object will now both be available on the
## Instantiating a client ## Instantiating a client
In order to interact with the API you'll need a client instance.
var client = coreapi.Client() var client = coreapi.Client()
Header authentication Typically you'll also want to provide some authentication credentials when
instantiating the client.
var auth = coreapi.auth.HeaderAuthentication({ #### Session authentication
value: 'JWT <token>'
The `SessionAuthentication` class allows session cookies to provide the user
authentication. You'll want to provide a standard HTML login flow, to allow
the user to login, and then instantiate a client using session authentication:
let auth = coreapi.auth.SessionAuthentication({
csrfCookieName: 'csrftoken',
csrfHeaderName: 'X-CSRFToken'
}) })
var client = coreapi.Client({auth: auth}) let client = coreapi.Client({auth: auth})
Basic authentication The authentication scheme will handle including a CSRF header in any outgoing
requests for unsafe HTTP methods.
var auth = coreapi.auth.BasicAuthentication({ #### Token authentication
userName: '<username>',
password: '<password>' The `TokenAuthentication` class can be used to support REST framework's built-in
`TokenAuthentication`, as well as OAuth and JWT schemes.
let auth = coreapi.auth.TokenAuthentication({
scheme: 'JWT'
token: '<token>'
}) })
var client = coreapi.Client({auth: auth}) let client = coreapi.Client({auth: auth})
Session authentication When using TokenAuthentication you'll probably need to implement a login flow
using the CoreAPI client.
// https://docs.djangoproject.com/en/dev/ref/csrf/#ajax A suggested pattern for this would be to initially make an unauthenticated client
let auth = coreapi.auth.SessionAuthentication({ request to an "obtain token" endpoint
csrfHeader: 'X-CSRFToken',
csrfToken: getCookie('csrftoken') For example, using the "Django REST framework JWT" package
// Setup some globally accessible state
window.client = coreapi.Client()
window.loggedIn = false
function loginUser(username, password) {
let action = ["api-token-auth", "obtain-token"]
let params = {username: "example", email: "example@example.com"}
client.action(schema, action, params).then(function(result) {
// On success, instantiate an authenticated client.
let auth = window.coreapi.auth.TokenAuthentication({
scheme: 'JWT',
token: result['token']
})
window.client = coreapi.Client({auth: auth})
window.loggedIn = true
}).catch(function (error) {
// Handle error case where eg. user provides incorrect credentials.
})
}
#### Basic authentication
The `BasicAuthentication` class can be used to support HTTP Basic Authentication.
let auth = coreapi.auth.BasicAuthentication({
username: '<username>',
password: '<password>'
}) })
let client = coreapi.Client({auth: auth}) let client = coreapi.Client({auth: auth})
## Using the client ## Using the client
Making requests Making requests:
let action = ["users", "list"] let action = ["users", "list"]
client.action(schema, action).then(function(result) { client.action(schema, action).then(function(result) {
// Return value is in 'result' // Return value is in 'result'
}) })
Including parameters Including parameters:
let action = ["users", "create"] let action = ["users", "create"]
let params = {username: "example", email: "example@example.com"} let params = {username: "example", email: "example@example.com"}
...@@ -388,7 +494,7 @@ Including parameters ...@@ -388,7 +494,7 @@ Including parameters
// Return value is in 'result' // Return value is in 'result'
}) })
Handling errors Handling errors:
client.action(schema, action, params).then(function(result) { client.action(schema, action, params).then(function(result) {
// Return value is in 'result' // Return value is in 'result'
...@@ -396,30 +502,6 @@ Handling errors ...@@ -396,30 +502,6 @@ Handling errors
// Error value is in 'error' // Error value is in 'error'
}) })
If you're using session authentication, and handling login requests using regular HTML forms then you probably won't need an authentication flow for the client. However, if you're using one of the other types of authentication,
A suggested pattern for this would be to initially make an unauthenticated client request to an "obtain token" endpoint
For example, using the "Django REST framework JWT" package
// Globally accessible state
window.client = coreapi.Client()
window.loggedIn = false
function loginUser(username, password) {
let action = ["api-token-auth", "obtain-token"]
let params = {username: "example", email: "example@example.com"}
client.action(schema, action, params).then(function(result) {
// On success, instantiate an authenticated client.
let authConfig = {value: 'JWT ' + result['token']}
let auth = window.coreapi.auth.HeaderAuthentication(authConfig)
window.client = coreapi.Client({auth: auth})
window.loggedIn = true
}).catch(function (error) {
// Handle error case where eg. user provides incorrect credentials.
})
}
## Installation with node ## Installation with node
The coreapi package is available on NPM. The coreapi package is available on NPM.
......
...@@ -8,10 +8,10 @@ ______ _____ _____ _____ __ ...@@ -8,10 +8,10 @@ ______ _____ _____ _____ __
""" """
__title__ = 'Django REST framework' __title__ = 'Django REST framework'
__version__ = '3.5.4' __version__ = '3.6.0'
__author__ = 'Tom Christie' __author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause' __license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2016 Tom Christie' __copyright__ = 'Copyright 2011-2017 Tom Christie'
# Version synonym # Version synonym
VERSION = __version__ VERSION = __version__
......
...@@ -251,6 +251,29 @@ except ImportError: ...@@ -251,6 +251,29 @@ except ImportError:
apply_markdown = None apply_markdown = None
try:
import pygments
from pygments.lexers import get_lexer_by_name
from pygments.formatters import HtmlFormatter
def pygments_highlight(text, lang, style):
lexer = get_lexer_by_name(lang, stripall=False)
formatter = HtmlFormatter(nowrap=True, style=style)
return pygments.highlight(text, lexer, formatter)
def pygments_css(style):
formatter = HtmlFormatter(style=style)
return formatter.get_style_defs('.highlight')
except ImportError:
pygments = None
def pygments_highlight(text, lang, style):
return text
def pygments_css(style):
return None
# `separators` argument to `json.dumps()` differs between 2.x and 3.x # `separators` argument to `json.dumps()` differs between 2.x and 3.x
# See: http://bugs.python.org/issue22767 # See: http://bugs.python.org/issue22767
if six.PY3: if six.PY3:
......
...@@ -45,9 +45,9 @@ if django_filters: ...@@ -45,9 +45,9 @@ if django_filters:
class FilterSet(DFFilterSet): class FilterSet(DFFilterSet):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
warnings.warn( warnings.warn(
"The built in 'rest_framework.filters.FilterSet' is pending deprecation. " "The built in 'rest_framework.filters.FilterSet' is deprecated. "
"You should use 'django_filters.rest_framework.FilterSet' instead.", "You should use 'django_filters.rest_framework.FilterSet' instead.",
PendingDeprecationWarning DeprecationWarning
) )
return super(FilterSet, self).__init__(*args, **kwargs) return super(FilterSet, self).__init__(*args, **kwargs)
else: else:
...@@ -64,9 +64,9 @@ class DjangoFilterBackend(BaseFilterBackend): ...@@ -64,9 +64,9 @@ class DjangoFilterBackend(BaseFilterBackend):
assert django_filters.VERSION >= (0, 15, 3), 'django-filter 0.15.3 and above is required' assert django_filters.VERSION >= (0, 15, 3), 'django-filter 0.15.3 and above is required'
warnings.warn( warnings.warn(
"The built in 'rest_framework.filters.DjangoFilterBackend' is pending deprecation. " "The built in 'rest_framework.filters.DjangoFilterBackend' is deprecated. "
"You should use 'django_filters.rest_framework.DjangoFilterBackend' instead.", "You should use 'django_filters.rest_framework.DjangoFilterBackend' instead.",
PendingDeprecationWarning DeprecationWarning
) )
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
......
...@@ -25,7 +25,7 @@ from django.utils.html import mark_safe ...@@ -25,7 +25,7 @@ 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 (
INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, coreapi, INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, coreapi,
template_render pygments_css, template_render
) )
from rest_framework.exceptions import ParseError from rest_framework.exceptions import ParseError
from rest_framework.request import is_form_media_type, override_method from rest_framework.request import is_form_media_type, override_method
...@@ -802,16 +802,13 @@ class DocumentationRenderer(BaseRenderer): ...@@ -802,16 +802,13 @@ class DocumentationRenderer(BaseRenderer):
charset = 'utf-8' charset = 'utf-8'
template = 'rest_framework/docs/index.html' template = 'rest_framework/docs/index.html'
code_style = 'emacs' code_style = 'emacs'
languages = ['shell', 'javascript', 'python']
def get_context(self, data, request): def get_context(self, data, request):
from pygments.formatters import HtmlFormatter
formatter = HtmlFormatter(style=self.code_style)
code_style = formatter.get_style_defs('.highlight')
langs = ['shell', 'javascript', 'python']
return { return {
'document': data, 'document': data,
'langs': langs, 'langs': self.languages,
'code_style': code_style, 'code_style': pygments_css(self.code_style),
'request': request 'request': request
} }
......
...@@ -320,9 +320,9 @@ class DefaultRouter(SimpleRouter): ...@@ -320,9 +320,9 @@ class DefaultRouter(SimpleRouter):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if 'schema_title' in kwargs: if 'schema_title' in kwargs:
warnings.warn( warnings.warn(
"Including a schema directly via a router is now pending " "Including a schema directly via a router is now deprecated. "
"deprecation. Use `get_schema_view()` instead.", "Use `get_schema_view()` instead.",
PendingDeprecationWarning DeprecationWarning
) )
if 'schema_renderers' in kwargs: if 'schema_renderers' in kwargs:
assert 'schema_title' in kwargs, 'Missing "schema_title" argument.' assert 'schema_title' in kwargs, 'Missing "schema_title" argument.'
...@@ -331,13 +331,6 @@ class DefaultRouter(SimpleRouter): ...@@ -331,13 +331,6 @@ class DefaultRouter(SimpleRouter):
self.schema_title = kwargs.pop('schema_title', None) self.schema_title = kwargs.pop('schema_title', None)
self.schema_url = kwargs.pop('schema_url', None) self.schema_url = kwargs.pop('schema_url', None)
self.schema_renderers = kwargs.pop('schema_renderers', self.default_schema_renderers) self.schema_renderers = kwargs.pop('schema_renderers', self.default_schema_renderers)
if self.default_schema_renderers:
warnings.warn(
"The 'DefaultRouter.default_schema_renderers' is pending "
"deprecation. You should override "
"'DefaultRouter.APISchemaView' instead.",
PendingDeprecationWarning
)
if 'root_renderers' in kwargs: if 'root_renderers' in kwargs:
self.root_renderers = kwargs.pop('root_renderers') self.root_renderers = kwargs.pop('root_renderers')
......
function normalizeHTTPHeader(str) { function normalizeHTTPHeader (str) {
return (str.charAt(0).toUpperCase() + str.substring(1)) // Capitalize HTTP headers for display.
.replace( /-(.)/g, function($1) { return $1.toUpperCase(); }) return (str.charAt(0).toUpperCase() + str.substring(1))
.replace( /(Www)/g, function($1) { return 'WWW'; }) .replace(/-(.)/g, function ($1) { return $1.toUpperCase() })
.replace( /(Xss)/g, function($1) { return 'XSS'; }) .replace(/(Www)/g, function ($1) { return 'WWW' })
.replace( /(Md5)/g, function($1) { return 'MD5'; }) .replace(/(Xss)/g, function ($1) { return 'XSS' })
.replace(/(Md5)/g, function ($1) { return 'MD5' })
} }
function getCookie(name) { let responseDisplay = 'data'
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;
}
let responseDisplay = 'data';
const coreapi = window.coreapi const coreapi = window.coreapi
const schema = window.schema const schema = window.schema
...@@ -55,28 +40,44 @@ $('form.api-interaction').submit(function(event) { ...@@ -55,28 +40,44 @@ $('form.api-interaction').submit(function(event) {
for (var [paramKey, paramValue] of formData.entries()) { for (var [paramKey, paramValue] of formData.entries()) {
var elem = form.find("[name=" + paramKey + "]") var elem = form.find("[name=" + paramKey + "]")
var dataType = elem.data('type') || 'string' var dataType = elem.data('type') || 'string'
var dataLocation = elem.data('location')
if (dataType === 'integer' && paramValue) { if (dataType === 'integer' && paramValue) {
paramValue = parseInt(paramValue) let value = parseInt(paramValue)
if (!isNaN(value)) {
params[paramKey] = value
}
} else if (dataType === 'number' && paramValue) { } else if (dataType === 'number' && paramValue) {
paramValue = parseFloat(paramValue) let value = parseFloat(paramValue)
if (!isNaN(value)) {
params[paramKey] = value
}
} else if (dataType === 'boolean' && paramValue) { } else if (dataType === 'boolean' && paramValue) {
paramValue = { let value = {
'true': true, 'true': true,
'false': false 'false': false
}[paramValue.toLowerCase()] }[paramValue.toLowerCase()]
if (value !== undefined) {
params[paramKey]
}
} else if (dataType === 'array' && paramValue) { } else if (dataType === 'array' && paramValue) {
paramValue = JSON.parse(paramValue) try {
} params[paramKey] = JSON.parse(paramValue)
} catch (err) {
if (dataLocation === 'query' && !paramValue) { // Ignore malformed JSON
continue }
} else if (dataType === 'object' && paramValue) {
try {
params[paramKey] = JSON.parse(paramValue)
} catch (err) {
// Ignore malformed JSON
}
} else if (dataType === 'string' && paramValue) {
params[paramKey] = paramValue
} }
params[paramKey] = paramValue
} }
form.find(":checkbox").each(function( index ) { form.find(":checkbox").each(function( index ) {
// Handle unselected checkboxes
var name = $(this).attr("name"); var name = $(this).attr("name");
if (!params.hasOwnProperty(name)) { if (!params.hasOwnProperty(name)) {
params[name] = false params[name] = false
...@@ -127,22 +128,23 @@ $('form.api-interaction').submit(function(event) { ...@@ -127,22 +128,23 @@ $('form.api-interaction').submit(function(event) {
// Setup authentication options. // Setup authentication options.
if (window.auth && window.auth.type === 'token') { if (window.auth && window.auth.type === 'token') {
// Header authentication // Header authentication
options.headers = { options.auth = new coreapi.auth.TokenAuthentication({
'Authorization': window.auth.value prefix: window.auth.scheme,
} token: window.auth.token
})
} else if (window.auth && window.auth.type === 'basic') { } else if (window.auth && window.auth.type === 'basic') {
// Basic authentication // Basic authentication
const token = window.auth.username + ':' + window.auth.password options.auth = new coreapi.auth.BasicAuthentication({
const hash = window.btoa(token) username: window.auth.username,
options.headers = { password: window.auth.password
'Authorization': 'Basic ' + hash })
}
} else if (window.auth && window.auth.type === 'session') { } else if (window.auth && window.auth.type === 'session') {
// Session authentication // Session authentication
options.csrf = { options.auth = new coreapi.auth.SessionAuthentication({
'X-CSRFToken': getCookie('csrftoken') csrfCookieName: 'csrftoken',
} csrfHeaderName: 'X-CSRFToken'
})
} }
const client = new coreapi.Client(options) const client = new coreapi.Client(options)
...@@ -202,12 +204,14 @@ $('#auth-control').find("[data-auth='none']").click(function (event) { ...@@ -202,12 +204,14 @@ $('#auth-control').find("[data-auth='none']").click(function (event) {
$('form.authentication-token-form').submit(function(event) { $('form.authentication-token-form').submit(function(event) {
event.preventDefault(); event.preventDefault();
const form = $(this).closest("form"); const form = $(this).closest("form");
const value = form.find('input').val(); const scheme = form.find('input#scheme').val();
const token = form.find('input#token').val();
window.auth = { window.auth = {
'type': 'token', 'type': 'token',
'value': value, 'scheme': scheme,
'token': token
}; };
$('#selected-authentication').text('header'); $('#selected-authentication').text('token');
$('#auth-control').children().removeClass('active'); $('#auth-control').children().removeClass('active');
$('#auth-control').find("[data-auth='token']").addClass('active'); $('#auth-control').find("[data-auth='token']").addClass('active');
$('#auth_token_modal').modal('hide'); $('#auth_token_modal').modal('hide');
...@@ -222,7 +226,7 @@ $('form.authentication-basic-form').submit(function(event) { ...@@ -222,7 +226,7 @@ $('form.authentication-basic-form').submit(function(event) {
window.auth = { window.auth = {
'type': 'basic', 'type': 'basic',
'username': username, 'username': username,
'password': password, 'password': password
}; };
$('#selected-authentication').text('basic'); $('#selected-authentication').text('basic');
$('#auth-control').children().removeClass('active'); $('#auth-control').children().removeClass('active');
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -5,24 +5,29 @@ ...@@ -5,24 +5,29 @@
<div class="modal-dialog modal-md" role="document"> <div class="modal-dialog modal-md" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title"><i class="fa fa-key"></i> Authentication Header</h3> <h3 class="modal-title"><i class="fa fa-key"></i> Token Authentication</h3>
</div> </div>
<form class="form-horizontal authentication-token-form"> <form class="form-horizontal authentication-token-form">
<div class="modal-body"> <div class="modal-body">
<div class="form-group"> <div class="form-group">
<label for="authorization" class="col-sm-2 control-label">Authorization:</label> <label for="prefix" class="col-sm-2 control-label">Scheme:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="scheme" placeholder="Bearer" aria-describedby="schemeHelpBlock" required>
<span id="schemeHelpBlock" class="help-block">Either a registered authentication scheme such as <code>Bearer</code>, or a custom schema such as <code>Token</code> or <code>JWT</code>.</span>
</div>
</div>
<div class="form-group">
<label for="token" class="col-sm-2 control-label">Token:</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" id="authorization" placeholder="Bearer XXXX-XXXX-XXXX-XXXX" aria-describedby="helpBlock" required> <input type="text" class="form-control" id="token" placeholder="XXXX-XXXX-XXXX-XXXX" aria-describedby="helpBlock" required>
<span id="helpBlock" class="help-block">The value to include for the <code>Authorization</code> header in outgoing HTTP requests.</span> <span id="tokenHelpBlock" class="help-block">A valid API token.</span>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Use Authentication Header</button> <button type="submit" class="btn btn-primary">Use Token Authentication</button>
</div> </div>
</form> </form>
......
...@@ -15,8 +15,10 @@ ...@@ -15,8 +15,10 @@
</div> </div>
{% for section_key, section in document.data.items %} {% for section_key, section in document.data.items %}
{% if section_key %}
<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>
{% endif %}
{% for link_key, link in section.links.items %} {% for link_key, link in section.links.items %}
{% include "rest_framework/docs/link.html" %} {% include "rest_framework/docs/link.html" %}
......
...@@ -16,8 +16,8 @@ ...@@ -16,8 +16,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> {% if code_style %}<style>{{ code_style }}</style>{% endif %}
<script src="{% static 'rest_framework/js/coreapi-0.0.20.js' %}"></script> <script src="{% static 'rest_framework/js/coreapi-0.1.0.js' %}"></script>
<script src="{% url 'api-docs:schema-js' %}"></script> <script src="{% url 'api-docs:schema-js' %}"></script>
</head> </head>
......
...@@ -6,9 +6,9 @@ ...@@ -6,9 +6,9 @@
<ul id="menu-content" class="menu-content collapse out"> <ul id="menu-content" class="menu-content collapse out">
{% for section_key, section in document.data.items %} {% for section_key, section in document.data.items %}
<li data-toggle="collapse" data-target="#{{ section_key }}-dropdown" class="collapsed"> <li data-toggle="collapse" data-target="#{{ section_key }}-dropdown" class="collapsed">
<a><i class="fa fa-dot-circle-o fa-lg"></i> {{ section_key }} <span class="arrow"></span></a> <a><i class="fa fa-dot-circle-o fa-lg"></i> {% if section_key %}{{ section_key }}{% else %}API Endpoints{% endif %} <span class="arrow"></span></a>
</li> </li>
<ul class="sub-menu collapse" id="{{ section_key }}-dropdown"> <ul class="sub-menu {% if section_key %}collapse{% endif %}" id="{{ section_key }}-dropdown">
{% for link_key, link in section.links.items %} {% for link_key, link in section.links.items %}
<li><a href="#{{ section_key }}-{{ link_key }}">{{ link.title|default:link_key }}</a></li> <li><a href="#{{ section_key }}-{{ link_key }}">{{ link.title|default:link_key }}</a></li>
{% endfor %} {% endfor %}
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
</li> </li>
<ul class="sub-menu collapse out" id="auth-control"> <ul class="sub-menu collapse out" id="auth-control">
<li data-auth="none" {% if not user.is_authenticated %}class="active"{% endif %}><a href="#" data-language="none">none</a></li> <li data-auth="none" {% if not user.is_authenticated %}class="active"{% endif %}><a href="#" data-language="none">none</a></li>
<li data-auth="token" data-toggle="modal" data-target="#auth_token_modal"><a href="#">header</a></li> <li data-auth="token" data-toggle="modal" data-target="#auth_token_modal"><a href="#">token</a></li>
<li data-auth="basic" data-toggle="modal" data-target="#auth_basic_modal"><a href="#">basic</a></li> <li data-auth="basic" data-toggle="modal" data-target="#auth_basic_modal"><a href="#">basic</a></li>
<li data-auth="session" data-toggle="modal" data-target="#auth_session_modal" {% if user.is_authenticated %}class="active"{% endif %}><a href="#">session</a></li> <li data-auth="session" data-toggle="modal" data-target="#auth_session_modal" {% if user.is_authenticated %}class="active"{% endif %}><a href="#">session</a></li>
</ul> </ul>
......
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
import re import re
from collections import OrderedDict from collections import OrderedDict
from django import template from django import template
from django.template import loader from django.template import loader
from django.utils import six from django.utils import six
from django.utils.encoding import force_text, iri_to_uri from django.utils.encoding import force_text, iri_to_uri
from django.utils.html import escape, format_html, smart_urlquote from django.utils.html import escape, format_html, smart_urlquote
from django.utils.safestring import SafeData, mark_safe from django.utils.safestring import SafeData, mark_safe
from markdown.extensions.fenced_code import FencedBlockPreprocessor
from rest_framework.compat import ( from rest_framework.compat import (
NoReverseMatch, markdown, reverse, template_render NoReverseMatch, markdown, pygments_highlight, 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
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")
@register.tag(name='code') @register.tag(name='code')
def highlight_code(parser, token): def highlight_code(parser, token):
code = token.split_contents()[-1] code = token.split_contents()[-1]
...@@ -56,14 +38,8 @@ class CodeNode(template.Node): ...@@ -56,14 +38,8 @@ class CodeNode(template.Node):
self.nodelist = code self.nodelist = code
def render(self, context): def render(self, context):
from pygments import highlight text = self.nodelist.render(context)
from pygments.lexers import get_lexer_by_name return pygments_highlight(text, self.lang, self.style)
from pygments.formatters import HtmlFormatter
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() @register.filter()
...@@ -92,7 +68,9 @@ def form_for_link(link): ...@@ -92,7 +68,9 @@ def form_for_link(link):
@register.simple_tag @register.simple_tag
def render_markdown(markdown_text): def render_markdown(markdown_text):
return markdown.markdown(markdown_text, extensions=[FencedCodeExtension(), "tables"]) if not markdown:
return markdown_text
return markdown.markdown(markdown_text)
@register.simple_tag @register.simple_tag
......
...@@ -180,8 +180,8 @@ class IntegrationTestFiltering(CommonFilteringTestCase): ...@@ -180,8 +180,8 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
assert response.status_code == status.HTTP_200_OK assert response.status_code == status.HTTP_200_OK
assert response.data == self.data assert response.data == self.data
self.assertTrue(issubclass(w[-1].category, PendingDeprecationWarning)) self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
self.assertIn("'rest_framework.filters.DjangoFilterBackend' is pending deprecation.", str(w[-1].message)) self.assertIn("'rest_framework.filters.DjangoFilterBackend' is deprecated.", str(w[-1].message))
@unittest.skipUnless(django_filters, 'django-filter not installed') @unittest.skipUnless(django_filters, 'django-filter not installed')
def test_no_df_deprecation(self): def test_no_df_deprecation(self):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment