Merge remote-tracking branch 'reference/master' into feature/improve_schema_shortcut

* reference/master:
  use django 1.11rc1 in tox
  Leave parameters with regex pattern as String
  restored original formatting
  Do not hint BigAutoField as integer (outside of range)
  Mention where the mixins live
  Try to improve browser support
  Cleanup and refactor docs api.js script
  Move bootstrap modal data attrs to anchor
  Remove unused base.js script
  Correctly set scheme in coreapi TokenAuthentication
  reverted to fix the issue without changing setting
  DEFAULT_PAGINATION_CLASS is changed to 'None'
  add content block and breadcrumbs_empty block to allow base.html to be reused
  Update 7-schemas-and-client-libraries.md
  Updated testimonial name on funding site
  ID must start from 1 again
This commit is contained in:
Xavier Ordoquy 2017-03-24 15:21:43 +01:00
commit 6ad0be44d3
10 changed files with 256 additions and 256 deletions

View File

@ -235,6 +235,8 @@ You may need to provide custom `ViewSet` classes that do not have the full set o
To create a base viewset class that provides `create`, `list` and `retrieve` operations, inherit from `GenericViewSet`, and mixin the required actions: To create a base viewset class that provides `create`, `list` and `retrieve` operations, inherit from `GenericViewSet`, and mixin the required actions:
from rest_framework import mixins
class CreateListRetrieveViewSet(mixins.CreateModelMixin, class CreateListRetrieveViewSet(mixins.CreateModelMixin,
mixins.ListModelMixin, mixins.ListModelMixin,
mixins.RetrieveModelMixin, mixins.RetrieveModelMixin,

View File

@ -147,7 +147,7 @@ Sign up for a paid plan today, and help ensure that REST framework becomes a sus
> It's really awesome that this project continues to endure. The code base is top notch and the maintainers are committed to the highest level of quality. > It's really awesome that this project continues to endure. The code base is top notch and the maintainers are committed to the highest level of quality.
DRF is one of the core reasons why Django is top choice among web frameworks today. In my opinion, it sets the standard for rest frameworks for the development community at large. DRF is one of the core reasons why Django is top choice among web frameworks today. In my opinion, it sets the standard for rest frameworks for the development community at large.
> >
> — agconti, Django REST framework user > — Andrew Conti, Django REST framework user
--- ---

View File

@ -209,7 +209,7 @@ We can make a successful request by including the username and password of one o
http -a tom:password123 POST http://127.0.0.1:8000/snippets/ code="print 789" http -a tom:password123 POST http://127.0.0.1:8000/snippets/ code="print 789"
{ {
"id": 5, "id": 1,
"owner": "tom", "owner": "tom",
"title": "foo", "title": "foo",
"code": "print 789", "code": "print 789",

View File

@ -41,7 +41,7 @@ view in our URL configuration.
schema_view = get_schema_view(title='Pastebin API') schema_view = get_schema_view(title='Pastebin API')
urlpatterns = [ urlpatterns = [
url('^schema/$', schema_view),        url(r'^schema/$', schema_view),
... ...
] ]

View File

@ -526,6 +526,7 @@ class SchemaGenerator(object):
title = '' title = ''
description = '' description = ''
schema_cls = coreschema.String schema_cls = coreschema.String
kwargs = {}
if model is not None: if model is not None:
# Attempt to infer a field description if possible. # Attempt to infer a field description if possible.
try: try:
@ -541,14 +542,16 @@ class SchemaGenerator(object):
elif model_field is not None and model_field.primary_key: elif model_field is not None and model_field.primary_key:
description = get_pk_description(model, model_field) description = get_pk_description(model, model_field)
if isinstance(model_field, models.AutoField): if hasattr(view, 'lookup_value_regex') and view.lookup_field == variable:
kwargs['pattern'] = view.lookup_value_regex
elif isinstance(model_field, models.AutoField):
schema_cls = coreschema.Integer schema_cls = coreschema.Integer
field = coreapi.Field( field = coreapi.Field(
name=variable, name=variable,
location='path', location='path',
required=True, required=True,
schema=schema_cls(title=title, description=description) schema=schema_cls(title=title, description=description, **kwargs)
) )
fields.append(field) fields.append(field)
@ -600,7 +603,8 @@ class SchemaGenerator(object):
if not is_list_view(path, method, view): if not is_list_view(path, method, view):
return [] return []
if not getattr(view, 'pagination_class', None): pagination = getattr(view, 'pagination_class', None)
if not pagination or not pagination.page_size:
return [] return []
paginator = view.pagination_class() paginator = view.pagination_class()

View File

@ -1,33 +1,24 @@
var responseDisplay = 'data'
var coreapi = window.coreapi
var schema = window.schema
function normalizeHTTPHeader (str) { function normalizeHTTPHeader (str) {
// Capitalize HTTP headers for display. // Capitalize HTTP headers for display.
return (str.charAt(0).toUpperCase() + str.substring(1)) return (str.charAt(0).toUpperCase() + str.substring(1))
.replace(/-(.)/g, function ($1) { return $1.toUpperCase() }) .replace(/-(.)/g, function ($1) {
.replace(/(Www)/g, function ($1) { return 'WWW' }) return $1.toUpperCase()
.replace(/(Xss)/g, function ($1) { return 'XSS' }) })
.replace(/(Md5)/g, function ($1) { return 'MD5' }) .replace(/(Www)/g, function ($1) {
return 'WWW'
})
.replace(/(Xss)/g, function ($1) {
return 'XSS'
})
.replace(/(Md5)/g, function ($1) {
return 'MD5'
})
} }
var responseDisplay = 'data'
const coreapi = window.coreapi
const schema = window.schema
// Language Control
$('#language-control li').click(function (event) {
event.preventDefault();
const languageMenuItem = $(this).find('a');
var language = languageMenuItem.data("language")
var languageControls = $(this).closest('ul').find('li');
languageControls.find('a').not('[data-language="' + language +'"]').parent().removeClass("active")
languageControls.find('a').filter('[data-language="' + language +'"]').parent().addClass("active")
$('#selected-language').text(language)
var codeBlocks = $('pre.highlight')
codeBlocks.not('[data-language="' + language +'"]').addClass("hide")
codeBlocks.filter('[data-language="' + language +'"]').removeClass("hide")
})
function formEntries (form) { function formEntries (form) {
// Polyfill for new FormData(form).entries() // Polyfill for new FormData(form).entries()
var formData = new FormData(form) var formData = new FormData(form)
@ -37,133 +28,171 @@ function formEntries (form) {
var entries = [] var entries = []
for (var {name, type, value, files, checked, selectedOptions} of Array.from(form.elements)) { for (var i = 0; i < form.elements.length; i++) {
if (!name) { var element = form.elements[i]
if (!element.name) {
continue continue
} }
if (type === 'file') { if (element.type === 'file') {
for (var file of files) { for (var j = 0; j < element.files.length; j++) {
entries.push([name, file]) entries.push([element.name, element.files[j]])
} }
} else if (type === 'select-multiple' || type === 'select-one') { } else if (element.type === 'select-multiple' || element.type === 'select-one') {
for (var elm of Array.from(selectedOptions)) { for (var j = 0; j < element.selectedOptions.length; j++) {
entries.push([name, elm.value]) entries.push([element.name, element.selectedOptions[j].value])
} }
} else if (type === 'checkbox') { } else if (element.type === 'checkbox') {
if (checked) { if (element.checked) {
entries.push([name, value]) entries.push([element.name, element.value])
} }
} else { } else {
entries.push([name, value]) entries.push([element.name, element.value])
} }
} }
return entries return entries
} }
// API Explorer $(function () {
$('form.api-interaction').submit(function(event) { var $selectedAuthentication = $('#selected-authentication')
event.preventDefault(); var $authControl = $('#auth-control')
var $authTokenModal = $('#auth_token_modal')
const form = $(this).closest("form"); // Language Control
const key = form.data("key"); $('#language-control li').click(function (event) {
var params = {}; event.preventDefault()
var $languageMenuItem = $(this).find('a')
var $languageControls = $(this).closest('ul').find('li')
var $languageControlLinks = $languageControls.find('a')
var language = $languageMenuItem.data('language')
const entries = formEntries(form.get()[0]); $languageControlLinks.not('[data-language="' + language + '"]').parent().removeClass('active')
for (var [paramKey, paramValue] of entries) { $languageControlLinks.filter('[data-language="' + language + '"]').parent().addClass('active')
var elem = form.find("[name=" + paramKey + "]")
var dataType = elem.data('type') || 'string'
if (dataType === 'integer' && paramValue) { $('#selected-language').text(language)
var value = parseInt(paramValue)
if (!isNaN(value)) { var $codeBlocks = $('pre.highlight')
params[paramKey] = value $codeBlocks.not('[data-language="' + language + '"]').addClass('hide')
} $codeBlocks.filter('[data-language="' + language + '"]').removeClass('hide')
} else if (dataType === 'number' && paramValue) { })
var value = parseFloat(paramValue)
if (!isNaN(value)) { // API Explorer
params[paramKey] = value $('form.api-interaction').submit(function (event) {
} event.preventDefault()
} else if (dataType === 'boolean' && paramValue) {
var value = { var $form = $(this).closest('form')
'true': true, var $requestMethod = $form.find('.request-method')
'false': false var $requestUrl = $form.find('.request-url')
}[paramValue.toLowerCase()] var $toggleView = $form.closest('.modal-content').find('.toggle-view')
if (value !== undefined) { var $responseStatusCode = $form.find('.response-status-code')
params[paramKey] var $meta = $form.find('.meta')
} var $responseRawResponse = $form.find('.response-raw-response')
} else if (dataType === 'array' && paramValue) { var $requestAwaiting = $form.find('.request-awaiting')
try { var $responseRaw = $form.find('.response-raw')
params[paramKey] = JSON.parse(paramValue) var $responseData = $form.find('.response-data')
} catch (err) { var key = $form.data('key')
// Ignore malformed JSON var params = {}
} var entries = formEntries($form.get()[0])
} else if (dataType === 'object' && paramValue) {
try { for (var i = 0; i < entries.length; i++) {
params[paramKey] = JSON.parse(paramValue) var entry = entries[i]
} catch (err) { var paramKey = entry[0]
// Ignore malformed JSON var paramValue = entry[1]
} var $elem = $form.find('[name=' + paramKey + ']')
} else if (dataType === 'string' && paramValue) { var dataType = $elem.data('type') || 'string'
params[paramKey] = paramValue
if (dataType === 'integer' && paramValue) {
var value = parseInt(paramValue)
if (!isNaN(value)) {
params[paramKey] = value
} }
} else if (dataType === 'number' && paramValue) {
var value = parseFloat(paramValue)
if (!isNaN(value)) {
params[paramKey] = value
}
} else if (dataType === 'boolean' && paramValue) {
var value = {
'true': true,
'false': false
}[paramValue.toLowerCase()]
if (value !== undefined) {
params[paramKey]
}
} else if (dataType === 'array' && paramValue) {
try {
params[paramKey] = JSON.parse(paramValue)
} catch (err) {
// Ignore malformed JSON
}
} else if (dataType === 'object' && paramValue) {
try {
params[paramKey] = JSON.parse(paramValue)
} catch (err) {
// Ignore malformed JSON
}
} else if (dataType === 'string' && paramValue) {
params[paramKey] = paramValue
}
} }
form.find(":checkbox").each(function( index ) { $form.find(':checkbox').each(function (index) {
// Handle unselected checkboxes // 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
} }
}) })
function requestCallback(request) { function requestCallback (request) {
// Fill in the "GET /foo/" display. // Fill in the "GET /foo/" display.
var parser = document.createElement('a'); var parser = document.createElement('a')
parser.href = request.url; parser.href = request.url
const method = request.options.method var method = request.options.method
const path = parser.pathname + parser.hash + parser.search var path = parser.pathname + parser.hash + parser.search
form.find(".request-method").text(method) $requestMethod.text(method)
form.find(".request-url").text(path) $requestUrl.text(path)
} }
function responseCallback(response, responseText) { function responseCallback (response, responseText) {
// Display the 'Data'/'Raw' control. // Display the 'Data'/'Raw' control.
form.closest(".modal-content").find(".toggle-view").removeClass("hide") $toggleView.removeClass('hide')
// Fill in the "200 OK" display. // Fill in the "200 OK" display.
form.find(".response-status-code").removeClass("label-success").removeClass("label-danger") $responseStatusCode.removeClass('label-success').removeClass('label-danger')
if (response.ok) { if (response.ok) {
form.find(".response-status-code").addClass("label-success") $responseStatusCode.addClass('label-success')
} else { } else {
form.find(".response-status-code").addClass("label-danger") $responseStatusCode.addClass('label-danger')
} }
form.find(".response-status-code").text(response.status) $responseStatusCode.text(response.status)
form.find(".meta").removeClass("hide") $meta.removeClass('hide')
// Fill in the Raw HTTP response display. // Fill in the Raw HTTP response display.
var panelText = 'HTTP/1.1 ' + response.status + ' ' + response.statusText + '\n'; var panelText = 'HTTP/1.1 ' + response.status + ' ' + response.statusText + '\n'
response.headers.forEach(function(header, key) { response.headers.forEach(function (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) $responseRawResponse.text(panelText)
} }
// Instantiate a client to make the outgoing request. // Instantiate a client to make the outgoing request.
var options = { var options = {
requestCallback: requestCallback, requestCallback: requestCallback,
responseCallback: responseCallback, responseCallback: responseCallback
} }
// 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.auth = new coreapi.auth.TokenAuthentication({ options.auth = new coreapi.auth.TokenAuthentication({
prefix: window.auth.scheme, scheme: window.auth.scheme,
token: window.auth.token token: window.auth.token
}) })
} else if (window.auth && window.auth.type === 'basic') { } else if (window.auth && window.auth.type === 'basic') {
@ -180,101 +209,104 @@ $('form.api-interaction').submit(function(event) {
}) })
} }
const client = new coreapi.Client(options) var client = new coreapi.Client(options)
client.action(schema, 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") $requestAwaiting.addClass('hide')
form.find(".response-raw").addClass("hide") $responseRaw.addClass('hide')
form.find(".response-data").addClass("hide") $responseData.addClass('hide').text('').jsonView(response)
form.find(".response-data").text('')
form.find(".response-data").jsonView(response)
if (responseDisplay === 'data') { if (responseDisplay === 'data') {
form.find(".response-data").removeClass("hide") $responseData.removeClass('hide')
} else { } else {
form.find(".response-raw").removeClass("hide") $responseRaw.removeClass('hide')
} }
}).catch(function (error) { }).catch(function (error) {
var response = JSON.stringify(error.content, null, 2); var response = JSON.stringify(error.content, null, 2)
form.find(".request-awaiting").addClass("hide") $requestAwaiting.addClass('hide')
form.find(".response-raw").addClass("hide") $responseRaw.addClass('hide')
form.find(".response-data").addClass("hide") $responseData.addClass('hide').text('').jsonView(response)
form.find(".response-data").text('')
form.find(".response-data").jsonView(response)
if (responseDisplay === 'data') { if (responseDisplay === 'data') {
form.find(".response-data").removeClass("hide") $responseData.removeClass('hide')
} else { } else {
form.find(".response-raw").removeClass("hide") $responseRaw.removeClass('hide')
} }
}) })
}); })
// 'Data'/'Raw' control
$('.toggle-view button').click(function () {
var $modalContent = $(this).closest('.modal-content')
var $modalResponseRaw = $modalContent.find('.response-raw')
var $modalResponseData = $modalContent.find('.response-data')
responseDisplay = $(this).data('display-toggle')
$(this).removeClass('btn-default').addClass('btn-info').siblings().removeClass('btn-info')
// 'Data'/'Raw' control
$('.toggle-view button').click(function() {
responseDisplay = $(this).data("display-toggle");
$(this).removeClass("btn-default").addClass('btn-info').siblings().removeClass('btn-info');
if (responseDisplay === 'raw') { if (responseDisplay === 'raw') {
$(this).closest(".modal-content").find(".response-raw").removeClass("hide"); $modalResponseRaw.removeClass('hide')
$(this).closest(".modal-content").find(".response-data").addClass("hide"); $modalResponseData.addClass('hide')
} else { } else {
$(this).closest(".modal-content").find(".response-data").removeClass("hide"); $modalResponseData.removeClass('hide')
$(this).closest(".modal-content").find(".response-raw").addClass("hide"); $modalResponseRaw.addClass('hide')
} }
}); })
// Authentication: none // Authentication: none
$('#auth-control').find("[data-auth='none']").click(function (event) { $authControl.find("[data-auth='none']").click(function (event) {
event.preventDefault(); event.preventDefault()
window.auth = null; window.auth = null
$('#selected-authentication').text('none'); $selectedAuthentication.text('none')
$('#auth-control').children().removeClass('active'); $authControl.children().removeClass('active')
$('#auth-control').find("[data-auth='none']").addClass('active'); $authControl.find("[data-auth='none']").addClass('active')
})
// Authentication: token
$('form.authentication-token-form').submit(function (event) {
event.preventDefault()
var $form = $(this).closest('form')
var scheme = $form.find('input#scheme').val()
var token = $form.find('input#token').val()
window.auth = {
'type': 'token',
'scheme': scheme,
'token': token
}
$selectedAuthentication.text('token')
$authControl.children().removeClass('active')
$authControl.find("[data-auth='token']").addClass('active')
$authTokenModal.modal('hide')
})
// Authentication: basic
$('form.authentication-basic-form').submit(function (event) {
event.preventDefault()
var $form = $(this).closest('form')
var username = $form.find('input#username').val()
var password = $form.find('input#password').val()
window.auth = {
'type': 'basic',
'username': username,
'password': password
}
$selectedAuthentication.text('basic')
$authControl.children().removeClass('active')
$authControl.find("[data-auth='basic']").addClass('active')
$authTokenModal.modal('hide')
})
// Authentication: session
$('form.authentication-session-form').submit(function (event) {
event.preventDefault()
window.auth = {
'type': 'session'
}
$selectedAuthentication.text('session')
$authControl.children().removeClass('active')
$authControl.find("[data-auth='session']").addClass('active')
$authTokenModal.modal('hide')
})
}) })
// Authentication: token
$('form.authentication-token-form').submit(function(event) {
event.preventDefault();
const form = $(this).closest("form");
const scheme = form.find('input#scheme').val();
const token = form.find('input#token').val();
window.auth = {
'type': 'token',
'scheme': scheme,
'token': token
};
$('#selected-authentication').text('token');
$('#auth-control').children().removeClass('active');
$('#auth-control').find("[data-auth='token']").addClass('active');
$('#auth_token_modal').modal('hide');
});
// Authentication: basic
$('form.authentication-basic-form').submit(function(event) {
event.preventDefault();
const form = $(this).closest("form");
const username = form.find('input#username').val();
const password = form.find('input#password').val();
window.auth = {
'type': 'basic',
'username': username,
'password': password
};
$('#selected-authentication').text('basic');
$('#auth-control').children().removeClass('active');
$('#auth-control').find("[data-auth='basic']").addClass('active');
$('#auth_basic_modal').modal('hide');
});
// Authentication: session
$('form.authentication-session-form').submit(function(event) {
event.preventDefault();
window.auth = {
'type': 'session',
};
$('#selected-authentication').text('session');
$('#auth-control').children().removeClass('active');
$('#auth-control').find("[data-auth='session']").addClass('active');
$('#auth_session_modal').modal('hide');
});

