Fix dropdown menu on iOS

This commit is contained in:
Tom Christie 2012-09-17 20:21:26 +01:00
parent 2f2182dc2d
commit efba6a7285
30 changed files with 201 additions and 49 deletions

View File

@ -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>

View File

@ -100,7 +100,8 @@
</div> </div>
<div id="main-content" class="span9"> <div id="main-content" class="span9">
<h1 id="content-negotiation">Content negotiation</h1> <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>
<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>
<p>&mdash; <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html">RFC 2616</a>, Fielding et al.</p> <p>&mdash; <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html">RFC 2616</a>, Fielding et al.</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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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 &amp; HATEOAS</h1>
<blockquote>
<p>You keep using that word "REST". I do not think it means what you think it means.</p>
<p>&mdash; 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 &amp; 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>

View File

@ -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>

View File

@ -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>

View File

@ -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&lt;pk&gt;[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>

View File

@ -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>

View File

@ -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>

View File

@ -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>