mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-12-04 23:44:07 +03:00
Fix dropdown menu on iOS
This commit is contained in:
parent
2f2182dc2d
commit
efba6a7285
|
@ -214,7 +214,7 @@ print token.key
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -100,6 +100,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="main-content" class="span9">
|
<div id="main-content" class="span9">
|
||||||
|
<p><a class="github" href="https://github.com/tomchristie/django-rest-framework/blob/restframework2/djangorestframework/negotiation.py"><span class="label label-info">negotiation.py</span></a></p>
|
||||||
<h1 id="content-negotiation">Content negotiation</h1>
|
<h1 id="content-negotiation">Content negotiation</h1>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
<p>HTTP has provisions for several mechanisms for "content negotiation" - the process of selecting the best representation for a given response when there are multiple representations available.</p>
|
<p>HTTP has provisions for several mechanisms for "content negotiation" - the process of selecting the best representation for a given response when there are multiple representations available.</p>
|
||||||
|
@ -123,7 +124,7 @@
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -176,7 +176,7 @@ Content-Length: 42
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -125,7 +125,7 @@ used all the time.</p>
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -125,7 +125,7 @@
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -122,7 +122,7 @@
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -191,7 +191,7 @@ def example_view(request, format=None):
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -122,7 +122,7 @@
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -163,7 +163,7 @@
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -135,7 +135,7 @@
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -151,7 +151,7 @@ class MyView(APIView):
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -317,7 +317,7 @@ The <code>ModelSerializer</code> class lets you automatically create a Serialize
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -220,7 +220,7 @@ print api_settings.DEFAULT_AUTHENTICATION
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -199,7 +199,7 @@ HTTP_511_NETWORD_AUTHENTICATION_REQUIRED
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -239,7 +239,7 @@ class UploadView(APIView):
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -154,7 +154,7 @@
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -239,7 +239,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</p>
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -181,7 +181,7 @@
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -243,7 +243,7 @@
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -124,7 +124,7 @@
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -173,7 +173,7 @@
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -128,7 +128,7 @@
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -147,7 +147,7 @@
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
151
topics/rest-hypermedia-hateoas.html
Normal file
151
topics/rest-hypermedia-hateoas.html
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Django REST framework</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="author" content="">
|
||||||
|
|
||||||
|
<!-- Le styles -->
|
||||||
|
<link href="http://tomchristie.github.com/django-rest-framework/css/prettify.css" rel="stylesheet">
|
||||||
|
<link href="http://tomchristie.github.com/django-rest-framework/css/bootstrap.css" rel="stylesheet">
|
||||||
|
<link href="http://tomchristie.github.com/django-rest-framework/css/bootstrap-responsive.css" rel="stylesheet">
|
||||||
|
<link href="http://tomchristie.github.com/django-rest-framework/css/default.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
<body onload="prettyPrint()" class="rest-hypermedia-hateoas">
|
||||||
|
|
||||||
|
<div class="navbar navbar-inverse navbar-fixed-top">
|
||||||
|
<div class="navbar-inner">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="repo-link btn btn-primary btn-small" href="https://github.com/tomchristie/django-rest-framework/tree/restframework2">GitHub</a>
|
||||||
|
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</a>
|
||||||
|
<a class="brand" href="http://tomchristie.github.com/django-rest-framework">Django REST framework</a>
|
||||||
|
<div class="nav-collapse collapse">
|
||||||
|
<ul class="nav">
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework">Home</a></li>
|
||||||
|
<li class="dropdown">
|
||||||
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Tutorial <b class="caret"></b></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/tutorial/1-serialization">1 - Serialization</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/tutorial/2-requests-and-responses">2 - Requests and responses</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/tutorial/3-class-based-views">3 - Class based views</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/tutorial/4-authentication-permissions-and-throttling">4 - Authentication, permissions and throttling</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/tutorial/5-relationships-and-hyperlinked-apis">5 - Relationships and hyperlinked APIs</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/tutorial/6-resource-orientated-projects">6 - Resource orientated projects</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown">
|
||||||
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">API Guide <b class="caret"></b></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/api-guide/requests">Requests</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/api-guide/responses">Responses</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/api-guide/views">Views</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/api-guide/generic-views">Generic views</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/api-guide/parsers">Parsers</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/api-guide/renderers">Renderers</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/api-guide/serializers">Serializers</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/api-guide/authentication">Authentication</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/api-guide/permissions">Permissions</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/api-guide/throttling">Throttling</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/api-guide/content-negotiation">Content negotiation</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/api-guide/format-suffixes">Format suffixes</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/api-guide/reverse">Returning URLs</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/api-guide/exceptions">Exceptions</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/api-guide/status-codes">Status codes</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/api-guide/settings">Settings</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown">
|
||||||
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Topics <b class="caret"></b></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/topics/csrf">Working with AJAX and CSRF</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/topics/formoverloading">Browser based PUT, PATCH and DELETE</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/topics/browsable-api">Working with the browsable API</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/topics/contributing">Contributing to REST framework</a></li>
|
||||||
|
<li><a href="http://tomchristie.github.com/django-rest-framework/topics/credits">Credits</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="nav pull-right">
|
||||||
|
<li class="dropdown">
|
||||||
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Version: 2.0.0 <b class="caret"></b></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="#">Trunk</a></li>
|
||||||
|
<li><a href="#">2.0.0</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div><!--/.nav-collapse -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row-fluid">
|
||||||
|
<div class="span3">
|
||||||
|
<div id="table-of-contents" class="well affix span3">
|
||||||
|
<ul class="nav nav-list side-nav">
|
||||||
|
<li class="main"><a href="#rest,-hypermedia-&-hateoas">REST, Hypermedia & HATEOAS</a></li>
|
||||||
|
<li class="main"><a href="#building-hypermedia-apis-with-rest-framework">Building Hypermedia APIs with REST framework</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="main-content" class="span9">
|
||||||
|
<h1 id="rest-hypermedia-hateoas">REST, Hypermedia & HATEOAS</h1>
|
||||||
|
<blockquote>
|
||||||
|
<p>You keep using that word "REST". I do not think it means what you think it means.</p>
|
||||||
|
<p>— Mike Amundsen, <a href="http://vimeo.com/channels/restfest/page:2">REST fest 2012 keynote</a>.</p>
|
||||||
|
</blockquote>
|
||||||
|
<p>First off, the disclaimer. The name "Django REST framework" was choosen with a view to making sure the project would be easily found by developers. Throughout the documentation we try to use the more simple and technically correct terminology of "Web APIs".</p>
|
||||||
|
<p>If you are serious about designing a Hypermedia APIs, you should look to resources outside of this documentation to help inform your design choices.</p>
|
||||||
|
<p>The following fall into the "required reading" category.</p>
|
||||||
|
<ul>
|
||||||
|
<li>Fielding's dissertation - <a href="http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm">Architectural Styles and
|
||||||
|
the Design of Network-based Software Architectures</a>.</li>
|
||||||
|
<li>Fielding's "<a href="http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven">REST APIs must be hypertext-driven</a>" blog post.</li>
|
||||||
|
<li>Leonard Richardson & Sam Ruby's <a href="">RESTful Web Services</a>.</li>
|
||||||
|
<li>Mike Amundsen's <a href="…">Building Hypermedia APIs with HTML5 and Node</a>.</li>
|
||||||
|
<li>Steve Klabnik's <a href="http://designinghypermediaapis.com/">Designing Hypermedia APIs</a>.</li>
|
||||||
|
<li>The <a href="http://martinfowler.com/articles/richardsonMaturityModel.html">Richardson Maturity Model</a>.</li>
|
||||||
|
</ul>
|
||||||
|
<p>For a more thorough background, check out Klabnik's <a href="http://blog.steveklabnik.com/posts/2012-02-27-hypermedia-api-reading-list">Hypermedia API reading list</a>.</p>
|
||||||
|
<h1 id="building-hypermedia-apis-with-rest-framework">Building Hypermedia APIs with REST framework</h1>
|
||||||
|
<p>REST framework is an agnositic Web API toolkit. It does help guide you towards building well-connected APIs, and makes it easy to design appropriate media types, but it does not strictly enforce any particular design style.</p>
|
||||||
|
<h3 id="what-rest-framework-does-provide">What REST framework <em>does</em> provide.</h3>
|
||||||
|
<p>It is self evident that REST framework makes it possible to build Hypermedia APIs. The browseable API that it offers is built on HTML - the hypermedia language of the web.</p>
|
||||||
|
<p>REST framework also includes <a href="../api-guide/serializers">serialization</a> and <a href="../api-guide/parsers">parser</a>/<a href="../api-guide/renderers">renderer</a> components that make it easy to build appropriate media types, <a href="../api-guide/fields">hyperlinked relations</a> for building well-connected systems, and great support for <a href="../api-guide/content-negotiation">content negotiation</a>.</p>
|
||||||
|
<h3 id="what-rest-framework-doesnt-provide">What REST framework <em>doesn't</em> provide.</h3>
|
||||||
|
<p>What REST framework doesn't do is give you is machine readable hypermedia formats such as <a href="http://www.amundsen.com/media-types/collection/">Collection+JSON</a> by default, or the ability to auto-magically create HATEOAS style APIs. Doing so would involve making opinionated choices about API design that should really remain outside of the framework's scope.</p>
|
||||||
|
</div><!--/span-->
|
||||||
|
</div><!--/row-->
|
||||||
|
</div><!--/.fluid-container-->
|
||||||
|
|
||||||
|
<!-- Le javascript
|
||||||
|
================================================== -->
|
||||||
|
<!-- Placed at the end of the document so the pages load faster -->
|
||||||
|
<script src="http://tomchristie.github.com/django-rest-framework/js/jquery-1.8.1-min.js"></script>
|
||||||
|
<script src="http://tomchristie.github.com/django-rest-framework/js/prettify.js"></script>
|
||||||
|
<script src="http://tomchristie.github.com/django-rest-framework/js/bootstrap-dropdown.js"></script>
|
||||||
|
<script src="http://tomchristie.github.com/django-rest-framework/js/bootstrap-scrollspy.js"></script>
|
||||||
|
<script src="http://tomchristie.github.com/django-rest-framework/js/bootstrap-collapse.js"></script>
|
||||||
|
<script>
|
||||||
|
//$('.side-nav').scrollspy()
|
||||||
|
var shiftWindow = function() { scrollBy(0, -50) };
|
||||||
|
if (location.hash) shiftWindow();
|
||||||
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body></html>
|
|
@ -270,7 +270,7 @@ class JSONResponse(HttpResponse):
|
||||||
comment.save()
|
comment.save()
|
||||||
return JSONResponse(serializer.data, status=201)
|
return JSONResponse(serializer.data, status=201)
|
||||||
else:
|
else:
|
||||||
return JSONResponse(serializer.error_data, status=400)
|
return JSONResponse(serializer.errors, status=400)
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>We'll also need a view which corrosponds to an individual comment, and can be used to retrieve, update or delete the comment.</p>
|
<p>We'll also need a view which corrosponds to an individual comment, and can be used to retrieve, update or delete the comment.</p>
|
||||||
<pre class="prettyprint lang-py"><code>def comment_instance(request, pk):
|
<pre class="prettyprint lang-py"><code>def comment_instance(request, pk):
|
||||||
|
@ -294,7 +294,7 @@ class JSONResponse(HttpResponse):
|
||||||
comment.save()
|
comment.save()
|
||||||
return JSONResponse(serializer.data)
|
return JSONResponse(serializer.data)
|
||||||
else:
|
else:
|
||||||
return JSONResponse(serializer.error_data, status=400)
|
return JSONResponse(serializer.errors, status=400)
|
||||||
|
|
||||||
elif request.method == 'DELETE':
|
elif request.method == 'DELETE':
|
||||||
comment.delete()
|
comment.delete()
|
||||||
|
@ -334,7 +334,7 @@ urlpatterns = patterns('blog.views',
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -156,7 +156,7 @@ def comment_root(request):
|
||||||
comment.save()
|
comment.save()
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
else:
|
else:
|
||||||
return Response(serializer.error_data, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>Our instance view is an improvement over the previous example. It's a little more concise, and the code now feels very similar to if we were working with the Forms API. We're also using named status codes, which makes the response meanings more obvious.</p>
|
<p>Our instance view is an improvement over the previous example. It's a little more concise, and the code now feels very similar to if we were working with the Forms API. We're also using named status codes, which makes the response meanings more obvious.</p>
|
||||||
<pre class="prettyprint lang-py"><code>@api_view(['GET', 'PUT', 'DELETE'])
|
<pre class="prettyprint lang-py"><code>@api_view(['GET', 'PUT', 'DELETE'])
|
||||||
|
@ -180,7 +180,7 @@ def comment_instance(request, pk):
|
||||||
comment.save()
|
comment.save()
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
else:
|
else:
|
||||||
return Response(serializer.error_data, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
elif request.method == 'DELETE':
|
elif request.method == 'DELETE':
|
||||||
comment.delete()
|
comment.delete()
|
||||||
|
@ -236,7 +236,7 @@ urlpatterns = format_suffix_patterns(urlpatterns)
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -130,8 +130,6 @@ class CommentRoot(APIView):
|
||||||
comment.save()
|
comment.save()
|
||||||
return Response(serializer.serialized, status=status.HTTP_201_CREATED)
|
return Response(serializer.serialized, status=status.HTTP_201_CREATED)
|
||||||
return Response(serializer.serialized_errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.serialized_errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
comment_root = CommentRoot.as_view()
|
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>So far, so good. It looks pretty similar to the previous case, but we've got better seperation between the different HTTP methods. We'll also need to update the instance view. </p>
|
<p>So far, so good. It looks pretty similar to the previous case, but we've got better seperation between the different HTTP methods. We'll also need to update the instance view. </p>
|
||||||
<pre class="prettyprint lang-py"><code>class CommentInstance(APIView):
|
<pre class="prettyprint lang-py"><code>class CommentInstance(APIView):
|
||||||
|
@ -157,17 +155,27 @@ comment_root = CommentRoot.as_view()
|
||||||
comment = serializer.deserialized
|
comment = serializer.deserialized
|
||||||
comment.save()
|
comment.save()
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
return Response(serializer.error_data, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
def delete(self, request, pk, format=None):
|
def delete(self, request, pk, format=None):
|
||||||
comment = self.get_object(pk)
|
comment = self.get_object(pk)
|
||||||
comment.delete()
|
comment.delete()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
comment_instance = CommentInstance.as_view()
|
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>That's looking good. Again, it's still pretty similar to the function based view right now.
|
<p>That's looking good. Again, it's still pretty similar to the function based view right now.</p>
|
||||||
Okay, we're done. If you run the development server everything should be working just as before.</p>
|
<p>We'll also need to refactor our URLconf slightly now we're using class based views.</p>
|
||||||
|
<pre class="prettyprint lang-py"><code>from django.conf.urls import patterns, url
|
||||||
|
from djangorestframework.urlpatterns import format_suffix_patterns
|
||||||
|
from blogpost import views
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(r'^$', views.CommentRoot.as_view()),
|
||||||
|
url(r'^(?P<pk>[0-9]+)$', views.CommentInstance.as_view())
|
||||||
|
)
|
||||||
|
|
||||||
|
urlpatterns = format_suffix_patterns(urlpatterns)
|
||||||
|
</code></pre>
|
||||||
|
<p>Okay, we're done. If you run the development server everything should be working just as before.</p>
|
||||||
<h2 id="using-mixins">Using mixins</h2>
|
<h2 id="using-mixins">Using mixins</h2>
|
||||||
<p>One of the big wins of using class based views is that it allows us to easily compose reusable bits of behaviour.</p>
|
<p>One of the big wins of using class based views is that it allows us to easily compose reusable bits of behaviour.</p>
|
||||||
<p>The create/retrieve/update/delete operations that we've been using so far are going to be pretty simliar for any model-backed API views we create. Those bits of common behaviour are implemented in REST framework's mixin classes.</p>
|
<p>The create/retrieve/update/delete operations that we've been using so far are going to be pretty simliar for any model-backed API views we create. Those bits of common behaviour are implemented in REST framework's mixin classes.</p>
|
||||||
|
@ -188,8 +196,6 @@ class CommentRoot(mixins.ListModelMixin,
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
return self.create(request, *args, **kwargs)
|
return self.create(request, *args, **kwargs)
|
||||||
|
|
||||||
comment_root = CommentRoot.as_view()
|
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>We'll take a moment to examine exactly what's happening here - We're building our view using <code>MultipleObjectBaseView</code>, and adding in <code>ListModelMixin</code> and <code>CreateModelMixin</code>.</p>
|
<p>We'll take a moment to examine exactly what's happening here - We're building our view using <code>MultipleObjectBaseView</code>, and adding in <code>ListModelMixin</code> and <code>CreateModelMixin</code>.</p>
|
||||||
<p>The base class provides the core functionality, and the mixin classes provide the <code>.list()</code> and <code>.create()</code> actions. We're then explictly binding the <code>get</code> and <code>post</code> methods to the appropriate actions. Simple enough stuff so far.</p>
|
<p>The base class provides the core functionality, and the mixin classes provide the <code>.list()</code> and <code>.create()</code> actions. We're then explictly binding the <code>get</code> and <code>post</code> methods to the appropriate actions. Simple enough stuff so far.</p>
|
||||||
|
@ -208,8 +214,6 @@ comment_root = CommentRoot.as_view()
|
||||||
|
|
||||||
def delete(self, request, *args, **kwargs):
|
def delete(self, request, *args, **kwargs):
|
||||||
return self.destroy(request, *args, **kwargs)
|
return self.destroy(request, *args, **kwargs)
|
||||||
|
|
||||||
comment_instance = CommentInstance.as_view()
|
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>Pretty similar. This time we're using the <code>SingleObjectBaseView</code> class to provide the core functionality, and adding in mixins to provide the <code>.retrieve()</code>, <code>.update()</code> and <code>.destroy()</code> actions.</p>
|
<p>Pretty similar. This time we're using the <code>SingleObjectBaseView</code> class to provide the core functionality, and adding in mixins to provide the <code>.retrieve()</code>, <code>.update()</code> and <code>.destroy()</code> actions.</p>
|
||||||
<h2 id="using-generic-class-based-views">Using generic class based views</h2>
|
<h2 id="using-generic-class-based-views">Using generic class based views</h2>
|
||||||
|
@ -222,13 +226,9 @@ class CommentRoot(generics.RootAPIView):
|
||||||
model = Comment
|
model = Comment
|
||||||
serializer_class = CommentSerializer
|
serializer_class = CommentSerializer
|
||||||
|
|
||||||
comment_root = CommentRoot.as_view()
|
|
||||||
|
|
||||||
class CommentInstance(generics.InstanceAPIView):
|
class CommentInstance(generics.InstanceAPIView):
|
||||||
model = Comment
|
model = Comment
|
||||||
serializer_class = CommentSerializer
|
serializer_class = CommentSerializer
|
||||||
|
|
||||||
comment_instance = CommentInstance.as_view()
|
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>Wow, that's pretty concise. We've got a huge amount for free, and our code looks like good, clean, idomatic Django.</p>
|
<p>Wow, that's pretty concise. We've got a huge amount for free, and our code looks like good, clean, idomatic Django.</p>
|
||||||
<p>Next we'll move onto <a href="4-authentication-permissions-and-throttling">part 4 of the tutorial</a>, where we'll take a look at how we can customize the behavior of our views to support a range of authentication, permissions, throttling and other aspects.</p>
|
<p>Next we'll move onto <a href="4-authentication-permissions-and-throttling">part 4 of the tutorial</a>, where we'll take a look at how we can customize the behavior of our views to support a range of authentication, permissions, throttling and other aspects.</p>
|
||||||
|
@ -250,7 +250,7 @@ comment_instance = CommentInstance.as_view()
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -118,7 +118,7 @@
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -124,7 +124,7 @@
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -184,7 +184,7 @@ urlpatterns = router.urlpatterns
|
||||||
if (location.hash) shiftWindow();
|
if (location.hash) shiftWindow();
|
||||||
window.addEventListener("hashchange", shiftWindow);
|
window.addEventListener("hashchange", shiftWindow);
|
||||||
|
|
||||||
$('.dropdown-menu').click(function(event) {
|
$('.dropdown-menu').on('click touchstart', function(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user