View File

@ -1,42 +0,0 @@
function getSearchTerm()
{
var sPageURL = window.location.search.substring(1);
var sURLVariables = sPageURL.split('&');
for (var i = 0; i < sURLVariables.length; i++)
{
var sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] == 'q')
{
return sParameterName[1];
}
}
}
$(document).ready(function() {
var search_term = getSearchTerm(),
$search_modal = $('#mkdocs_search_modal');
if(search_term){
$search_modal.modal();
}
// make sure search input gets autofocus everytime modal opens.
$search_modal.on('shown.bs.modal', function () {
$search_modal.find('#mkdocs-search-query').focus();
});
// Highlight.js
hljs.initHighlightingOnLoad();
$('table').addClass('table table-striped table-hover');
});
$('body').scrollspy({
target: '.bs-sidebar',
});
/* Prevent disabled links from causing a page reload */
$("li.disabled a").click(function() {
event.preventDefault();
});

View File

@ -63,12 +63,15 @@
{% else %} {% else %}
<li><a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a></li> <li><a href="{{ breadcrumb_url }}">{{ breadcrumb_name }}</a></li>
{% endif %} {% endif %}
{% empty %}
{% block breadcrumbs_empty %}&nbsp;{% endblock breadcrumbs_empty %}
{% endfor %} {% endfor %}
</ul> </ul>
{% endblock %} {% endblock %}
<!-- Content --> <!-- Content -->
<div id="content"> <div id="content">
{% block content %}
{% if 'GET' in allowed_methods %} {% if 'GET' in allowed_methods %}
<form id="get-form" class="pull-right"> <form id="get-form" class="pull-right">
@ -252,6 +255,7 @@
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endblock content %}
</div><!-- /.content --> </div><!-- /.content -->
</div><!-- /.container --> </div><!-- /.container -->
</div><!-- ./wrapper --> </div><!-- ./wrapper -->

View File

@ -22,10 +22,10 @@
<a><i class="fa fa-user fa-lg"></i> Authentication</a> <span id="selected-authentication">{% if user.is_authenticated %}session{% else %}none{% endif %}</span> <a><i class="fa fa-user fa-lg"></i> Authentication</a> <span id="selected-authentication">{% if user.is_authenticated %}session{% else %}none{% endif %}</span>
</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 {% if not user.is_authenticated %}class="active"{% endif %}><a data-auth="none" href="#">none</a></li>
<li data-auth="token" data-toggle="modal" data-target="#auth_token_modal"><a href="#">token</a></li> <li><a data-auth="token" data-toggle="modal" data-target="#auth_token_modal" href="#">token</a></li>
<li data-auth="basic" data-toggle="modal" data-target="#auth_basic_modal"><a href="#">basic</a></li> <li><a data-auth="basic" data-toggle="modal" data-target="#auth_basic_modal" 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 {% if user.is_authenticated %}class="active"{% endif %}><a data-auth="session" data-toggle="modal" data-target="#auth_session_modal" href="#">session</a></li>
</ul> </ul>
<li data-toggle="collapse" data-target="#language-control" class="collapsed"> <li data-toggle="collapse" data-target="#language-control" class="collapsed">

View File

@ -26,7 +26,7 @@ deps =
django18: Django>=1.8,<1.9 django18: Django>=1.8,<1.9
django19: Django>=1.9,<1.10 django19: Django>=1.9,<1.10
django110: Django>=1.10,<1.11 django110: Django>=1.10,<1.11
django111: Django>=1.11b1,<2.0 django111: Django>=1.11rc1,<2.0
djangomaster: https://github.com/django/django/archive/master.tar.gz djangomaster: https://github.com/django/django/archive/master.tar.gz
-rrequirements/requirements-testing.txt -rrequirements/requirements-testing.txt
-rrequirements/requirements-optionals.txt -rrequirements/requirements-optionals.txt