From 3d4bb4b5533fa281c2f11c12ceb0a9ae61aa0d54 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 22:03:07 +0100 Subject: [PATCH 01/38] Ensure action kwargs properly handdled. Refs #940. --- htmlcov/coverage_html.js | 372 +++ htmlcov/index.html | 404 ++++ htmlcov/jquery-1.4.3.min.js | 166 ++ htmlcov/jquery.hotkeys.js | 99 + htmlcov/jquery.isonscreen.js | 53 + htmlcov/jquery.tablesorter.min.js | 2 + htmlcov/keybd_closed.png | Bin 0 -> 264 bytes htmlcov/keybd_open.png | Bin 0 -> 267 bytes htmlcov/rest_framework___init__.html | 99 + htmlcov/rest_framework_authentication.html | 767 +++++++ .../rest_framework_authtoken___init__.html | 81 + htmlcov/rest_framework_authtoken_models.html | 151 ++ .../rest_framework_authtoken_serializers.html | 129 ++ htmlcov/rest_framework_authtoken_views.html | 133 ++ htmlcov/rest_framework_decorators.html | 339 +++ htmlcov/rest_framework_exceptions.html | 257 +++ htmlcov/rest_framework_fields.html | 1991 ++++++++++++++++ htmlcov/rest_framework_filters.html | 367 +++ htmlcov/rest_framework_generics.html | 1079 +++++++++ htmlcov/rest_framework_mixins.html | 449 ++++ htmlcov/rest_framework_models.html | 83 + htmlcov/rest_framework_negotiation.html | 259 +++ htmlcov/rest_framework_pagination.html | 269 +++ htmlcov/rest_framework_parsers.html | 671 ++++++ htmlcov/rest_framework_permissions.html | 429 ++++ htmlcov/rest_framework_relations.html | 1347 +++++++++++ htmlcov/rest_framework_renderers.html | 1227 ++++++++++ htmlcov/rest_framework_request.html | 819 +++++++ htmlcov/rest_framework_response.html | 249 ++ htmlcov/rest_framework_reverse.html | 127 ++ htmlcov/rest_framework_routers.html | 595 +++++ htmlcov/rest_framework_serializers.html | 2011 +++++++++++++++++ htmlcov/rest_framework_settings.html | 465 ++++ htmlcov/rest_framework_status.html | 187 ++ htmlcov/rest_framework_throttling.html | 533 +++++ htmlcov/rest_framework_urlpatterns.html | 205 ++ htmlcov/rest_framework_urls.html | 129 ++ htmlcov/rest_framework_utils___init__.html | 81 + htmlcov/rest_framework_utils_breadcrumbs.html | 189 ++ htmlcov/rest_framework_utils_encoders.html | 275 +++ htmlcov/rest_framework_utils_formatting.html | 241 ++ htmlcov/rest_framework_utils_mediatypes.html | 257 +++ htmlcov/rest_framework_views.html | 793 +++++++ htmlcov/rest_framework_viewsets.html | 359 +++ htmlcov/status.dat | 1258 +++++++++++ htmlcov/style.css | 275 +++ rest_framework/tests/test_routers.py | 35 +- 47 files changed, 20303 insertions(+), 3 deletions(-) create mode 100644 htmlcov/coverage_html.js create mode 100644 htmlcov/index.html create mode 100644 htmlcov/jquery-1.4.3.min.js create mode 100644 htmlcov/jquery.hotkeys.js create mode 100644 htmlcov/jquery.isonscreen.js create mode 100644 htmlcov/jquery.tablesorter.min.js create mode 100644 htmlcov/keybd_closed.png create mode 100644 htmlcov/keybd_open.png create mode 100644 htmlcov/rest_framework___init__.html create mode 100644 htmlcov/rest_framework_authentication.html create mode 100644 htmlcov/rest_framework_authtoken___init__.html create mode 100644 htmlcov/rest_framework_authtoken_models.html create mode 100644 htmlcov/rest_framework_authtoken_serializers.html create mode 100644 htmlcov/rest_framework_authtoken_views.html create mode 100644 htmlcov/rest_framework_decorators.html create mode 100644 htmlcov/rest_framework_exceptions.html create mode 100644 htmlcov/rest_framework_fields.html create mode 100644 htmlcov/rest_framework_filters.html create mode 100644 htmlcov/rest_framework_generics.html create mode 100644 htmlcov/rest_framework_mixins.html create mode 100644 htmlcov/rest_framework_models.html create mode 100644 htmlcov/rest_framework_negotiation.html create mode 100644 htmlcov/rest_framework_pagination.html create mode 100644 htmlcov/rest_framework_parsers.html create mode 100644 htmlcov/rest_framework_permissions.html create mode 100644 htmlcov/rest_framework_relations.html create mode 100644 htmlcov/rest_framework_renderers.html create mode 100644 htmlcov/rest_framework_request.html create mode 100644 htmlcov/rest_framework_response.html create mode 100644 htmlcov/rest_framework_reverse.html create mode 100644 htmlcov/rest_framework_routers.html create mode 100644 htmlcov/rest_framework_serializers.html create mode 100644 htmlcov/rest_framework_settings.html create mode 100644 htmlcov/rest_framework_status.html create mode 100644 htmlcov/rest_framework_throttling.html create mode 100644 htmlcov/rest_framework_urlpatterns.html create mode 100644 htmlcov/rest_framework_urls.html create mode 100644 htmlcov/rest_framework_utils___init__.html create mode 100644 htmlcov/rest_framework_utils_breadcrumbs.html create mode 100644 htmlcov/rest_framework_utils_encoders.html create mode 100644 htmlcov/rest_framework_utils_formatting.html create mode 100644 htmlcov/rest_framework_utils_mediatypes.html create mode 100644 htmlcov/rest_framework_views.html create mode 100644 htmlcov/rest_framework_viewsets.html create mode 100644 htmlcov/status.dat create mode 100644 htmlcov/style.css diff --git a/htmlcov/coverage_html.js b/htmlcov/coverage_html.js new file mode 100644 index 000000000..da3e22c81 --- /dev/null +++ b/htmlcov/coverage_html.js @@ -0,0 +1,372 @@ +// Coverage.py HTML report browser code. +/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */ +/*global coverage: true, document, window, $ */ + +coverage = {}; + +// Find all the elements with shortkey_* class, and use them to assign a shotrtcut key. +coverage.assign_shortkeys = function () { + $("*[class*='shortkey_']").each(function (i, e) { + $.each($(e).attr("class").split(" "), function (i, c) { + if (/^shortkey_/.test(c)) { + $(document).bind('keydown', c.substr(9), function () { + $(e).click(); + }); + } + }); + }); +}; + +// Create the events for the help panel. +coverage.wire_up_help_panel = function () { + $("#keyboard_icon").click(function () { + // Show the help panel, and position it so the keyboard icon in the + // panel is in the same place as the keyboard icon in the header. + $(".help_panel").show(); + var koff = $("#keyboard_icon").offset(); + var poff = $("#panel_icon").position(); + $(".help_panel").offset({ + top: koff.top-poff.top, + left: koff.left-poff.left + }); + }); + $("#panel_icon").click(function () { + $(".help_panel").hide(); + }); +}; + +// Loaded on index.html +coverage.index_ready = function ($) { + // Look for a cookie containing previous sort settings: + var sort_list = []; + var cookie_name = "COVERAGE_INDEX_SORT"; + var i; + + // This almost makes it worth installing the jQuery cookie plugin: + if (document.cookie.indexOf(cookie_name) > -1) { + var cookies = document.cookie.split(";"); + for (i = 0; i < cookies.length; i++) { + var parts = cookies[i].split("="); + + if ($.trim(parts[0]) === cookie_name && parts[1]) { + sort_list = eval("[[" + parts[1] + "]]"); + break; + } + } + } + + // Create a new widget which exists only to save and restore + // the sort order: + $.tablesorter.addWidget({ + id: "persistentSort", + + // Format is called by the widget before displaying: + format: function (table) { + if (table.config.sortList.length === 0 && sort_list.length > 0) { + // This table hasn't been sorted before - we'll use + // our stored settings: + $(table).trigger('sorton', [sort_list]); + } + else { + // This is not the first load - something has + // already defined sorting so we'll just update + // our stored value to match: + sort_list = table.config.sortList; + } + } + }); + + // Configure our tablesorter to handle the variable number of + // columns produced depending on report options: + var headers = []; + var col_count = $("table.index > thead > tr > th").length; + + headers[0] = { sorter: 'text' }; + for (i = 1; i < col_count-1; i++) { + headers[i] = { sorter: 'digit' }; + } + headers[col_count-1] = { sorter: 'percent' }; + + // Enable the table sorter: + $("table.index").tablesorter({ + widgets: ['persistentSort'], + headers: headers + }); + + coverage.assign_shortkeys(); + coverage.wire_up_help_panel(); + + // Watch for page unload events so we can save the final sort settings: + $(window).unload(function () { + document.cookie = cookie_name + "=" + sort_list.toString() + "; path=/"; + }); +}; + +// -- pyfile stuff -- + +coverage.pyfile_ready = function ($) { + // If we're directed to a particular line number, highlight the line. + var frag = location.hash; + if (frag.length > 2 && frag[1] === 'n') { + $(frag).addClass('highlight'); + coverage.set_sel(parseInt(frag.substr(2), 10)); + } + else { + coverage.set_sel(0); + } + + $(document) + .bind('keydown', 'j', coverage.to_next_chunk_nicely) + .bind('keydown', 'k', coverage.to_prev_chunk_nicely) + .bind('keydown', '0', coverage.to_top) + .bind('keydown', '1', coverage.to_first_chunk) + ; + + coverage.assign_shortkeys(); + coverage.wire_up_help_panel(); +}; + +coverage.toggle_lines = function (btn, cls) { + btn = $(btn); + var hide = "hide_"+cls; + if (btn.hasClass(hide)) { + $("#source ."+cls).removeClass(hide); + btn.removeClass(hide); + } + else { + $("#source ."+cls).addClass(hide); + btn.addClass(hide); + } +}; + +// Return the nth line div. +coverage.line_elt = function (n) { + return $("#t" + n); +}; + +// Return the nth line number div. +coverage.num_elt = function (n) { + return $("#n" + n); +}; + +// Return the container of all the code. +coverage.code_container = function () { + return $(".linenos"); +}; + +// Set the selection. b and e are line numbers. +coverage.set_sel = function (b, e) { + // The first line selected. + coverage.sel_begin = b; + // The next line not selected. + coverage.sel_end = (e === undefined) ? b+1 : e; +}; + +coverage.to_top = function () { + coverage.set_sel(0, 1); + coverage.scroll_window(0); +}; + +coverage.to_first_chunk = function () { + coverage.set_sel(0, 1); + coverage.to_next_chunk(); +}; + +coverage.is_transparent = function (color) { + // Different browsers return different colors for "none". + return color === "transparent" || color === "rgba(0, 0, 0, 0)"; +}; + +coverage.to_next_chunk = function () { + var c = coverage; + + // Find the start of the next colored chunk. + var probe = c.sel_end; + while (true) { + var probe_line = c.line_elt(probe); + if (probe_line.length === 0) { + return; + } + var color = probe_line.css("background-color"); + if (!c.is_transparent(color)) { + break; + } + probe++; + } + + // There's a next chunk, `probe` points to it. + var begin = probe; + + // Find the end of this chunk. + var next_color = color; + while (next_color === color) { + probe++; + probe_line = c.line_elt(probe); + next_color = probe_line.css("background-color"); + } + c.set_sel(begin, probe); + c.show_selection(); +}; + +coverage.to_prev_chunk = function () { + var c = coverage; + + // Find the end of the prev colored chunk. + var probe = c.sel_begin-1; + var probe_line = c.line_elt(probe); + if (probe_line.length === 0) { + return; + } + var color = probe_line.css("background-color"); + while (probe > 0 && c.is_transparent(color)) { + probe--; + probe_line = c.line_elt(probe); + if (probe_line.length === 0) { + return; + } + color = probe_line.css("background-color"); + } + + // There's a prev chunk, `probe` points to its last line. + var end = probe+1; + + // Find the beginning of this chunk. + var prev_color = color; + while (prev_color === color) { + probe--; + probe_line = c.line_elt(probe); + prev_color = probe_line.css("background-color"); + } + c.set_sel(probe+1, end); + c.show_selection(); +}; + +// Return the line number of the line nearest pixel position pos +coverage.line_at_pos = function (pos) { + var l1 = coverage.line_elt(1), + l2 = coverage.line_elt(2), + result; + if (l1.length && l2.length) { + var l1_top = l1.offset().top, + line_height = l2.offset().top - l1_top, + nlines = (pos - l1_top) / line_height; + if (nlines < 1) { + result = 1; + } + else { + result = Math.ceil(nlines); + } + } + else { + result = 1; + } + return result; +}; + +// Returns 0, 1, or 2: how many of the two ends of the selection are on +// the screen right now? +coverage.selection_ends_on_screen = function () { + if (coverage.sel_begin === 0) { + return 0; + } + + var top = coverage.line_elt(coverage.sel_begin); + var next = coverage.line_elt(coverage.sel_end-1); + + return ( + (top.isOnScreen() ? 1 : 0) + + (next.isOnScreen() ? 1 : 0) + ); +}; + +coverage.to_next_chunk_nicely = function () { + coverage.finish_scrolling(); + if (coverage.selection_ends_on_screen() === 0) { + // The selection is entirely off the screen: select the top line on + // the screen. + var win = $(window); + coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop())); + } + coverage.to_next_chunk(); +}; + +coverage.to_prev_chunk_nicely = function () { + coverage.finish_scrolling(); + if (coverage.selection_ends_on_screen() === 0) { + var win = $(window); + coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop() + win.height())); + } + coverage.to_prev_chunk(); +}; + +// Select line number lineno, or if it is in a colored chunk, select the +// entire chunk +coverage.select_line_or_chunk = function (lineno) { + var c = coverage; + var probe_line = c.line_elt(lineno); + if (probe_line.length === 0) { + return; + } + var the_color = probe_line.css("background-color"); + if (!c.is_transparent(the_color)) { + // The line is in a highlighted chunk. + // Search backward for the first line. + var probe = lineno; + var color = the_color; + while (probe > 0 && color === the_color) { + probe--; + probe_line = c.line_elt(probe); + if (probe_line.length === 0) { + break; + } + color = probe_line.css("background-color"); + } + var begin = probe + 1; + + // Search forward for the last line. + probe = lineno; + color = the_color; + while (color === the_color) { + probe++; + probe_line = c.line_elt(probe); + color = probe_line.css("background-color"); + } + + coverage.set_sel(begin, probe); + } + else { + coverage.set_sel(lineno); + } +}; + +coverage.show_selection = function () { + var c = coverage; + + // Highlight the lines in the chunk + c.code_container().find(".highlight").removeClass("highlight"); + for (var probe = c.sel_begin; probe > 0 && probe < c.sel_end; probe++) { + c.num_elt(probe).addClass("highlight"); + } + + c.scroll_to_selection(); +}; + +coverage.scroll_to_selection = function () { + // Scroll the page if the chunk isn't fully visible. + if (coverage.selection_ends_on_screen() < 2) { + // Need to move the page. The html,body trick makes it scroll in all + // browsers, got it from http://stackoverflow.com/questions/3042651 + var top = coverage.line_elt(coverage.sel_begin); + var top_pos = parseInt(top.offset().top, 10); + coverage.scroll_window(top_pos - 30); + } +}; + +coverage.scroll_window = function (to_pos) { + $("html,body").animate({scrollTop: to_pos}, 200); +}; + +coverage.finish_scrolling = function () { + $("html,body").stop(true, true); +}; + diff --git a/htmlcov/index.html b/htmlcov/index.html new file mode 100644 index 000000000..983451658 --- /dev/null +++ b/htmlcov/index.html @@ -0,0 +1,404 @@ + + + + + Coverage report + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ n + s + m + x + + c   change column sorting +

+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Modulestatementsmissingexcludedcoverage
Total3621406089%
rest_framework/__init__400100%
rest_framework/authentication16933080%
rest_framework/authtoken/__init__000100%
rest_framework/authtoken/models211095%
rest_framework/authtoken/serializers172088%
rest_framework/authtoken/views2100100%
rest_framework/decorators6000100%
rest_framework/exceptions512096%
rest_framework/fields59480087%
rest_framework/filters776092%
rest_framework/generics19634083%
rest_framework/mixins977093%
rest_framework/models000100%
rest_framework/negotiation414090%
rest_framework/pagination4300100%
rest_framework/parsers15313092%
rest_framework/permissions6312081%
rest_framework/relations36588076%
rest_framework/renderers28223092%
rest_framework/request1618095%
rest_framework/response421098%
rest_framework/reverse123075%
rest_framework/routers1087094%
rest_framework/serializers46427094%
rest_framework/settings442095%
rest_framework/status4600100%
rest_framework/throttling9017081%
rest_framework/urlpatterns314087%
rest_framework/urls400100%
rest_framework/utils/__init__000100%
rest_framework/utils/breadcrumbs2700100%
rest_framework/utils/encoders7019073%
rest_framework/utils/formatting391097%
rest_framework/utils/mediatypes4410077%
rest_framework/views14600100%
rest_framework/viewsets392095%
+
+ + + + + diff --git a/htmlcov/jquery-1.4.3.min.js b/htmlcov/jquery-1.4.3.min.js new file mode 100644 index 000000000..c941a5f7a --- /dev/null +++ b/htmlcov/jquery-1.4.3.min.js @@ -0,0 +1,166 @@ +/*! + * jQuery JavaScript Library v1.4.3 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Thu Oct 14 23:10:06 2010 -0400 + */ +(function(E,A){function U(){return false}function ba(){return true}function ja(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function Ga(a){var b,d,e=[],f=[],h,k,l,n,s,v,B,D;k=c.data(this,this.nodeType?"events":"__events__");if(typeof k==="function")k=k.events;if(!(a.liveFired===this||!k||!k.live||a.button&&a.type==="click")){if(a.namespace)D=RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)");a.liveFired=this;var H=k.live.slice(0);for(n=0;nd)break;a.currentTarget=f.elem;a.data=f.handleObj.data; +a.handleObj=f.handleObj;D=f.handleObj.origHandler.apply(f.elem,arguments);if(D===false||a.isPropagationStopped()){d=f.level;if(D===false)b=false}}return b}}function Y(a,b){return(a&&a!=="*"?a+".":"")+b.replace(Ha,"`").replace(Ia,"&")}function ka(a,b,d){if(c.isFunction(b))return c.grep(a,function(f,h){return!!b.call(f,h,f)===d});else if(b.nodeType)return c.grep(a,function(f){return f===b===d});else if(typeof b==="string"){var e=c.grep(a,function(f){return f.nodeType===1});if(Ja.test(b))return c.filter(b, +e,!d);else b=c.filter(b,e)}return c.grep(a,function(f){return c.inArray(f,b)>=0===d})}function la(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var e=c.data(a[d++]),f=c.data(this,e);if(e=e&&e.events){delete f.handle;f.events={};for(var h in e)for(var k in e[h])c.event.add(this,h,e[h][k],e[h][k].data)}}})}function Ka(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)} +function ma(a,b,d){var e=b==="width"?a.offsetWidth:a.offsetHeight;if(d==="border")return e;c.each(b==="width"?La:Ma,function(){d||(e-=parseFloat(c.css(a,"padding"+this))||0);if(d==="margin")e+=parseFloat(c.css(a,"margin"+this))||0;else e-=parseFloat(c.css(a,"border"+this+"Width"))||0});return e}function ca(a,b,d,e){if(c.isArray(b)&&b.length)c.each(b,function(f,h){d||Na.test(a)?e(a,h):ca(a+"["+(typeof h==="object"||c.isArray(h)?f:"")+"]",h,d,e)});else if(!d&&b!=null&&typeof b==="object")c.isEmptyObject(b)? +e(a,""):c.each(b,function(f,h){ca(a+"["+f+"]",h,d,e)});else e(a,b)}function S(a,b){var d={};c.each(na.concat.apply([],na.slice(0,b)),function(){d[this]=a});return d}function oa(a){if(!da[a]){var b=c("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d==="")d="block";da[a]=d}return da[a]}function ea(a){return c.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var u=E.document,c=function(){function a(){if(!b.isReady){try{u.documentElement.doScroll("left")}catch(i){setTimeout(a, +1);return}b.ready()}}var b=function(i,r){return new b.fn.init(i,r)},d=E.jQuery,e=E.$,f,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,k=/\S/,l=/^\s+/,n=/\s+$/,s=/\W/,v=/\d/,B=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,D=/^[\],:{}\s]*$/,H=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,w=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,G=/(?:^|:|,)(?:\s*\[)+/g,M=/(webkit)[ \/]([\w.]+)/,g=/(opera)(?:.*version)?[ \/]([\w.]+)/,j=/(msie) ([\w.]+)/,o=/(mozilla)(?:.*? rv:([\w.]+))?/,m=navigator.userAgent,p=false, +q=[],t,x=Object.prototype.toString,C=Object.prototype.hasOwnProperty,P=Array.prototype.push,N=Array.prototype.slice,R=String.prototype.trim,Q=Array.prototype.indexOf,L={};b.fn=b.prototype={init:function(i,r){var y,z,F;if(!i)return this;if(i.nodeType){this.context=this[0]=i;this.length=1;return this}if(i==="body"&&!r&&u.body){this.context=u;this[0]=u.body;this.selector="body";this.length=1;return this}if(typeof i==="string")if((y=h.exec(i))&&(y[1]||!r))if(y[1]){F=r?r.ownerDocument||r:u;if(z=B.exec(i))if(b.isPlainObject(r)){i= +[u.createElement(z[1])];b.fn.attr.call(i,r,true)}else i=[F.createElement(z[1])];else{z=b.buildFragment([y[1]],[F]);i=(z.cacheable?z.fragment.cloneNode(true):z.fragment).childNodes}return b.merge(this,i)}else{if((z=u.getElementById(y[2]))&&z.parentNode){if(z.id!==y[2])return f.find(i);this.length=1;this[0]=z}this.context=u;this.selector=i;return this}else if(!r&&!s.test(i)){this.selector=i;this.context=u;i=u.getElementsByTagName(i);return b.merge(this,i)}else return!r||r.jquery?(r||f).find(i):b(r).find(i); +else if(b.isFunction(i))return f.ready(i);if(i.selector!==A){this.selector=i.selector;this.context=i.context}return b.makeArray(i,this)},selector:"",jquery:"1.4.3",length:0,size:function(){return this.length},toArray:function(){return N.call(this,0)},get:function(i){return i==null?this.toArray():i<0?this.slice(i)[0]:this[i]},pushStack:function(i,r,y){var z=b();b.isArray(i)?P.apply(z,i):b.merge(z,i);z.prevObject=this;z.context=this.context;if(r==="find")z.selector=this.selector+(this.selector?" ": +"")+y;else if(r)z.selector=this.selector+"."+r+"("+y+")";return z},each:function(i,r){return b.each(this,i,r)},ready:function(i){b.bindReady();if(b.isReady)i.call(u,b);else q&&q.push(i);return this},eq:function(i){return i===-1?this.slice(i):this.slice(i,+i+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(N.apply(this,arguments),"slice",N.call(arguments).join(","))},map:function(i){return this.pushStack(b.map(this,function(r,y){return i.call(r, +y,r)}))},end:function(){return this.prevObject||b(null)},push:P,sort:[].sort,splice:[].splice};b.fn.init.prototype=b.fn;b.extend=b.fn.extend=function(){var i=arguments[0]||{},r=1,y=arguments.length,z=false,F,I,K,J,fa;if(typeof i==="boolean"){z=i;i=arguments[1]||{};r=2}if(typeof i!=="object"&&!b.isFunction(i))i={};if(y===r){i=this;--r}for(;r0)){if(q){for(var r=0;i=q[r++];)i.call(u,b);q=null}b.fn.triggerHandler&&b(u).triggerHandler("ready")}}},bindReady:function(){if(!p){p=true;if(u.readyState==="complete")return setTimeout(b.ready, +1);if(u.addEventListener){u.addEventListener("DOMContentLoaded",t,false);E.addEventListener("load",b.ready,false)}else if(u.attachEvent){u.attachEvent("onreadystatechange",t);E.attachEvent("onload",b.ready);var i=false;try{i=E.frameElement==null}catch(r){}u.documentElement.doScroll&&i&&a()}}},isFunction:function(i){return b.type(i)==="function"},isArray:Array.isArray||function(i){return b.type(i)==="array"},isWindow:function(i){return i&&typeof i==="object"&&"setInterval"in i},isNaN:function(i){return i== +null||!v.test(i)||isNaN(i)},type:function(i){return i==null?String(i):L[x.call(i)]||"object"},isPlainObject:function(i){if(!i||b.type(i)!=="object"||i.nodeType||b.isWindow(i))return false;if(i.constructor&&!C.call(i,"constructor")&&!C.call(i.constructor.prototype,"isPrototypeOf"))return false;for(var r in i);return r===A||C.call(i,r)},isEmptyObject:function(i){for(var r in i)return false;return true},error:function(i){throw i;},parseJSON:function(i){if(typeof i!=="string"||!i)return null;i=b.trim(i); +if(D.test(i.replace(H,"@").replace(w,"]").replace(G,"")))return E.JSON&&E.JSON.parse?E.JSON.parse(i):(new Function("return "+i))();else b.error("Invalid JSON: "+i)},noop:function(){},globalEval:function(i){if(i&&k.test(i)){var r=u.getElementsByTagName("head")[0]||u.documentElement,y=u.createElement("script");y.type="text/javascript";if(b.support.scriptEval)y.appendChild(u.createTextNode(i));else y.text=i;r.insertBefore(y,r.firstChild);r.removeChild(y)}},nodeName:function(i,r){return i.nodeName&&i.nodeName.toUpperCase()=== +r.toUpperCase()},each:function(i,r,y){var z,F=0,I=i.length,K=I===A||b.isFunction(i);if(y)if(K)for(z in i){if(r.apply(i[z],y)===false)break}else for(;F";a=u.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var s=u.createElement("div"); +s.style.width=s.style.paddingLeft="1px";u.body.appendChild(s);c.boxModel=c.support.boxModel=s.offsetWidth===2;if("zoom"in s.style){s.style.display="inline";s.style.zoom=1;c.support.inlineBlockNeedsLayout=s.offsetWidth===2;s.style.display="";s.innerHTML="
";c.support.shrinkWrapBlocks=s.offsetWidth!==2}s.innerHTML="
t
";var v=s.getElementsByTagName("td");c.support.reliableHiddenOffsets=v[0].offsetHeight=== +0;v[0].style.display="";v[1].style.display="none";c.support.reliableHiddenOffsets=c.support.reliableHiddenOffsets&&v[0].offsetHeight===0;s.innerHTML="";u.body.removeChild(s).style.display="none"});a=function(s){var v=u.createElement("div");s="on"+s;var B=s in v;if(!B){v.setAttribute(s,"return;");B=typeof v[s]==="function"}return B};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=f=h=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength", +cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var pa={},Oa=/^(?:\{.*\}|\[.*\])$/;c.extend({cache:{},uuid:0,expando:"jQuery"+c.now(),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},data:function(a,b,d){if(c.acceptData(a)){a=a==E?pa:a;var e=a.nodeType,f=e?a[c.expando]:null,h=c.cache;if(!(e&&!f&&typeof b==="string"&&d===A)){if(e)f||(a[c.expando]=f=++c.uuid);else h=a;if(typeof b==="object")if(e)h[f]= +c.extend(h[f],b);else c.extend(h,b);else if(e&&!h[f])h[f]={};a=e?h[f]:h;if(d!==A)a[b]=d;return typeof b==="string"?a[b]:a}}},removeData:function(a,b){if(c.acceptData(a)){a=a==E?pa:a;var d=a.nodeType,e=d?a[c.expando]:a,f=c.cache,h=d?f[e]:e;if(b){if(h){delete h[b];d&&c.isEmptyObject(h)&&c.removeData(a)}}else if(d&&c.support.deleteExpando)delete a[c.expando];else if(a.removeAttribute)a.removeAttribute(c.expando);else if(d)delete f[e];else for(var k in a)delete a[k]}},acceptData:function(a){if(a.nodeName){var b= +c.noData[a.nodeName.toLowerCase()];if(b)return!(b===true||a.getAttribute("classid")!==b)}return true}});c.fn.extend({data:function(a,b){if(typeof a==="undefined")return this.length?c.data(this[0]):null;else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===A){var e=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(e===A&&this.length){e=c.data(this[0],a);if(e===A&&this[0].nodeType===1){e=this[0].getAttribute("data-"+a);if(typeof e=== +"string")try{e=e==="true"?true:e==="false"?false:e==="null"?null:!c.isNaN(e)?parseFloat(e):Oa.test(e)?c.parseJSON(e):e}catch(f){}else e=A}}return e===A&&d[1]?this.data(d[0]):e}else return this.each(function(){var h=c(this),k=[d[0],b];h.triggerHandler("setData"+d[1]+"!",k);c.data(this,a,b);h.triggerHandler("changeData"+d[1]+"!",k)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var e=c.data(a,b);if(!d)return e|| +[];if(!e||c.isArray(d))e=c.data(a,b,c.makeArray(d));else e.push(d);return e}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),e=d.shift();if(e==="inprogress")e=d.shift();if(e){b==="fx"&&d.unshift("inprogress");e.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===A)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this, +a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var qa=/[\n\t]/g,ga=/\s+/,Pa=/\r/g,Qa=/^(?:href|src|style)$/,Ra=/^(?:button|input)$/i,Sa=/^(?:button|input|object|select|textarea)$/i,Ta=/^a(?:rea)?$/i,ra=/^(?:radio|checkbox)$/i;c.fn.extend({attr:function(a,b){return c.access(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this, +a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(s){var v=c(this);v.addClass(a.call(this,s,v.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ga),d=0,e=this.length;d-1)return true;return false}, +val:function(a){if(!arguments.length){var b=this[0];if(b){if(c.nodeName(b,"option")){var d=b.attributes.value;return!d||d.specified?b.value:b.text}if(c.nodeName(b,"select")){var e=b.selectedIndex;d=[];var f=b.options;b=b.type==="select-one";if(e<0)return null;var h=b?e:0;for(e=b?e+1:f.length;h=0;else if(c.nodeName(this,"select")){var B=c.makeArray(v);c("option",this).each(function(){this.selected= +c.inArray(c(this).val(),B)>=0});if(!B.length)this.selectedIndex=-1}else this.value=v}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,e){if(!a||a.nodeType===3||a.nodeType===8)return A;if(e&&b in c.attrFn)return c(a)[b](d);e=a.nodeType!==1||!c.isXMLDoc(a);var f=d!==A;b=e&&c.props[b]||b;if(a.nodeType===1){var h=Qa.test(b);if((b in a||a[b]!==A)&&e&&!h){if(f){b==="type"&&Ra.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); +if(d===null)a.nodeType===1&&a.removeAttribute(b);else a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:Sa.test(a.nodeName)||Ta.test(a.nodeName)&&a.href?0:A;return a[b]}if(!c.support.style&&e&&b==="style"){if(f)a.style.cssText=""+d;return a.style.cssText}f&&a.setAttribute(b,""+d);if(!a.attributes[b]&&a.hasAttribute&&!a.hasAttribute(b))return A;a=!c.support.hrefNormalized&&e&& +h?a.getAttribute(b,2):a.getAttribute(b);return a===null?A:a}}});var X=/\.(.*)$/,ha=/^(?:textarea|input|select)$/i,Ha=/\./g,Ia=/ /g,Ua=/[^\w\s.|`]/g,Va=function(a){return a.replace(Ua,"\\$&")},sa={focusin:0,focusout:0};c.event={add:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(c.isWindow(a)&&a!==E&&!a.frameElement)a=E;if(d===false)d=U;var f,h;if(d.handler){f=d;d=f.handler}if(!d.guid)d.guid=c.guid++;if(h=c.data(a)){var k=a.nodeType?"events":"__events__",l=h[k],n=h.handle;if(typeof l=== +"function"){n=l.handle;l=l.events}else if(!l){a.nodeType||(h[k]=h=function(){});h.events=l={}}if(!n)h.handle=n=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(n.elem,arguments):A};n.elem=a;b=b.split(" ");for(var s=0,v;k=b[s++];){h=f?c.extend({},f):{handler:d,data:e};if(k.indexOf(".")>-1){v=k.split(".");k=v.shift();h.namespace=v.slice(0).sort().join(".")}else{v=[];h.namespace=""}h.type=k;if(!h.guid)h.guid=d.guid;var B=l[k],D=c.event.special[k]||{};if(!B){B=l[k]=[]; +if(!D.setup||D.setup.call(a,e,v,n)===false)if(a.addEventListener)a.addEventListener(k,n,false);else a.attachEvent&&a.attachEvent("on"+k,n)}if(D.add){D.add.call(a,h);if(!h.handler.guid)h.handler.guid=d.guid}B.push(h);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(d===false)d=U;var f,h,k=0,l,n,s,v,B,D,H=a.nodeType?"events":"__events__",w=c.data(a),G=w&&w[H];if(w&&G){if(typeof G==="function"){w=G;G=G.events}if(b&&b.type){d=b.handler;b=b.type}if(!b|| +typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(f in G)c.event.remove(a,f+b)}else{for(b=b.split(" ");f=b[k++];){v=f;l=f.indexOf(".")<0;n=[];if(!l){n=f.split(".");f=n.shift();s=RegExp("(^|\\.)"+c.map(n.slice(0).sort(),Va).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(B=G[f])if(d){v=c.event.special[f]||{};for(h=e||0;h=0){a.type= +f=f.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[f]&&c.each(c.cache,function(){this.events&&this.events[f]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return A;a.result=A;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(e=d.nodeType?c.data(d,"handle"):(c.data(d,"__events__")||{}).handle)&&e.apply(d,b);e=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+f]&&d["on"+f].apply(d,b)=== +false){a.result=false;a.preventDefault()}}catch(h){}if(!a.isPropagationStopped()&&e)c.event.trigger(a,b,e,true);else if(!a.isDefaultPrevented()){e=a.target;var k,l=f.replace(X,""),n=c.nodeName(e,"a")&&l==="click",s=c.event.special[l]||{};if((!s._default||s._default.call(d,a)===false)&&!n&&!(e&&e.nodeName&&c.noData[e.nodeName.toLowerCase()])){try{if(e[l]){if(k=e["on"+l])e["on"+l]=null;c.event.triggered=true;e[l]()}}catch(v){}if(k)e["on"+l]=k;c.event.triggered=false}}},handle:function(a){var b,d,e; +d=[];var f,h=c.makeArray(arguments);a=h[0]=c.event.fix(a||E.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;if(!b){e=a.type.split(".");a.type=e.shift();d=e.slice(0).sort();e=RegExp("(^|\\.)"+d.join("\\.(?:.*\\.)?")+"(\\.|$)")}a.namespace=a.namespace||d.join(".");f=c.data(this,this.nodeType?"events":"__events__");if(typeof f==="function")f=f.events;d=(f||{})[a.type];if(f&&d){d=d.slice(0);f=0;for(var k=d.length;f-1?c.map(a.options,function(e){return e.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},Z=function(a,b){var d=a.target,e,f;if(!(!ha.test(d.nodeName)||d.readOnly)){e=c.data(d,"_change_data");f=va(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",f);if(!(e===A||f===e))if(e!=null||f){a.type="change";a.liveFired= +A;return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:Z,beforedeactivate:Z,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return Z.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return Z.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,"_change_data",va(a))}},setup:function(){if(this.type=== +"file")return false;for(var a in V)c.event.add(this,a+".specialChange",V[a]);return ha.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return ha.test(this.nodeName)}};V=c.event.special.change.filters;V.focus=V.beforeactivate}u.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(e){e=c.event.fix(e);e.type=b;return c.event.trigger(e,null,e.target)}c.event.special[b]={setup:function(){sa[b]++===0&&u.addEventListener(a,d,true)},teardown:function(){--sa[b]=== +0&&u.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,e,f){if(typeof d==="object"){for(var h in d)this[b](h,e,d[h],f);return this}if(c.isFunction(e)||e===false){f=e;e=A}var k=b==="one"?c.proxy(f,function(n){c(this).unbind(n,k);return f.apply(this,arguments)}):f;if(d==="unload"&&b!=="one")this.one(d,e,f);else{h=0;for(var l=this.length;h0?this.bind(b,d,e):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});E.attachEvent&&!E.addEventListener&&c(E).bind("unload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}}); +(function(){function a(g,j,o,m,p,q){p=0;for(var t=m.length;p0){C=x;break}}x=x[g]}m[p]=C}}}var d=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,h=false,k=true;[0,0].sort(function(){k=false;return 0});var l=function(g,j,o,m){o=o||[];var p=j=j||u;if(j.nodeType!==1&&j.nodeType!==9)return[];if(!g||typeof g!=="string")return o;var q=[],t,x,C,P,N=true,R=l.isXML(j),Q=g,L;do{d.exec("");if(t=d.exec(Q)){Q=t[3];q.push(t[1]);if(t[2]){P=t[3]; +break}}}while(t);if(q.length>1&&s.exec(g))if(q.length===2&&n.relative[q[0]])x=M(q[0]+q[1],j);else for(x=n.relative[q[0]]?[j]:l(q.shift(),j);q.length;){g=q.shift();if(n.relative[g])g+=q.shift();x=M(g,x)}else{if(!m&&q.length>1&&j.nodeType===9&&!R&&n.match.ID.test(q[0])&&!n.match.ID.test(q[q.length-1])){t=l.find(q.shift(),j,R);j=t.expr?l.filter(t.expr,t.set)[0]:t.set[0]}if(j){t=m?{expr:q.pop(),set:D(m)}:l.find(q.pop(),q.length===1&&(q[0]==="~"||q[0]==="+")&&j.parentNode?j.parentNode:j,R);x=t.expr?l.filter(t.expr, +t.set):t.set;if(q.length>0)C=D(x);else N=false;for(;q.length;){t=L=q.pop();if(n.relative[L])t=q.pop();else L="";if(t==null)t=j;n.relative[L](C,t,R)}}else C=[]}C||(C=x);C||l.error(L||g);if(f.call(C)==="[object Array]")if(N)if(j&&j.nodeType===1)for(g=0;C[g]!=null;g++){if(C[g]&&(C[g]===true||C[g].nodeType===1&&l.contains(j,C[g])))o.push(x[g])}else for(g=0;C[g]!=null;g++)C[g]&&C[g].nodeType===1&&o.push(x[g]);else o.push.apply(o,C);else D(C,o);if(P){l(P,p,o,m);l.uniqueSort(o)}return o};l.uniqueSort=function(g){if(w){h= +k;g.sort(w);if(h)for(var j=1;j0};l.find=function(g,j,o){var m;if(!g)return[];for(var p=0,q=n.order.length;p":function(g,j){var o=typeof j==="string",m,p=0,q=g.length;if(o&&!/\W/.test(j))for(j=j.toLowerCase();p=0))o||m.push(t);else if(o)j[q]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},CHILD:function(g){if(g[1]==="nth"){var j=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=j[1]+(j[2]||1)-0;g[3]=j[3]-0}g[0]=e++;return g},ATTR:function(g,j,o, +m,p,q){j=g[1].replace(/\\/g,"");if(!q&&n.attrMap[j])g[1]=n.attrMap[j];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,j,o,m,p){if(g[1]==="not")if((d.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=l(g[3],null,null,j);else{g=l.filter(g[3],j,o,true^p);o||m.push.apply(m,g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled=== +true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,j,o){return!!l(o[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"=== +g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},setFilters:{first:function(g,j){return j===0},last:function(g,j,o,m){return j===m.length-1},even:function(g,j){return j%2===0},odd:function(g,j){return j%2===1},lt:function(g,j,o){return jo[3]-0},nth:function(g,j,o){return o[3]- +0===j},eq:function(g,j,o){return o[3]-0===j}},filter:{PSEUDO:function(g,j,o,m){var p=j[1],q=n.filters[p];if(q)return q(g,o,j,m);else if(p==="contains")return(g.textContent||g.innerText||l.getText([g])||"").indexOf(j[3])>=0;else if(p==="not"){j=j[3];o=0;for(m=j.length;o=0}},ID:function(g,j){return g.nodeType===1&&g.getAttribute("id")===j},TAG:function(g,j){return j==="*"&&g.nodeType===1||g.nodeName.toLowerCase()=== +j},CLASS:function(g,j){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(j)>-1},ATTR:function(g,j){var o=j[1];o=n.attrHandle[o]?n.attrHandle[o](g):g[o]!=null?g[o]:g.getAttribute(o);var m=o+"",p=j[2],q=j[4];return o==null?p==="!=":p==="="?m===q:p==="*="?m.indexOf(q)>=0:p==="~="?(" "+m+" ").indexOf(q)>=0:!q?m&&o!==false:p==="!="?m!==q:p==="^="?m.indexOf(q)===0:p==="$="?m.substr(m.length-q.length)===q:p==="|="?m===q||m.substr(0,q.length+1)===q+"-":false},POS:function(g,j,o,m){var p=n.setFilters[j[2]]; +if(p)return p(g,o,j,m)}}},s=n.match.POS,v=function(g,j){return"\\"+(j-0+1)},B;for(B in n.match){n.match[B]=RegExp(n.match[B].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[B]=RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[B].source.replace(/\\(\d+)/g,v))}var D=function(g,j){g=Array.prototype.slice.call(g,0);if(j){j.push.apply(j,g);return j}return g};try{Array.prototype.slice.call(u.documentElement.childNodes,0)}catch(H){D=function(g,j){var o=j||[],m=0;if(f.call(g)==="[object Array]")Array.prototype.push.apply(o, +g);else if(typeof g.length==="number")for(var p=g.length;m";var o=u.documentElement;o.insertBefore(g,o.firstChild);if(u.getElementById(j)){n.find.ID=function(m,p,q){if(typeof p.getElementById!=="undefined"&&!q)return(p=p.getElementById(m[1]))?p.id===m[1]||typeof p.getAttributeNode!=="undefined"&&p.getAttributeNode("id").nodeValue===m[1]?[p]:A:[]};n.filter.ID=function(m,p){var q=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&q&&q.nodeValue===p}}o.removeChild(g); +o=g=null})();(function(){var g=u.createElement("div");g.appendChild(u.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(j,o){var m=o.getElementsByTagName(j[1]);if(j[1]==="*"){for(var p=[],q=0;m[q];q++)m[q].nodeType===1&&p.push(m[q]);m=p}return m};g.innerHTML="";if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(j){return j.getAttribute("href",2)};g=null})();u.querySelectorAll&& +function(){var g=l,j=u.createElement("div");j.innerHTML="

";if(!(j.querySelectorAll&&j.querySelectorAll(".TEST").length===0)){l=function(m,p,q,t){p=p||u;if(!t&&!l.isXML(p))if(p.nodeType===9)try{return D(p.querySelectorAll(m),q)}catch(x){}else if(p.nodeType===1&&p.nodeName.toLowerCase()!=="object"){var C=p.id,P=p.id="__sizzle__";try{return D(p.querySelectorAll("#"+P+" "+m),q)}catch(N){}finally{if(C)p.id=C;else p.removeAttribute("id")}}return g(m,p,q,t)};for(var o in g)l[o]=g[o]; +j=null}}();(function(){var g=u.documentElement,j=g.matchesSelector||g.mozMatchesSelector||g.webkitMatchesSelector||g.msMatchesSelector,o=false;try{j.call(u.documentElement,":sizzle")}catch(m){o=true}if(j)l.matchesSelector=function(p,q){try{if(o||!n.match.PSEUDO.test(q))return j.call(p,q)}catch(t){}return l(q,null,null,[p]).length>0}})();(function(){var g=u.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length=== +0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(j,o,m){if(typeof o.getElementsByClassName!=="undefined"&&!m)return o.getElementsByClassName(j[1])};g=null}}})();l.contains=u.documentElement.contains?function(g,j){return g!==j&&(g.contains?g.contains(j):true)}:function(g,j){return!!(g.compareDocumentPosition(j)&16)};l.isXML=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false};var M=function(g, +j){for(var o=[],m="",p,q=j.nodeType?[j]:j;p=n.match.PSEUDO.exec(g);){m+=p[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;p=0;for(var t=q.length;p0)for(var h=d;h0},closest:function(a, +b){var d=[],e,f,h=this[0];if(c.isArray(a)){var k={},l,n=1;if(h&&a.length){e=0;for(f=a.length;e-1:c(h).is(e))d.push({selector:l,elem:h,level:n})}h=h.parentNode;n++}}return d}k=$a.test(a)?c(a,b||this.context):null;e=0;for(f=this.length;e-1:c.find.matchesSelector(h,a)){d.push(h);break}else{h=h.parentNode;if(!h|| +!h.ownerDocument||h===b)break}d=d.length>1?c.unique(d):d;return this.pushStack(d,"closest",a)},index:function(a){if(!a||typeof a==="string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var d=typeof a==="string"?c(a,b||this.context):c.makeArray(a),e=c.merge(this.get(),d);return this.pushStack(!d[0]||!d[0].parentNode||d[0].parentNode.nodeType===11||!e[0]||!e[0].parentNode||e[0].parentNode.nodeType===11?e:c.unique(e))},andSelf:function(){return this.add(this.prevObject)}}); +c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling", +d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,e){var f=c.map(this,b,d);Wa.test(a)||(e=d);if(e&&typeof e==="string")f=c.filter(e,f);f=this.length>1?c.unique(f):f;if((this.length>1||Ya.test(e))&&Xa.test(a))f=f.reverse();return this.pushStack(f,a,Za.call(arguments).join(","))}}); +c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return b.length===1?c.find.matchesSelector(b[0],a)?[b[0]]:[]:c.find.matches(a,b)},dir:function(a,b,d){var e=[];for(a=a[b];a&&a.nodeType!==9&&(d===A||a.nodeType!==1||!c(a).is(d));){a.nodeType===1&&e.push(a);a=a[b]}return e},nth:function(a,b,d){b=b||1;for(var e=0;a;a=a[d])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var xa=/ jQuery\d+="(?:\d+|null)"/g, +$=/^\s+/,ya=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,za=/<([\w:]+)/,ab=/\s]+\/)>/g,O={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"], +area:[1,"",""],_default:[0,"",""]};O.optgroup=O.option;O.tbody=O.tfoot=O.colgroup=O.caption=O.thead;O.th=O.td;if(!c.support.htmlSerialize)O._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==A)return this.empty().append((this[0]&&this[0].ownerDocument||u).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this, +d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})}, +unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a= +c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,e;(e=this[d])!=null;d++)if(!a||c.filter(a,[e]).length){if(!b&&e.nodeType===1){c.cleanData(e.getElementsByTagName("*")); +c.cleanData([e])}e.parentNode&&e.parentNode.removeChild(e)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,e=this.ownerDocument;if(!d){d=e.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(xa,"").replace(cb,'="$1">').replace($, +"")],e)[0]}else return this.cloneNode(true)});if(a===true){la(this,b);la(this.find("*"),b.find("*"))}return b},html:function(a){if(a===A)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(xa,""):null;else if(typeof a==="string"&&!Aa.test(a)&&(c.support.leadingWhitespace||!$.test(a))&&!O[(za.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ya,"<$1>");try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?l.cloneNode(true):l)}k.length&&c.each(k,Ka)}return this}});c.buildFragment=function(a,b,d){var e,f,h;b=b&&b[0]?b[0].ownerDocument||b[0]:u;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===u&&!Aa.test(a[0])&&(c.support.checkClone|| +!Ba.test(a[0]))){f=true;if(h=c.fragments[a[0]])if(h!==1)e=h}if(!e){e=b.createDocumentFragment();c.clean(a,b,e,d)}if(f)c.fragments[a[0]]=h?e:1;return{fragment:e,cacheable:f}};c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var e=[];d=c(d);var f=this.length===1&&this[0].parentNode;if(f&&f.nodeType===11&&f.childNodes.length===1&&d.length===1){d[b](this[0]);return this}else{f=0;for(var h= +d.length;f0?this.clone(true):this).get();c(d[f])[b](k);e=e.concat(k)}return this.pushStack(e,a,d.selector)}}});c.extend({clean:function(a,b,d,e){b=b||u;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||u;for(var f=[],h=0,k;(k=a[h])!=null;h++){if(typeof k==="number")k+="";if(k){if(typeof k==="string"&&!bb.test(k))k=b.createTextNode(k);else if(typeof k==="string"){k=k.replace(ya,"<$1>");var l=(za.exec(k)||["",""])[1].toLowerCase(),n=O[l]||O._default, +s=n[0],v=b.createElement("div");for(v.innerHTML=n[1]+k+n[2];s--;)v=v.lastChild;if(!c.support.tbody){s=ab.test(k);l=l==="table"&&!s?v.firstChild&&v.firstChild.childNodes:n[1]===""&&!s?v.childNodes:[];for(n=l.length-1;n>=0;--n)c.nodeName(l[n],"tbody")&&!l[n].childNodes.length&&l[n].parentNode.removeChild(l[n])}!c.support.leadingWhitespace&&$.test(k)&&v.insertBefore(b.createTextNode($.exec(k)[0]),v.firstChild);k=v.childNodes}if(k.nodeType)f.push(k);else f=c.merge(f,k)}}if(d)for(h=0;f[h];h++)if(e&& +c.nodeName(f[h],"script")&&(!f[h].type||f[h].type.toLowerCase()==="text/javascript"))e.push(f[h].parentNode?f[h].parentNode.removeChild(f[h]):f[h]);else{f[h].nodeType===1&&f.splice.apply(f,[h+1,0].concat(c.makeArray(f[h].getElementsByTagName("script"))));d.appendChild(f[h])}return f},cleanData:function(a){for(var b,d,e=c.cache,f=c.event.special,h=c.support.deleteExpando,k=0,l;(l=a[k])!=null;k++)if(!(l.nodeName&&c.noData[l.nodeName.toLowerCase()]))if(d=l[c.expando]){if((b=e[d])&&b.events)for(var n in b.events)f[n]? +c.event.remove(l,n):c.removeEvent(l,n,b.handle);if(h)delete l[c.expando];else l.removeAttribute&&l.removeAttribute(c.expando);delete e[d]}}});var Ca=/alpha\([^)]*\)/i,db=/opacity=([^)]*)/,eb=/-([a-z])/ig,fb=/([A-Z])/g,Da=/^-?\d+(?:px)?$/i,gb=/^-?\d/,hb={position:"absolute",visibility:"hidden",display:"block"},La=["Left","Right"],Ma=["Top","Bottom"],W,ib=u.defaultView&&u.defaultView.getComputedStyle,jb=function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){if(arguments.length===2&&b===A)return this; +return c.access(this,a,b,true,function(d,e,f){return f!==A?c.style(d,e,f):c.css(d,e)})};c.extend({cssHooks:{opacity:{get:function(a,b){if(b){var d=W(a,"opacity","opacity");return d===""?"1":d}else return a.style.opacity}}},cssNumber:{zIndex:true,fontWeight:true,opacity:true,zoom:true,lineHeight:true},cssProps:{"float":c.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,d,e){if(!(!a||a.nodeType===3||a.nodeType===8||!a.style)){var f,h=c.camelCase(b),k=a.style,l=c.cssHooks[h];b=c.cssProps[h]|| +h;if(d!==A){if(!(typeof d==="number"&&isNaN(d)||d==null)){if(typeof d==="number"&&!c.cssNumber[h])d+="px";if(!l||!("set"in l)||(d=l.set(a,d))!==A)try{k[b]=d}catch(n){}}}else{if(l&&"get"in l&&(f=l.get(a,false,e))!==A)return f;return k[b]}}},css:function(a,b,d){var e,f=c.camelCase(b),h=c.cssHooks[f];b=c.cssProps[f]||f;if(h&&"get"in h&&(e=h.get(a,true,d))!==A)return e;else if(W)return W(a,b,f)},swap:function(a,b,d){var e={},f;for(f in b){e[f]=a.style[f];a.style[f]=b[f]}d.call(a);for(f in b)a.style[f]= +e[f]},camelCase:function(a){return a.replace(eb,jb)}});c.curCSS=c.css;c.each(["height","width"],function(a,b){c.cssHooks[b]={get:function(d,e,f){var h;if(e){if(d.offsetWidth!==0)h=ma(d,b,f);else c.swap(d,hb,function(){h=ma(d,b,f)});return h+"px"}},set:function(d,e){if(Da.test(e)){e=parseFloat(e);if(e>=0)return e+"px"}else return e}}});if(!c.support.opacity)c.cssHooks.opacity={get:function(a,b){return db.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"": +b?"1":""},set:function(a,b){var d=a.style;d.zoom=1;var e=c.isNaN(b)?"":"alpha(opacity="+b*100+")",f=d.filter||"";d.filter=Ca.test(f)?f.replace(Ca,e):d.filter+" "+e}};if(ib)W=function(a,b,d){var e;d=d.replace(fb,"-$1").toLowerCase();if(!(b=a.ownerDocument.defaultView))return A;if(b=b.getComputedStyle(a,null)){e=b.getPropertyValue(d);if(e===""&&!c.contains(a.ownerDocument.documentElement,a))e=c.style(a,d)}return e};else if(u.documentElement.currentStyle)W=function(a,b){var d,e,f=a.currentStyle&&a.currentStyle[b], +h=a.style;if(!Da.test(f)&&gb.test(f)){d=h.left;e=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;h.left=b==="fontSize"?"1em":f||0;f=h.pixelLeft+"px";h.left=d;a.runtimeStyle.left=e}return f};if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=a.offsetHeight;return a.offsetWidth===0&&b===0||!c.support.reliableHiddenOffsets&&(a.style.display||c.css(a,"display"))==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var kb=c.now(),lb=/)<[^<]*)*<\/script>/gi, +mb=/^(?:select|textarea)/i,nb=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ob=/^(?:GET|HEAD|DELETE)$/,Na=/\[\]$/,T=/\=\?(&|$)/,ia=/\?/,pb=/([?&])_=[^&]*/,qb=/^(\w+:)?\/\/([^\/?#]+)/,rb=/%20/g,sb=/#.*$/,Ea=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!=="string"&&Ea)return Ea.apply(this,arguments);else if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var f=a.slice(e,a.length);a=a.slice(0,e)}e="GET";if(b)if(c.isFunction(b)){d= +b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);e="POST"}var h=this;c.ajax({url:a,type:e,dataType:"html",data:b,complete:function(k,l){if(l==="success"||l==="notmodified")h.html(f?c("
").append(k.responseText.replace(lb,"")).find(f):k.responseText);d&&h.each(d,[k.responseText,l,k])}});return this},serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&& +!this.disabled&&(this.checked||mb.test(this.nodeName)||nb.test(this.type))}).map(function(a,b){var d=c(this).val();return d==null?null:c.isArray(d)?c.map(d,function(e){return{name:b.name,value:e}}):{name:b.name,value:d}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:e})}, +getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:e})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return new E.XMLHttpRequest},accepts:{xml:"application/xml, text/xml",html:"text/html", +script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},ajax:function(a){var b=c.extend(true,{},c.ajaxSettings,a),d,e,f,h=b.type.toUpperCase(),k=ob.test(h);b.url=b.url.replace(sb,"");b.context=a&&a.context!=null?a.context:b;if(b.data&&b.processData&&typeof b.data!=="string")b.data=c.param(b.data,b.traditional);if(b.dataType==="jsonp"){if(h==="GET")T.test(b.url)||(b.url+=(ia.test(b.url)?"&":"?")+(b.jsonp||"callback")+"=?");else if(!b.data|| +!T.test(b.data))b.data=(b.data?b.data+"&":"")+(b.jsonp||"callback")+"=?";b.dataType="json"}if(b.dataType==="json"&&(b.data&&T.test(b.data)||T.test(b.url))){d=b.jsonpCallback||"jsonp"+kb++;if(b.data)b.data=(b.data+"").replace(T,"="+d+"$1");b.url=b.url.replace(T,"="+d+"$1");b.dataType="script";var l=E[d];E[d]=function(m){f=m;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);if(c.isFunction(l))l(m);else{E[d]=A;try{delete E[d]}catch(p){}}v&&v.removeChild(B)}}if(b.dataType==="script"&&b.cache===null)b.cache= +false;if(b.cache===false&&h==="GET"){var n=c.now(),s=b.url.replace(pb,"$1_="+n);b.url=s+(s===b.url?(ia.test(b.url)?"&":"?")+"_="+n:"")}if(b.data&&h==="GET")b.url+=(ia.test(b.url)?"&":"?")+b.data;b.global&&c.active++===0&&c.event.trigger("ajaxStart");n=(n=qb.exec(b.url))&&(n[1]&&n[1]!==location.protocol||n[2]!==location.host);if(b.dataType==="script"&&h==="GET"&&n){var v=u.getElementsByTagName("head")[0]||u.documentElement,B=u.createElement("script");if(b.scriptCharset)B.charset=b.scriptCharset;B.src= +b.url;if(!d){var D=false;B.onload=B.onreadystatechange=function(){if(!D&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){D=true;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);B.onload=B.onreadystatechange=null;v&&B.parentNode&&v.removeChild(B)}}}v.insertBefore(B,v.firstChild);return A}var H=false,w=b.xhr();if(w){b.username?w.open(h,b.url,b.async,b.username,b.password):w.open(h,b.url,b.async);try{if(b.data!=null&&!k||a&&a.contentType)w.setRequestHeader("Content-Type", +b.contentType);if(b.ifModified){c.lastModified[b.url]&&w.setRequestHeader("If-Modified-Since",c.lastModified[b.url]);c.etag[b.url]&&w.setRequestHeader("If-None-Match",c.etag[b.url])}n||w.setRequestHeader("X-Requested-With","XMLHttpRequest");w.setRequestHeader("Accept",b.dataType&&b.accepts[b.dataType]?b.accepts[b.dataType]+", */*; q=0.01":b.accepts._default)}catch(G){}if(b.beforeSend&&b.beforeSend.call(b.context,w,b)===false){b.global&&c.active--===1&&c.event.trigger("ajaxStop");w.abort();return false}b.global&& +c.triggerGlobal(b,"ajaxSend",[w,b]);var M=w.onreadystatechange=function(m){if(!w||w.readyState===0||m==="abort"){H||c.handleComplete(b,w,e,f);H=true;if(w)w.onreadystatechange=c.noop}else if(!H&&w&&(w.readyState===4||m==="timeout")){H=true;w.onreadystatechange=c.noop;e=m==="timeout"?"timeout":!c.httpSuccess(w)?"error":b.ifModified&&c.httpNotModified(w,b.url)?"notmodified":"success";var p;if(e==="success")try{f=c.httpData(w,b.dataType,b)}catch(q){e="parsererror";p=q}if(e==="success"||e==="notmodified")d|| +c.handleSuccess(b,w,e,f);else c.handleError(b,w,e,p);d||c.handleComplete(b,w,e,f);m==="timeout"&&w.abort();if(b.async)w=null}};try{var g=w.abort;w.abort=function(){w&&g.call&&g.call(w);M("abort")}}catch(j){}b.async&&b.timeout>0&&setTimeout(function(){w&&!H&&M("timeout")},b.timeout);try{w.send(k||b.data==null?null:b.data)}catch(o){c.handleError(b,w,null,o);c.handleComplete(b,w,e,f)}b.async||M();return w}},param:function(a,b){var d=[],e=function(h,k){k=c.isFunction(k)?k():k;d[d.length]=encodeURIComponent(h)+ +"="+encodeURIComponent(k)};if(b===A)b=c.ajaxSettings.traditional;if(c.isArray(a)||a.jquery)c.each(a,function(){e(this.name,this.value)});else for(var f in a)ca(f,a[f],b,e);return d.join("&").replace(rb,"+")}});c.extend({active:0,lastModified:{},etag:{},handleError:function(a,b,d,e){a.error&&a.error.call(a.context,b,d,e);a.global&&c.triggerGlobal(a,"ajaxError",[b,a,e])},handleSuccess:function(a,b,d,e){a.success&&a.success.call(a.context,e,d,b);a.global&&c.triggerGlobal(a,"ajaxSuccess",[b,a])},handleComplete:function(a, +b,d){a.complete&&a.complete.call(a.context,b,d);a.global&&c.triggerGlobal(a,"ajaxComplete",[b,a]);a.global&&c.active--===1&&c.event.trigger("ajaxStop")},triggerGlobal:function(a,b,d){(a.context&&a.context.url==null?c(a.context):c.event).trigger(b,d)},httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===1223}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),e=a.getResponseHeader("Etag"); +if(d)c.lastModified[b]=d;if(e)c.etag[b]=e;return a.status===304},httpData:function(a,b,d){var e=a.getResponseHeader("content-type")||"",f=b==="xml"||!b&&e.indexOf("xml")>=0;a=f?a.responseXML:a.responseText;f&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b==="json"||!b&&e.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&e.indexOf("javascript")>=0)c.globalEval(a);return a}});if(E.ActiveXObject)c.ajaxSettings.xhr= +function(){if(E.location.protocol!=="file:")try{return new E.XMLHttpRequest}catch(a){}try{return new E.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}};c.support.ajax=!!c.ajaxSettings.xhr();var da={},tb=/^(?:toggle|show|hide)$/,ub=/^([+\-]=)?([\d+.\-]+)(.*)$/,aa,na=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b,d){if(a||a===0)return this.animate(S("show",3),a,b,d);else{a= +0;for(b=this.length;a=0;e--)if(d[e].elem===this){b&&d[e](true);d.splice(e,1)}});b||this.dequeue();return this}});c.each({slideDown:S("show",1),slideUp:S("hide",1),slideToggle:S("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,e,f){return this.animate(b, +d,e,f)}});c.extend({speed:function(a,b,d){var e=a&&typeof a==="object"?c.extend({},a):{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};e.duration=c.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in c.fx.speeds?c.fx.speeds[e.duration]:c.fx.speeds._default;e.old=e.complete;e.complete=function(){e.queue!==false&&c(this).dequeue();c.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,d,e){return d+e*a},swing:function(a,b,d,e){return(-Math.cos(a* +Math.PI)/2+0.5)*e+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||c.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a=parseFloat(c.css(this.elem,this.prop));return a&&a>-1E4?a:0},custom:function(a,b,d){function e(h){return f.step(h)} +this.startTime=c.now();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;this.pos=this.state=0;var f=this;a=c.fx;e.elem=this.elem;if(e()&&c.timers.push(e)&&!aa)aa=setInterval(a.tick,a.interval)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true; +this.custom(this.cur(),0)},step:function(a){var b=c.now(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var e in this.options.curAnim)if(this.options.curAnim[e]!==true)d=false;if(d){if(this.options.overflow!=null&&!c.support.shrinkWrapBlocks){var f=this.elem,h=this.options;c.each(["","X","Y"],function(l,n){f.style["overflow"+n]=h.overflow[l]})}this.options.hide&&c(this.elem).hide();if(this.options.hide|| +this.options.show)for(var k in this.options.curAnim)c.style(this.elem,k,this.options.orig[k]);this.options.complete.call(this.elem)}return false}else{a=b-this.startTime;this.state=a/this.options.duration;b=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||b](this.state,a,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a= +c.timers,b=0;b-1;e={};var s={};if(n)s=f.position();k=n?s.top:parseInt(k,10)||0;l=n?s.left:parseInt(l,10)||0;if(c.isFunction(b))b=b.call(a,d,h);if(b.top!=null)e.top=b.top-h.top+k;if(b.left!=null)e.left=b.left-h.left+l;"using"in b?b.using.call(a, +e):f.css(e)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),e=Fa.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.css(a,"marginTop"))||0;d.left-=parseFloat(c.css(a,"marginLeft"))||0;e.top+=parseFloat(c.css(b[0],"borderTopWidth"))||0;e.left+=parseFloat(c.css(b[0],"borderLeftWidth"))||0;return{top:d.top-e.top,left:d.left-e.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||u.body;a&&!Fa.test(a.nodeName)&& +c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(e){var f=this[0],h;if(!f)return null;if(e!==A)return this.each(function(){if(h=ea(this))h.scrollTo(!a?e:c(h).scrollLeft(),a?e:c(h).scrollTop());else this[d]=e});else return(h=ea(f))?"pageXOffset"in h?h[a?"pageYOffset":"pageXOffset"]:c.support.boxModel&&h.document.documentElement[d]||h.document.body[d]:f[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase(); +c.fn["inner"+b]=function(){return this[0]?parseFloat(c.css(this[0],d,"padding")):null};c.fn["outer"+b]=function(e){return this[0]?parseFloat(c.css(this[0],d,e?"margin":"border")):null};c.fn[d]=function(e){var f=this[0];if(!f)return e==null?null:this;if(c.isFunction(e))return this.each(function(h){var k=c(this);k[d](e.call(this,h,k[d]()))});return c.isWindow(f)?f.document.compatMode==="CSS1Compat"&&f.document.documentElement["client"+b]||f.document.body["client"+b]:f.nodeType===9?Math.max(f.documentElement["client"+ +b],f.body["scroll"+b],f.documentElement["scroll"+b],f.body["offset"+b],f.documentElement["offset"+b]):e===A?parseFloat(c.css(f,d)):this.css(d,typeof e==="string"?e:e+"px")}})})(window); diff --git a/htmlcov/jquery.hotkeys.js b/htmlcov/jquery.hotkeys.js new file mode 100644 index 000000000..09b21e03c --- /dev/null +++ b/htmlcov/jquery.hotkeys.js @@ -0,0 +1,99 @@ +/* + * jQuery Hotkeys Plugin + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * + * Based upon the plugin by Tzury Bar Yochay: + * http://github.com/tzuryby/hotkeys + * + * Original idea by: + * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ +*/ + +(function(jQuery){ + + jQuery.hotkeys = { + version: "0.8", + + specialKeys: { + 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", + 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", + 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", + 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", + 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", + 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", + 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta" + }, + + shiftNums: { + "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", + "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", + ".": ">", "/": "?", "\\": "|" + } + }; + + function keyHandler( handleObj ) { + // Only care when a possible input has been specified + if ( typeof handleObj.data !== "string" ) { + return; + } + + var origHandler = handleObj.handler, + keys = handleObj.data.toLowerCase().split(" "); + + handleObj.handler = function( event ) { + // Don't fire in text-accepting inputs that we didn't directly bind to + if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || + event.target.type === "text") ) { + return; + } + + // Keypress represents characters, not special keys + var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], + character = String.fromCharCode( event.which ).toLowerCase(), + key, modif = "", possible = {}; + + // check combinations (alt|ctrl|shift+anything) + if ( event.altKey && special !== "alt" ) { + modif += "alt+"; + } + + if ( event.ctrlKey && special !== "ctrl" ) { + modif += "ctrl+"; + } + + // TODO: Need to make sure this works consistently across platforms + if ( event.metaKey && !event.ctrlKey && special !== "meta" ) { + modif += "meta+"; + } + + if ( event.shiftKey && special !== "shift" ) { + modif += "shift+"; + } + + if ( special ) { + possible[ modif + special ] = true; + + } else { + possible[ modif + character ] = true; + possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true; + + // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" + if ( modif === "shift+" ) { + possible[ jQuery.hotkeys.shiftNums[ character ] ] = true; + } + } + + for ( var i = 0, l = keys.length; i < l; i++ ) { + if ( possible[ keys[i] ] ) { + return origHandler.apply( this, arguments ); + } + } + }; + } + + jQuery.each([ "keydown", "keyup", "keypress" ], function() { + jQuery.event.special[ this ] = { add: keyHandler }; + }); + +})( jQuery ); diff --git a/htmlcov/jquery.isonscreen.js b/htmlcov/jquery.isonscreen.js new file mode 100644 index 000000000..0182ebd21 --- /dev/null +++ b/htmlcov/jquery.isonscreen.js @@ -0,0 +1,53 @@ +/* Copyright (c) 2010 + * @author Laurence Wheway + * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) + * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. + * + * @version 1.2.0 + */ +(function($) { + jQuery.extend({ + isOnScreen: function(box, container) { + //ensure numbers come in as intgers (not strings) and remove 'px' is it's there + for(var i in box){box[i] = parseFloat(box[i])}; + for(var i in container){container[i] = parseFloat(container[i])}; + + if(!container){ + container = { + left: $(window).scrollLeft(), + top: $(window).scrollTop(), + width: $(window).width(), + height: $(window).height() + } + } + + if( box.left+box.width-container.left > 0 && + box.left < container.width+container.left && + box.top+box.height-container.top > 0 && + box.top < container.height+container.top + ) return true; + return false; + } + }) + + + jQuery.fn.isOnScreen = function (container) { + for(var i in container){container[i] = parseFloat(container[i])}; + + if(!container){ + container = { + left: $(window).scrollLeft(), + top: $(window).scrollTop(), + width: $(window).width(), + height: $(window).height() + } + } + + if( $(this).offset().left+$(this).width()-container.left > 0 && + $(this).offset().left < container.width+container.left && + $(this).offset().top+$(this).height()-container.top > 0 && + $(this).offset().top < container.height+container.top + ) return true; + return false; + } +})(jQuery); diff --git a/htmlcov/jquery.tablesorter.min.js b/htmlcov/jquery.tablesorter.min.js new file mode 100644 index 000000000..64c700712 --- /dev/null +++ b/htmlcov/jquery.tablesorter.min.js @@ -0,0 +1,2 @@ + +(function($){$.extend({tablesorter:new function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'.',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}var rows=table.tBodies[0].rows;if(table.tBodies[0].rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('
').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;ib)?1:0));};function sortTextDesc(a,b){return((ba)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){$this.trigger("sortStart");var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){var $cell=$(this);var i=this.column;this.order=this.count++%2;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;iD6{MWQjEnx?oJHr&dIz4a@dl*-CY>| zgW!U_%O?XxI14-?iy0WWg+Z8+Vb&Z8pdfpRr>`sfZ8lau9@bl*u7(4JIy_w*Lo808 zo$Afkpupp@{Fv_bobxQ#pD>iB3oNa1d9=pM`D99*FvsH{pKJfpB1-4UD;=6}F=+gKX>Gx9b=!>PY1_pdfo@{(boFyt=akR{ E04sl8JOBUy literal 0 HcmV?d00001 diff --git a/htmlcov/keybd_open.png b/htmlcov/keybd_open.png new file mode 100644 index 0000000000000000000000000000000000000000..a77961db5424cfff43a63d399972ee85fc0dfdb1 GIT binary patch literal 267 zcmeAS@N?(olHy`uVBq!ia0vp^%0SG+!3HE>D6{MWQjEnx?oJHr&dIz4a@dl*-CY>| zgW!U_%O?XxI14-?iy0WWg+Z8+Vb&Z8pdfpRr>`sfZ8lau9%kc-1xY}mZci7-5R21$ zCp+>TR^VYdE*ieC^FGV{Cyeh_21=Rotz3KNq=!VmdK II;Vst00jnQH~;_u literal 0 HcmV?d00001 diff --git a/htmlcov/rest_framework___init__.html b/htmlcov/rest_framework___init__.html new file mode 100644 index 000000000..9cb0c53ac --- /dev/null +++ b/htmlcov/rest_framework___init__.html @@ -0,0 +1,99 @@ + + + + + + + + Coverage for rest_framework/__init__: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+
+ + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+ +
+

__version__ = '2.3.5' 

+

 

+

VERSION = __version__  # synonym 

+

 

+

# Header encoding (see RFC5987) 

+

HTTP_HEADER_ENCODING = 'iso-8859-1' 

+

 

+

# Default datetime input and output formats 

+

ISO_8601 = 'iso-8601' 

+ +
+ + + + + + diff --git a/htmlcov/rest_framework_authentication.html b/htmlcov/rest_framework_authentication.html new file mode 100644 index 000000000..899d06777 --- /dev/null +++ b/htmlcov/rest_framework_authentication.html @@ -0,0 +1,767 @@ + + + + + + + + Coverage for rest_framework/authentication: 80% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+

296

+

297

+

298

+

299

+

300

+

301

+

302

+

303

+

304

+

305

+

306

+

307

+

308

+

309

+

310

+

311

+

312

+

313

+

314

+

315

+

316

+

317

+

318

+

319

+

320

+

321

+

322

+

323

+

324

+

325

+

326

+

327

+

328

+

329

+

330

+

331

+

332

+

333

+

334

+

335

+

336

+

337

+

338

+

339

+

340

+

341

+

342

+

343

+ +
+

""" 

+

Provides various authentication policies. 

+

""" 

+

from __future__ import unicode_literals 

+

import base64 

+

from datetime import datetime 

+

 

+

from django.contrib.auth import authenticate 

+

from django.core.exceptions import ImproperlyConfigured 

+

from rest_framework import exceptions, HTTP_HEADER_ENCODING 

+

from rest_framework.compat import CsrfViewMiddleware 

+

from rest_framework.compat import oauth, oauth_provider, oauth_provider_store 

+

from rest_framework.compat import oauth2_provider 

+

from rest_framework.authtoken.models import Token 

+

 

+

 

+

def get_authorization_header(request): 

+

    """ 

+

    Return request's 'Authorization:' header, as a bytestring. 

+

 

+

    Hide some test client ickyness where the header can be unicode. 

+

    """ 

+

    auth = request.META.get('HTTP_AUTHORIZATION', b'') 

+

    if type(auth) == type(''): 

+

        # Work around django test client oddness 

+

        auth = auth.encode(HTTP_HEADER_ENCODING) 

+

    return auth 

+

 

+

 

+

class BaseAuthentication(object): 

+

    """ 

+

    All authentication classes should extend BaseAuthentication. 

+

    """ 

+

 

+

    def authenticate(self, request): 

+

        """ 

+

        Authenticate the request and return a two-tuple of (user, token). 

+

        """ 

+

        raise NotImplementedError(".authenticate() must be overridden.") 

+

 

+

    def authenticate_header(self, request): 

+

        """ 

+

        Return a string to be used as the value of the `WWW-Authenticate` 

+

        header in a `401 Unauthenticated` response, or `None` if the 

+

        authentication scheme should return `403 Permission Denied` responses. 

+

        """ 

+

        pass 

+

 

+

 

+

class BasicAuthentication(BaseAuthentication): 

+

    """ 

+

    HTTP Basic authentication against username/password. 

+

    """ 

+

    www_authenticate_realm = 'api' 

+

 

+

    def authenticate(self, request): 

+

        """ 

+

        Returns a `User` if a correct username and password have been supplied 

+

        using HTTP Basic authentication.  Otherwise returns `None`. 

+

        """ 

+

        auth = get_authorization_header(request).split() 

+

 

+

        if not auth or auth[0].lower() != b'basic': 

+

            return None 

+

 

+

        if len(auth) == 1: 

+

            msg = 'Invalid basic header. No credentials provided.' 

+

            raise exceptions.AuthenticationFailed(msg) 

+

        elif len(auth) > 2: 

+

            msg = 'Invalid basic header. Credentials string should not contain spaces.' 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        try: 

+

            auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':') 

+

        except (TypeError, UnicodeDecodeError): 

+

            msg = 'Invalid basic header. Credentials not correctly base64 encoded' 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        userid, password = auth_parts[0], auth_parts[2] 

+

        return self.authenticate_credentials(userid, password) 

+

 

+

    def authenticate_credentials(self, userid, password): 

+

        """ 

+

        Authenticate the userid and password against username and password. 

+

        """ 

+

        user = authenticate(username=userid, password=password) 

+

        if user is None or not user.is_active: 

+

            raise exceptions.AuthenticationFailed('Invalid username/password') 

+

        return (user, None) 

+

 

+

    def authenticate_header(self, request): 

+

        return 'Basic realm="%s"' % self.www_authenticate_realm 

+

 

+

 

+

class SessionAuthentication(BaseAuthentication): 

+

    """ 

+

    Use Django's session framework for authentication. 

+

    """ 

+

 

+

    def authenticate(self, request): 

+

        """ 

+

        Returns a `User` if the request session currently has a logged in user. 

+

        Otherwise returns `None`. 

+

        """ 

+

 

+

        # Get the underlying HttpRequest object 

+

        http_request = request._request 

+

        user = getattr(http_request, 'user', None) 

+

 

+

        # Unauthenticated, CSRF validation not required 

+

        if not user or not user.is_active: 

+

            return None 

+

 

+

        # Enforce CSRF validation for session based authentication. 

+

        class CSRFCheck(CsrfViewMiddleware): 

+

            def _reject(self, request, reason): 

+

                # Return the failure reason instead of an HttpResponse 

+

                return reason 

+

 

+

        reason = CSRFCheck().process_view(http_request, None, (), {}) 

+

        if reason: 

+

            # CSRF failed, bail with explicit error message 

+

            raise exceptions.AuthenticationFailed('CSRF Failed: %s' % reason) 

+

 

+

        # CSRF passed with authenticated user 

+

        return (user, None) 

+

 

+

 

+

class TokenAuthentication(BaseAuthentication): 

+

    """ 

+

    Simple token based authentication. 

+

 

+

    Clients should authenticate by passing the token key in the "Authorization" 

+

    HTTP header, prepended with the string "Token ".  For example: 

+

 

+

        Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a 

+

    """ 

+

 

+

    model = Token 

+

    """ 

+

    A custom token model may be used, but must have the following properties. 

+

 

+

    * key -- The string identifying the token 

+

    * user -- The user to which the token belongs 

+

    """ 

+

 

+

    def authenticate(self, request): 

+

        auth = get_authorization_header(request).split() 

+

 

+

        if not auth or auth[0].lower() != b'token': 

+

            return None 

+

 

+

        if len(auth) == 1: 

+

            msg = 'Invalid token header. No credentials provided.' 

+

            raise exceptions.AuthenticationFailed(msg) 

+

        elif len(auth) > 2: 

+

            msg = 'Invalid token header. Token string should not contain spaces.' 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        return self.authenticate_credentials(auth[1]) 

+

 

+

    def authenticate_credentials(self, key): 

+

        try: 

+

            token = self.model.objects.get(key=key) 

+

        except self.model.DoesNotExist: 

+

            raise exceptions.AuthenticationFailed('Invalid token') 

+

 

+

        if not token.user.is_active: 

+

            raise exceptions.AuthenticationFailed('User inactive or deleted') 

+

 

+

        return (token.user, token) 

+

 

+

    def authenticate_header(self, request): 

+

        return 'Token' 

+

 

+

 

+

class OAuthAuthentication(BaseAuthentication): 

+

    """ 

+

    OAuth 1.0a authentication backend using `django-oauth-plus` and `oauth2`. 

+

 

+

    Note: The `oauth2` package actually provides oauth1.0a support.  Urg. 

+

          We import it from the `compat` module as `oauth`. 

+

    """ 

+

    www_authenticate_realm = 'api' 

+

 

+

    def __init__(self, *args, **kwargs): 

+

        super(OAuthAuthentication, self).__init__(*args, **kwargs) 

+

 

+

        if oauth is None: 

+

            raise ImproperlyConfigured( 

+

                "The 'oauth2' package could not be imported." 

+

                "It is required for use with the 'OAuthAuthentication' class.") 

+

 

+

        if oauth_provider is None: 

+

            raise ImproperlyConfigured( 

+

                "The 'django-oauth-plus' package could not be imported." 

+

                "It is required for use with the 'OAuthAuthentication' class.") 

+

 

+

    def authenticate(self, request): 

+

        """ 

+

        Returns two-tuple of (user, token) if authentication succeeds, 

+

        or None otherwise. 

+

        """ 

+

        try: 

+

            oauth_request = oauth_provider.utils.get_oauth_request(request) 

+

        except oauth.Error as err: 

+

            raise exceptions.AuthenticationFailed(err.message) 

+

 

+

        if not oauth_request: 

+

            return None 

+

 

+

        oauth_params = oauth_provider.consts.OAUTH_PARAMETERS_NAMES 

+

 

+

        found = any(param for param in oauth_params if param in oauth_request) 

+

        missing = list(param for param in oauth_params if param not in oauth_request) 

+

 

+

        if not found: 

+

            # OAuth authentication was not attempted. 

+

            return None 

+

 

+

        if missing: 

+

            # OAuth was attempted but missing parameters. 

+

            msg = 'Missing parameters: %s' % (', '.join(missing)) 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        if not self.check_nonce(request, oauth_request): 

+

            msg = 'Nonce check failed' 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        try: 

+

            consumer_key = oauth_request.get_parameter('oauth_consumer_key') 

+

            consumer = oauth_provider_store.get_consumer(request, oauth_request, consumer_key) 

+

        except oauth_provider.store.InvalidConsumerError: 

+

            msg = 'Invalid consumer token: %s' % oauth_request.get_parameter('oauth_consumer_key') 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        if consumer.status != oauth_provider.consts.ACCEPTED: 

+

            msg = 'Invalid consumer key status: %s' % consumer.get_status_display() 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        try: 

+

            token_param = oauth_request.get_parameter('oauth_token') 

+

            token = oauth_provider_store.get_access_token(request, oauth_request, consumer, token_param) 

+

        except oauth_provider.store.InvalidTokenError: 

+

            msg = 'Invalid access token: %s' % oauth_request.get_parameter('oauth_token') 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        try: 

+

            self.validate_token(request, consumer, token) 

+

        except oauth.Error as err: 

+

            raise exceptions.AuthenticationFailed(err.message) 

+

 

+

        user = token.user 

+

 

+

        if not user.is_active: 

+

            msg = 'User inactive or deleted: %s' % user.username 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        return (token.user, token) 

+

 

+

    def authenticate_header(self, request): 

+

        """ 

+

        If permission is denied, return a '401 Unauthorized' response, 

+

        with an appropraite 'WWW-Authenticate' header. 

+

        """ 

+

        return 'OAuth realm="%s"' % self.www_authenticate_realm 

+

 

+

    def validate_token(self, request, consumer, token): 

+

        """ 

+

        Check the token and raise an `oauth.Error` exception if invalid. 

+

        """ 

+

        oauth_server, oauth_request = oauth_provider.utils.initialize_server_request(request) 

+

        oauth_server.verify_request(oauth_request, consumer, token) 

+

 

+

    def check_nonce(self, request, oauth_request): 

+

        """ 

+

        Checks nonce of request, and return True if valid. 

+

        """ 

+

        return oauth_provider_store.check_nonce(request, oauth_request, oauth_request['oauth_nonce']) 

+

 

+

 

+

class OAuth2Authentication(BaseAuthentication): 

+

    """ 

+

    OAuth 2 authentication backend using `django-oauth2-provider` 

+

    """ 

+

    www_authenticate_realm = 'api' 

+

 

+

    def __init__(self, *args, **kwargs): 

+

        super(OAuth2Authentication, self).__init__(*args, **kwargs) 

+

 

+

        if oauth2_provider is None: 

+

            raise ImproperlyConfigured( 

+

                "The 'django-oauth2-provider' package could not be imported. " 

+

                "It is required for use with the 'OAuth2Authentication' class.") 

+

 

+

    def authenticate(self, request): 

+

        """ 

+

        Returns two-tuple of (user, token) if authentication succeeds, 

+

        or None otherwise. 

+

        """ 

+

 

+

        auth = get_authorization_header(request).split() 

+

 

+

        if not auth or auth[0].lower() != b'bearer': 

+

            return None 

+

 

+

        if len(auth) == 1: 

+

            msg = 'Invalid bearer header. No credentials provided.' 

+

            raise exceptions.AuthenticationFailed(msg) 

+

        elif len(auth) > 2: 

+

            msg = 'Invalid bearer header. Token string should not contain spaces.' 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        return self.authenticate_credentials(request, auth[1]) 

+

 

+

    def authenticate_credentials(self, request, access_token): 

+

        """ 

+

        Authenticate the request, given the access token. 

+

        """ 

+

 

+

        try: 

+

            token = oauth2_provider.models.AccessToken.objects.select_related('user') 

+

            # TODO: Change to timezone aware datetime when oauth2_provider add 

+

            # support to it. 

+

            token = token.get(token=access_token, expires__gt=datetime.now()) 

+

        except oauth2_provider.models.AccessToken.DoesNotExist: 

+

            raise exceptions.AuthenticationFailed('Invalid token') 

+

 

+

        user = token.user 

+

 

+

        if not user.is_active: 

+

            msg = 'User inactive or deleted: %s' % user.username 

+

            raise exceptions.AuthenticationFailed(msg) 

+

 

+

        return (user, token) 

+

 

+

    def authenticate_header(self, request): 

+

        """ 

+

        Bearer is the only finalized type currently 

+

 

+

        Check details on the `OAuth2Authentication.authenticate` method 

+

        """ 

+

        return 'Bearer realm="%s"' % self.www_authenticate_realm 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_authtoken___init__.html b/htmlcov/rest_framework_authtoken___init__.html new file mode 100644 index 000000000..f72574937 --- /dev/null +++ b/htmlcov/rest_framework_authtoken___init__.html @@ -0,0 +1,81 @@ + + + + + + + + Coverage for rest_framework/authtoken/__init__: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+ + + +
+
+ + + + + diff --git a/htmlcov/rest_framework_authtoken_models.html b/htmlcov/rest_framework_authtoken_models.html new file mode 100644 index 000000000..27d2fff1d --- /dev/null +++ b/htmlcov/rest_framework_authtoken_models.html @@ -0,0 +1,151 @@ + + + + + + + + Coverage for rest_framework/authtoken/models: 95% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+ +
+

import uuid 

+

import hmac 

+

from hashlib import sha1 

+

from rest_framework.compat import User 

+

from django.conf import settings 

+

from django.db import models 

+

 

+

 

+

class Token(models.Model): 

+

    """ 

+

    The default authorization token model. 

+

    """ 

+

    key = models.CharField(max_length=40, primary_key=True) 

+

    user = models.OneToOneField(User, related_name='auth_token') 

+

    created = models.DateTimeField(auto_now_add=True) 

+

 

+

    class Meta: 

+

        # Work around for a bug in Django: 

+

        # https://code.djangoproject.com/ticket/19422 

+

        # 

+

        # Also see corresponding ticket: 

+

        # https://github.com/tomchristie/django-rest-framework/issues/705 

+

        abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS 

+

 

+

    def save(self, *args, **kwargs): 

+

        if not self.key: 

+

            self.key = self.generate_key() 

+

        return super(Token, self).save(*args, **kwargs) 

+

 

+

    def generate_key(self): 

+

        unique = uuid.uuid4() 

+

        return hmac.new(unique.bytes, digestmod=sha1).hexdigest() 

+

 

+

    def __unicode__(self): 

+

        return self.key 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_authtoken_serializers.html b/htmlcov/rest_framework_authtoken_serializers.html new file mode 100644 index 000000000..8997d9a7b --- /dev/null +++ b/htmlcov/rest_framework_authtoken_serializers.html @@ -0,0 +1,129 @@ + + + + + + + + Coverage for rest_framework/authtoken/serializers: 88% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+ +
+

from django.contrib.auth import authenticate 

+

from rest_framework import serializers 

+

 

+

 

+

class AuthTokenSerializer(serializers.Serializer): 

+

    username = serializers.CharField() 

+

    password = serializers.CharField() 

+

 

+

    def validate(self, attrs): 

+

        username = attrs.get('username') 

+

        password = attrs.get('password') 

+

 

+

        if username and password: 

+

            user = authenticate(username=username, password=password) 

+

 

+

            if user: 

+

                if not user.is_active: 

+

                    raise serializers.ValidationError('User account is disabled.') 

+

                attrs['user'] = user 

+

                return attrs 

+

            else: 

+

                raise serializers.ValidationError('Unable to login with provided credentials.') 

+

        else: 

+

            raise serializers.ValidationError('Must include "username" and "password"') 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_authtoken_views.html b/htmlcov/rest_framework_authtoken_views.html new file mode 100644 index 000000000..d13746ea8 --- /dev/null +++ b/htmlcov/rest_framework_authtoken_views.html @@ -0,0 +1,133 @@ + + + + + + + + Coverage for rest_framework/authtoken/views: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+ +
+

from rest_framework.views import APIView 

+

from rest_framework import status 

+

from rest_framework import parsers 

+

from rest_framework import renderers 

+

from rest_framework.response import Response 

+

from rest_framework.authtoken.models import Token 

+

from rest_framework.authtoken.serializers import AuthTokenSerializer 

+

 

+

 

+

class ObtainAuthToken(APIView): 

+

    throttle_classes = () 

+

    permission_classes = () 

+

    parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,) 

+

    renderer_classes = (renderers.JSONRenderer,) 

+

    serializer_class = AuthTokenSerializer 

+

    model = Token 

+

 

+

    def post(self, request): 

+

        serializer = self.serializer_class(data=request.DATA) 

+

        if serializer.is_valid(): 

+

            token, created = Token.objects.get_or_create(user=serializer.object['user']) 

+

            return Response({'token': token.key}) 

+

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 

+

 

+

 

+

obtain_auth_token = ObtainAuthToken.as_view() 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_decorators.html b/htmlcov/rest_framework_decorators.html new file mode 100644 index 000000000..6ad6f6b51 --- /dev/null +++ b/htmlcov/rest_framework_decorators.html @@ -0,0 +1,339 @@ + + + + + + + + Coverage for rest_framework/decorators: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+ +
+

""" 

+

The most important decorator in this module is `@api_view`, which is used 

+

for writing function-based views with REST framework. 

+

 

+

There are also various decorators for setting the API policies on function 

+

based views, as well as the `@action` and `@link` decorators, which are 

+

used to annotate methods on viewsets that should be included by routers. 

+

""" 

+

from __future__ import unicode_literals 

+

from rest_framework.compat import six 

+

from rest_framework.views import APIView 

+

import types 

+

 

+

 

+

def api_view(http_method_names): 

+

 

+

    """ 

+

    Decorator that converts a function-based view into an APIView subclass. 

+

    Takes a list of allowed methods for the view as an argument. 

+

    """ 

+

 

+

    def decorator(func): 

+

 

+

        WrappedAPIView = type( 

+

            six.PY3 and 'WrappedAPIView' or b'WrappedAPIView', 

+

            (APIView,), 

+

            {'__doc__': func.__doc__} 

+

        ) 

+

 

+

        # Note, the above allows us to set the docstring. 

+

        # It is the equivalent of: 

+

        # 

+

        #     class WrappedAPIView(APIView): 

+

        #         pass 

+

        #     WrappedAPIView.__doc__ = func.doc    <--- Not possible to do this 

+

 

+

        # api_view applied without (method_names) 

+

        assert not(isinstance(http_method_names, types.FunctionType)), \ 

+

            '@api_view missing list of allowed HTTP methods' 

+

 

+

        # api_view applied with eg. string instead of list of strings 

+

        assert isinstance(http_method_names, (list, tuple)), \ 

+

            '@api_view expected a list of strings, received %s' % type(http_method_names).__name__ 

+

 

+

        allowed_methods = set(http_method_names) | set(('options',)) 

+

        WrappedAPIView.http_method_names = [method.lower() for method in allowed_methods] 

+

 

+

        def handler(self, *args, **kwargs): 

+

            return func(*args, **kwargs) 

+

 

+

        for method in http_method_names: 

+

            setattr(WrappedAPIView, method.lower(), handler) 

+

 

+

        WrappedAPIView.__name__ = func.__name__ 

+

 

+

        WrappedAPIView.renderer_classes = getattr(func, 'renderer_classes', 

+

                                                  APIView.renderer_classes) 

+

 

+

        WrappedAPIView.parser_classes = getattr(func, 'parser_classes', 

+

                                                APIView.parser_classes) 

+

 

+

        WrappedAPIView.authentication_classes = getattr(func, 'authentication_classes', 

+

                                                        APIView.authentication_classes) 

+

 

+

        WrappedAPIView.throttle_classes = getattr(func, 'throttle_classes', 

+

                                                  APIView.throttle_classes) 

+

 

+

        WrappedAPIView.permission_classes = getattr(func, 'permission_classes', 

+

                                                    APIView.permission_classes) 

+

 

+

        return WrappedAPIView.as_view() 

+

    return decorator 

+

 

+

 

+

def renderer_classes(renderer_classes): 

+

    def decorator(func): 

+

        func.renderer_classes = renderer_classes 

+

        return func 

+

    return decorator 

+

 

+

 

+

def parser_classes(parser_classes): 

+

    def decorator(func): 

+

        func.parser_classes = parser_classes 

+

        return func 

+

    return decorator 

+

 

+

 

+

def authentication_classes(authentication_classes): 

+

    def decorator(func): 

+

        func.authentication_classes = authentication_classes 

+

        return func 

+

    return decorator 

+

 

+

 

+

def throttle_classes(throttle_classes): 

+

    def decorator(func): 

+

        func.throttle_classes = throttle_classes 

+

        return func 

+

    return decorator 

+

 

+

 

+

def permission_classes(permission_classes): 

+

    def decorator(func): 

+

        func.permission_classes = permission_classes 

+

        return func 

+

    return decorator 

+

 

+

 

+

def link(**kwargs): 

+

    """ 

+

    Used to mark a method on a ViewSet that should be routed for GET requests. 

+

    """ 

+

    def decorator(func): 

+

        func.bind_to_methods = ['get'] 

+

        func.kwargs = kwargs 

+

        return func 

+

    return decorator 

+

 

+

 

+

def action(methods=['post'], **kwargs): 

+

    """ 

+

    Used to mark a method on a ViewSet that should be routed for POST requests. 

+

    """ 

+

    def decorator(func): 

+

        func.bind_to_methods = methods 

+

        func.kwargs = kwargs 

+

        return func 

+

    return decorator 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_exceptions.html b/htmlcov/rest_framework_exceptions.html new file mode 100644 index 000000000..d975a8481 --- /dev/null +++ b/htmlcov/rest_framework_exceptions.html @@ -0,0 +1,257 @@ + + + + + + + + Coverage for rest_framework/exceptions: 96% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+ +
+

""" 

+

Handled exceptions raised by REST framework. 

+

 

+

In addition Django's built in 403 and 404 exceptions are handled. 

+

(`django.http.Http404` and `django.core.exceptions.PermissionDenied`) 

+

""" 

+

from __future__ import unicode_literals 

+

from rest_framework import status 

+

 

+

 

+

class APIException(Exception): 

+

    """ 

+

    Base class for REST framework exceptions. 

+

    Subclasses should provide `.status_code` and `.detail` properties. 

+

    """ 

+

    pass 

+

 

+

 

+

class ParseError(APIException): 

+

    status_code = status.HTTP_400_BAD_REQUEST 

+

    default_detail = 'Malformed request.' 

+

 

+

    def __init__(self, detail=None): 

+

        self.detail = detail or self.default_detail 

+

 

+

 

+

class AuthenticationFailed(APIException): 

+

    status_code = status.HTTP_401_UNAUTHORIZED 

+

    default_detail = 'Incorrect authentication credentials.' 

+

 

+

    def __init__(self, detail=None): 

+

        self.detail = detail or self.default_detail 

+

 

+

 

+

class NotAuthenticated(APIException): 

+

    status_code = status.HTTP_401_UNAUTHORIZED 

+

    default_detail = 'Authentication credentials were not provided.' 

+

 

+

    def __init__(self, detail=None): 

+

        self.detail = detail or self.default_detail 

+

 

+

 

+

class PermissionDenied(APIException): 

+

    status_code = status.HTTP_403_FORBIDDEN 

+

    default_detail = 'You do not have permission to perform this action.' 

+

 

+

    def __init__(self, detail=None): 

+

        self.detail = detail or self.default_detail 

+

 

+

 

+

class MethodNotAllowed(APIException): 

+

    status_code = status.HTTP_405_METHOD_NOT_ALLOWED 

+

    default_detail = "Method '%s' not allowed." 

+

 

+

    def __init__(self, method, detail=None): 

+

        self.detail = (detail or self.default_detail) % method 

+

 

+

 

+

class NotAcceptable(APIException): 

+

    status_code = status.HTTP_406_NOT_ACCEPTABLE 

+

    default_detail = "Could not satisfy the request's Accept header" 

+

 

+

    def __init__(self, detail=None, available_renderers=None): 

+

        self.detail = detail or self.default_detail 

+

        self.available_renderers = available_renderers 

+

 

+

 

+

class UnsupportedMediaType(APIException): 

+

    status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE 

+

    default_detail = "Unsupported media type '%s' in request." 

+

 

+

    def __init__(self, media_type, detail=None): 

+

        self.detail = (detail or self.default_detail) % media_type 

+

 

+

 

+

class Throttled(APIException): 

+

    status_code = status.HTTP_429_TOO_MANY_REQUESTS 

+

    default_detail = "Request was throttled." 

+

    extra_detail = "Expected available in %d second%s." 

+

 

+

    def __init__(self, wait=None, detail=None): 

+

        import math 

+

        self.wait = wait and math.ceil(wait) or None 

+

        if wait is not None: 

+

            format = detail or self.default_detail + self.extra_detail 

+

            self.detail = format % (self.wait, self.wait != 1 and 's' or '') 

+

        else: 

+

            self.detail = detail or self.default_detail 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_fields.html b/htmlcov/rest_framework_fields.html new file mode 100644 index 000000000..cf2731d25 --- /dev/null +++ b/htmlcov/rest_framework_fields.html @@ -0,0 +1,1991 @@ + + + + + + + + Coverage for rest_framework/fields: 87% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+

296

+

297

+

298

+

299

+

300

+

301

+

302

+

303

+

304

+

305

+

306

+

307

+

308

+

309

+

310

+

311

+

312

+

313

+

314

+

315

+

316

+

317

+

318

+

319

+

320

+

321

+

322

+

323

+

324

+

325

+

326

+

327

+

328

+

329

+

330

+

331

+

332

+

333

+

334

+

335

+

336

+

337

+

338

+

339

+

340

+

341

+

342

+

343

+

344

+

345

+

346

+

347

+

348

+

349

+

350

+

351

+

352

+

353

+

354

+

355

+

356

+

357

+

358

+

359

+

360

+

361

+

362

+

363

+

364

+

365

+

366

+

367

+

368

+

369

+

370

+

371

+

372

+

373

+

374

+

375

+

376

+

377

+

378

+

379

+

380

+

381

+

382

+

383

+

384

+

385

+

386

+

387

+

388

+

389

+

390

+

391

+

392

+

393

+

394

+

395

+

396

+

397

+

398

+

399

+

400

+

401

+

402

+

403

+

404

+

405

+

406

+

407

+

408

+

409

+

410

+

411

+

412

+

413

+

414

+

415

+

416

+

417

+

418

+

419

+

420

+

421

+

422

+

423

+

424

+

425

+

426

+

427

+

428

+

429

+

430

+

431

+

432

+

433

+

434

+

435

+

436

+

437

+

438

+

439

+

440

+

441

+

442

+

443

+

444

+

445

+

446

+

447

+

448

+

449

+

450

+

451

+

452

+

453

+

454

+

455

+

456

+

457

+

458

+

459

+

460

+

461

+

462

+

463

+

464

+

465

+

466

+

467

+

468

+

469

+

470

+

471

+

472

+

473

+

474

+

475

+

476

+

477

+

478

+

479

+

480

+

481

+

482

+

483

+

484

+

485

+

486

+

487

+

488

+

489

+

490

+

491

+

492

+

493

+

494

+

495

+

496

+

497

+

498

+

499

+

500

+

501

+

502

+

503

+

504

+

505

+

506

+

507

+

508

+

509

+

510

+

511

+

512

+

513

+

514

+

515

+

516

+

517

+

518

+

519

+

520

+

521

+

522

+

523

+

524

+

525

+

526

+

527

+

528

+

529

+

530

+

531

+

532

+

533

+

534

+

535

+

536

+

537

+

538

+

539

+

540

+

541

+

542

+

543

+

544

+

545

+

546

+

547

+

548

+

549

+

550

+

551

+

552

+

553

+

554

+

555

+

556

+

557

+

558

+

559

+

560

+

561

+

562

+

563

+

564

+

565

+

566

+

567

+

568

+

569

+

570

+

571

+

572

+

573

+

574

+

575

+

576

+

577

+

578

+

579

+

580

+

581

+

582

+

583

+

584

+

585

+

586

+

587

+

588

+

589

+

590

+

591

+

592

+

593

+

594

+

595

+

596

+

597

+

598

+

599

+

600

+

601

+

602

+

603

+

604

+

605

+

606

+

607

+

608

+

609

+

610

+

611

+

612

+

613

+

614

+

615

+

616

+

617

+

618

+

619

+

620

+

621

+

622

+

623

+

624

+

625

+

626

+

627

+

628

+

629

+

630

+

631

+

632

+

633

+

634

+

635

+

636

+

637

+

638

+

639

+

640

+

641

+

642

+

643

+

644

+

645

+

646

+

647

+

648

+

649

+

650

+

651

+

652

+

653

+

654

+

655

+

656

+

657

+

658

+

659

+

660

+

661

+

662

+

663

+

664

+

665

+

666

+

667

+

668

+

669

+

670

+

671

+

672

+

673

+

674

+

675

+

676

+

677

+

678

+

679

+

680

+

681

+

682

+

683

+

684

+

685

+

686

+

687

+

688

+

689

+

690

+

691

+

692

+

693

+

694

+

695

+

696

+

697

+

698

+

699

+

700

+

701

+

702

+

703

+

704

+

705

+

706

+

707

+

708

+

709

+

710

+

711

+

712

+

713

+

714

+

715

+

716

+

717

+

718

+

719

+

720

+

721

+

722

+

723

+

724

+

725

+

726

+

727

+

728

+

729

+

730

+

731

+

732

+

733

+

734

+

735

+

736

+

737

+

738

+

739

+

740

+

741

+

742

+

743

+

744

+

745

+

746

+

747

+

748

+

749

+

750

+

751

+

752

+

753

+

754

+

755

+

756

+

757

+

758

+

759

+

760

+

761

+

762

+

763

+

764

+

765

+

766

+

767

+

768

+

769

+

770

+

771

+

772

+

773

+

774

+

775

+

776

+

777

+

778

+

779

+

780

+

781

+

782

+

783

+

784

+

785

+

786

+

787

+

788

+

789

+

790

+

791

+

792

+

793

+

794

+

795

+

796

+

797

+

798

+

799

+

800

+

801

+

802

+

803

+

804

+

805

+

806

+

807

+

808

+

809

+

810

+

811

+

812

+

813

+

814

+

815

+

816

+

817

+

818

+

819

+

820

+

821

+

822

+

823

+

824

+

825

+

826

+

827

+

828

+

829

+

830

+

831

+

832

+

833

+

834

+

835

+

836

+

837

+

838

+

839

+

840

+

841

+

842

+

843

+

844

+

845

+

846

+

847

+

848

+

849

+

850

+

851

+

852

+

853

+

854

+

855

+

856

+

857

+

858

+

859

+

860

+

861

+

862

+

863

+

864

+

865

+

866

+

867

+

868

+

869

+

870

+

871

+

872

+

873

+

874

+

875

+

876

+

877

+

878

+

879

+

880

+

881

+

882

+

883

+

884

+

885

+

886

+

887

+

888

+

889

+

890

+

891

+

892

+

893

+

894

+

895

+

896

+

897

+

898

+

899

+

900

+

901

+

902

+

903

+

904

+

905

+

906

+

907

+

908

+

909

+

910

+

911

+

912

+

913

+

914

+

915

+

916

+

917

+

918

+

919

+

920

+

921

+

922

+

923

+

924

+

925

+

926

+

927

+

928

+

929

+

930

+

931

+

932

+

933

+

934

+

935

+

936

+

937

+

938

+

939

+

940

+

941

+

942

+

943

+

944

+

945

+

946

+

947

+

948

+

949

+

950

+

951

+

952

+

953

+

954

+

955

+ +
+

""" 

+

Serializer fields perform validation on incoming data. 

+

 

+

They are very similar to Django's form fields. 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

import copy 

+

import datetime 

+

import inspect 

+

import re 

+

import warnings 

+

from decimal import Decimal, DecimalException 

+

from django import forms 

+

from django.core import validators 

+

from django.core.exceptions import ValidationError 

+

from django.conf import settings 

+

from django.db.models.fields import BLANK_CHOICE_DASH 

+

from django.forms import widgets 

+

from django.utils.encoding import is_protected_type 

+

from django.utils.translation import ugettext_lazy as _ 

+

from django.utils.datastructures import SortedDict 

+

from rest_framework import ISO_8601 

+

from rest_framework.compat import ( 

+

    timezone, parse_date, parse_datetime, parse_time, BytesIO, six, smart_text, 

+

    force_text, is_non_str_iterable 

+

) 

+

from rest_framework.settings import api_settings 

+

 

+

 

+

def is_simple_callable(obj): 

+

    """ 

+

    True if the object is a callable that takes no arguments. 

+

    """ 

+

    function = inspect.isfunction(obj) 

+

    method = inspect.ismethod(obj) 

+

 

+

    if not (function or method): 

+

        return False 

+

 

+

    args, _, _, defaults = inspect.getargspec(obj) 

+

    len_args = len(args) if function else len(args) - 1 

+

    len_defaults = len(defaults) if defaults else 0 

+

    return len_args <= len_defaults 

+

 

+

 

+

def get_component(obj, attr_name): 

+

    """ 

+

    Given an object, and an attribute name, 

+

    return that attribute on the object. 

+

    """ 

+

    if isinstance(obj, dict): 

+

        val = obj.get(attr_name) 

+

    else: 

+

        val = getattr(obj, attr_name) 

+

 

+

    if is_simple_callable(val): 

+

        return val() 

+

    return val 

+

 

+

 

+

def readable_datetime_formats(formats): 

+

    format = ', '.join(formats).replace(ISO_8601, 

+

             'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HHMM|-HHMM|Z]') 

+

    return humanize_strptime(format) 

+

 

+

 

+

def readable_date_formats(formats): 

+

    format = ', '.join(formats).replace(ISO_8601, 'YYYY[-MM[-DD]]') 

+

    return humanize_strptime(format) 

+

 

+

 

+

def readable_time_formats(formats): 

+

    format = ', '.join(formats).replace(ISO_8601, 'hh:mm[:ss[.uuuuuu]]') 

+

    return humanize_strptime(format) 

+

 

+

 

+

def humanize_strptime(format_string): 

+

    # Note that we're missing some of the locale specific mappings that 

+

    # don't really make sense. 

+

    mapping = { 

+

        "%Y": "YYYY", 

+

        "%y": "YY", 

+

        "%m": "MM", 

+

        "%b": "[Jan-Dec]", 

+

        "%B": "[January-December]", 

+

        "%d": "DD", 

+

        "%H": "hh", 

+

        "%I": "hh",  # Requires '%p' to differentiate from '%H'. 

+

        "%M": "mm", 

+

        "%S": "ss", 

+

        "%f": "uuuuuu", 

+

        "%a": "[Mon-Sun]", 

+

        "%A": "[Monday-Sunday]", 

+

        "%p": "[AM|PM]", 

+

        "%z": "[+HHMM|-HHMM]" 

+

    } 

+

    for key, val in mapping.items(): 

+

        format_string = format_string.replace(key, val) 

+

    return format_string 

+

 

+

 

+

class Field(object): 

+

    read_only = True 

+

    creation_counter = 0 

+

    empty = '' 

+

    type_name = None 

+

    partial = False 

+

    use_files = False 

+

    form_field_class = forms.CharField 

+

    type_label = 'field' 

+

 

+

    def __init__(self, source=None, label=None, help_text=None): 

+

        self.parent = None 

+

 

+

        self.creation_counter = Field.creation_counter 

+

        Field.creation_counter += 1 

+

 

+

        self.source = source 

+

 

+

        if label is not None: 

+

            self.label = smart_text(label) 

+

 

+

        if help_text is not None: 

+

            self.help_text = smart_text(help_text) 

+

 

+

    def initialize(self, parent, field_name): 

+

        """ 

+

        Called to set up a field prior to field_to_native or field_from_native. 

+

 

+

        parent - The parent serializer. 

+

        model_field - The model field this field corresponds to, if one exists. 

+

        """ 

+

        self.parent = parent 

+

        self.root = parent.root or parent 

+

        self.context = self.root.context 

+

        self.partial = self.root.partial 

+

        if self.partial: 

+

            self.required = False 

+

 

+

    def field_from_native(self, data, files, field_name, into): 

+

        """ 

+

        Given a dictionary and a field name, updates the dictionary `into`, 

+

        with the field and it's deserialized value. 

+

        """ 

+

        return 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        """ 

+

        Given and object and a field name, returns the value that should be 

+

        serialized for that field. 

+

        """ 

+

        if obj is None: 

+

            return self.empty 

+

 

+

        if self.source == '*': 

+

            return self.to_native(obj) 

+

 

+

        source = self.source or field_name 

+

        value = obj 

+

 

+

        for component in source.split('.'): 

+

            value = get_component(value, component) 

+

            if value is None: 

+

                break 

+

 

+

        return self.to_native(value) 

+

 

+

    def to_native(self, value): 

+

        """ 

+

        Converts the field's value into it's simple representation. 

+

        """ 

+

        if is_simple_callable(value): 

+

            value = value() 

+

 

+

        if is_protected_type(value): 

+

            return value 

+

        elif (is_non_str_iterable(value) and 

+

              not isinstance(value, (dict, six.string_types))): 

+

            return [self.to_native(item) for item in value] 

+

        elif isinstance(value, dict): 

+

            # Make sure we preserve field ordering, if it exists 

+

            ret = SortedDict() 

+

            for key, val in value.items(): 

+

                ret[key] = self.to_native(val) 

+

            return ret 

+

        return force_text(value) 

+

 

+

    def attributes(self): 

+

        """ 

+

        Returns a dictionary of attributes to be used when serializing to xml. 

+

        """ 

+

        if self.type_name: 

+

            return {'type': self.type_name} 

+

        return {} 

+

 

+

    def metadata(self): 

+

        metadata = SortedDict() 

+

        metadata['type'] = self.type_label 

+

        metadata['required'] = getattr(self, 'required', False) 

+

        optional_attrs = ['read_only', 'label', 'help_text', 

+

                          'min_length', 'max_length'] 

+

        for attr in optional_attrs: 

+

            value = getattr(self, attr, None) 

+

            if value is not None and value != '': 

+

                metadata[attr] = force_text(value, strings_only=True) 

+

        return metadata 

+

 

+

 

+

class WritableField(Field): 

+

    """ 

+

    Base for read/write fields. 

+

    """ 

+

    default_validators = [] 

+

    default_error_messages = { 

+

        'required': _('This field is required.'), 

+

        'invalid': _('Invalid value.'), 

+

    } 

+

    widget = widgets.TextInput 

+

    default = None 

+

 

+

    def __init__(self, source=None, label=None, help_text=None, 

+

                 read_only=False, required=None, 

+

                 validators=[], error_messages=None, widget=None, 

+

                 default=None, blank=None): 

+

 

+

        # 'blank' is to be deprecated in favor of 'required' 

+

        if blank is not None: 

+

            warnings.warn('The `blank` keyword argument is deprecated. ' 

+

                          'Use the `required` keyword argument instead.', 

+

                          DeprecationWarning, stacklevel=2) 

+

            required = not(blank) 

+

 

+

        super(WritableField, self).__init__(source=source, label=label, help_text=help_text) 

+

 

+

        self.read_only = read_only 

+

        if required is None: 

+

            self.required = not(read_only) 

+

        else: 

+

            assert not (read_only and required), "Cannot set required=True and read_only=True" 

+

            self.required = required 

+

 

+

        messages = {} 

+

        for c in reversed(self.__class__.__mro__): 

+

            messages.update(getattr(c, 'default_error_messages', {})) 

+

        messages.update(error_messages or {}) 

+

        self.error_messages = messages 

+

 

+

        self.validators = self.default_validators + validators 

+

        self.default = default if default is not None else self.default 

+

 

+

        # Widgets are ony used for HTML forms. 

+

        widget = widget or self.widget 

+

        if isinstance(widget, type): 

+

            widget = widget() 

+

        self.widget = widget 

+

 

+

    def __deepcopy__(self, memo): 

+

        result = copy.copy(self) 

+

        memo[id(self)] = result 

+

        result.validators = self.validators[:] 

+

        return result 

+

 

+

    def validate(self, value): 

+

        if value in validators.EMPTY_VALUES and self.required: 

+

            raise ValidationError(self.error_messages['required']) 

+

 

+

    def run_validators(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return 

+

        errors = [] 

+

        for v in self.validators: 

+

            try: 

+

                v(value) 

+

            except ValidationError as e: 

+

                if hasattr(e, 'code') and e.code in self.error_messages: 

+

                    message = self.error_messages[e.code] 

+

                    if e.params: 

+

                        message = message % e.params 

+

                    errors.append(message) 

+

                else: 

+

                    errors.extend(e.messages) 

+

        if errors: 

+

            raise ValidationError(errors) 

+

 

+

    def field_from_native(self, data, files, field_name, into): 

+

        """ 

+

        Given a dictionary and a field name, updates the dictionary `into`, 

+

        with the field and it's deserialized value. 

+

        """ 

+

        if self.read_only: 

+

            return 

+

 

+

        try: 

+

            if self.use_files: 

+

                files = files or {} 

+

                native = files[field_name] 

+

            else: 

+

                native = data[field_name] 

+

        except KeyError: 

+

            if self.default is not None and not self.partial: 

+

                # Note: partial updates shouldn't set defaults 

+

                if is_simple_callable(self.default): 

+

                    native = self.default() 

+

                else: 

+

                    native = self.default 

+

            else: 

+

                if self.required: 

+

                    raise ValidationError(self.error_messages['required']) 

+

                return 

+

 

+

        value = self.from_native(native) 

+

        if self.source == '*': 

+

            if value: 

+

                into.update(value) 

+

        else: 

+

            self.validate(value) 

+

            self.run_validators(value) 

+

            into[self.source or field_name] = value 

+

 

+

    def from_native(self, value): 

+

        """ 

+

        Reverts a simple representation back to the field's value. 

+

        """ 

+

        return value 

+

 

+

 

+

class ModelField(WritableField): 

+

    """ 

+

    A generic field that can be used against an arbitrary model field. 

+

    """ 

+

    def __init__(self, *args, **kwargs): 

+

        try: 

+

            self.model_field = kwargs.pop('model_field') 

+

        except KeyError: 

+

            raise ValueError("ModelField requires 'model_field' kwarg") 

+

 

+

        self.min_length = kwargs.pop('min_length', 

+

                                     getattr(self.model_field, 'min_length', None)) 

+

        self.max_length = kwargs.pop('max_length', 

+

                                     getattr(self.model_field, 'max_length', None)) 

+

        self.min_value = kwargs.pop('min_value', 

+

                                    getattr(self.model_field, 'min_value', None)) 

+

        self.max_value = kwargs.pop('max_value', 

+

                                    getattr(self.model_field, 'max_value', None)) 

+

 

+

        super(ModelField, self).__init__(*args, **kwargs) 

+

 

+

        if self.min_length is not None: 

+

            self.validators.append(validators.MinLengthValidator(self.min_length)) 

+

        if self.max_length is not None: 

+

            self.validators.append(validators.MaxLengthValidator(self.max_length)) 

+

        if self.min_value is not None: 

+

            self.validators.append(validators.MinValueValidator(self.min_value)) 

+

        if self.max_value is not None: 

+

            self.validators.append(validators.MaxValueValidator(self.max_value)) 

+

 

+

    def from_native(self, value): 

+

        rel = getattr(self.model_field, "rel", None) 

+

        if rel is not None: 

+

            return rel.to._meta.get_field(rel.field_name).to_python(value) 

+

        else: 

+

            return self.model_field.to_python(value) 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        value = self.model_field._get_val_from_obj(obj) 

+

        if is_protected_type(value): 

+

            return value 

+

        return self.model_field.value_to_string(obj) 

+

 

+

    def attributes(self): 

+

        return { 

+

            "type": self.model_field.get_internal_type() 

+

        } 

+

 

+

 

+

##### Typed Fields ##### 

+

 

+

class BooleanField(WritableField): 

+

    type_name = 'BooleanField' 

+

    type_label = 'boolean' 

+

    form_field_class = forms.BooleanField 

+

    widget = widgets.CheckboxInput 

+

    default_error_messages = { 

+

        'invalid': _("'%s' value must be either True or False."), 

+

    } 

+

    empty = False 

+

 

+

    # Note: we set default to `False` in order to fill in missing value not 

+

    # supplied by html form.  TODO: Fix so that only html form input gets 

+

    # this behavior. 

+

    default = False 

+

 

+

    def from_native(self, value): 

+

        if value in ('true', 't', 'True', '1'): 

+

            return True 

+

        if value in ('false', 'f', 'False', '0'): 

+

            return False 

+

        return bool(value) 

+

 

+

 

+

class CharField(WritableField): 

+

    type_name = 'CharField' 

+

    type_label = 'string' 

+

    form_field_class = forms.CharField 

+

 

+

    def __init__(self, max_length=None, min_length=None, *args, **kwargs): 

+

        self.max_length, self.min_length = max_length, min_length 

+

        super(CharField, self).__init__(*args, **kwargs) 

+

        if min_length is not None: 

+

            self.validators.append(validators.MinLengthValidator(min_length)) 

+

        if max_length is not None: 

+

            self.validators.append(validators.MaxLengthValidator(max_length)) 

+

 

+

    def from_native(self, value): 

+

        if isinstance(value, six.string_types) or value is None: 

+

            return value 

+

        return smart_text(value) 

+

 

+

 

+

class URLField(CharField): 

+

    type_name = 'URLField' 

+

    type_label = 'url' 

+

 

+

    def __init__(self, **kwargs): 

+

        kwargs['validators'] = [validators.URLValidator()] 

+

        super(URLField, self).__init__(**kwargs) 

+

 

+

 

+

class SlugField(CharField): 

+

    type_name = 'SlugField' 

+

    type_label = 'slug' 

+

    form_field_class = forms.SlugField 

+

 

+

    default_error_messages = { 

+

        'invalid': _("Enter a valid 'slug' consisting of letters, numbers," 

+

                     " underscores or hyphens."), 

+

    } 

+

    default_validators = [validators.validate_slug] 

+

 

+

    def __init__(self, *args, **kwargs): 

+

        super(SlugField, self).__init__(*args, **kwargs) 

+

 

+

 

+

class ChoiceField(WritableField): 

+

    type_name = 'ChoiceField' 

+

    type_label = 'multiple choice' 

+

    form_field_class = forms.ChoiceField 

+

    widget = widgets.Select 

+

    default_error_messages = { 

+

        'invalid_choice': _('Select a valid choice. %(value)s is not one of ' 

+

                            'the available choices.'), 

+

    } 

+

 

+

    def __init__(self, choices=(), *args, **kwargs): 

+

        super(ChoiceField, self).__init__(*args, **kwargs) 

+

        self.choices = choices 

+

        if not self.required: 

+

            self.choices = BLANK_CHOICE_DASH + self.choices 

+

 

+

    def _get_choices(self): 

+

        return self._choices 

+

 

+

    def _set_choices(self, value): 

+

        # Setting choices also sets the choices on the widget. 

+

        # choices can be any iterable, but we call list() on it because 

+

        # it will be consumed more than once. 

+

        self._choices = self.widget.choices = list(value) 

+

 

+

    choices = property(_get_choices, _set_choices) 

+

 

+

    def validate(self, value): 

+

        """ 

+

        Validates that the input is in self.choices. 

+

        """ 

+

        super(ChoiceField, self).validate(value) 

+

        if value and not self.valid_value(value): 

+

            raise ValidationError(self.error_messages['invalid_choice'] % {'value': value}) 

+

 

+

    def valid_value(self, value): 

+

        """ 

+

        Check to see if the provided value is a valid choice. 

+

        """ 

+

        for k, v in self.choices: 

+

            if isinstance(v, (list, tuple)): 

+

                # This is an optgroup, so look inside the group for options 

+

                for k2, v2 in v: 

+

                    if value == smart_text(k2): 

+

                        return True 

+

            else: 

+

                if value == smart_text(k) or value == k: 

+

                    return True 

+

        return False 

+

 

+

 

+

class EmailField(CharField): 

+

    type_name = 'EmailField' 

+

    type_label = 'email' 

+

    form_field_class = forms.EmailField 

+

 

+

    default_error_messages = { 

+

        'invalid': _('Enter a valid e-mail address.'), 

+

    } 

+

    default_validators = [validators.validate_email] 

+

 

+

    def from_native(self, value): 

+

        ret = super(EmailField, self).from_native(value) 

+

        if ret is None: 

+

            return None 

+

        return ret.strip() 

+

 

+

 

+

class RegexField(CharField): 

+

    type_name = 'RegexField' 

+

    type_label = 'regex' 

+

    form_field_class = forms.RegexField 

+

 

+

    def __init__(self, regex, max_length=None, min_length=None, *args, **kwargs): 

+

        super(RegexField, self).__init__(max_length, min_length, *args, **kwargs) 

+

        self.regex = regex 

+

 

+

    def _get_regex(self): 

+

        return self._regex 

+

 

+

    def _set_regex(self, regex): 

+

        if isinstance(regex, six.string_types): 

+

            regex = re.compile(regex) 

+

        self._regex = regex 

+

        if hasattr(self, '_regex_validator') and self._regex_validator in self.validators: 

+

            self.validators.remove(self._regex_validator) 

+

        self._regex_validator = validators.RegexValidator(regex=regex) 

+

        self.validators.append(self._regex_validator) 

+

 

+

    regex = property(_get_regex, _set_regex) 

+

 

+

 

+

class DateField(WritableField): 

+

    type_name = 'DateField' 

+

    type_label = 'date' 

+

    widget = widgets.DateInput 

+

    form_field_class = forms.DateField 

+

 

+

    default_error_messages = { 

+

        'invalid': _("Date has wrong format. Use one of these formats instead: %s"), 

+

    } 

+

    empty = None 

+

    input_formats = api_settings.DATE_INPUT_FORMATS 

+

    format = api_settings.DATE_FORMAT 

+

 

+

    def __init__(self, input_formats=None, format=None, *args, **kwargs): 

+

        self.input_formats = input_formats if input_formats is not None else self.input_formats 

+

        self.format = format if format is not None else self.format 

+

        super(DateField, self).__init__(*args, **kwargs) 

+

 

+

    def from_native(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        if isinstance(value, datetime.datetime): 

+

            if timezone and settings.USE_TZ and timezone.is_aware(value): 

+

                # Convert aware datetimes to the default time zone 

+

                # before casting them to dates (#17742). 

+

                default_timezone = timezone.get_default_timezone() 

+

                value = timezone.make_naive(value, default_timezone) 

+

            return value.date() 

+

        if isinstance(value, datetime.date): 

+

            return value 

+

 

+

        for format in self.input_formats: 

+

            if format.lower() == ISO_8601: 

+

                try: 

+

                    parsed = parse_date(value) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    if parsed is not None: 

+

                        return parsed 

+

            else: 

+

                try: 

+

                    parsed = datetime.datetime.strptime(value, format) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    return parsed.date() 

+

 

+

        msg = self.error_messages['invalid'] % readable_date_formats(self.input_formats) 

+

        raise ValidationError(msg) 

+

 

+

    def to_native(self, value): 

+

        if value is None or self.format is None: 

+

            return value 

+

 

+

        if isinstance(value, datetime.datetime): 

+

            value = value.date() 

+

 

+

        if self.format.lower() == ISO_8601: 

+

            return value.isoformat() 

+

        return value.strftime(self.format) 

+

 

+

 

+

class DateTimeField(WritableField): 

+

    type_name = 'DateTimeField' 

+

    type_label = 'datetime' 

+

    widget = widgets.DateTimeInput 

+

    form_field_class = forms.DateTimeField 

+

 

+

    default_error_messages = { 

+

        'invalid': _("Datetime has wrong format. Use one of these formats instead: %s"), 

+

    } 

+

    empty = None 

+

    input_formats = api_settings.DATETIME_INPUT_FORMATS 

+

    format = api_settings.DATETIME_FORMAT 

+

 

+

    def __init__(self, input_formats=None, format=None, *args, **kwargs): 

+

        self.input_formats = input_formats if input_formats is not None else self.input_formats 

+

        self.format = format if format is not None else self.format 

+

        super(DateTimeField, self).__init__(*args, **kwargs) 

+

 

+

    def from_native(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        if isinstance(value, datetime.datetime): 

+

            return value 

+

        if isinstance(value, datetime.date): 

+

            value = datetime.datetime(value.year, value.month, value.day) 

+

            if settings.USE_TZ: 

+

                # For backwards compatibility, interpret naive datetimes in 

+

                # local time. This won't work during DST change, but we can't 

+

                # do much about it, so we let the exceptions percolate up the 

+

                # call stack. 

+

                warnings.warn("DateTimeField received a naive datetime (%s)" 

+

                              " while time zone support is active." % value, 

+

                              RuntimeWarning) 

+

                default_timezone = timezone.get_default_timezone() 

+

                value = timezone.make_aware(value, default_timezone) 

+

            return value 

+

 

+

        for format in self.input_formats: 

+

            if format.lower() == ISO_8601: 

+

                try: 

+

                    parsed = parse_datetime(value) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    if parsed is not None: 

+

                        return parsed 

+

            else: 

+

                try: 

+

                    parsed = datetime.datetime.strptime(value, format) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    return parsed 

+

 

+

        msg = self.error_messages['invalid'] % readable_datetime_formats(self.input_formats) 

+

        raise ValidationError(msg) 

+

 

+

    def to_native(self, value): 

+

        if value is None or self.format is None: 

+

            return value 

+

 

+

        if self.format.lower() == ISO_8601: 

+

            ret = value.isoformat() 

+

            if ret.endswith('+00:00'): 

+

                ret = ret[:-6] + 'Z' 

+

            return ret 

+

        return value.strftime(self.format) 

+

 

+

 

+

class TimeField(WritableField): 

+

    type_name = 'TimeField' 

+

    type_label = 'time' 

+

    widget = widgets.TimeInput 

+

    form_field_class = forms.TimeField 

+

 

+

    default_error_messages = { 

+

        'invalid': _("Time has wrong format. Use one of these formats instead: %s"), 

+

    } 

+

    empty = None 

+

    input_formats = api_settings.TIME_INPUT_FORMATS 

+

    format = api_settings.TIME_FORMAT 

+

 

+

    def __init__(self, input_formats=None, format=None, *args, **kwargs): 

+

        self.input_formats = input_formats if input_formats is not None else self.input_formats 

+

        self.format = format if format is not None else self.format 

+

        super(TimeField, self).__init__(*args, **kwargs) 

+

 

+

    def from_native(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        if isinstance(value, datetime.time): 

+

            return value 

+

 

+

        for format in self.input_formats: 

+

            if format.lower() == ISO_8601: 

+

                try: 

+

                    parsed = parse_time(value) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    if parsed is not None: 

+

                        return parsed 

+

            else: 

+

                try: 

+

                    parsed = datetime.datetime.strptime(value, format) 

+

                except (ValueError, TypeError): 

+

                    pass 

+

                else: 

+

                    return parsed.time() 

+

 

+

        msg = self.error_messages['invalid'] % readable_time_formats(self.input_formats) 

+

        raise ValidationError(msg) 

+

 

+

    def to_native(self, value): 

+

        if value is None or self.format is None: 

+

            return value 

+

 

+

        if isinstance(value, datetime.datetime): 

+

            value = value.time() 

+

 

+

        if self.format.lower() == ISO_8601: 

+

            return value.isoformat() 

+

        return value.strftime(self.format) 

+

 

+

 

+

class IntegerField(WritableField): 

+

    type_name = 'IntegerField' 

+

    type_label = 'integer' 

+

    form_field_class = forms.IntegerField 

+

 

+

    default_error_messages = { 

+

        'invalid': _('Enter a whole number.'), 

+

        'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), 

+

        'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), 

+

    } 

+

 

+

    def __init__(self, max_value=None, min_value=None, *args, **kwargs): 

+

        self.max_value, self.min_value = max_value, min_value 

+

        super(IntegerField, self).__init__(*args, **kwargs) 

+

 

+

        if max_value is not None: 

+

            self.validators.append(validators.MaxValueValidator(max_value)) 

+

        if min_value is not None: 

+

            self.validators.append(validators.MinValueValidator(min_value)) 

+

 

+

    def from_native(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        try: 

+

            value = int(str(value)) 

+

        except (ValueError, TypeError): 

+

            raise ValidationError(self.error_messages['invalid']) 

+

        return value 

+

 

+

 

+

class FloatField(WritableField): 

+

    type_name = 'FloatField' 

+

    type_label = 'float' 

+

    form_field_class = forms.FloatField 

+

 

+

    default_error_messages = { 

+

        'invalid': _("'%s' value must be a float."), 

+

    } 

+

 

+

    def from_native(self, value): 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        try: 

+

            return float(value) 

+

        except (TypeError, ValueError): 

+

            msg = self.error_messages['invalid'] % value 

+

            raise ValidationError(msg) 

+

 

+

 

+

class DecimalField(WritableField): 

+

    type_name = 'DecimalField' 

+

    type_label = 'decimal' 

+

    form_field_class = forms.DecimalField 

+

 

+

    default_error_messages = { 

+

        'invalid': _('Enter a number.'), 

+

        'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), 

+

        'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), 

+

        'max_digits': _('Ensure that there are no more than %s digits in total.'), 

+

        'max_decimal_places': _('Ensure that there are no more than %s decimal places.'), 

+

        'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.') 

+

    } 

+

 

+

    def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs): 

+

        self.max_value, self.min_value = max_value, min_value 

+

        self.max_digits, self.decimal_places = max_digits, decimal_places 

+

        super(DecimalField, self).__init__(*args, **kwargs) 

+

 

+

        if max_value is not None: 

+

            self.validators.append(validators.MaxValueValidator(max_value)) 

+

        if min_value is not None: 

+

            self.validators.append(validators.MinValueValidator(min_value)) 

+

 

+

    def from_native(self, value): 

+

        """ 

+

        Validates that the input is a decimal number. Returns a Decimal 

+

        instance. Returns None for empty values. Ensures that there are no more 

+

        than max_digits in the number, and no more than decimal_places digits 

+

        after the decimal point. 

+

        """ 

+

        if value in validators.EMPTY_VALUES: 

+

            return None 

+

        value = smart_text(value).strip() 

+

        try: 

+

            value = Decimal(value) 

+

        except DecimalException: 

+

            raise ValidationError(self.error_messages['invalid']) 

+

        return value 

+

 

+

    def validate(self, value): 

+

        super(DecimalField, self).validate(value) 

+

        if value in validators.EMPTY_VALUES: 

+

            return 

+

        # Check for NaN, Inf and -Inf values. We can't compare directly for NaN, 

+

        # since it is never equal to itself. However, NaN is the only value that 

+

        # isn't equal to itself, so we can use this to identify NaN 

+

        if value != value or value == Decimal("Inf") or value == Decimal("-Inf"): 

+

            raise ValidationError(self.error_messages['invalid']) 

+

        sign, digittuple, exponent = value.as_tuple() 

+

        decimals = abs(exponent) 

+

        # digittuple doesn't include any leading zeros. 

+

        digits = len(digittuple) 

+

        if decimals > digits: 

+

            # We have leading zeros up to or past the decimal point.  Count 

+

            # everything past the decimal point as a digit.  We do not count 

+

            # 0 before the decimal point as a digit since that would mean 

+

            # we would not allow max_digits = decimal_places. 

+

            digits = decimals 

+

        whole_digits = digits - decimals 

+

 

+

        if self.max_digits is not None and digits > self.max_digits: 

+

            raise ValidationError(self.error_messages['max_digits'] % self.max_digits) 

+

        if self.decimal_places is not None and decimals > self.decimal_places: 

+

            raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places) 

+

        if self.max_digits is not None and self.decimal_places is not None and whole_digits > (self.max_digits - self.decimal_places): 

+

            raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places)) 

+

        return value 

+

 

+

 

+

class FileField(WritableField): 

+

    use_files = True 

+

    type_name = 'FileField' 

+

    type_label = 'file upload' 

+

    form_field_class = forms.FileField 

+

    widget = widgets.FileInput 

+

 

+

    default_error_messages = { 

+

        'invalid': _("No file was submitted. Check the encoding type on the form."), 

+

        'missing': _("No file was submitted."), 

+

        'empty': _("The submitted file is empty."), 

+

        'max_length': _('Ensure this filename has at most %(max)d characters (it has %(length)d).'), 

+

        'contradiction': _('Please either submit a file or check the clear checkbox, not both.') 

+

    } 

+

 

+

    def __init__(self, *args, **kwargs): 

+

        self.max_length = kwargs.pop('max_length', None) 

+

        self.allow_empty_file = kwargs.pop('allow_empty_file', False) 

+

        super(FileField, self).__init__(*args, **kwargs) 

+

 

+

    def from_native(self, data): 

+

        if data in validators.EMPTY_VALUES: 

+

            return None 

+

 

+

        # UploadedFile objects should have name and size attributes. 

+

        try: 

+

            file_name = data.name 

+

            file_size = data.size 

+

        except AttributeError: 

+

            raise ValidationError(self.error_messages['invalid']) 

+

 

+

        if self.max_length is not None and len(file_name) > self.max_length: 

+

            error_values = {'max': self.max_length, 'length': len(file_name)} 

+

            raise ValidationError(self.error_messages['max_length'] % error_values) 

+

        if not file_name: 

+

            raise ValidationError(self.error_messages['invalid']) 

+

        if not self.allow_empty_file and not file_size: 

+

            raise ValidationError(self.error_messages['empty']) 

+

 

+

        return data 

+

 

+

    def to_native(self, value): 

+

        return value.name 

+

 

+

 

+

class ImageField(FileField): 

+

    use_files = True 

+

    type_name = 'ImageField' 

+

    type_label = 'image upload' 

+

    form_field_class = forms.ImageField 

+

 

+

    default_error_messages = { 

+

        'invalid_image': _("Upload a valid image. The file you uploaded was " 

+

                           "either not an image or a corrupted image."), 

+

    } 

+

 

+

    def from_native(self, data): 

+

        """ 

+

        Checks that the file-upload field data contains a valid image (GIF, JPG, 

+

        PNG, possibly others -- whatever the Python Imaging Library supports). 

+

        """ 

+

        f = super(ImageField, self).from_native(data) 

+

        if f is None: 

+

            return None 

+

 

+

        from compat import Image 

+

        assert Image is not None, 'PIL must be installed for ImageField support' 

+

 

+

        # We need to get a file object for PIL. We might have a path or we might 

+

        # have to read the data into memory. 

+

        if hasattr(data, 'temporary_file_path'): 

+

            file = data.temporary_file_path() 

+

        else: 

+

            if hasattr(data, 'read'): 

+

                file = BytesIO(data.read()) 

+

            else: 

+

                file = BytesIO(data['content']) 

+

 

+

        try: 

+

            # load() could spot a truncated JPEG, but it loads the entire 

+

            # image in memory, which is a DoS vector. See #3848 and #18520. 

+

            # verify() must be called immediately after the constructor. 

+

            Image.open(file).verify() 

+

        except ImportError: 

+

            # Under PyPy, it is possible to import PIL. However, the underlying 

+

            # _imaging C module isn't available, so an ImportError will be 

+

            # raised. Catch and re-raise. 

+

            raise 

+

        except Exception:  # Python Imaging Library doesn't recognize it as an image 

+

            raise ValidationError(self.error_messages['invalid_image']) 

+

        if hasattr(f, 'seek') and callable(f.seek): 

+

            f.seek(0) 

+

        return f 

+

 

+

 

+

class SerializerMethodField(Field): 

+

    """ 

+

    A field that gets its value by calling a method on the serializer it's attached to. 

+

    """ 

+

 

+

    def __init__(self, method_name): 

+

        self.method_name = method_name 

+

        super(SerializerMethodField, self).__init__() 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        value = getattr(self.parent, self.method_name)(obj) 

+

        return self.to_native(value) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_filters.html b/htmlcov/rest_framework_filters.html new file mode 100644 index 000000000..28b6eaae5 --- /dev/null +++ b/htmlcov/rest_framework_filters.html @@ -0,0 +1,367 @@ + + + + + + + + Coverage for rest_framework/filters: 92% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+ +
+

""" 

+

Provides generic filtering backends that can be used to filter the results 

+

returned by list views. 

+

""" 

+

from __future__ import unicode_literals 

+

from django.db import models 

+

from rest_framework.compat import django_filters, six 

+

from functools import reduce 

+

import operator 

+

 

+

FilterSet = django_filters and django_filters.FilterSet or None 

+

 

+

 

+

class BaseFilterBackend(object): 

+

    """ 

+

    A base class from which all filter backend classes should inherit. 

+

    """ 

+

 

+

    def filter_queryset(self, request, queryset, view): 

+

        """ 

+

        Return a filtered queryset. 

+

        """ 

+

        raise NotImplementedError(".filter_queryset() must be overridden.") 

+

 

+

 

+

class DjangoFilterBackend(BaseFilterBackend): 

+

    """ 

+

    A filter backend that uses django-filter. 

+

    """ 

+

    default_filter_set = FilterSet 

+

 

+

    def __init__(self): 

+

        assert django_filters, 'Using DjangoFilterBackend, but django-filter is not installed' 

+

 

+

    def get_filter_class(self, view, queryset=None): 

+

        """ 

+

        Return the django-filters `FilterSet` used to filter the queryset. 

+

        """ 

+

        filter_class = getattr(view, 'filter_class', None) 

+

        filter_fields = getattr(view, 'filter_fields', None) 

+

 

+

        if filter_class: 

+

            filter_model = filter_class.Meta.model 

+

 

+

            assert issubclass(filter_model, queryset.model), \ 

+

                'FilterSet model %s does not match queryset model %s' % \ 

+

                (filter_model, queryset.model) 

+

 

+

            return filter_class 

+

 

+

        if filter_fields: 

+

            class AutoFilterSet(self.default_filter_set): 

+

                class Meta: 

+

                    model = queryset.model 

+

                    fields = filter_fields 

+

            return AutoFilterSet 

+

 

+

        return None 

+

 

+

    def filter_queryset(self, request, queryset, view): 

+

        filter_class = self.get_filter_class(view, queryset) 

+

 

+

        if filter_class: 

+

            return filter_class(request.QUERY_PARAMS, queryset=queryset).qs 

+

 

+

        return queryset 

+

 

+

 

+

class SearchFilter(BaseFilterBackend): 

+

    search_param = 'search'  # The URL query parameter used for the search. 

+

 

+

    def get_search_terms(self, request): 

+

        """ 

+

        Search terms are set by a ?search=... query parameter, 

+

        and may be comma and/or whitespace delimited. 

+

        """ 

+

        params = request.QUERY_PARAMS.get(self.search_param, '') 

+

        return params.replace(',', ' ').split() 

+

 

+

    def construct_search(self, field_name): 

+

        if field_name.startswith('^'): 

+

            return "%s__istartswith" % field_name[1:] 

+

        elif field_name.startswith('='): 

+

            return "%s__iexact" % field_name[1:] 

+

        elif field_name.startswith('@'): 

+

            return "%s__search" % field_name[1:] 

+

        else: 

+

            return "%s__icontains" % field_name 

+

 

+

    def filter_queryset(self, request, queryset, view): 

+

        search_fields = getattr(view, 'search_fields', None) 

+

 

+

        if not search_fields: 

+

            return queryset 

+

 

+

        orm_lookups = [self.construct_search(str(search_field)) 

+

                       for search_field in search_fields] 

+

 

+

        for search_term in self.get_search_terms(request): 

+

            or_queries = [models.Q(**{orm_lookup: search_term}) 

+

                          for orm_lookup in orm_lookups] 

+

            queryset = queryset.filter(reduce(operator.or_, or_queries)) 

+

 

+

        return queryset 

+

 

+

 

+

class OrderingFilter(BaseFilterBackend): 

+

    ordering_param = 'ordering'  # The URL query parameter used for the ordering. 

+

 

+

    def get_ordering(self, request): 

+

        """ 

+

        Search terms are set by a ?search=... query parameter, 

+

        and may be comma and/or whitespace delimited. 

+

        """ 

+

        params = request.QUERY_PARAMS.get(self.ordering_param) 

+

        if params: 

+

            return [param.strip() for param in params.split(',')] 

+

 

+

    def get_default_ordering(self, view): 

+

        ordering = getattr(view, 'ordering', None) 

+

        if isinstance(ordering, six.string_types): 

+

            return (ordering,) 

+

        return ordering 

+

 

+

    def remove_invalid_fields(self, queryset, ordering): 

+

        field_names = [field.name for field in queryset.model._meta.fields] 

+

        return [term for term in ordering if term.lstrip('-') in field_names] 

+

 

+

    def filter_queryset(self, request, queryset, view): 

+

        ordering = self.get_ordering(request) 

+

 

+

        if ordering: 

+

            # Skip any incorrect parameters 

+

            ordering = self.remove_invalid_fields(queryset, ordering) 

+

 

+

        if not ordering: 

+

            # Use 'ordering' attribtue by default 

+

            ordering = self.get_default_ordering(view) 

+

 

+

        if ordering: 

+

            return queryset.order_by(*ordering) 

+

 

+

        return queryset 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_generics.html b/htmlcov/rest_framework_generics.html new file mode 100644 index 000000000..5f5851cbd --- /dev/null +++ b/htmlcov/rest_framework_generics.html @@ -0,0 +1,1079 @@ + + + + + + + + Coverage for rest_framework/generics: 83% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+

296

+

297

+

298

+

299

+

300

+

301

+

302

+

303

+

304

+

305

+

306

+

307

+

308

+

309

+

310

+

311

+

312

+

313

+

314

+

315

+

316

+

317

+

318

+

319

+

320

+

321

+

322

+

323

+

324

+

325

+

326

+

327

+

328

+

329

+

330

+

331

+

332

+

333

+

334

+

335

+

336

+

337

+

338

+

339

+

340

+

341

+

342

+

343

+

344

+

345

+

346

+

347

+

348

+

349

+

350

+

351

+

352

+

353

+

354

+

355

+

356

+

357

+

358

+

359

+

360

+

361

+

362

+

363

+

364

+

365

+

366

+

367

+

368

+

369

+

370

+

371

+

372

+

373

+

374

+

375

+

376

+

377

+

378

+

379

+

380

+

381

+

382

+

383

+

384

+

385

+

386

+

387

+

388

+

389

+

390

+

391

+

392

+

393

+

394

+

395

+

396

+

397

+

398

+

399

+

400

+

401

+

402

+

403

+

404

+

405

+

406

+

407

+

408

+

409

+

410

+

411

+

412

+

413

+

414

+

415

+

416

+

417

+

418

+

419

+

420

+

421

+

422

+

423

+

424

+

425

+

426

+

427

+

428

+

429

+

430

+

431

+

432

+

433

+

434

+

435

+

436

+

437

+

438

+

439

+

440

+

441

+

442

+

443

+

444

+

445

+

446

+

447

+

448

+

449

+

450

+

451

+

452

+

453

+

454

+

455

+

456

+

457

+

458

+

459

+

460

+

461

+

462

+

463

+

464

+

465

+

466

+

467

+

468

+

469

+

470

+

471

+

472

+

473

+

474

+

475

+

476

+

477

+

478

+

479

+

480

+

481

+

482

+

483

+

484

+

485

+

486

+

487

+

488

+

489

+

490

+

491

+

492

+

493

+

494

+

495

+

496

+

497

+

498

+

499

+ +
+

""" 

+

Generic views that provide commonly needed behaviour. 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

from django.core.exceptions import ImproperlyConfigured, PermissionDenied 

+

from django.core.paginator import Paginator, InvalidPage 

+

from django.http import Http404 

+

from django.shortcuts import get_object_or_404 as _get_object_or_404 

+

from django.utils.translation import ugettext as _ 

+

from rest_framework import views, mixins, exceptions 

+

from rest_framework.request import clone_request 

+

from rest_framework.settings import api_settings 

+

import warnings 

+

 

+

 

+

def get_object_or_404(queryset, **filter_kwargs): 

+

    """ 

+

    Same as Django's standard shortcut, but make sure to raise 404 

+

    if the filter_kwargs don't match the required types. 

+

    """ 

+

    try: 

+

        return _get_object_or_404(queryset, **filter_kwargs) 

+

    except (TypeError, ValueError): 

+

        raise Http404 

+

 

+

 

+

class GenericAPIView(views.APIView): 

+

    """ 

+

    Base class for all other generic views. 

+

    """ 

+

 

+

    # You'll need to either set these attributes, 

+

    # or override `get_queryset()`/`get_serializer_class()`. 

+

    queryset = None 

+

    serializer_class = None 

+

 

+

    # This shortcut may be used instead of setting either or both 

+

    # of the `queryset`/`serializer_class` attributes, although using 

+

    # the explicit style is generally preferred. 

+

    model = None 

+

 

+

    # If you want to use object lookups other than pk, set this attribute. 

+

    # For more complex lookup requirements override `get_object()`. 

+

    lookup_field = 'pk' 

+

 

+

    # Pagination settings 

+

    paginate_by = api_settings.PAGINATE_BY 

+

    paginate_by_param = api_settings.PAGINATE_BY_PARAM 

+

    pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS 

+

    page_kwarg = 'page' 

+

 

+

    # The filter backend classes to use for queryset filtering 

+

    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS 

+

 

+

    # The following attributes may be subject to change, 

+

    # and should be considered private API. 

+

    model_serializer_class = api_settings.DEFAULT_MODEL_SERIALIZER_CLASS 

+

    paginator_class = Paginator 

+

 

+

    ###################################### 

+

    # These are pending deprecation... 

+

 

+

    pk_url_kwarg = 'pk' 

+

    slug_url_kwarg = 'slug' 

+

    slug_field = 'slug' 

+

    allow_empty = True 

+

    filter_backend = api_settings.FILTER_BACKEND 

+

 

+

    def get_serializer_context(self): 

+

        """ 

+

        Extra context provided to the serializer class. 

+

        """ 

+

        return { 

+

            'request': self.request, 

+

            'format': self.format_kwarg, 

+

            'view': self 

+

        } 

+

 

+

    def get_serializer(self, instance=None, data=None, 

+

                       files=None, many=False, partial=False): 

+

        """ 

+

        Return the serializer instance that should be used for validating and 

+

        deserializing input, and for serializing output. 

+

        """ 

+

        serializer_class = self.get_serializer_class() 

+

        context = self.get_serializer_context() 

+

        return serializer_class(instance, data=data, files=files, 

+

                                many=many, partial=partial, context=context) 

+

 

+

    def get_pagination_serializer(self, page): 

+

        """ 

+

        Return a serializer instance to use with paginated data. 

+

        """ 

+

        class SerializerClass(self.pagination_serializer_class): 

+

            class Meta: 

+

                object_serializer_class = self.get_serializer_class() 

+

 

+

        pagination_serializer_class = SerializerClass 

+

        context = self.get_serializer_context() 

+

        return pagination_serializer_class(instance=page, context=context) 

+

 

+

    def paginate_queryset(self, queryset, page_size=None): 

+

        """ 

+

        Paginate a queryset if required, either returning a page object, 

+

        or `None` if pagination is not configured for this view. 

+

        """ 

+

        deprecated_style = False 

+

        if page_size is not None: 

+

            warnings.warn('The `page_size` parameter to `paginate_queryset()` ' 

+

                          'is due to be deprecated. ' 

+

                          'Note that the return style of this method is also ' 

+

                          'changed, and will simply return a page object ' 

+

                          'when called without a `page_size` argument.', 

+

                          PendingDeprecationWarning, stacklevel=2) 

+

            deprecated_style = True 

+

        else: 

+

            # Determine the required page size. 

+

            # If pagination is not configured, simply return None. 

+

            page_size = self.get_paginate_by() 

+

            if not page_size: 

+

                return None 

+

 

+

        if not self.allow_empty: 

+

            warnings.warn( 

+

                'The `allow_empty` parameter is due to be deprecated. ' 

+

                'To use `allow_empty=False` style behavior, You should override ' 

+

                '`get_queryset()` and explicitly raise a 404 on empty querysets.', 

+

                PendingDeprecationWarning, stacklevel=2 

+

            ) 

+

 

+

        paginator = self.paginator_class(queryset, page_size, 

+

                                         allow_empty_first_page=self.allow_empty) 

+

        page_kwarg = self.kwargs.get(self.page_kwarg) 

+

        page_query_param = self.request.QUERY_PARAMS.get(self.page_kwarg) 

+

        page = page_kwarg or page_query_param or 1 

+

        try: 

+

            page_number = int(page) 

+

        except ValueError: 

+

            if page == 'last': 

+

                page_number = paginator.num_pages 

+

            else: 

+

                raise Http404(_("Page is not 'last', nor can it be converted to an int.")) 

+

        try: 

+

            page = paginator.page(page_number) 

+

        except InvalidPage as e: 

+

            raise Http404(_('Invalid page (%(page_number)s): %(message)s') % { 

+

                                'page_number': page_number, 

+

                                'message': str(e) 

+

            }) 

+

 

+

        if deprecated_style: 

+

            return (paginator, page, page.object_list, page.has_other_pages()) 

+

        return page 

+

 

+

    def filter_queryset(self, queryset): 

+

        """ 

+

        Given a queryset, filter it with whichever filter backend is in use. 

+

 

+

        You are unlikely to want to override this method, although you may need 

+

        to call it either from a list view, or from a custom `get_object` 

+

        method if you want to apply the configured filtering backend to the 

+

        default queryset. 

+

        """ 

+

        filter_backends = self.filter_backends or [] 

+

        if not filter_backends and self.filter_backend: 

+

            warnings.warn( 

+

                'The `filter_backend` attribute and `FILTER_BACKEND` setting ' 

+

                'are due to be deprecated in favor of a `filter_backends` ' 

+

                'attribute and `DEFAULT_FILTER_BACKENDS` setting, that take ' 

+

                'a *list* of filter backend classes.', 

+

                PendingDeprecationWarning, stacklevel=2 

+

            ) 

+

            filter_backends = [self.filter_backend] 

+

 

+

        for backend in filter_backends: 

+

            queryset = backend().filter_queryset(self.request, queryset, self) 

+

        return queryset 

+

 

+

    ######################## 

+

    ### The following methods provide default implementations 

+

    ### that you may want to override for more complex cases. 

+

 

+

    def get_paginate_by(self, queryset=None): 

+

        """ 

+

        Return the size of pages to use with pagination. 

+

 

+

        If `PAGINATE_BY_PARAM` is set it will attempt to get the page size 

+

        from a named query parameter in the url, eg. ?page_size=100 

+

 

+

        Otherwise defaults to using `self.paginate_by`. 

+

        """ 

+

        if queryset is not None: 

+

            warnings.warn('The `queryset` parameter to `get_paginate_by()` ' 

+

                          'is due to be deprecated.', 

+

                          PendingDeprecationWarning, stacklevel=2) 

+

 

+

        if self.paginate_by_param: 

+

            query_params = self.request.QUERY_PARAMS 

+

            try: 

+

                return int(query_params[self.paginate_by_param]) 

+

            except (KeyError, ValueError): 

+

                pass 

+

 

+

        return self.paginate_by 

+

 

+

    def get_serializer_class(self): 

+

        """ 

+

        Return the class to use for the serializer. 

+

        Defaults to using `self.serializer_class`. 

+

 

+

        You may want to override this if you need to provide different 

+

        serializations depending on the incoming request. 

+

 

+

        (Eg. admins get full serialization, others get basic serialization) 

+

        """ 

+

        serializer_class = self.serializer_class 

+

        if serializer_class is not None: 

+

            return serializer_class 

+

 

+

        assert self.model is not None, \ 

+

            "'%s' should either include a 'serializer_class' attribute, " \ 

+

            "or use the 'model' attribute as a shortcut for " \ 

+

            "automatically generating a serializer class." \ 

+

            % self.__class__.__name__ 

+

 

+

        class DefaultSerializer(self.model_serializer_class): 

+

            class Meta: 

+

                model = self.model 

+

        return DefaultSerializer 

+

 

+

    def get_queryset(self): 

+

        """ 

+

        Get the list of items for this view. 

+

        This must be an iterable, and may be a queryset. 

+

        Defaults to using `self.queryset`. 

+

 

+

        You may want to override this if you need to provide different 

+

        querysets depending on the incoming request. 

+

 

+

        (Eg. return a list of items that is specific to the user) 

+

        """ 

+

        if self.queryset is not None: 

+

            return self.queryset._clone() 

+

 

+

        if self.model is not None: 

+

            return self.model._default_manager.all() 

+

 

+

        raise ImproperlyConfigured("'%s' must define 'queryset' or 'model'" 

+

                                    % self.__class__.__name__) 

+

 

+

    def get_object(self, queryset=None): 

+

        """ 

+

        Returns the object the view is displaying. 

+

 

+

        You may want to override this if you need to provide non-standard 

+

        queryset lookups.  Eg if objects are referenced using multiple 

+

        keyword arguments in the url conf. 

+

        """ 

+

        # Determine the base queryset to use. 

+

        if queryset is None: 

+

            queryset = self.filter_queryset(self.get_queryset()) 

+

        else: 

+

            pass  # Deprecation warning 

+

 

+

        # Perform the lookup filtering. 

+

        pk = self.kwargs.get(self.pk_url_kwarg, None) 

+

        slug = self.kwargs.get(self.slug_url_kwarg, None) 

+

        lookup = self.kwargs.get(self.lookup_field, None) 

+

 

+

        if lookup is not None: 

+

            filter_kwargs = {self.lookup_field: lookup} 

+

        elif pk is not None and self.lookup_field == 'pk': 

+

            warnings.warn( 

+

                'The `pk_url_kwarg` attribute is due to be deprecated. ' 

+

                'Use the `lookup_field` attribute instead', 

+

                PendingDeprecationWarning 

+

            ) 

+

            filter_kwargs = {'pk': pk} 

+

        elif slug is not None and self.lookup_field == 'pk': 

+

            warnings.warn( 

+

                'The `slug_url_kwarg` attribute is due to be deprecated. ' 

+

                'Use the `lookup_field` attribute instead', 

+

                PendingDeprecationWarning 

+

            ) 

+

            filter_kwargs = {self.slug_field: slug} 

+

        else: 

+

            raise ImproperlyConfigured( 

+

                'Expected view %s to be called with a URL keyword argument ' 

+

                'named "%s". Fix your URL conf, or set the `.lookup_field` ' 

+

                'attribute on the view correctly.' % 

+

                (self.__class__.__name__, self.lookup_field) 

+

            ) 

+

 

+

        obj = get_object_or_404(queryset, **filter_kwargs) 

+

 

+

        # May raise a permission denied 

+

        self.check_object_permissions(self.request, obj) 

+

 

+

        return obj 

+

 

+

    ######################## 

+

    ### The following are placeholder methods, 

+

    ### and are intended to be overridden. 

+

    ### 

+

    ### The are not called by GenericAPIView directly, 

+

    ### but are used by the mixin methods. 

+

 

+

    def pre_save(self, obj): 

+

        """ 

+

        Placeholder method for calling before saving an object. 

+

 

+

        May be used to set attributes on the object that are implicit 

+

        in either the request, or the url. 

+

        """ 

+

        pass 

+

 

+

    def post_save(self, obj, created=False): 

+

        """ 

+

        Placeholder method for calling after saving an object. 

+

        """ 

+

        pass 

+

 

+

    def metadata(self, request): 

+

        """ 

+

        Return a dictionary of metadata about the view. 

+

        Used to return responses for OPTIONS requests. 

+

 

+

        We override the default behavior, and add some extra information 

+

        about the required request body for POST and PUT operations. 

+

        """ 

+

        ret = super(GenericAPIView, self).metadata(request) 

+

 

+

        actions = {} 

+

        for method in ('PUT', 'POST'): 

+

            if method not in self.allowed_methods: 

+

                continue 

+

 

+

            cloned_request = clone_request(request, method) 

+

            try: 

+

                # Test global permissions 

+

                self.check_permissions(cloned_request) 

+

                # Test object permissions 

+

                if method == 'PUT': 

+

                    self.get_object() 

+

            except (exceptions.APIException, PermissionDenied, Http404): 

+

                pass 

+

            else: 

+

                # If user has appropriate permissions for the view, include 

+

                # appropriate metadata about the fields that should be supplied. 

+

                serializer = self.get_serializer() 

+

                actions[method] = serializer.metadata() 

+

 

+

        if actions: 

+

            ret['actions'] = actions 

+

 

+

        return ret 

+

 

+

 

+

########################################################## 

+

### Concrete view classes that provide method handlers ### 

+

### by composing the mixin classes with the base view. ### 

+

########################################################## 

+

 

+

class CreateAPIView(mixins.CreateModelMixin, 

+

                    GenericAPIView): 

+

 

+

    """ 

+

    Concrete view for creating a model instance. 

+

    """ 

+

    def post(self, request, *args, **kwargs): 

+

        return self.create(request, *args, **kwargs) 

+

 

+

 

+

class ListAPIView(mixins.ListModelMixin, 

+

                  GenericAPIView): 

+

    """ 

+

    Concrete view for listing a queryset. 

+

    """ 

+

    def get(self, request, *args, **kwargs): 

+

        return self.list(request, *args, **kwargs) 

+

 

+

 

+

class RetrieveAPIView(mixins.RetrieveModelMixin, 

+

                      GenericAPIView): 

+

    """ 

+

    Concrete view for retrieving a model instance. 

+

    """ 

+

    def get(self, request, *args, **kwargs): 

+

        return self.retrieve(request, *args, **kwargs) 

+

 

+

 

+

class DestroyAPIView(mixins.DestroyModelMixin, 

+

                     GenericAPIView): 

+

 

+

    """ 

+

    Concrete view for deleting a model instance. 

+

    """ 

+

    def delete(self, request, *args, **kwargs): 

+

        return self.destroy(request, *args, **kwargs) 

+

 

+

 

+

class UpdateAPIView(mixins.UpdateModelMixin, 

+

                    GenericAPIView): 

+

 

+

    """ 

+

    Concrete view for updating a model instance. 

+

    """ 

+

    def put(self, request, *args, **kwargs): 

+

        return self.update(request, *args, **kwargs) 

+

 

+

    def patch(self, request, *args, **kwargs): 

+

        return self.partial_update(request, *args, **kwargs) 

+

 

+

 

+

class ListCreateAPIView(mixins.ListModelMixin, 

+

                        mixins.CreateModelMixin, 

+

                        GenericAPIView): 

+

    """ 

+

    Concrete view for listing a queryset or creating a model instance. 

+

    """ 

+

    def get(self, request, *args, **kwargs): 

+

        return self.list(request, *args, **kwargs) 

+

 

+

    def post(self, request, *args, **kwargs): 

+

        return self.create(request, *args, **kwargs) 

+

 

+

 

+

class RetrieveUpdateAPIView(mixins.RetrieveModelMixin, 

+

                            mixins.UpdateModelMixin, 

+

                            GenericAPIView): 

+

    """ 

+

    Concrete view for retrieving, updating a model instance. 

+

    """ 

+

    def get(self, request, *args, **kwargs): 

+

        return self.retrieve(request, *args, **kwargs) 

+

 

+

    def put(self, request, *args, **kwargs): 

+

        return self.update(request, *args, **kwargs) 

+

 

+

    def patch(self, request, *args, **kwargs): 

+

        return self.partial_update(request, *args, **kwargs) 

+

 

+

 

+

class RetrieveDestroyAPIView(mixins.RetrieveModelMixin, 

+

                             mixins.DestroyModelMixin, 

+

                             GenericAPIView): 

+

    """ 

+

    Concrete view for retrieving or deleting a model instance. 

+

    """ 

+

    def get(self, request, *args, **kwargs): 

+

        return self.retrieve(request, *args, **kwargs) 

+

 

+

    def delete(self, request, *args, **kwargs): 

+

        return self.destroy(request, *args, **kwargs) 

+

 

+

 

+

class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, 

+

                                   mixins.UpdateModelMixin, 

+

                                   mixins.DestroyModelMixin, 

+

                                   GenericAPIView): 

+

    """ 

+

    Concrete view for retrieving, updating or deleting a model instance. 

+

    """ 

+

    def get(self, request, *args, **kwargs): 

+

        return self.retrieve(request, *args, **kwargs) 

+

 

+

    def put(self, request, *args, **kwargs): 

+

        return self.update(request, *args, **kwargs) 

+

 

+

    def patch(self, request, *args, **kwargs): 

+

        return self.partial_update(request, *args, **kwargs) 

+

 

+

    def delete(self, request, *args, **kwargs): 

+

        return self.destroy(request, *args, **kwargs) 

+

 

+

 

+

########################## 

+

### Deprecated classes ### 

+

########################## 

+

 

+

class MultipleObjectAPIView(GenericAPIView): 

+

    def __init__(self, *args, **kwargs): 

+

        warnings.warn( 

+

            'Subclassing `MultipleObjectAPIView` is due to be deprecated. ' 

+

            'You should simply subclass `GenericAPIView` instead.', 

+

            PendingDeprecationWarning, stacklevel=2 

+

        ) 

+

        super(MultipleObjectAPIView, self).__init__(*args, **kwargs) 

+

 

+

 

+

class SingleObjectAPIView(GenericAPIView): 

+

    def __init__(self, *args, **kwargs): 

+

        warnings.warn( 

+

            'Subclassing `SingleObjectAPIView` is due to be deprecated. ' 

+

            'You should simply subclass `GenericAPIView` instead.', 

+

            PendingDeprecationWarning, stacklevel=2 

+

        ) 

+

        super(SingleObjectAPIView, self).__init__(*args, **kwargs) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_mixins.html b/htmlcov/rest_framework_mixins.html new file mode 100644 index 000000000..fa62f2ae8 --- /dev/null +++ b/htmlcov/rest_framework_mixins.html @@ -0,0 +1,449 @@ + + + + + + + + Coverage for rest_framework/mixins: 93% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+ +
+

""" 

+

Basic building blocks for generic class based views. 

+

 

+

We don't bind behaviour to http method handlers yet, 

+

which allows mixin classes to be composed in interesting ways. 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

from django.http import Http404 

+

from rest_framework import status 

+

from rest_framework.response import Response 

+

from rest_framework.request import clone_request 

+

import warnings 

+

 

+

 

+

def _get_validation_exclusions(obj, pk=None, slug_field=None, lookup_field=None): 

+

    """ 

+

    Given a model instance, and an optional pk and slug field, 

+

    return the full list of all other field names on that model. 

+

 

+

    For use when performing full_clean on a model instance, 

+

    so we only clean the required fields. 

+

    """ 

+

    include = [] 

+

 

+

    if pk: 

+

        # Pending deprecation 

+

        pk_field = obj._meta.pk 

+

        while pk_field.rel: 

+

            pk_field = pk_field.rel.to._meta.pk 

+

        include.append(pk_field.name) 

+

 

+

    if slug_field: 

+

        # Pending deprecation 

+

        include.append(slug_field) 

+

 

+

    if lookup_field and lookup_field != 'pk': 

+

        include.append(lookup_field) 

+

 

+

    return [field.name for field in obj._meta.fields if field.name not in include] 

+

 

+

 

+

class CreateModelMixin(object): 

+

    """ 

+

    Create a model instance. 

+

    """ 

+

    def create(self, request, *args, **kwargs): 

+

        serializer = self.get_serializer(data=request.DATA, files=request.FILES) 

+

 

+

        if serializer.is_valid(): 

+

            self.pre_save(serializer.object) 

+

            self.object = serializer.save(force_insert=True) 

+

            self.post_save(self.object, created=True) 

+

            headers = self.get_success_headers(serializer.data) 

+

            return Response(serializer.data, status=status.HTTP_201_CREATED, 

+

                            headers=headers) 

+

 

+

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 

+

 

+

    def get_success_headers(self, data): 

+

        try: 

+

            return {'Location': data['url']} 

+

        except (TypeError, KeyError): 

+

            return {} 

+

 

+

 

+

class ListModelMixin(object): 

+

    """ 

+

    List a queryset. 

+

    """ 

+

    empty_error = "Empty list and '%(class_name)s.allow_empty' is False." 

+

 

+

    def list(self, request, *args, **kwargs): 

+

        self.object_list = self.filter_queryset(self.get_queryset()) 

+

 

+

        # Default is to allow empty querysets.  This can be altered by setting 

+

        # `.allow_empty = False`, to raise 404 errors on empty querysets. 

+

        if not self.allow_empty and not self.object_list: 

+

            warnings.warn( 

+

                'The `allow_empty` parameter is due to be deprecated. ' 

+

                'To use `allow_empty=False` style behavior, You should override ' 

+

                '`get_queryset()` and explicitly raise a 404 on empty querysets.', 

+

                PendingDeprecationWarning 

+

            ) 

+

            class_name = self.__class__.__name__ 

+

            error_msg = self.empty_error % {'class_name': class_name} 

+

            raise Http404(error_msg) 

+

 

+

        # Switch between paginated or standard style responses 

+

        page = self.paginate_queryset(self.object_list) 

+

        if page is not None: 

+

            serializer = self.get_pagination_serializer(page) 

+

        else: 

+

            serializer = self.get_serializer(self.object_list, many=True) 

+

 

+

        return Response(serializer.data) 

+

 

+

 

+

class RetrieveModelMixin(object): 

+

    """ 

+

    Retrieve a model instance. 

+

    """ 

+

    def retrieve(self, request, *args, **kwargs): 

+

        self.object = self.get_object() 

+

        serializer = self.get_serializer(self.object) 

+

        return Response(serializer.data) 

+

 

+

 

+

class UpdateModelMixin(object): 

+

    """ 

+

    Update a model instance. 

+

    """ 

+

    def update(self, request, *args, **kwargs): 

+

        partial = kwargs.pop('partial', False) 

+

        self.object = self.get_object_or_none() 

+

 

+

        if self.object is None: 

+

            created = True 

+

            save_kwargs = {'force_insert': True} 

+

            success_status_code = status.HTTP_201_CREATED 

+

        else: 

+

            created = False 

+

            save_kwargs = {'force_update': True} 

+

            success_status_code = status.HTTP_200_OK 

+

 

+

        serializer = self.get_serializer(self.object, data=request.DATA, 

+

                                         files=request.FILES, partial=partial) 

+

 

+

        if serializer.is_valid(): 

+

            self.pre_save(serializer.object) 

+

            self.object = serializer.save(**save_kwargs) 

+

            self.post_save(self.object, created=created) 

+

            return Response(serializer.data, status=success_status_code) 

+

 

+

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 

+

 

+

    def partial_update(self, request, *args, **kwargs): 

+

        kwargs['partial'] = True 

+

        return self.update(request, *args, **kwargs) 

+

 

+

    def get_object_or_none(self): 

+

        try: 

+

            return self.get_object() 

+

        except Http404: 

+

            # If this is a PUT-as-create operation, we need to ensure that 

+

            # we have relevant permissions, as if this was a POST request. 

+

            # This will either raise a PermissionDenied exception, 

+

            # or simply return None 

+

            self.check_permissions(clone_request(self.request, 'POST')) 

+

 

+

    def pre_save(self, obj): 

+

        """ 

+

        Set any attributes on the object that are implicit in the request. 

+

        """ 

+

        # pk and/or slug attributes are implicit in the URL. 

+

        lookup = self.kwargs.get(self.lookup_field, None) 

+

        pk = self.kwargs.get(self.pk_url_kwarg, None) 

+

        slug = self.kwargs.get(self.slug_url_kwarg, None) 

+

        slug_field = slug and self.slug_field or None 

+

 

+

        if lookup: 

+

            setattr(obj, self.lookup_field, lookup) 

+

 

+

        if pk: 

+

            setattr(obj, 'pk', pk) 

+

 

+

        if slug: 

+

            setattr(obj, slug_field, slug) 

+

 

+

        # Ensure we clean the attributes so that we don't eg return integer 

+

        # pk using a string representation, as provided by the url conf kwarg. 

+

        if hasattr(obj, 'full_clean'): 

+

            exclude = _get_validation_exclusions(obj, pk, slug_field, self.lookup_field) 

+

            obj.full_clean(exclude) 

+

 

+

 

+

class DestroyModelMixin(object): 

+

    """ 

+

    Destroy a model instance. 

+

    """ 

+

    def destroy(self, request, *args, **kwargs): 

+

        obj = self.get_object() 

+

        obj.delete() 

+

        return Response(status=status.HTTP_204_NO_CONTENT) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_models.html b/htmlcov/rest_framework_models.html new file mode 100644 index 000000000..6786c620a --- /dev/null +++ b/htmlcov/rest_framework_models.html @@ -0,0 +1,83 @@ + + + + + + + + Coverage for rest_framework/models: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+ +
+

# Just to keep things like ./manage.py test happy 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_negotiation.html b/htmlcov/rest_framework_negotiation.html new file mode 100644 index 000000000..7ed526c97 --- /dev/null +++ b/htmlcov/rest_framework_negotiation.html @@ -0,0 +1,259 @@ + + + + + + + + Coverage for rest_framework/negotiation: 90% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+ +
+

""" 

+

Content negotiation deals with selecting an appropriate renderer given the 

+

incoming request.  Typically this will be based on the request's Accept header. 

+

""" 

+

from __future__ import unicode_literals 

+

from django.http import Http404 

+

from rest_framework import exceptions 

+

from rest_framework.settings import api_settings 

+

from rest_framework.utils.mediatypes import order_by_precedence, media_type_matches 

+

from rest_framework.utils.mediatypes import _MediaType 

+

 

+

 

+

class BaseContentNegotiation(object): 

+

    def select_parser(self, request, parsers): 

+

        raise NotImplementedError('.select_parser() must be implemented') 

+

 

+

    def select_renderer(self, request, renderers, format_suffix=None): 

+

        raise NotImplementedError('.select_renderer() must be implemented') 

+

 

+

 

+

class DefaultContentNegotiation(BaseContentNegotiation): 

+

    settings = api_settings 

+

 

+

    def select_parser(self, request, parsers): 

+

        """ 

+

        Given a list of parsers and a media type, return the appropriate 

+

        parser to handle the incoming request. 

+

        """ 

+

        for parser in parsers: 

+

            if media_type_matches(parser.media_type, request.content_type): 

+

                return parser 

+

        return None 

+

 

+

    def select_renderer(self, request, renderers, format_suffix=None): 

+

        """ 

+

        Given a request and a list of renderers, return a two-tuple of: 

+

        (renderer, media type). 

+

        """ 

+

        # Allow URL style format override.  eg. "?format=json 

+

        format_query_param = self.settings.URL_FORMAT_OVERRIDE 

+

        format = format_suffix or request.QUERY_PARAMS.get(format_query_param) 

+

 

+

        if format: 

+

            renderers = self.filter_renderers(renderers, format) 

+

 

+

        accepts = self.get_accept_list(request) 

+

 

+

        # Check the acceptable media types against each renderer, 

+

        # attempting more specific media types first 

+

        # NB. The inner loop here isn't as bad as it first looks :) 

+

        #     Worst case is we're looping over len(accept_list) * len(self.renderers) 

+

        for media_type_set in order_by_precedence(accepts): 

+

            for renderer in renderers: 

+

                for media_type in media_type_set: 

+

                    if media_type_matches(renderer.media_type, media_type): 

+

                        # Return the most specific media type as accepted. 

+

                        if (_MediaType(renderer.media_type).precedence > 

+

                            _MediaType(media_type).precedence): 

+

                            # Eg client requests '*/*' 

+

                            # Accepted media type is 'application/json' 

+

                            return renderer, renderer.media_type 

+

                        else: 

+

                            # Eg client requests 'application/json; indent=8' 

+

                            # Accepted media type is 'application/json; indent=8' 

+

                            return renderer, media_type 

+

 

+

        raise exceptions.NotAcceptable(available_renderers=renderers) 

+

 

+

    def filter_renderers(self, renderers, format): 

+

        """ 

+

        If there is a '.json' style format suffix, filter the renderers 

+

        so that we only negotiation against those that accept that format. 

+

        """ 

+

        renderers = [renderer for renderer in renderers 

+

                     if renderer.format == format] 

+

        if not renderers: 

+

            raise Http404 

+

        return renderers 

+

 

+

    def get_accept_list(self, request): 

+

        """ 

+

        Given the incoming request, return a tokenised list of media 

+

        type strings. 

+

 

+

        Allows URL style accept override.  eg. "?accept=application/json" 

+

        """ 

+

        header = request.META.get('HTTP_ACCEPT', '*/*') 

+

        header = request.QUERY_PARAMS.get(self.settings.URL_ACCEPT_OVERRIDE, header) 

+

        return [token.strip() for token in header.split(',')] 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_pagination.html b/htmlcov/rest_framework_pagination.html new file mode 100644 index 000000000..5a3f76d82 --- /dev/null +++ b/htmlcov/rest_framework_pagination.html @@ -0,0 +1,269 @@ + + + + + + + + Coverage for rest_framework/pagination: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+ +
+

""" 

+

Pagination serializers determine the structure of the output that should 

+

be used for paginated responses. 

+

""" 

+

from __future__ import unicode_literals 

+

from rest_framework import serializers 

+

from rest_framework.templatetags.rest_framework import replace_query_param 

+

 

+

 

+

class NextPageField(serializers.Field): 

+

    """ 

+

    Field that returns a link to the next page in paginated results. 

+

    """ 

+

    page_field = 'page' 

+

 

+

    def to_native(self, value): 

+

        if not value.has_next(): 

+

            return None 

+

        page = value.next_page_number() 

+

        request = self.context.get('request') 

+

        url = request and request.build_absolute_uri() or '' 

+

        return replace_query_param(url, self.page_field, page) 

+

 

+

 

+

class PreviousPageField(serializers.Field): 

+

    """ 

+

    Field that returns a link to the previous page in paginated results. 

+

    """ 

+

    page_field = 'page' 

+

 

+

    def to_native(self, value): 

+

        if not value.has_previous(): 

+

            return None 

+

        page = value.previous_page_number() 

+

        request = self.context.get('request') 

+

        url = request and request.build_absolute_uri() or '' 

+

        return replace_query_param(url, self.page_field, page) 

+

 

+

 

+

class DefaultObjectSerializer(serializers.Field): 

+

    """ 

+

    If no object serializer is specified, then this serializer will be applied 

+

    as the default. 

+

    """ 

+

 

+

    def __init__(self, source=None, context=None): 

+

        # Note: Swallow context kwarg - only required for eg. ModelSerializer. 

+

        super(DefaultObjectSerializer, self).__init__(source=source) 

+

 

+

 

+

class PaginationSerializerOptions(serializers.SerializerOptions): 

+

    """ 

+

    An object that stores the options that may be provided to a 

+

    pagination serializer by using the inner `Meta` class. 

+

 

+

    Accessible on the instance as `serializer.opts`. 

+

    """ 

+

    def __init__(self, meta): 

+

        super(PaginationSerializerOptions, self).__init__(meta) 

+

        self.object_serializer_class = getattr(meta, 'object_serializer_class', 

+

                                               DefaultObjectSerializer) 

+

 

+

 

+

class BasePaginationSerializer(serializers.Serializer): 

+

    """ 

+

    A base class for pagination serializers to inherit from, 

+

    to make implementing custom serializers more easy. 

+

    """ 

+

    _options_class = PaginationSerializerOptions 

+

    results_field = 'results' 

+

 

+

    def __init__(self, *args, **kwargs): 

+

        """ 

+

        Override init to add in the object serializer field on-the-fly. 

+

        """ 

+

        super(BasePaginationSerializer, self).__init__(*args, **kwargs) 

+

        results_field = self.results_field 

+

        object_serializer = self.opts.object_serializer_class 

+

 

+

        if 'context' in kwargs: 

+

            context_kwarg = {'context': kwargs['context']} 

+

        else: 

+

            context_kwarg = {} 

+

 

+

        self.fields[results_field] = object_serializer(source='object_list', **context_kwarg) 

+

 

+

 

+

class PaginationSerializer(BasePaginationSerializer): 

+

    """ 

+

    A default implementation of a pagination serializer. 

+

    """ 

+

    count = serializers.Field(source='paginator.count') 

+

    next = NextPageField(source='*') 

+

    previous = PreviousPageField(source='*') 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_parsers.html b/htmlcov/rest_framework_parsers.html new file mode 100644 index 000000000..92f1db62d --- /dev/null +++ b/htmlcov/rest_framework_parsers.html @@ -0,0 +1,671 @@ + + + + + + + + Coverage for rest_framework/parsers: 92% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+ +
+

""" 

+

Parsers are used to parse the content of incoming HTTP requests. 

+

 

+

They give us a generic way of being able to handle various media types 

+

on the request, such as form content or json encoded data. 

+

""" 

+

from __future__ import unicode_literals 

+

from django.conf import settings 

+

from django.core.files.uploadhandler import StopFutureHandlers 

+

from django.http import QueryDict 

+

from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser 

+

from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter 

+

from rest_framework.compat import yaml, etree 

+

from rest_framework.exceptions import ParseError 

+

from rest_framework.compat import six 

+

import json 

+

import datetime 

+

import decimal 

+

 

+

 

+

class DataAndFiles(object): 

+

    def __init__(self, data, files): 

+

        self.data = data 

+

        self.files = files 

+

 

+

 

+

class BaseParser(object): 

+

    """ 

+

    All parsers should extend `BaseParser`, specifying a `media_type` 

+

    attribute, and overriding the `.parse()` method. 

+

    """ 

+

 

+

    media_type = None 

+

 

+

    def parse(self, stream, media_type=None, parser_context=None): 

+

        """ 

+

        Given a stream to read from, return the parsed representation. 

+

        Should return parsed data, or a `DataAndFiles` object consisting of the 

+

        parsed data and files. 

+

        """ 

+

        raise NotImplementedError(".parse() must be overridden.") 

+

 

+

 

+

class JSONParser(BaseParser): 

+

    """ 

+

    Parses JSON-serialized data. 

+

    """ 

+

 

+

    media_type = 'application/json' 

+

 

+

    def parse(self, stream, media_type=None, parser_context=None): 

+

        """ 

+

        Returns a 2-tuple of `(data, files)`. 

+

 

+

        `data` will be an object which is the parsed content of the response. 

+

        `files` will always be `None`. 

+

        """ 

+

        parser_context = parser_context or {} 

+

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

+

 

+

        try: 

+

            data = stream.read().decode(encoding) 

+

            return json.loads(data) 

+

        except ValueError as exc: 

+

            raise ParseError('JSON parse error - %s' % six.text_type(exc)) 

+

 

+

 

+

class YAMLParser(BaseParser): 

+

    """ 

+

    Parses YAML-serialized data. 

+

    """ 

+

 

+

    media_type = 'application/yaml' 

+

 

+

    def parse(self, stream, media_type=None, parser_context=None): 

+

        """ 

+

        Returns a 2-tuple of `(data, files)`. 

+

 

+

        `data` will be an object which is the parsed content of the response. 

+

        `files` will always be `None`. 

+

        """ 

+

        assert yaml, 'YAMLParser requires pyyaml to be installed' 

+

 

+

        parser_context = parser_context or {} 

+

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

+

 

+

        try: 

+

            data = stream.read().decode(encoding) 

+

            return yaml.safe_load(data) 

+

        except (ValueError, yaml.parser.ParserError) as exc: 

+

            raise ParseError('YAML parse error - %s' % six.u(exc)) 

+

 

+

 

+

class FormParser(BaseParser): 

+

    """ 

+

    Parser for form data. 

+

    """ 

+

 

+

    media_type = 'application/x-www-form-urlencoded' 

+

 

+

    def parse(self, stream, media_type=None, parser_context=None): 

+

        """ 

+

        Returns a 2-tuple of `(data, files)`. 

+

 

+

        `data` will be a :class:`QueryDict` containing all the form parameters. 

+

        `files` will always be :const:`None`. 

+

        """ 

+

        parser_context = parser_context or {} 

+

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

+

        data = QueryDict(stream.read(), encoding=encoding) 

+

        return data 

+

 

+

 

+

class MultiPartParser(BaseParser): 

+

    """ 

+

    Parser for multipart form data, which may include file data. 

+

    """ 

+

 

+

    media_type = 'multipart/form-data' 

+

 

+

    def parse(self, stream, media_type=None, parser_context=None): 

+

        """ 

+

        Returns a DataAndFiles object. 

+

 

+

        `.data` will be a `QueryDict` containing all the form parameters. 

+

        `.files` will be a `QueryDict` containing all the form files. 

+

        """ 

+

        parser_context = parser_context or {} 

+

        request = parser_context['request'] 

+

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

+

        meta = request.META 

+

        upload_handlers = request.upload_handlers 

+

 

+

        try: 

+

            parser = DjangoMultiPartParser(meta, stream, upload_handlers, encoding) 

+

            data, files = parser.parse() 

+

            return DataAndFiles(data, files) 

+

        except MultiPartParserError as exc: 

+

            raise ParseError('Multipart form parse error - %s' % six.u(exc)) 

+

 

+

 

+

class XMLParser(BaseParser): 

+

    """ 

+

    XML parser. 

+

    """ 

+

 

+

    media_type = 'application/xml' 

+

 

+

    def parse(self, stream, media_type=None, parser_context=None): 

+

        assert etree, 'XMLParser requires defusedxml to be installed' 

+

 

+

        parser_context = parser_context or {} 

+

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

+

        parser = etree.DefusedXMLParser(encoding=encoding) 

+

        try: 

+

            tree = etree.parse(stream, parser=parser, forbid_dtd=True) 

+

        except (etree.ParseError, ValueError) as exc: 

+

            raise ParseError('XML parse error - %s' % six.u(exc)) 

+

        data = self._xml_convert(tree.getroot()) 

+

 

+

        return data 

+

 

+

    def _xml_convert(self, element): 

+

        """ 

+

        convert the xml `element` into the corresponding python object 

+

        """ 

+

 

+

        children = list(element) 

+

 

+

        if len(children) == 0: 

+

            return self._type_convert(element.text) 

+

        else: 

+

            # if the fist child tag is list-item means all children are list-item 

+

            if children[0].tag == "list-item": 

+

                data = [] 

+

                for child in children: 

+

                    data.append(self._xml_convert(child)) 

+

            else: 

+

                data = {} 

+

                for child in children: 

+

                    data[child.tag] = self._xml_convert(child) 

+

 

+

            return data 

+

 

+

    def _type_convert(self, value): 

+

        """ 

+

        Converts the value returned by the XMl parse into the equivalent 

+

        Python type 

+

        """ 

+

        if value is None: 

+

            return value 

+

 

+

        try: 

+

            return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S') 

+

        except ValueError: 

+

            pass 

+

 

+

        try: 

+

            return int(value) 

+

        except ValueError: 

+

            pass 

+

 

+

        try: 

+

            return decimal.Decimal(value) 

+

        except decimal.InvalidOperation: 

+

            pass 

+

 

+

        return value 

+

 

+

 

+

class FileUploadParser(BaseParser): 

+

    """ 

+

    Parser for file upload data. 

+

    """ 

+

    media_type = '*/*' 

+

 

+

    def parse(self, stream, media_type=None, parser_context=None): 

+

        """ 

+

        Returns a DataAndFiles object. 

+

 

+

        `.data` will be None (we expect request body to be a file content). 

+

        `.files` will be a `QueryDict` containing one 'file' element. 

+

        """ 

+

 

+

        parser_context = parser_context or {} 

+

        request = parser_context['request'] 

+

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

+

        meta = request.META 

+

        upload_handlers = request.upload_handlers 

+

        filename = self.get_filename(stream, media_type, parser_context) 

+

 

+

        # Note that this code is extracted from Django's handling of 

+

        # file uploads in MultiPartParser. 

+

        content_type = meta.get('HTTP_CONTENT_TYPE', 

+

                                meta.get('CONTENT_TYPE', '')) 

+

        try: 

+

            content_length = int(meta.get('HTTP_CONTENT_LENGTH', 

+

                                          meta.get('CONTENT_LENGTH', 0))) 

+

        except (ValueError, TypeError): 

+

            content_length = None 

+

 

+

        # See if the handler will want to take care of the parsing. 

+

        for handler in upload_handlers: 

+

            result = handler.handle_raw_input(None, 

+

                                              meta, 

+

                                              content_length, 

+

                                              None, 

+

                                              encoding) 

+

            if result is not None: 

+

                return DataAndFiles(None, {'file': result[1]}) 

+

 

+

        # This is the standard case. 

+

        possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size] 

+

        chunk_size = min([2 ** 31 - 4] + possible_sizes) 

+

        chunks = ChunkIter(stream, chunk_size) 

+

        counters = [0] * len(upload_handlers) 

+

 

+

        for handler in upload_handlers: 

+

            try: 

+

                handler.new_file(None, filename, content_type, 

+

                                 content_length, encoding) 

+

            except StopFutureHandlers: 

+

                break 

+

 

+

        for chunk in chunks: 

+

            for i, handler in enumerate(upload_handlers): 

+

                chunk_length = len(chunk) 

+

                chunk = handler.receive_data_chunk(chunk, counters[i]) 

+

                counters[i] += chunk_length 

+

                if chunk is None: 

+

                    break 

+

 

+

        for i, handler in enumerate(upload_handlers): 

+

            file_obj = handler.file_complete(counters[i]) 

+

            if file_obj: 

+

                return DataAndFiles(None, {'file': file_obj}) 

+

        raise ParseError("FileUpload parse error - " 

+

                         "none of upload handlers can handle the stream") 

+

 

+

    def get_filename(self, stream, media_type, parser_context): 

+

        """ 

+

        Detects the uploaded file name. First searches a 'filename' url kwarg. 

+

        Then tries to parse Content-Disposition header. 

+

        """ 

+

        try: 

+

            return parser_context['kwargs']['filename'] 

+

        except KeyError: 

+

            pass 

+

 

+

        try: 

+

            meta = parser_context['request'].META 

+

            disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION']) 

+

            return disposition[1]['filename'] 

+

        except (AttributeError, KeyError): 

+

            pass 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_permissions.html b/htmlcov/rest_framework_permissions.html new file mode 100644 index 000000000..20a29522b --- /dev/null +++ b/htmlcov/rest_framework_permissions.html @@ -0,0 +1,429 @@ + + + + + + + + Coverage for rest_framework/permissions: 81% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+ +
+

""" 

+

Provides a set of pluggable permission policies. 

+

""" 

+

from __future__ import unicode_literals 

+

import inspect 

+

import warnings 

+

 

+

SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS'] 

+

 

+

from rest_framework.compat import oauth2_provider_scope, oauth2_constants 

+

 

+

 

+

class BasePermission(object): 

+

    """ 

+

    A base class from which all permission classes should inherit. 

+

    """ 

+

 

+

    def has_permission(self, request, view): 

+

        """ 

+

        Return `True` if permission is granted, `False` otherwise. 

+

        """ 

+

        return True 

+

 

+

    def has_object_permission(self, request, view, obj): 

+

        """ 

+

        Return `True` if permission is granted, `False` otherwise. 

+

        """ 

+

        if len(inspect.getargspec(self.has_permission).args) == 4: 

+

            warnings.warn( 

+

                'The `obj` argument in `has_permission` is deprecated. ' 

+

                'Use `has_object_permission()` instead for object permissions.', 

+

                DeprecationWarning, stacklevel=2 

+

            ) 

+

            return self.has_permission(request, view, obj) 

+

        return True 

+

 

+

 

+

class AllowAny(BasePermission): 

+

    """ 

+

    Allow any access. 

+

    This isn't strictly required, since you could use an empty 

+

    permission_classes list, but it's useful because it makes the intention 

+

    more explicit. 

+

    """ 

+

    def has_permission(self, request, view): 

+

        return True 

+

 

+

 

+

class IsAuthenticated(BasePermission): 

+

    """ 

+

    Allows access only to authenticated users. 

+

    """ 

+

 

+

    def has_permission(self, request, view): 

+

        if request.user and request.user.is_authenticated(): 

+

            return True 

+

        return False 

+

 

+

 

+

class IsAdminUser(BasePermission): 

+

    """ 

+

    Allows access only to admin users. 

+

    """ 

+

 

+

    def has_permission(self, request, view): 

+

        if request.user and request.user.is_staff: 

+

            return True 

+

        return False 

+

 

+

 

+

class IsAuthenticatedOrReadOnly(BasePermission): 

+

    """ 

+

    The request is authenticated as a user, or is a read-only request. 

+

    """ 

+

 

+

    def has_permission(self, request, view): 

+

        if (request.method in SAFE_METHODS or 

+

            request.user and 

+

            request.user.is_authenticated()): 

+

            return True 

+

        return False 

+

 

+

 

+

class DjangoModelPermissions(BasePermission): 

+

    """ 

+

    The request is authenticated using `django.contrib.auth` permissions. 

+

    See: https://docs.djangoproject.com/en/dev/topics/auth/#permissions 

+

 

+

    It ensures that the user is authenticated, and has the appropriate 

+

    `add`/`change`/`delete` permissions on the model. 

+

 

+

    This permission can only be applied against view classes that 

+

    provide a `.model` or `.queryset` attribute. 

+

    """ 

+

 

+

    # Map methods into required permission codes. 

+

    # Override this if you need to also provide 'view' permissions, 

+

    # or if you want to provide custom permission codes. 

+

    perms_map = { 

+

        'GET': [], 

+

        'OPTIONS': [], 

+

        'HEAD': [], 

+

        'POST': ['%(app_label)s.add_%(model_name)s'], 

+

        'PUT': ['%(app_label)s.change_%(model_name)s'], 

+

        'PATCH': ['%(app_label)s.change_%(model_name)s'], 

+

        'DELETE': ['%(app_label)s.delete_%(model_name)s'], 

+

    } 

+

 

+

    authenticated_users_only = True 

+

 

+

    def get_required_permissions(self, method, model_cls): 

+

        """ 

+

        Given a model and an HTTP method, return the list of permission 

+

        codes that the user is required to have. 

+

        """ 

+

        kwargs = { 

+

            'app_label': model_cls._meta.app_label, 

+

            'model_name': model_cls._meta.module_name 

+

        } 

+

        return [perm % kwargs for perm in self.perms_map[method]] 

+

 

+

    def has_permission(self, request, view): 

+

        model_cls = getattr(view, 'model', None) 

+

        queryset = getattr(view, 'queryset', None) 

+

 

+

        if model_cls is None and queryset is not None: 

+

            model_cls = queryset.model 

+

 

+

        # Workaround to ensure DjangoModelPermissions are not applied 

+

        # to the root view when using DefaultRouter. 

+

        if model_cls is None and getattr(view, '_ignore_model_permissions', False): 

+

            return True 

+

 

+

        assert model_cls, ('Cannot apply DjangoModelPermissions on a view that' 

+

                           ' does not have `.model` or `.queryset` property.') 

+

 

+

        perms = self.get_required_permissions(request.method, model_cls) 

+

 

+

        if (request.user and 

+

            (request.user.is_authenticated() or not self.authenticated_users_only) and 

+

            request.user.has_perms(perms)): 

+

            return True 

+

        return False 

+

 

+

 

+

class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions): 

+

    """ 

+

    Similar to DjangoModelPermissions, except that anonymous users are 

+

    allowed read-only access. 

+

    """ 

+

    authenticated_users_only = False 

+

 

+

 

+

class TokenHasReadWriteScope(BasePermission): 

+

    """ 

+

    The request is authenticated as a user and the token used has the right scope 

+

    """ 

+

 

+

    def has_permission(self, request, view): 

+

        token = request.auth 

+

        read_only = request.method in SAFE_METHODS 

+

 

+

        if not token: 

+

            return False 

+

 

+

        if hasattr(token, 'resource'):  # OAuth 1 

+

            return read_only or not request.auth.resource.is_readonly 

+

        elif hasattr(token, 'scope'):  # OAuth 2 

+

            required = oauth2_constants.READ if read_only else oauth2_constants.WRITE 

+

            return oauth2_provider_scope.check(required, request.auth.scope) 

+

 

+

        assert False, ('TokenHasReadWriteScope requires either the' 

+

        '`OAuthAuthentication` or `OAuth2Authentication` authentication ' 

+

        'class to be used.') 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_relations.html b/htmlcov/rest_framework_relations.html new file mode 100644 index 000000000..29ad3cf65 --- /dev/null +++ b/htmlcov/rest_framework_relations.html @@ -0,0 +1,1347 @@ + + + + + + + + Coverage for rest_framework/relations: 76% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+

296

+

297

+

298

+

299

+

300

+

301

+

302

+

303

+

304

+

305

+

306

+

307

+

308

+

309

+

310

+

311

+

312

+

313

+

314

+

315

+

316

+

317

+

318

+

319

+

320

+

321

+

322

+

323

+

324

+

325

+

326

+

327

+

328

+

329

+

330

+

331

+

332

+

333

+

334

+

335

+

336

+

337

+

338

+

339

+

340

+

341

+

342

+

343

+

344

+

345

+

346

+

347

+

348

+

349

+

350

+

351

+

352

+

353

+

354

+

355

+

356

+

357

+

358

+

359

+

360

+

361

+

362

+

363

+

364

+

365

+

366

+

367

+

368

+

369

+

370

+

371

+

372

+

373

+

374

+

375

+

376

+

377

+

378

+

379

+

380

+

381

+

382

+

383

+

384

+

385

+

386

+

387

+

388

+

389

+

390

+

391

+

392

+

393

+

394

+

395

+

396

+

397

+

398

+

399

+

400

+

401

+

402

+

403

+

404

+

405

+

406

+

407

+

408

+

409

+

410

+

411

+

412

+

413

+

414

+

415

+

416

+

417

+

418

+

419

+

420

+

421

+

422

+

423

+

424

+

425

+

426

+

427

+

428

+

429

+

430

+

431

+

432

+

433

+

434

+

435

+

436

+

437

+

438

+

439

+

440

+

441

+

442

+

443

+

444

+

445

+

446

+

447

+

448

+

449

+

450

+

451

+

452

+

453

+

454

+

455

+

456

+

457

+

458

+

459

+

460

+

461

+

462

+

463

+

464

+

465

+

466

+

467

+

468

+

469

+

470

+

471

+

472

+

473

+

474

+

475

+

476

+

477

+

478

+

479

+

480

+

481

+

482

+

483

+

484

+

485

+

486

+

487

+

488

+

489

+

490

+

491

+

492

+

493

+

494

+

495

+

496

+

497

+

498

+

499

+

500

+

501

+

502

+

503

+

504

+

505

+

506

+

507

+

508

+

509

+

510

+

511

+

512

+

513

+

514

+

515

+

516

+

517

+

518

+

519

+

520

+

521

+

522

+

523

+

524

+

525

+

526

+

527

+

528

+

529

+

530

+

531

+

532

+

533

+

534

+

535

+

536

+

537

+

538

+

539

+

540

+

541

+

542

+

543

+

544

+

545

+

546

+

547

+

548

+

549

+

550

+

551

+

552

+

553

+

554

+

555

+

556

+

557

+

558

+

559

+

560

+

561

+

562

+

563

+

564

+

565

+

566

+

567

+

568

+

569

+

570

+

571

+

572

+

573

+

574

+

575

+

576

+

577

+

578

+

579

+

580

+

581

+

582

+

583

+

584

+

585

+

586

+

587

+

588

+

589

+

590

+

591

+

592

+

593

+

594

+

595

+

596

+

597

+

598

+

599

+

600

+

601

+

602

+

603

+

604

+

605

+

606

+

607

+

608

+

609

+

610

+

611

+

612

+

613

+

614

+

615

+

616

+

617

+

618

+

619

+

620

+

621

+

622

+

623

+

624

+

625

+

626

+

627

+

628

+

629

+

630

+

631

+

632

+

633

+ +
+

""" 

+

Serializer fields that deal with relationships. 

+

 

+

These fields allow you to specify the style that should be used to represent 

+

model relationships, including hyperlinks, primary keys, or slugs. 

+

""" 

+

from __future__ import unicode_literals 

+

from django.core.exceptions import ObjectDoesNotExist, ValidationError 

+

from django.core.urlresolvers import resolve, get_script_prefix, NoReverseMatch 

+

from django import forms 

+

from django.db.models.fields import BLANK_CHOICE_DASH 

+

from django.forms import widgets 

+

from django.forms.models import ModelChoiceIterator 

+

from django.utils.translation import ugettext_lazy as _ 

+

from rest_framework.fields import Field, WritableField, get_component, is_simple_callable 

+

from rest_framework.reverse import reverse 

+

from rest_framework.compat import urlparse 

+

from rest_framework.compat import smart_text 

+

import warnings 

+

 

+

 

+

##### Relational fields ##### 

+

 

+

 

+

# Not actually Writable, but subclasses may need to be. 

+

class RelatedField(WritableField): 

+

    """ 

+

    Base class for related model fields. 

+

 

+

    This represents a relationship using the unicode representation of the target. 

+

    """ 

+

    widget = widgets.Select 

+

    many_widget = widgets.SelectMultiple 

+

    form_field_class = forms.ChoiceField 

+

    many_form_field_class = forms.MultipleChoiceField 

+

 

+

    cache_choices = False 

+

    empty_label = None 

+

    read_only = True 

+

    many = False 

+

 

+

    def __init__(self, *args, **kwargs): 

+

 

+

        # 'null' is to be deprecated in favor of 'required' 

+

        if 'null' in kwargs: 

+

            warnings.warn('The `null` keyword argument is deprecated. ' 

+

                          'Use the `required` keyword argument instead.', 

+

                          DeprecationWarning, stacklevel=2) 

+

            kwargs['required'] = not kwargs.pop('null') 

+

 

+

        queryset = kwargs.pop('queryset', None) 

+

        self.many = kwargs.pop('many', self.many) 

+

        if self.many: 

+

            self.widget = self.many_widget 

+

            self.form_field_class = self.many_form_field_class 

+

 

+

        kwargs['read_only'] = kwargs.pop('read_only', self.read_only) 

+

        super(RelatedField, self).__init__(*args, **kwargs) 

+

 

+

        if not self.required: 

+

            self.empty_label = BLANK_CHOICE_DASH[0][1] 

+

 

+

        self.queryset = queryset 

+

 

+

    def initialize(self, parent, field_name): 

+

        super(RelatedField, self).initialize(parent, field_name) 

+

        if self.queryset is None and not self.read_only: 

+

            try: 

+

                manager = getattr(self.parent.opts.model, self.source or field_name) 

+

                if hasattr(manager, 'related'):  # Forward 

+

                    self.queryset = manager.related.model._default_manager.all() 

+

                else:  # Reverse 

+

                    self.queryset = manager.field.rel.to._default_manager.all() 

+

            except Exception: 

+

                msg = ('Serializer related fields must include a `queryset`' + 

+

                       ' argument or set `read_only=True') 

+

                raise Exception(msg) 

+

 

+

    ### We need this stuff to make form choices work... 

+

 

+

    def prepare_value(self, obj): 

+

        return self.to_native(obj) 

+

 

+

    def label_from_instance(self, obj): 

+

        """ 

+

        Return a readable representation for use with eg. select widgets. 

+

        """ 

+

        desc = smart_text(obj) 

+

        ident = smart_text(self.to_native(obj)) 

+

        if desc == ident: 

+

            return desc 

+

        return "%s - %s" % (desc, ident) 

+

 

+

    def _get_queryset(self): 

+

        return self._queryset 

+

 

+

    def _set_queryset(self, queryset): 

+

        self._queryset = queryset 

+

        self.widget.choices = self.choices 

+

 

+

    queryset = property(_get_queryset, _set_queryset) 

+

 

+

    def _get_choices(self): 

+

        # If self._choices is set, then somebody must have manually set 

+

        # the property self.choices. In this case, just return self._choices. 

+

        if hasattr(self, '_choices'): 

+

            return self._choices 

+

 

+

        # Otherwise, execute the QuerySet in self.queryset to determine the 

+

        # choices dynamically. Return a fresh ModelChoiceIterator that has not been 

+

        # consumed. Note that we're instantiating a new ModelChoiceIterator *each* 

+

        # time _get_choices() is called (and, thus, each time self.choices is 

+

        # accessed) so that we can ensure the QuerySet has not been consumed. This 

+

        # construct might look complicated but it allows for lazy evaluation of 

+

        # the queryset. 

+

        return ModelChoiceIterator(self) 

+

 

+

    def _set_choices(self, value): 

+

        # Setting choices also sets the choices on the widget. 

+

        # choices can be any iterable, but we call list() on it because 

+

        # it will be consumed more than once. 

+

        self._choices = self.widget.choices = list(value) 

+

 

+

    choices = property(_get_choices, _set_choices) 

+

 

+

    ### Regular serializer stuff... 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        try: 

+

            if self.source == '*': 

+

                return self.to_native(obj) 

+

 

+

            source = self.source or field_name 

+

            value = obj 

+

 

+

            for component in source.split('.'): 

+

                value = get_component(value, component) 

+

                if value is None: 

+

                    break 

+

        except ObjectDoesNotExist: 

+

            return None 

+

 

+

        if value is None: 

+

            return None 

+

 

+

        if self.many: 

+

            if is_simple_callable(getattr(value, 'all', None)): 

+

                return [self.to_native(item) for item in value.all()] 

+

            else: 

+

                # Also support non-queryset iterables. 

+

                # This allows us to also support plain lists of related items. 

+

                return [self.to_native(item) for item in value] 

+

        return self.to_native(value) 

+

 

+

    def field_from_native(self, data, files, field_name, into): 

+

        if self.read_only: 

+

            return 

+

 

+

        try: 

+

            if self.many: 

+

                try: 

+

                    # Form data 

+

                    value = data.getlist(field_name) 

+

                    if value == [''] or value == []: 

+

                        raise KeyError 

+

                except AttributeError: 

+

                    # Non-form data 

+

                    value = data[field_name] 

+

            else: 

+

                value = data[field_name] 

+

        except KeyError: 

+

            if self.partial: 

+

                return 

+

            value = [] if self.many else None 

+

 

+

        if value in (None, '') and self.required: 

+

            raise ValidationError(self.error_messages['required']) 

+

        elif value in (None, ''): 

+

            into[(self.source or field_name)] = None 

+

        elif self.many: 

+

            into[(self.source or field_name)] = [self.from_native(item) for item in value] 

+

        else: 

+

            into[(self.source or field_name)] = self.from_native(value) 

+

 

+

 

+

### PrimaryKey relationships 

+

 

+

class PrimaryKeyRelatedField(RelatedField): 

+

    """ 

+

    Represents a relationship as a pk value. 

+

    """ 

+

    read_only = False 

+

 

+

    default_error_messages = { 

+

        'does_not_exist': _("Invalid pk '%s' - object does not exist."), 

+

        'incorrect_type': _('Incorrect type.  Expected pk value, received %s.'), 

+

    } 

+

 

+

    # TODO: Remove these field hacks... 

+

    def prepare_value(self, obj): 

+

        return self.to_native(obj.pk) 

+

 

+

    def label_from_instance(self, obj): 

+

        """ 

+

        Return a readable representation for use with eg. select widgets. 

+

        """ 

+

        desc = smart_text(obj) 

+

        ident = smart_text(self.to_native(obj.pk)) 

+

        if desc == ident: 

+

            return desc 

+

        return "%s - %s" % (desc, ident) 

+

 

+

    # TODO: Possibly change this to just take `obj`, through prob less performant 

+

    def to_native(self, pk): 

+

        return pk 

+

 

+

    def from_native(self, data): 

+

        if self.queryset is None: 

+

            raise Exception('Writable related fields must include a `queryset` argument') 

+

 

+

        try: 

+

            return self.queryset.get(pk=data) 

+

        except ObjectDoesNotExist: 

+

            msg = self.error_messages['does_not_exist'] % smart_text(data) 

+

            raise ValidationError(msg) 

+

        except (TypeError, ValueError): 

+

            received = type(data).__name__ 

+

            msg = self.error_messages['incorrect_type'] % received 

+

            raise ValidationError(msg) 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        if self.many: 

+

            # To-many relationship 

+

 

+

            queryset = None 

+

            if not self.source: 

+

                # Prefer obj.serializable_value for performance reasons 

+

                try: 

+

                    queryset = obj.serializable_value(field_name) 

+

                except AttributeError: 

+

                    pass 

+

            if queryset is None: 

+

                # RelatedManager (reverse relationship) 

+

                source = self.source or field_name 

+

                queryset = obj 

+

                for component in source.split('.'): 

+

                    queryset = get_component(queryset, component) 

+

 

+

            # Forward relationship 

+

            if is_simple_callable(getattr(queryset, 'all', None)): 

+

                return [self.to_native(item.pk) for item in queryset.all()] 

+

            else: 

+

                # Also support non-queryset iterables. 

+

                # This allows us to also support plain lists of related items. 

+

                return [self.to_native(item.pk) for item in queryset] 

+

 

+

        # To-one relationship 

+

        try: 

+

            # Prefer obj.serializable_value for performance reasons 

+

            pk = obj.serializable_value(self.source or field_name) 

+

        except AttributeError: 

+

            # RelatedObject (reverse relationship) 

+

            try: 

+

                pk = getattr(obj, self.source or field_name).pk 

+

            except ObjectDoesNotExist: 

+

                return None 

+

 

+

        # Forward relationship 

+

        return self.to_native(pk) 

+

 

+

 

+

### Slug relationships 

+

 

+

 

+

class SlugRelatedField(RelatedField): 

+

    """ 

+

    Represents a relationship using a unique field on the target. 

+

    """ 

+

    read_only = False 

+

 

+

    default_error_messages = { 

+

        'does_not_exist': _("Object with %s=%s does not exist."), 

+

        'invalid': _('Invalid value.'), 

+

    } 

+

 

+

    def __init__(self, *args, **kwargs): 

+

        self.slug_field = kwargs.pop('slug_field', None) 

+

        assert self.slug_field, 'slug_field is required' 

+

        super(SlugRelatedField, self).__init__(*args, **kwargs) 

+

 

+

    def to_native(self, obj): 

+

        return getattr(obj, self.slug_field) 

+

 

+

    def from_native(self, data): 

+

        if self.queryset is None: 

+

            raise Exception('Writable related fields must include a `queryset` argument') 

+

 

+

        try: 

+

            return self.queryset.get(**{self.slug_field: data}) 

+

        except ObjectDoesNotExist: 

+

            raise ValidationError(self.error_messages['does_not_exist'] % 

+

                                  (self.slug_field, smart_text(data))) 

+

        except (TypeError, ValueError): 

+

            msg = self.error_messages['invalid'] 

+

            raise ValidationError(msg) 

+

 

+

 

+

### Hyperlinked relationships 

+

 

+

class HyperlinkedRelatedField(RelatedField): 

+

    """ 

+

    Represents a relationship using hyperlinking. 

+

    """ 

+

    read_only = False 

+

    lookup_field = 'pk' 

+

 

+

    default_error_messages = { 

+

        'no_match': _('Invalid hyperlink - No URL match'), 

+

        'incorrect_match': _('Invalid hyperlink - Incorrect URL match'), 

+

        'configuration_error': _('Invalid hyperlink due to configuration error'), 

+

        'does_not_exist': _("Invalid hyperlink - object does not exist."), 

+

        'incorrect_type': _('Incorrect type.  Expected url string, received %s.'), 

+

    } 

+

 

+

    # These are all pending deprecation 

+

    pk_url_kwarg = 'pk' 

+

    slug_field = 'slug' 

+

    slug_url_kwarg = None  # Defaults to same as `slug_field` unless overridden 

+

 

+

    def __init__(self, *args, **kwargs): 

+

        try: 

+

            self.view_name = kwargs.pop('view_name') 

+

        except KeyError: 

+

            raise ValueError("Hyperlinked field requires 'view_name' kwarg") 

+

 

+

        self.lookup_field = kwargs.pop('lookup_field', self.lookup_field) 

+

        self.format = kwargs.pop('format', None) 

+

 

+

        # These are pending deprecation 

+

        if 'pk_url_kwarg' in kwargs: 

+

            msg = 'pk_url_kwarg is pending deprecation. Use lookup_field instead.' 

+

            warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

+

        if 'slug_url_kwarg' in kwargs: 

+

            msg = 'slug_url_kwarg is pending deprecation. Use lookup_field instead.' 

+

            warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

+

        if 'slug_field' in kwargs: 

+

            msg = 'slug_field is pending deprecation. Use lookup_field instead.' 

+

            warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

+

 

+

        self.pk_url_kwarg = kwargs.pop('pk_url_kwarg', self.pk_url_kwarg) 

+

        self.slug_field = kwargs.pop('slug_field', self.slug_field) 

+

        default_slug_kwarg = self.slug_url_kwarg or self.slug_field 

+

        self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', default_slug_kwarg) 

+

 

+

        super(HyperlinkedRelatedField, self).__init__(*args, **kwargs) 

+

 

+

    def get_url(self, obj, view_name, request, format): 

+

        """ 

+

        Given an object, return the URL that hyperlinks to the object. 

+

 

+

        May raise a `NoReverseMatch` if the `view_name` and `lookup_field` 

+

        attributes are not configured to correctly match the URL conf. 

+

        """ 

+

        lookup_field = getattr(obj, self.lookup_field) 

+

        kwargs = {self.lookup_field: lookup_field} 

+

        try: 

+

            return reverse(view_name, kwargs=kwargs, request=request, format=format) 

+

        except NoReverseMatch: 

+

            pass 

+

 

+

        if self.pk_url_kwarg != 'pk': 

+

            # Only try pk if it has been explicitly set. 

+

            # Otherwise, the default `lookup_field = 'pk'` has us covered. 

+

            pk = obj.pk 

+

            kwargs = {self.pk_url_kwarg: pk} 

+

            try: 

+

                return reverse(view_name, kwargs=kwargs, request=request, format=format) 

+

            except NoReverseMatch: 

+

                pass 

+

 

+

        slug = getattr(obj, self.slug_field, None) 

+

        if slug is not None: 

+

            # Only try slug if it corresponds to an attribute on the object. 

+

            kwargs = {self.slug_url_kwarg: slug} 

+

            try: 

+

                ret = reverse(view_name, kwargs=kwargs, request=request, format=format) 

+

                if self.slug_field == 'slug' and self.slug_url_kwarg == 'slug': 

+

                    # If the lookup succeeds using the default slug params, 

+

                    # then `slug_field` is being used implicitly, and we 

+

                    # we need to warn about the pending deprecation. 

+

                    msg = 'Implicit slug field hyperlinked fields are pending deprecation.' \ 

+

                          'You should set `lookup_field=slug` on the HyperlinkedRelatedField.' 

+

                    warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

+

                return ret 

+

            except NoReverseMatch: 

+

                pass 

+

 

+

        raise NoReverseMatch() 

+

 

+

    def get_object(self, queryset, view_name, view_args, view_kwargs): 

+

        """ 

+

        Return the object corresponding to a matched URL. 

+

 

+

        Takes the matched URL conf arguments, and the queryset, and should 

+

        return an object instance, or raise an `ObjectDoesNotExist` exception. 

+

        """ 

+

        lookup = view_kwargs.get(self.lookup_field, None) 

+

        pk = view_kwargs.get(self.pk_url_kwarg, None) 

+

        slug = view_kwargs.get(self.slug_url_kwarg, None) 

+

 

+

        if lookup is not None: 

+

            filter_kwargs = {self.lookup_field: lookup} 

+

        elif pk is not None: 

+

            filter_kwargs = {'pk': pk} 

+

        elif slug is not None: 

+

            filter_kwargs = {self.slug_field: slug} 

+

        else: 

+

            raise ObjectDoesNotExist() 

+

 

+

        return queryset.get(**filter_kwargs) 

+

 

+

    def to_native(self, obj): 

+

        view_name = self.view_name 

+

        request = self.context.get('request', None) 

+

        format = self.format or self.context.get('format', None) 

+

 

+

        if request is None: 

+

            msg = ( 

+

                "Using `HyperlinkedRelatedField` without including the request " 

+

                "in the serializer context is deprecated. " 

+

                "Add `context={'request': request}` when instantiating " 

+

                "the serializer." 

+

            ) 

+

            warnings.warn(msg, DeprecationWarning, stacklevel=4) 

+

 

+

        # If the object has not yet been saved then we cannot hyperlink to it. 

+

        if getattr(obj, 'pk', None) is None: 

+

            return 

+

 

+

        # Return the hyperlink, or error if incorrectly configured. 

+

        try: 

+

            return self.get_url(obj, view_name, request, format) 

+

        except NoReverseMatch: 

+

            msg = ( 

+

                'Could not resolve URL for hyperlinked relationship using ' 

+

                'view name "%s". You may have failed to include the related ' 

+

                'model in your API, or incorrectly configured the ' 

+

                '`lookup_field` attribute on this field.' 

+

            ) 

+

            raise Exception(msg % view_name) 

+

 

+

    def from_native(self, value): 

+

        # Convert URL -> model instance pk 

+

        # TODO: Use values_list 

+

        queryset = self.queryset 

+

        if queryset is None: 

+

            raise Exception('Writable related fields must include a `queryset` argument') 

+

 

+

        try: 

+

            http_prefix = value.startswith(('http:', 'https:')) 

+

        except AttributeError: 

+

            msg = self.error_messages['incorrect_type'] 

+

            raise ValidationError(msg % type(value).__name__) 

+

 

+

        if http_prefix: 

+

            # If needed convert absolute URLs to relative path 

+

            value = urlparse.urlparse(value).path 

+

            prefix = get_script_prefix() 

+

            if value.startswith(prefix): 

+

                value = '/' + value[len(prefix):] 

+

 

+

        try: 

+

            match = resolve(value) 

+

        except Exception: 

+

            raise ValidationError(self.error_messages['no_match']) 

+

 

+

        if match.view_name != self.view_name: 

+

            raise ValidationError(self.error_messages['incorrect_match']) 

+

 

+

        try: 

+

            return self.get_object(queryset, match.view_name, 

+

                                   match.args, match.kwargs) 

+

        except (ObjectDoesNotExist, TypeError, ValueError): 

+

            raise ValidationError(self.error_messages['does_not_exist']) 

+

 

+

 

+

class HyperlinkedIdentityField(Field): 

+

    """ 

+

    Represents the instance, or a property on the instance, using hyperlinking. 

+

    """ 

+

    lookup_field = 'pk' 

+

    read_only = True 

+

 

+

    # These are all pending deprecation 

+

    pk_url_kwarg = 'pk' 

+

    slug_field = 'slug' 

+

    slug_url_kwarg = None  # Defaults to same as `slug_field` unless overridden 

+

 

+

    def __init__(self, *args, **kwargs): 

+

        try: 

+

            self.view_name = kwargs.pop('view_name') 

+

        except KeyError: 

+

            msg = "HyperlinkedIdentityField requires 'view_name' argument" 

+

            raise ValueError(msg) 

+

 

+

        self.format = kwargs.pop('format', None) 

+

        lookup_field = kwargs.pop('lookup_field', None) 

+

        self.lookup_field = lookup_field or self.lookup_field 

+

 

+

        # These are pending deprecation 

+

        if 'pk_url_kwarg' in kwargs: 

+

            msg = 'pk_url_kwarg is pending deprecation. Use lookup_field instead.' 

+

            warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

+

        if 'slug_url_kwarg' in kwargs: 

+

            msg = 'slug_url_kwarg is pending deprecation. Use lookup_field instead.' 

+

            warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

+

        if 'slug_field' in kwargs: 

+

            msg = 'slug_field is pending deprecation. Use lookup_field instead.' 

+

            warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

+

 

+

        self.slug_field = kwargs.pop('slug_field', self.slug_field) 

+

        default_slug_kwarg = self.slug_url_kwarg or self.slug_field 

+

        self.pk_url_kwarg = kwargs.pop('pk_url_kwarg', self.pk_url_kwarg) 

+

        self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', default_slug_kwarg) 

+

 

+

        super(HyperlinkedIdentityField, self).__init__(*args, **kwargs) 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        request = self.context.get('request', None) 

+

        format = self.context.get('format', None) 

+

        view_name = self.view_name 

+

 

+

        if request is None: 

+

            warnings.warn("Using `HyperlinkedIdentityField` without including the " 

+

                          "request in the serializer context is deprecated. " 

+

                          "Add `context={'request': request}` when instantiating the serializer.", 

+

                          DeprecationWarning, stacklevel=4) 

+

 

+

        # By default use whatever format is given for the current context 

+

        # unless the target is a different type to the source. 

+

        # 

+

        # Eg. Consider a HyperlinkedIdentityField pointing from a json 

+

        # representation to an html property of that representation... 

+

        # 

+

        # '/snippets/1/' should link to '/snippets/1/highlight/' 

+

        # ...but... 

+

        # '/snippets/1/.json' should link to '/snippets/1/highlight/.html' 

+

        if format and self.format and self.format != format: 

+

            format = self.format 

+

 

+

        # Return the hyperlink, or error if incorrectly configured. 

+

        try: 

+

            return self.get_url(obj, view_name, request, format) 

+

        except NoReverseMatch: 

+

            msg = ( 

+

                'Could not resolve URL for hyperlinked relationship using ' 

+

                'view name "%s". You may have failed to include the related ' 

+

                'model in your API, or incorrectly configured the ' 

+

                '`lookup_field` attribute on this field.' 

+

            ) 

+

            raise Exception(msg % view_name) 

+

 

+

    def get_url(self, obj, view_name, request, format): 

+

        """ 

+

        Given an object, return the URL that hyperlinks to the object. 

+

 

+

        May raise a `NoReverseMatch` if the `view_name` and `lookup_field` 

+

        attributes are not configured to correctly match the URL conf. 

+

        """ 

+

        lookup_field = getattr(obj, self.lookup_field) 

+

        kwargs = {self.lookup_field: lookup_field} 

+

        try: 

+

            return reverse(view_name, kwargs=kwargs, request=request, format=format) 

+

        except NoReverseMatch: 

+

            pass 

+

 

+

        if self.pk_url_kwarg != 'pk': 

+

            # Only try pk lookup if it has been explicitly set. 

+

            # Otherwise, the default `lookup_field = 'pk'` has us covered. 

+

            kwargs = {self.pk_url_kwarg: obj.pk} 

+

            try: 

+

                return reverse(view_name, kwargs=kwargs, request=request, format=format) 

+

            except NoReverseMatch: 

+

                pass 

+

 

+

        slug = getattr(obj, self.slug_field, None) 

+

        if slug: 

+

            # Only use slug lookup if a slug field exists on the model 

+

            kwargs = {self.slug_url_kwarg: slug} 

+

            try: 

+

                return reverse(view_name, kwargs=kwargs, request=request, format=format) 

+

            except NoReverseMatch: 

+

                pass 

+

 

+

        raise NoReverseMatch() 

+

 

+

 

+

### Old-style many classes for backwards compat 

+

 

+

class ManyRelatedField(RelatedField): 

+

    def __init__(self, *args, **kwargs): 

+

        warnings.warn('`ManyRelatedField()` is deprecated. ' 

+

                      'Use `RelatedField(many=True)` instead.', 

+

                       DeprecationWarning, stacklevel=2) 

+

        kwargs['many'] = True 

+

        super(ManyRelatedField, self).__init__(*args, **kwargs) 

+

 

+

 

+

class ManyPrimaryKeyRelatedField(PrimaryKeyRelatedField): 

+

    def __init__(self, *args, **kwargs): 

+

        warnings.warn('`ManyPrimaryKeyRelatedField()` is deprecated. ' 

+

                      'Use `PrimaryKeyRelatedField(many=True)` instead.', 

+

                       DeprecationWarning, stacklevel=2) 

+

        kwargs['many'] = True 

+

        super(ManyPrimaryKeyRelatedField, self).__init__(*args, **kwargs) 

+

 

+

 

+

class ManySlugRelatedField(SlugRelatedField): 

+

    def __init__(self, *args, **kwargs): 

+

        warnings.warn('`ManySlugRelatedField()` is deprecated. ' 

+

                      'Use `SlugRelatedField(many=True)` instead.', 

+

                       DeprecationWarning, stacklevel=2) 

+

        kwargs['many'] = True 

+

        super(ManySlugRelatedField, self).__init__(*args, **kwargs) 

+

 

+

 

+

class ManyHyperlinkedRelatedField(HyperlinkedRelatedField): 

+

    def __init__(self, *args, **kwargs): 

+

        warnings.warn('`ManyHyperlinkedRelatedField()` is deprecated. ' 

+

                      'Use `HyperlinkedRelatedField(many=True)` instead.', 

+

                       DeprecationWarning, stacklevel=2) 

+

        kwargs['many'] = True 

+

        super(ManyHyperlinkedRelatedField, self).__init__(*args, **kwargs) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_renderers.html b/htmlcov/rest_framework_renderers.html new file mode 100644 index 000000000..58c71b855 --- /dev/null +++ b/htmlcov/rest_framework_renderers.html @@ -0,0 +1,1227 @@ + + + + + + + + Coverage for rest_framework/renderers: 92% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+

296

+

297

+

298

+

299

+

300

+

301

+

302

+

303

+

304

+

305

+

306

+

307

+

308

+

309

+

310

+

311

+

312

+

313

+

314

+

315

+

316

+

317

+

318

+

319

+

320

+

321

+

322

+

323

+

324

+

325

+

326

+

327

+

328

+

329

+

330

+

331

+

332

+

333

+

334

+

335

+

336

+

337

+

338

+

339

+

340

+

341

+

342

+

343

+

344

+

345

+

346

+

347

+

348

+

349

+

350

+

351

+

352

+

353

+

354

+

355

+

356

+

357

+

358

+

359

+

360

+

361

+

362

+

363

+

364

+

365

+

366

+

367

+

368

+

369

+

370

+

371

+

372

+

373

+

374

+

375

+

376

+

377

+

378

+

379

+

380

+

381

+

382

+

383

+

384

+

385

+

386

+

387

+

388

+

389

+

390

+

391

+

392

+

393

+

394

+

395

+

396

+

397

+

398

+

399

+

400

+

401

+

402

+

403

+

404

+

405

+

406

+

407

+

408

+

409

+

410

+

411

+

412

+

413

+

414

+

415

+

416

+

417

+

418

+

419

+

420

+

421

+

422

+

423

+

424

+

425

+

426

+

427

+

428

+

429

+

430

+

431

+

432

+

433

+

434

+

435

+

436

+

437

+

438

+

439

+

440

+

441

+

442

+

443

+

444

+

445

+

446

+

447

+

448

+

449

+

450

+

451

+

452

+

453

+

454

+

455

+

456

+

457

+

458

+

459

+

460

+

461

+

462

+

463

+

464

+

465

+

466

+

467

+

468

+

469

+

470

+

471

+

472

+

473

+

474

+

475

+

476

+

477

+

478

+

479

+

480

+

481

+

482

+

483

+

484

+

485

+

486

+

487

+

488

+

489

+

490

+

491

+

492

+

493

+

494

+

495

+

496

+

497

+

498

+

499

+

500

+

501

+

502

+

503

+

504

+

505

+

506

+

507

+

508

+

509

+

510

+

511

+

512

+

513

+

514

+

515

+

516

+

517

+

518

+

519

+

520

+

521

+

522

+

523

+

524

+

525

+

526

+

527

+

528

+

529

+

530

+

531

+

532

+

533

+

534

+

535

+

536

+

537

+

538

+

539

+

540

+

541

+

542

+

543

+

544

+

545

+

546

+

547

+

548

+

549

+

550

+

551

+

552

+

553

+

554

+

555

+

556

+

557

+

558

+

559

+

560

+

561

+

562

+

563

+

564

+

565

+

566

+

567

+

568

+

569

+

570

+

571

+

572

+

573

+ +
+

""" 

+

Renderers are used to serialize a response into specific media types. 

+

 

+

They give us a generic way of being able to handle various media types 

+

on the response, such as JSON encoded data or HTML output. 

+

 

+

REST framework also provides an HTML renderer the renders the browsable API. 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

import copy 

+

import json 

+

from django import forms 

+

from django.core.exceptions import ImproperlyConfigured 

+

from django.http.multipartparser import parse_header 

+

from django.template import RequestContext, loader, Template 

+

from django.utils.xmlutils import SimplerXMLGenerator 

+

from rest_framework.compat import StringIO 

+

from rest_framework.compat import six 

+

from rest_framework.compat import smart_text 

+

from rest_framework.compat import yaml 

+

from rest_framework.settings import api_settings 

+

from rest_framework.request import clone_request 

+

from rest_framework.utils import encoders 

+

from rest_framework.utils.breadcrumbs import get_breadcrumbs 

+

from rest_framework.utils.formatting import get_view_name, get_view_description 

+

from rest_framework import exceptions, parsers, status, VERSION 

+

 

+

 

+

class BaseRenderer(object): 

+

    """ 

+

    All renderers should extend this class, setting the `media_type` 

+

    and `format` attributes, and override the `.render()` method. 

+

    """ 

+

 

+

    media_type = None 

+

    format = None 

+

    charset = 'utf-8' 

+

 

+

    def render(self, data, accepted_media_type=None, renderer_context=None): 

+

        raise NotImplemented('Renderer class requires .render() to be implemented') 

+

 

+

 

+

class JSONRenderer(BaseRenderer): 

+

    """ 

+

    Renderer which serializes to JSON. 

+

    Applies JSON's backslash-u character escaping for non-ascii characters. 

+

    """ 

+

 

+

    media_type = 'application/json' 

+

    format = 'json' 

+

    encoder_class = encoders.JSONEncoder 

+

    ensure_ascii = True 

+

    charset = 'utf-8' 

+

    # Note that JSON encodings must be utf-8, utf-16 or utf-32. 

+

    # See: http://www.ietf.org/rfc/rfc4627.txt 

+

 

+

    def render(self, data, accepted_media_type=None, renderer_context=None): 

+

        """ 

+

        Render `data` into JSON. 

+

        """ 

+

        if data is None: 

+

            return '' 

+

 

+

        # If 'indent' is provided in the context, then pretty print the result. 

+

        # E.g. If we're being called by the BrowsableAPIRenderer. 

+

        renderer_context = renderer_context or {} 

+

        indent = renderer_context.get('indent', None) 

+

 

+

        if accepted_media_type: 

+

            # If the media type looks like 'application/json; indent=4', 

+

            # then pretty print the result. 

+

            base_media_type, params = parse_header(accepted_media_type.encode('ascii')) 

+

            indent = params.get('indent', indent) 

+

            try: 

+

                indent = max(min(int(indent), 8), 0) 

+

            except (ValueError, TypeError): 

+

                indent = None 

+

 

+

        ret = json.dumps(data, cls=self.encoder_class, 

+

            indent=indent, ensure_ascii=self.ensure_ascii) 

+

 

+

        # On python 2.x json.dumps() returns bytestrings if ensure_ascii=True, 

+

        # but if ensure_ascii=False, the return type is underspecified, 

+

        # and may (or may not) be unicode. 

+

        # On python 3.x json.dumps() returns unicode strings. 

+

        if isinstance(ret, six.text_type): 

+

            return bytes(ret.encode(self.charset)) 

+

        return ret 

+

 

+

 

+

class UnicodeJSONRenderer(JSONRenderer): 

+

    ensure_ascii = False 

+

    charset = 'utf-8' 

+

    """ 

+

    Renderer which serializes to JSON. 

+

    Does *not* apply JSON's character escaping for non-ascii characters. 

+

    """ 

+

 

+

 

+

class JSONPRenderer(JSONRenderer): 

+

    """ 

+

    Renderer which serializes to json, 

+

    wrapping the json output in a callback function. 

+

    """ 

+

 

+

    media_type = 'application/javascript' 

+

    format = 'jsonp' 

+

    callback_parameter = 'callback' 

+

    default_callback = 'callback' 

+

 

+

    def get_callback(self, renderer_context): 

+

        """ 

+

        Determine the name of the callback to wrap around the json output. 

+

        """ 

+

        request = renderer_context.get('request', None) 

+

        params = request and request.QUERY_PARAMS or {} 

+

        return params.get(self.callback_parameter, self.default_callback) 

+

 

+

    def render(self, data, accepted_media_type=None, renderer_context=None): 

+

        """ 

+

        Renders into jsonp, wrapping the json output in a callback function. 

+

 

+

        Clients may set the callback function name using a query parameter 

+

        on the URL, for example: ?callback=exampleCallbackName 

+

        """ 

+

        renderer_context = renderer_context or {} 

+

        callback = self.get_callback(renderer_context) 

+

        json = super(JSONPRenderer, self).render(data, accepted_media_type, 

+

                                                 renderer_context) 

+

        return callback.encode(self.charset) + b'(' + json + b');' 

+

 

+

 

+

class XMLRenderer(BaseRenderer): 

+

    """ 

+

    Renderer which serializes to XML. 

+

    """ 

+

 

+

    media_type = 'application/xml' 

+

    format = 'xml' 

+

    charset = 'utf-8' 

+

 

+

    def render(self, data, accepted_media_type=None, renderer_context=None): 

+

        """ 

+

        Renders *obj* into serialized XML. 

+

        """ 

+

        if data is None: 

+

            return '' 

+

 

+

        stream = StringIO() 

+

 

+

        xml = SimplerXMLGenerator(stream, self.charset) 

+

        xml.startDocument() 

+

        xml.startElement("root", {}) 

+

 

+

        self._to_xml(xml, data) 

+

 

+

        xml.endElement("root") 

+

        xml.endDocument() 

+

        return stream.getvalue() 

+

 

+

    def _to_xml(self, xml, data): 

+

        if isinstance(data, (list, tuple)): 

+

            for item in data: 

+

                xml.startElement("list-item", {}) 

+

                self._to_xml(xml, item) 

+

                xml.endElement("list-item") 

+

 

+

        elif isinstance(data, dict): 

+

            for key, value in six.iteritems(data): 

+

                xml.startElement(key, {}) 

+

                self._to_xml(xml, value) 

+

                xml.endElement(key) 

+

 

+

        elif data is None: 

+

            # Don't output any value 

+

            pass 

+

 

+

        else: 

+

            xml.characters(smart_text(data)) 

+

 

+

 

+

class YAMLRenderer(BaseRenderer): 

+

    """ 

+

    Renderer which serializes to YAML. 

+

    """ 

+

 

+

    media_type = 'application/yaml' 

+

    format = 'yaml' 

+

    encoder = encoders.SafeDumper 

+

    charset = 'utf-8' 

+

 

+

    def render(self, data, accepted_media_type=None, renderer_context=None): 

+

        """ 

+

        Renders *obj* into serialized YAML. 

+

        """ 

+

        assert yaml, 'YAMLRenderer requires pyyaml to be installed' 

+

 

+

        if data is None: 

+

            return '' 

+

 

+

        return yaml.dump(data, stream=None, encoding=self.charset, Dumper=self.encoder) 

+

 

+

 

+

class TemplateHTMLRenderer(BaseRenderer): 

+

    """ 

+

    An HTML renderer for use with templates. 

+

 

+

    The data supplied to the Response object should be a dictionary that will 

+

    be used as context for the template. 

+

 

+

    The template name is determined by (in order of preference): 

+

 

+

    1. An explicit `.template_name` attribute set on the response. 

+

    2. An explicit `.template_name` attribute set on this class. 

+

    3. The return result of calling `view.get_template_names()`. 

+

 

+

    For example: 

+

        data = {'users': User.objects.all()} 

+

        return Response(data, template_name='users.html') 

+

 

+

    For pre-rendered HTML, see StaticHTMLRenderer. 

+

    """ 

+

 

+

    media_type = 'text/html' 

+

    format = 'html' 

+

    template_name = None 

+

    exception_template_names = [ 

+

        '%(status_code)s.html', 

+

        'api_exception.html' 

+

    ] 

+

    charset = 'utf-8' 

+

 

+

    def render(self, data, accepted_media_type=None, renderer_context=None): 

+

        """ 

+

        Renders data to HTML, using Django's standard template rendering. 

+

 

+

        The template name is determined by (in order of preference): 

+

 

+

        1. An explicit .template_name set on the response. 

+

        2. An explicit .template_name set on this class. 

+

        3. The return result of calling view.get_template_names(). 

+

        """ 

+

        renderer_context = renderer_context or {} 

+

        view = renderer_context['view'] 

+

        request = renderer_context['request'] 

+

        response = renderer_context['response'] 

+

 

+

        if response.exception: 

+

            template = self.get_exception_template(response) 

+

        else: 

+

            template_names = self.get_template_names(response, view) 

+

            template = self.resolve_template(template_names) 

+

 

+

        context = self.resolve_context(data, request, response) 

+

        return template.render(context) 

+

 

+

    def resolve_template(self, template_names): 

+

        return loader.select_template(template_names) 

+

 

+

    def resolve_context(self, data, request, response): 

+

        if response.exception: 

+

            data['status_code'] = response.status_code 

+

        return RequestContext(request, data) 

+

 

+

    def get_template_names(self, response, view): 

+

        if response.template_name: 

+

            return [response.template_name] 

+

        elif self.template_name: 

+

            return [self.template_name] 

+

        elif hasattr(view, 'get_template_names'): 

+

            return view.get_template_names() 

+

        raise ImproperlyConfigured('Returned a template response with no template_name') 

+

 

+

    def get_exception_template(self, response): 

+

        template_names = [name % {'status_code': response.status_code} 

+

                          for name in self.exception_template_names] 

+

 

+

        try: 

+

            # Try to find an appropriate error template 

+

            return self.resolve_template(template_names) 

+

        except Exception: 

+

            # Fall back to using eg '404 Not Found' 

+

            return Template('%d %s' % (response.status_code, 

+

                                       response.status_text.title())) 

+

 

+

 

+

# Note, subclass TemplateHTMLRenderer simply for the exception behavior 

+

class StaticHTMLRenderer(TemplateHTMLRenderer): 

+

    """ 

+

    An HTML renderer class that simply returns pre-rendered HTML. 

+

 

+

    The data supplied to the Response object should be a string representing 

+

    the pre-rendered HTML content. 

+

 

+

    For example: 

+

        data = '<html><body>example</body></html>' 

+

        return Response(data) 

+

 

+

    For template rendered HTML, see TemplateHTMLRenderer. 

+

    """ 

+

    media_type = 'text/html' 

+

    format = 'html' 

+

    charset = 'utf-8' 

+

 

+

    def render(self, data, accepted_media_type=None, renderer_context=None): 

+

        renderer_context = renderer_context or {} 

+

        response = renderer_context['response'] 

+

 

+

        if response and response.exception: 

+

            request = renderer_context['request'] 

+

            template = self.get_exception_template(response) 

+

            context = self.resolve_context(data, request, response) 

+

            return template.render(context) 

+

 

+

        return data 

+

 

+

 

+

class BrowsableAPIRenderer(BaseRenderer): 

+

    """ 

+

    HTML renderer used to self-document the API. 

+

    """ 

+

    media_type = 'text/html' 

+

    format = 'api' 

+

    template = 'rest_framework/api.html' 

+

    charset = 'utf-8' 

+

 

+

    def get_default_renderer(self, view): 

+

        """ 

+

        Return an instance of the first valid renderer. 

+

        (Don't use another documenting renderer.) 

+

        """ 

+

        renderers = [renderer for renderer in view.renderer_classes 

+

                     if not issubclass(renderer, BrowsableAPIRenderer)] 

+

        if not renderers: 

+

            return None 

+

        return renderers[0]() 

+

 

+

    def get_content(self, renderer, data, 

+

                    accepted_media_type, renderer_context): 

+

        """ 

+

        Get the content as if it had been rendered by the default 

+

        non-documenting renderer. 

+

        """ 

+

        if not renderer: 

+

            return '[No renderers were found]' 

+

 

+

        renderer_context['indent'] = 4 

+

        content = renderer.render(data, accepted_media_type, renderer_context) 

+

 

+

        if renderer.charset is None: 

+

            return '[%d bytes of binary content]' % len(content) 

+

 

+

        return content 

+

 

+

    def show_form_for_method(self, view, method, request, obj): 

+

        """ 

+

        Returns True if a form should be shown for this method. 

+

        """ 

+

        if not method in view.allowed_methods: 

+

            return  # Not a valid method 

+

 

+

        if not api_settings.FORM_METHOD_OVERRIDE: 

+

            return  # Cannot use form overloading 

+

 

+

        try: 

+

            view.check_permissions(request) 

+

            if obj is not None: 

+

                view.check_object_permissions(request, obj) 

+

        except exceptions.APIException: 

+

            return False  # Doesn't have permissions 

+

        return True 

+

 

+

    def serializer_to_form_fields(self, serializer): 

+

        fields = {} 

+

        for k, v in serializer.get_fields().items(): 

+

            if getattr(v, 'read_only', True): 

+

                continue 

+

 

+

            kwargs = {} 

+

            kwargs['required'] = v.required 

+

 

+

            #if getattr(v, 'queryset', None): 

+

            #    kwargs['queryset'] = v.queryset 

+

 

+

            if getattr(v, 'choices', None) is not None: 

+

                kwargs['choices'] = v.choices 

+

 

+

            if getattr(v, 'regex', None) is not None: 

+

                kwargs['regex'] = v.regex 

+

 

+

            if getattr(v, 'widget', None): 

+

                widget = copy.deepcopy(v.widget) 

+

                kwargs['widget'] = widget 

+

 

+

            if getattr(v, 'default', None) is not None: 

+

                kwargs['initial'] = v.default 

+

 

+

            if getattr(v, 'label', None) is not None: 

+

                kwargs['label'] = v.label 

+

 

+

            if getattr(v, 'help_text', None) is not None: 

+

                kwargs['help_text'] = v.help_text 

+

 

+

            fields[k] = v.form_field_class(**kwargs) 

+

 

+

        return fields 

+

 

+

    def _get_form(self, view, method, request): 

+

        # We need to impersonate a request with the correct method, 

+

        # so that eg. any dynamic get_serializer_class methods return the 

+

        # correct form for each method. 

+

        restore = view.request 

+

        request = clone_request(request, method) 

+

        view.request = request 

+

        try: 

+

            return self.get_form(view, method, request) 

+

        finally: 

+

            view.request = restore 

+

 

+

    def _get_raw_data_form(self, view, method, request, media_types): 

+

        # We need to impersonate a request with the correct method, 

+

        # so that eg. any dynamic get_serializer_class methods return the 

+

        # correct form for each method. 

+

        restore = view.request 

+

        request = clone_request(request, method) 

+

        view.request = request 

+

        try: 

+

            return self.get_raw_data_form(view, method, request, media_types) 

+

        finally: 

+

            view.request = restore 

+

 

+

    def get_form(self, view, method, request): 

+

        """ 

+

        Get a form, possibly bound to either the input or output data. 

+

        In the absence on of the Resource having an associated form then 

+

        provide a form that can be used to submit arbitrary content. 

+

        """ 

+

        obj = getattr(view, 'object', None) 

+

        if not self.show_form_for_method(view, method, request, obj): 

+

            return 

+

 

+

        if method in ('DELETE', 'OPTIONS'): 

+

            return True  # Don't actually need to return a form 

+

 

+

        if not getattr(view, 'get_serializer', None) or not parsers.FormParser in view.parser_classes: 

+

            return 

+

 

+

        serializer = view.get_serializer(instance=obj) 

+

        fields = self.serializer_to_form_fields(serializer) 

+

 

+

        # Creating an on the fly form see: 

+

        # http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python 

+

        OnTheFlyForm = type(str("OnTheFlyForm"), (forms.Form,), fields) 

+

        data = (obj is not None) and serializer.data or None 

+

        form_instance = OnTheFlyForm(data) 

+

        return form_instance 

+

 

+

    def get_raw_data_form(self, view, method, request, media_types): 

+

        """ 

+

        Returns a form that allows for arbitrary content types to be tunneled 

+

        via standard HTML forms. 

+

        (Which are typically application/x-www-form-urlencoded) 

+

        """ 

+

 

+

        # If we're not using content overloading there's no point in supplying a generic form, 

+

        # as the view won't treat the form's value as the content of the request. 

+

        if not (api_settings.FORM_CONTENT_OVERRIDE 

+

                and api_settings.FORM_CONTENTTYPE_OVERRIDE): 

+

            return None 

+

 

+

        # Check permissions 

+

        obj = getattr(view, 'object', None) 

+

        if not self.show_form_for_method(view, method, request, obj): 

+

            return 

+

 

+

        content_type_field = api_settings.FORM_CONTENTTYPE_OVERRIDE 

+

        content_field = api_settings.FORM_CONTENT_OVERRIDE 

+

        choices = [(media_type, media_type) for media_type in media_types] 

+

        initial = media_types[0] 

+

 

+

        # NB. http://jacobian.org/writing/dynamic-form-generation/ 

+

        class GenericContentForm(forms.Form): 

+

            def __init__(self): 

+

                super(GenericContentForm, self).__init__() 

+

 

+

                self.fields[content_type_field] = forms.ChoiceField( 

+

                    label='Media type', 

+

                    choices=choices, 

+

                    initial=initial 

+

                ) 

+

                self.fields[content_field] = forms.CharField( 

+

                    label='Content', 

+

                    widget=forms.Textarea 

+

                ) 

+

 

+

        return GenericContentForm() 

+

 

+

    def get_name(self, view): 

+

        return get_view_name(view.__class__, getattr(view, 'suffix', None)) 

+

 

+

    def get_description(self, view): 

+

        return get_view_description(view.__class__, html=True) 

+

 

+

    def get_breadcrumbs(self, request): 

+

        return get_breadcrumbs(request.path) 

+

 

+

    def render(self, data, accepted_media_type=None, renderer_context=None): 

+

        """ 

+

        Render the HTML for the browsable API representation. 

+

        """ 

+

        accepted_media_type = accepted_media_type or '' 

+

        renderer_context = renderer_context or {} 

+

 

+

        view = renderer_context['view'] 

+

        request = renderer_context['request'] 

+

        response = renderer_context['response'] 

+

        media_types = [parser.media_type for parser in view.parser_classes] 

+

 

+

        renderer = self.get_default_renderer(view) 

+

        content = self.get_content(renderer, data, accepted_media_type, renderer_context) 

+

 

+

        put_form = self._get_form(view, 'PUT', request) 

+

        post_form = self._get_form(view, 'POST', request) 

+

        patch_form = self._get_form(view, 'PATCH', request) 

+

        delete_form = self._get_form(view, 'DELETE', request) 

+

        options_form = self._get_form(view, 'OPTIONS', request) 

+

 

+

        raw_data_put_form = self._get_raw_data_form(view, 'PUT', request, media_types) 

+

        raw_data_post_form = self._get_raw_data_form(view, 'POST', request, media_types) 

+

        raw_data_patch_form = self._get_raw_data_form(view, 'PATCH', request, media_types) 

+

        raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form 

+

 

+

        name = self.get_name(view) 

+

        description = self.get_description(view) 

+

        breadcrumb_list = self.get_breadcrumbs(request) 

+

 

+

        template = loader.get_template(self.template) 

+

        context = RequestContext(request, { 

+

            'content': content, 

+

            'view': view, 

+

            'request': request, 

+

            'response': response, 

+

            'description': description, 

+

            'name': name, 

+

            'version': VERSION, 

+

            'breadcrumblist': breadcrumb_list, 

+

            'allowed_methods': view.allowed_methods, 

+

            'available_formats': [renderer.format for renderer in view.renderer_classes], 

+

 

+

            'put_form': put_form, 

+

            'post_form': post_form, 

+

            'patch_form': patch_form, 

+

            'delete_form': delete_form, 

+

            'options_form': options_form, 

+

 

+

            'raw_data_put_form': raw_data_put_form, 

+

            'raw_data_post_form': raw_data_post_form, 

+

            'raw_data_patch_form': raw_data_patch_form, 

+

            'raw_data_put_or_patch_form': raw_data_put_or_patch_form, 

+

 

+

            'api_settings': api_settings 

+

        }) 

+

 

+

        ret = template.render(context) 

+

 

+

        # Munge DELETE Response code to allow us to return content 

+

        # (Do this *after* we've rendered the template so that we include 

+

        # the normal deletion response code in the output) 

+

        if response.status_code == status.HTTP_204_NO_CONTENT: 

+

            response.status_code = status.HTTP_200_OK 

+

 

+

        return ret 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_request.html b/htmlcov/rest_framework_request.html new file mode 100644 index 000000000..03f2c3e34 --- /dev/null +++ b/htmlcov/rest_framework_request.html @@ -0,0 +1,819 @@ + + + + + + + + Coverage for rest_framework/request: 95% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+

296

+

297

+

298

+

299

+

300

+

301

+

302

+

303

+

304

+

305

+

306

+

307

+

308

+

309

+

310

+

311

+

312

+

313

+

314

+

315

+

316

+

317

+

318

+

319

+

320

+

321

+

322

+

323

+

324

+

325

+

326

+

327

+

328

+

329

+

330

+

331

+

332

+

333

+

334

+

335

+

336

+

337

+

338

+

339

+

340

+

341

+

342

+

343

+

344

+

345

+

346

+

347

+

348

+

349

+

350

+

351

+

352

+

353

+

354

+

355

+

356

+

357

+

358

+

359

+

360

+

361

+

362

+

363

+

364

+

365

+

366

+

367

+

368

+

369

+ +
+

""" 

+

The Request class is used as a wrapper around the standard request object. 

+

 

+

The wrapped request then offers a richer API, in particular : 

+

 

+

    - content automatically parsed according to `Content-Type` header, 

+

      and available as `request.DATA` 

+

    - full support of PUT method, including support for file uploads 

+

    - form overloading of HTTP method, content type and content 

+

""" 

+

from __future__ import unicode_literals 

+

from django.conf import settings 

+

from django.http import QueryDict 

+

from django.http.multipartparser import parse_header 

+

from django.utils.datastructures import MultiValueDict 

+

from rest_framework import HTTP_HEADER_ENCODING 

+

from rest_framework import exceptions 

+

from rest_framework.compat import BytesIO 

+

from rest_framework.settings import api_settings 

+

 

+

 

+

def is_form_media_type(media_type): 

+

    """ 

+

    Return True if the media type is a valid form media type. 

+

    """ 

+

    base_media_type, params = parse_header(media_type.encode(HTTP_HEADER_ENCODING)) 

+

    return (base_media_type == 'application/x-www-form-urlencoded' or 

+

            base_media_type == 'multipart/form-data') 

+

 

+

 

+

class Empty(object): 

+

    """ 

+

    Placeholder for unset attributes. 

+

    Cannot use `None`, as that may be a valid value. 

+

    """ 

+

    pass 

+

 

+

 

+

def _hasattr(obj, name): 

+

    return not getattr(obj, name) is Empty 

+

 

+

 

+

def clone_request(request, method): 

+

    """ 

+

    Internal helper method to clone a request, replacing with a different 

+

    HTTP method.  Used for checking permissions against other methods. 

+

    """ 

+

    ret = Request(request=request._request, 

+

                  parsers=request.parsers, 

+

                  authenticators=request.authenticators, 

+

                  negotiator=request.negotiator, 

+

                  parser_context=request.parser_context) 

+

    ret._data = request._data 

+

    ret._files = request._files 

+

    ret._content_type = request._content_type 

+

    ret._stream = request._stream 

+

    ret._method = method 

+

    if hasattr(request, '_user'): 

+

        ret._user = request._user 

+

    if hasattr(request, '_auth'): 

+

        ret._auth = request._auth 

+

    if hasattr(request, '_authenticator'): 

+

        ret._authenticator = request._authenticator 

+

    return ret 

+

 

+

 

+

class Request(object): 

+

    """ 

+

    Wrapper allowing to enhance a standard `HttpRequest` instance. 

+

 

+

    Kwargs: 

+

        - request(HttpRequest). The original request instance. 

+

        - parsers_classes(list/tuple). The parsers to use for parsing the 

+

          request content. 

+

        - authentication_classes(list/tuple). The authentications used to try 

+

          authenticating the request's user. 

+

    """ 

+

 

+

    _METHOD_PARAM = api_settings.FORM_METHOD_OVERRIDE 

+

    _CONTENT_PARAM = api_settings.FORM_CONTENT_OVERRIDE 

+

    _CONTENTTYPE_PARAM = api_settings.FORM_CONTENTTYPE_OVERRIDE 

+

 

+

    def __init__(self, request, parsers=None, authenticators=None, 

+

                 negotiator=None, parser_context=None): 

+

        self._request = request 

+

        self.parsers = parsers or () 

+

        self.authenticators = authenticators or () 

+

        self.negotiator = negotiator or self._default_negotiator() 

+

        self.parser_context = parser_context 

+

        self._data = Empty 

+

        self._files = Empty 

+

        self._method = Empty 

+

        self._content_type = Empty 

+

        self._stream = Empty 

+

 

+

        if self.parser_context is None: 

+

            self.parser_context = {} 

+

        self.parser_context['request'] = self 

+

        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET 

+

 

+

    def _default_negotiator(self): 

+

        return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS() 

+

 

+

    @property 

+

    def method(self): 

+

        """ 

+

        Returns the HTTP method. 

+

 

+

        This allows the `method` to be overridden by using a hidden `form` 

+

        field on a form POST request. 

+

        """ 

+

        if not _hasattr(self, '_method'): 

+

            self._load_method_and_content_type() 

+

        return self._method 

+

 

+

    @property 

+

    def content_type(self): 

+

        """ 

+

        Returns the content type header. 

+

 

+

        This should be used instead of `request.META.get('HTTP_CONTENT_TYPE')`, 

+

        as it allows the content type to be overridden by using a hidden form 

+

        field on a form POST request. 

+

        """ 

+

        if not _hasattr(self, '_content_type'): 

+

            self._load_method_and_content_type() 

+

        return self._content_type 

+

 

+

    @property 

+

    def stream(self): 

+

        """ 

+

        Returns an object that may be used to stream the request content. 

+

        """ 

+

        if not _hasattr(self, '_stream'): 

+

            self._load_stream() 

+

        return self._stream 

+

 

+

    @property 

+

    def QUERY_PARAMS(self): 

+

        """ 

+

        More semantically correct name for request.GET. 

+

        """ 

+

        return self._request.GET 

+

 

+

    @property 

+

    def DATA(self): 

+

        """ 

+

        Parses the request body and returns the data. 

+

 

+

        Similar to usual behaviour of `request.POST`, except that it handles 

+

        arbitrary parsers, and also works on methods other than POST (eg PUT). 

+

        """ 

+

        if not _hasattr(self, '_data'): 

+

            self._load_data_and_files() 

+

        return self._data 

+

 

+

    @property 

+

    def FILES(self): 

+

        """ 

+

        Parses the request body and returns any files uploaded in the request. 

+

 

+

        Similar to usual behaviour of `request.FILES`, except that it handles 

+

        arbitrary parsers, and also works on methods other than POST (eg PUT). 

+

        """ 

+

        if not _hasattr(self, '_files'): 

+

            self._load_data_and_files() 

+

        return self._files 

+

 

+

    @property 

+

    def user(self): 

+

        """ 

+

        Returns the user associated with the current request, as authenticated 

+

        by the authentication classes provided to the request. 

+

        """ 

+

        if not hasattr(self, '_user'): 

+

            self._authenticate() 

+

        return self._user 

+

 

+

    @user.setter 

+

    def user(self, value): 

+

        """ 

+

        Sets the user on the current request. This is necessary to maintain 

+

        compatilbility with django.contrib.auth where the user proprety is 

+

        set in the login and logout functions. 

+

        """ 

+

        self._user = value 

+

 

+

    @property 

+

    def auth(self): 

+

        """ 

+

        Returns any non-user authentication information associated with the 

+

        request, such as an authentication token. 

+

        """ 

+

        if not hasattr(self, '_auth'): 

+

            self._authenticate() 

+

        return self._auth 

+

 

+

    @auth.setter 

+

    def auth(self, value): 

+

        """ 

+

        Sets any non-user authentication information associated with the 

+

        request, such as an authentication token. 

+

        """ 

+

        self._auth = value 

+

 

+

    @property 

+

    def successful_authenticator(self): 

+

        """ 

+

        Return the instance of the authentication instance class that was used 

+

        to authenticate the request, or `None`. 

+

        """ 

+

        if not hasattr(self, '_authenticator'): 

+

            self._authenticate() 

+

        return self._authenticator 

+

 

+

    def _load_data_and_files(self): 

+

        """ 

+

        Parses the request content into self.DATA and self.FILES. 

+

        """ 

+

        if not _hasattr(self, '_content_type'): 

+

            self._load_method_and_content_type() 

+

 

+

        if not _hasattr(self, '_data'): 

+

            self._data, self._files = self._parse() 

+

 

+

    def _load_method_and_content_type(self): 

+

        """ 

+

        Sets the method and content_type, and then check if they've 

+

        been overridden. 

+

        """ 

+

        self._content_type = self.META.get('HTTP_CONTENT_TYPE', 

+

                                           self.META.get('CONTENT_TYPE', '')) 

+

 

+

        self._perform_form_overloading() 

+

 

+

        if not _hasattr(self, '_method'): 

+

            self._method = self._request.method 

+

 

+

            if self._method == 'POST': 

+

                # Allow X-HTTP-METHOD-OVERRIDE header 

+

                self._method = self.META.get('HTTP_X_HTTP_METHOD_OVERRIDE', 

+

                                             self._method) 

+

 

+

    def _load_stream(self): 

+

        """ 

+

        Return the content body of the request, as a stream. 

+

        """ 

+

        try: 

+

            content_length = int(self.META.get('CONTENT_LENGTH', 

+

                                    self.META.get('HTTP_CONTENT_LENGTH'))) 

+

        except (ValueError, TypeError): 

+

            content_length = 0 

+

 

+

        if content_length == 0: 

+

            self._stream = None 

+

        elif hasattr(self._request, 'read'): 

+

            self._stream = self._request 

+

        else: 

+

            self._stream = BytesIO(self.raw_post_data) 

+

 

+

    def _perform_form_overloading(self): 

+

        """ 

+

        If this is a form POST request, then we need to check if the method and 

+

        content/content_type have been overridden by setting them in hidden 

+

        form fields or not. 

+

        """ 

+

 

+

        USE_FORM_OVERLOADING = ( 

+

            self._METHOD_PARAM or 

+

            (self._CONTENT_PARAM and self._CONTENTTYPE_PARAM) 

+

        ) 

+

 

+

        # We only need to use form overloading on form POST requests. 

+

        if (not USE_FORM_OVERLOADING 

+

            or self._request.method != 'POST' 

+

            or not is_form_media_type(self._content_type)): 

+

            return 

+

 

+

        # At this point we're committed to parsing the request as form data. 

+

        self._data = self._request.POST 

+

        self._files = self._request.FILES 

+

 

+

        # Method overloading - change the method and remove the param from the content. 

+

        if (self._METHOD_PARAM and 

+

            self._METHOD_PARAM in self._data): 

+

            self._method = self._data[self._METHOD_PARAM].upper() 

+

 

+

        # Content overloading - modify the content type, and force re-parse. 

+

        if (self._CONTENT_PARAM and 

+

            self._CONTENTTYPE_PARAM and 

+

            self._CONTENT_PARAM in self._data and 

+

            self._CONTENTTYPE_PARAM in self._data): 

+

            self._content_type = self._data[self._CONTENTTYPE_PARAM] 

+

            self._stream = BytesIO(self._data[self._CONTENT_PARAM].encode(HTTP_HEADER_ENCODING)) 

+

            self._data, self._files = (Empty, Empty) 

+

 

+

    def _parse(self): 

+

        """ 

+

        Parse the request content, returning a two-tuple of (data, files) 

+

 

+

        May raise an `UnsupportedMediaType`, or `ParseError` exception. 

+

        """ 

+

        stream = self.stream 

+

        media_type = self.content_type 

+

 

+

        if stream is None or media_type is None: 

+

            empty_data = QueryDict('', self._request._encoding) 

+

            empty_files = MultiValueDict() 

+

            return (empty_data, empty_files) 

+

 

+

        parser = self.negotiator.select_parser(self, self.parsers) 

+

 

+

        if not parser: 

+

            raise exceptions.UnsupportedMediaType(media_type) 

+

 

+

        parsed = parser.parse(stream, media_type, self.parser_context) 

+

 

+

        # Parser classes may return the raw data, or a 

+

        # DataAndFiles object.  Unpack the result as required. 

+

        try: 

+

            return (parsed.data, parsed.files) 

+

        except AttributeError: 

+

            empty_files = MultiValueDict() 

+

            return (parsed, empty_files) 

+

 

+

    def _authenticate(self): 

+

        """ 

+

        Attempt to authenticate the request using each authentication instance 

+

        in turn. 

+

        Returns a three-tuple of (authenticator, user, authtoken). 

+

        """ 

+

        for authenticator in self.authenticators: 

+

            try: 

+

                user_auth_tuple = authenticator.authenticate(self) 

+

            except exceptions.APIException: 

+

                self._not_authenticated() 

+

                raise 

+

 

+

            if not user_auth_tuple is None: 

+

                self._authenticator = authenticator 

+

                self._user, self._auth = user_auth_tuple 

+

                return 

+

 

+

        self._not_authenticated() 

+

 

+

    def _not_authenticated(self): 

+

        """ 

+

        Return a three-tuple of (authenticator, user, authtoken), representing 

+

        an unauthenticated request. 

+

 

+

        By default this will be (None, AnonymousUser, None). 

+

        """ 

+

        self._authenticator = None 

+

 

+

        if api_settings.UNAUTHENTICATED_USER: 

+

            self._user = api_settings.UNAUTHENTICATED_USER() 

+

        else: 

+

            self._user = None 

+

 

+

        if api_settings.UNAUTHENTICATED_TOKEN: 

+

            self._auth = api_settings.UNAUTHENTICATED_TOKEN() 

+

        else: 

+

            self._auth = None 

+

 

+

    def __getattr__(self, attr): 

+

        """ 

+

        Proxy other attributes to the underlying HttpRequest object. 

+

        """ 

+

        return getattr(self._request, attr) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_response.html b/htmlcov/rest_framework_response.html new file mode 100644 index 000000000..d297ecd0b --- /dev/null +++ b/htmlcov/rest_framework_response.html @@ -0,0 +1,249 @@ + + + + + + + + Coverage for rest_framework/response: 98% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+ +
+

""" 

+

The Response class in REST framework is similar to HTTPResponse, except that 

+

it is initialized with unrendered data, instead of a pre-rendered string. 

+

 

+

The appropriate renderer is called during Django's template response rendering. 

+

""" 

+

from __future__ import unicode_literals 

+

from django.core.handlers.wsgi import STATUS_CODE_TEXT 

+

from django.template.response import SimpleTemplateResponse 

+

from rest_framework.compat import six 

+

 

+

 

+

class Response(SimpleTemplateResponse): 

+

    """ 

+

    An HttpResponse that allows its data to be rendered into 

+

    arbitrary media types. 

+

    """ 

+

 

+

    def __init__(self, data=None, status=200, 

+

                 template_name=None, headers=None, 

+

                 exception=False, content_type=None): 

+

        """ 

+

        Alters the init arguments slightly. 

+

        For example, drop 'template_name', and instead use 'data'. 

+

 

+

        Setting 'renderer' and 'media_type' will typically be deferred, 

+

        For example being set automatically by the `APIView`. 

+

        """ 

+

        super(Response, self).__init__(None, status=status) 

+

        self.data = data 

+

        self.template_name = template_name 

+

        self.exception = exception 

+

        self.content_type = content_type 

+

 

+

        if headers: 

+

            for name, value in six.iteritems(headers): 

+

                self[name] = value 

+

 

+

    @property 

+

    def rendered_content(self): 

+

        renderer = getattr(self, 'accepted_renderer', None) 

+

        media_type = getattr(self, 'accepted_media_type', None) 

+

        context = getattr(self, 'renderer_context', None) 

+

 

+

        assert renderer, ".accepted_renderer not set on Response" 

+

        assert media_type, ".accepted_media_type not set on Response" 

+

        assert context, ".renderer_context not set on Response" 

+

        context['response'] = self 

+

 

+

        charset = renderer.charset 

+

        content_type = self.content_type 

+

 

+

        if content_type is None and charset is not None: 

+

            content_type = "{0}; charset={1}".format(media_type, charset) 

+

        elif content_type is None: 

+

            content_type = media_type 

+

        self['Content-Type'] = content_type 

+

 

+

        ret = renderer.render(self.data, media_type, context) 

+

        if isinstance(ret, six.text_type): 

+

            assert charset, 'renderer returned unicode, and did not specify ' \ 

+

            'a charset value.' 

+

            return bytes(ret.encode(charset)) 

+

        return ret 

+

 

+

    @property 

+

    def status_text(self): 

+

        """ 

+

        Returns reason text corresponding to our HTTP response status code. 

+

        Provided for convenience. 

+

        """ 

+

        # TODO: Deprecate and use a template tag instead 

+

        # TODO: Status code text for RFC 6585 status codes 

+

        return STATUS_CODE_TEXT.get(self.status_code, '') 

+

 

+

    def __getstate__(self): 

+

        """ 

+

        Remove attributes from the response that shouldn't be cached 

+

        """ 

+

        state = super(Response, self).__getstate__() 

+

        for key in ('accepted_renderer', 'renderer_context', 'data'): 

+

            if key in state: 

+

                del state[key] 

+

        return state 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_reverse.html b/htmlcov/rest_framework_reverse.html new file mode 100644 index 000000000..4e7a8de23 --- /dev/null +++ b/htmlcov/rest_framework_reverse.html @@ -0,0 +1,127 @@ + + + + + + + + Coverage for rest_framework/reverse: 75% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+ +
+

""" 

+

Provide reverse functions that return fully qualified URLs 

+

""" 

+

from __future__ import unicode_literals 

+

from django.core.urlresolvers import reverse as django_reverse 

+

from django.utils.functional import lazy 

+

 

+

 

+

def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra): 

+

    """ 

+

    Same as `django.core.urlresolvers.reverse`, but optionally takes a request 

+

    and returns a fully qualified URL, using the request to get the base URL. 

+

    """ 

+

    if format is not None: 

+

        kwargs = kwargs or {} 

+

        kwargs['format'] = format 

+

    url = django_reverse(viewname, args=args, kwargs=kwargs, **extra) 

+

    if request: 

+

        return request.build_absolute_uri(url) 

+

    return url 

+

 

+

 

+

reverse_lazy = lazy(reverse, str) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_routers.html b/htmlcov/rest_framework_routers.html new file mode 100644 index 000000000..f08d5007e --- /dev/null +++ b/htmlcov/rest_framework_routers.html @@ -0,0 +1,595 @@ + + + + + + + + Coverage for rest_framework/routers: 94% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+ +
+

""" 

+

Routers provide a convenient and consistent way of automatically 

+

determining the URL conf for your API. 

+

 

+

They are used by simply instantiating a Router class, and then registering 

+

all the required ViewSets with that router. 

+

 

+

For example, you might have a `urls.py` that looks something like this: 

+

 

+

    router = routers.DefaultRouter() 

+

    router.register('users', UserViewSet, 'user') 

+

    router.register('accounts', AccountViewSet, 'account') 

+

 

+

    urlpatterns = router.urls 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

from collections import namedtuple 

+

from rest_framework import views 

+

from rest_framework.compat import patterns, url 

+

from rest_framework.response import Response 

+

from rest_framework.reverse import reverse 

+

from rest_framework.urlpatterns import format_suffix_patterns 

+

 

+

 

+

Route = namedtuple('Route', ['url', 'mapping', 'name', 'initkwargs']) 

+

 

+

 

+

def replace_methodname(format_string, methodname): 

+

    """ 

+

    Partially format a format_string, swapping out any 

+

    '{methodname}' or '{methodnamehyphen}' components. 

+

    """ 

+

    methodnamehyphen = methodname.replace('_', '-') 

+

    ret = format_string 

+

    ret = ret.replace('{methodname}', methodname) 

+

    ret = ret.replace('{methodnamehyphen}', methodnamehyphen) 

+

    return ret 

+

 

+

 

+

class BaseRouter(object): 

+

    def __init__(self): 

+

        self.registry = [] 

+

 

+

    def register(self, prefix, viewset, base_name=None): 

+

        if base_name is None: 

+

            base_name = self.get_default_base_name(viewset) 

+

        self.registry.append((prefix, viewset, base_name)) 

+

 

+

    def get_default_base_name(self, viewset): 

+

        """ 

+

        If `base_name` is not specified, attempt to automatically determine 

+

        it from the viewset. 

+

        """ 

+

        raise NotImplemented('get_default_base_name must be overridden') 

+

 

+

    def get_urls(self): 

+

        """ 

+

        Return a list of URL patterns, given the registered viewsets. 

+

        """ 

+

        raise NotImplemented('get_urls must be overridden') 

+

 

+

    @property 

+

    def urls(self): 

+

        if not hasattr(self, '_urls'): 

+

            self._urls = patterns('', *self.get_urls()) 

+

        return self._urls 

+

 

+

 

+

class SimpleRouter(BaseRouter): 

+

    routes = [ 

+

        # List route. 

+

        Route( 

+

            url=r'^{prefix}{trailing_slash}$', 

+

            mapping={ 

+

                'get': 'list', 

+

                'post': 'create' 

+

            }, 

+

            name='{basename}-list', 

+

            initkwargs={'suffix': 'List'} 

+

        ), 

+

        # Detail route. 

+

        Route( 

+

            url=r'^{prefix}/{lookup}{trailing_slash}$', 

+

            mapping={ 

+

                'get': 'retrieve', 

+

                'put': 'update', 

+

                'patch': 'partial_update', 

+

                'delete': 'destroy' 

+

            }, 

+

            name='{basename}-detail', 

+

            initkwargs={'suffix': 'Instance'} 

+

        ), 

+

        # Dynamically generated routes. 

+

        # Generated using @action or @link decorators on methods of the viewset. 

+

        Route( 

+

            url=r'^{prefix}/{lookup}/{methodname}{trailing_slash}$', 

+

            mapping={ 

+

                '{httpmethod}': '{methodname}', 

+

            }, 

+

            name='{basename}-{methodnamehyphen}', 

+

            initkwargs={} 

+

        ), 

+

    ] 

+

 

+

    def __init__(self, trailing_slash=True): 

+

        self.trailing_slash = trailing_slash and '/' or '' 

+

        super(SimpleRouter, self).__init__() 

+

 

+

    def get_default_base_name(self, viewset): 

+

        """ 

+

        If `base_name` is not specified, attempt to automatically determine 

+

        it from the viewset. 

+

        """ 

+

        model_cls = getattr(viewset, 'model', None) 

+

        queryset = getattr(viewset, 'queryset', None) 

+

        if model_cls is None and queryset is not None: 

+

            model_cls = queryset.model 

+

 

+

        assert model_cls, '`name` not argument not specified, and could ' \ 

+

            'not automatically determine the name from the viewset, as ' \ 

+

            'it does not have a `.model` or `.queryset` attribute.' 

+

 

+

        return model_cls._meta.object_name.lower() 

+

 

+

    def get_routes(self, viewset): 

+

        """ 

+

        Augment `self.routes` with any dynamically generated routes. 

+

 

+

        Returns a list of the Route namedtuple. 

+

        """ 

+

 

+

        # Determine any `@action` or `@link` decorated methods on the viewset 

+

        dynamic_routes = [] 

+

        for methodname in dir(viewset): 

+

            attr = getattr(viewset, methodname) 

+

            httpmethods = getattr(attr, 'bind_to_methods', None) 

+

            if httpmethods: 

+

                dynamic_routes.append((httpmethods, methodname)) 

+

 

+

        ret = [] 

+

        for route in self.routes: 

+

            if route.mapping == {'{httpmethod}': '{methodname}'}: 

+

                # Dynamic routes (@link or @action decorator) 

+

                for httpmethods, methodname in dynamic_routes: 

+

                    initkwargs = route.initkwargs.copy() 

+

                    initkwargs.update(getattr(viewset, methodname).kwargs) 

+

                    ret.append(Route( 

+

                        url=replace_methodname(route.url, methodname), 

+

                        mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), 

+

                        name=replace_methodname(route.name, methodname), 

+

                        initkwargs=initkwargs, 

+

                    )) 

+

            else: 

+

                # Standard route 

+

                ret.append(route) 

+

 

+

        return ret 

+

 

+

    def get_method_map(self, viewset, method_map): 

+

        """ 

+

        Given a viewset, and a mapping of http methods to actions, 

+

        return a new mapping which only includes any mappings that 

+

        are actually implemented by the viewset. 

+

        """ 

+

        bound_methods = {} 

+

        for method, action in method_map.items(): 

+

            if hasattr(viewset, action): 

+

                bound_methods[method] = action 

+

        return bound_methods 

+

 

+

    def get_lookup_regex(self, viewset): 

+

        """ 

+

        Given a viewset, return the portion of URL regex that is used 

+

        to match against a single instance. 

+

        """ 

+

        base_regex = '(?P<{lookup_field}>[^/]+)' 

+

        lookup_field = getattr(viewset, 'lookup_field', 'pk') 

+

        return base_regex.format(lookup_field=lookup_field) 

+

 

+

    def get_urls(self): 

+

        """ 

+

        Use the registered viewsets to generate a list of URL patterns. 

+

        """ 

+

        ret = [] 

+

 

+

        for prefix, viewset, basename in self.registry: 

+

            lookup = self.get_lookup_regex(viewset) 

+

            routes = self.get_routes(viewset) 

+

 

+

            for route in routes: 

+

 

+

                # Only actions which actually exist on the viewset will be bound 

+

                mapping = self.get_method_map(viewset, route.mapping) 

+

                if not mapping: 

+

                    continue 

+

 

+

                # Build the url pattern 

+

                regex = route.url.format( 

+

                    prefix=prefix, 

+

                    lookup=lookup, 

+

                    trailing_slash=self.trailing_slash 

+

                ) 

+

                view = viewset.as_view(mapping, **route.initkwargs) 

+

                name = route.name.format(basename=basename) 

+

                ret.append(url(regex, view, name=name)) 

+

 

+

        return ret 

+

 

+

 

+

class DefaultRouter(SimpleRouter): 

+

    """ 

+

    The default router extends the SimpleRouter, but also adds in a default 

+

    API root view, and adds format suffix patterns to the URLs. 

+

    """ 

+

    include_root_view = True 

+

    include_format_suffixes = True 

+

    root_view_name = 'api-root' 

+

 

+

    def get_api_root_view(self): 

+

        """ 

+

        Return a view to use as the API root. 

+

        """ 

+

        api_root_dict = {} 

+

        list_name = self.routes[0].name 

+

        for prefix, viewset, basename in self.registry: 

+

            api_root_dict[prefix] = list_name.format(basename=basename) 

+

 

+

        class APIRoot(views.APIView): 

+

            _ignore_model_permissions = True 

+

 

+

            def get(self, request, format=None): 

+

                ret = {} 

+

                for key, url_name in api_root_dict.items(): 

+

                    ret[key] = reverse(url_name, request=request, format=format) 

+

                return Response(ret) 

+

 

+

        return APIRoot.as_view() 

+

 

+

    def get_urls(self): 

+

        """ 

+

        Generate the list of URL patterns, including a default root view 

+

        for the API, and appending `.json` style format suffixes. 

+

        """ 

+

        urls = [] 

+

 

+

        if self.include_root_view: 

+

            root_url = url(r'^$', self.get_api_root_view(), name=self.root_view_name) 

+

            urls.append(root_url) 

+

 

+

        default_urls = super(DefaultRouter, self).get_urls() 

+

        urls.extend(default_urls) 

+

 

+

        if self.include_format_suffixes: 

+

            urls = format_suffix_patterns(urls) 

+

 

+

        return urls 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_serializers.html b/htmlcov/rest_framework_serializers.html new file mode 100644 index 000000000..79dc56474 --- /dev/null +++ b/htmlcov/rest_framework_serializers.html @@ -0,0 +1,2011 @@ + + + + + + + + Coverage for rest_framework/serializers: 94% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+

296

+

297

+

298

+

299

+

300

+

301

+

302

+

303

+

304

+

305

+

306

+

307

+

308

+

309

+

310

+

311

+

312

+

313

+

314

+

315

+

316

+

317

+

318

+

319

+

320

+

321

+

322

+

323

+

324

+

325

+

326

+

327

+

328

+

329

+

330

+

331

+

332

+

333

+

334

+

335

+

336

+

337

+

338

+

339

+

340

+

341

+

342

+

343

+

344

+

345

+

346

+

347

+

348

+

349

+

350

+

351

+

352

+

353

+

354

+

355

+

356

+

357

+

358

+

359

+

360

+

361

+

362

+

363

+

364

+

365

+

366

+

367

+

368

+

369

+

370

+

371

+

372

+

373

+

374

+

375

+

376

+

377

+

378

+

379

+

380

+

381

+

382

+

383

+

384

+

385

+

386

+

387

+

388

+

389

+

390

+

391

+

392

+

393

+

394

+

395

+

396

+

397

+

398

+

399

+

400

+

401

+

402

+

403

+

404

+

405

+

406

+

407

+

408

+

409

+

410

+

411

+

412

+

413

+

414

+

415

+

416

+

417

+

418

+

419

+

420

+

421

+

422

+

423

+

424

+

425

+

426

+

427

+

428

+

429

+

430

+

431

+

432

+

433

+

434

+

435

+

436

+

437

+

438

+

439

+

440

+

441

+

442

+

443

+

444

+

445

+

446

+

447

+

448

+

449

+

450

+

451

+

452

+

453

+

454

+

455

+

456

+

457

+

458

+

459

+

460

+

461

+

462

+

463

+

464

+

465

+

466

+

467

+

468

+

469

+

470

+

471

+

472

+

473

+

474

+

475

+

476

+

477

+

478

+

479

+

480

+

481

+

482

+

483

+

484

+

485

+

486

+

487

+

488

+

489

+

490

+

491

+

492

+

493

+

494

+

495

+

496

+

497

+

498

+

499

+

500

+

501

+

502

+

503

+

504

+

505

+

506

+

507

+

508

+

509

+

510

+

511

+

512

+

513

+

514

+

515

+

516

+

517

+

518

+

519

+

520

+

521

+

522

+

523

+

524

+

525

+

526

+

527

+

528

+

529

+

530

+

531

+

532

+

533

+

534

+

535

+

536

+

537

+

538

+

539

+

540

+

541

+

542

+

543

+

544

+

545

+

546

+

547

+

548

+

549

+

550

+

551

+

552

+

553

+

554

+

555

+

556

+

557

+

558

+

559

+

560

+

561

+

562

+

563

+

564

+

565

+

566

+

567

+

568

+

569

+

570

+

571

+

572

+

573

+

574

+

575

+

576

+

577

+

578

+

579

+

580

+

581

+

582

+

583

+

584

+

585

+

586

+

587

+

588

+

589

+

590

+

591

+

592

+

593

+

594

+

595

+

596

+

597

+

598

+

599

+

600

+

601

+

602

+

603

+

604

+

605

+

606

+

607

+

608

+

609

+

610

+

611

+

612

+

613

+

614

+

615

+

616

+

617

+

618

+

619

+

620

+

621

+

622

+

623

+

624

+

625

+

626

+

627

+

628

+

629

+

630

+

631

+

632

+

633

+

634

+

635

+

636

+

637

+

638

+

639

+

640

+

641

+

642

+

643

+

644

+

645

+

646

+

647

+

648

+

649

+

650

+

651

+

652

+

653

+

654

+

655

+

656

+

657

+

658

+

659

+

660

+

661

+

662

+

663

+

664

+

665

+

666

+

667

+

668

+

669

+

670

+

671

+

672

+

673

+

674

+

675

+

676

+

677

+

678

+

679

+

680

+

681

+

682

+

683

+

684

+

685

+

686

+

687

+

688

+

689

+

690

+

691

+

692

+

693

+

694

+

695

+

696

+

697

+

698

+

699

+

700

+

701

+

702

+

703

+

704

+

705

+

706

+

707

+

708

+

709

+

710

+

711

+

712

+

713

+

714

+

715

+

716

+

717

+

718

+

719

+

720

+

721

+

722

+

723

+

724

+

725

+

726

+

727

+

728

+

729

+

730

+

731

+

732

+

733

+

734

+

735

+

736

+

737

+

738

+

739

+

740

+

741

+

742

+

743

+

744

+

745

+

746

+

747

+

748

+

749

+

750

+

751

+

752

+

753

+

754

+

755

+

756

+

757

+

758

+

759

+

760

+

761

+

762

+

763

+

764

+

765

+

766

+

767

+

768

+

769

+

770

+

771

+

772

+

773

+

774

+

775

+

776

+

777

+

778

+

779

+

780

+

781

+

782

+

783

+

784

+

785

+

786

+

787

+

788

+

789

+

790

+

791

+

792

+

793

+

794

+

795

+

796

+

797

+

798

+

799

+

800

+

801

+

802

+

803

+

804

+

805

+

806

+

807

+

808

+

809

+

810

+

811

+

812

+

813

+

814

+

815

+

816

+

817

+

818

+

819

+

820

+

821

+

822

+

823

+

824

+

825

+

826

+

827

+

828

+

829

+

830

+

831

+

832

+

833

+

834

+

835

+

836

+

837

+

838

+

839

+

840

+

841

+

842

+

843

+

844

+

845

+

846

+

847

+

848

+

849

+

850

+

851

+

852

+

853

+

854

+

855

+

856

+

857

+

858

+

859

+

860

+

861

+

862

+

863

+

864

+

865

+

866

+

867

+

868

+

869

+

870

+

871

+

872

+

873

+

874

+

875

+

876

+

877

+

878

+

879

+

880

+

881

+

882

+

883

+

884

+

885

+

886

+

887

+

888

+

889

+

890

+

891

+

892

+

893

+

894

+

895

+

896

+

897

+

898

+

899

+

900

+

901

+

902

+

903

+

904

+

905

+

906

+

907

+

908

+

909

+

910

+

911

+

912

+

913

+

914

+

915

+

916

+

917

+

918

+

919

+

920

+

921

+

922

+

923

+

924

+

925

+

926

+

927

+

928

+

929

+

930

+

931

+

932

+

933

+

934

+

935

+

936

+

937

+

938

+

939

+

940

+

941

+

942

+

943

+

944

+

945

+

946

+

947

+

948

+

949

+

950

+

951

+

952

+

953

+

954

+

955

+

956

+

957

+

958

+

959

+

960

+

961

+

962

+

963

+

964

+

965

+ +
+

""" 

+

Serializers and ModelSerializers are similar to Forms and ModelForms. 

+

Unlike forms, they are not constrained to dealing with HTML output, and 

+

form encoded input. 

+

 

+

Serialization in REST framework is a two-phase process: 

+

 

+

1. Serializers marshal between complex types like model instances, and 

+

python primatives. 

+

2. The process of marshalling between python primatives and request and 

+

response content is handled by parsers and renderers. 

+

""" 

+

from __future__ import unicode_literals 

+

import copy 

+

import datetime 

+

import types 

+

from decimal import Decimal 

+

from django.core.paginator import Page 

+

from django.db import models 

+

from django.forms import widgets 

+

from django.utils.datastructures import SortedDict 

+

from rest_framework.compat import get_concrete_model, six 

+

 

+

# Note: We do the following so that users of the framework can use this style: 

+

# 

+

#     example_field = serializers.CharField(...) 

+

# 

+

# This helps keep the separation between model fields, form fields, and 

+

# serializer fields more explicit. 

+

 

+

from rest_framework.relations import * 

+

from rest_framework.fields import * 

+

 

+

 

+

class NestedValidationError(ValidationError): 

+

    """ 

+

    The default ValidationError behavior is to stringify each item in the list 

+

    if the messages are a list of error messages. 

+

 

+

    In the case of nested serializers, where the parent has many children, 

+

    then the child's `serializer.errors` will be a list of dicts.  In the case 

+

    of a single child, the `serializer.errors` will be a dict. 

+

 

+

    We need to override the default behavior to get properly nested error dicts. 

+

    """ 

+

 

+

    def __init__(self, message): 

+

        if isinstance(message, dict): 

+

            self.messages = [message] 

+

        else: 

+

            self.messages = message 

+

 

+

 

+

class DictWithMetadata(dict): 

+

    """ 

+

    A dict-like object, that can have additional properties attached. 

+

    """ 

+

    def __getstate__(self): 

+

        """ 

+

        Used by pickle (e.g., caching). 

+

        Overridden to remove the metadata from the dict, since it shouldn't be 

+

        pickled and may in some instances be unpickleable. 

+

        """ 

+

        return dict(self) 

+

 

+

 

+

class SortedDictWithMetadata(SortedDict): 

+

    """ 

+

    A sorted dict-like object, that can have additional properties attached. 

+

    """ 

+

    def __getstate__(self): 

+

        """ 

+

        Used by pickle (e.g., caching). 

+

        Overriden to remove the metadata from the dict, since it shouldn't be 

+

        pickle and may in some instances be unpickleable. 

+

        """ 

+

        return SortedDict(self).__dict__ 

+

 

+

 

+

def _is_protected_type(obj): 

+

    """ 

+

    True if the object is a native datatype that does not need to 

+

    be serialized further. 

+

    """ 

+

    return isinstance(obj, ( 

+

        types.NoneType, 

+

        int, long, 

+

        datetime.datetime, datetime.date, datetime.time, 

+

        float, Decimal, 

+

        basestring) 

+

    ) 

+

 

+

 

+

def _get_declared_fields(bases, attrs): 

+

    """ 

+

    Create a list of serializer field instances from the passed in 'attrs', 

+

    plus any fields on the base classes (in 'bases'). 

+

 

+

    Note that all fields from the base classes are used. 

+

    """ 

+

    fields = [(field_name, attrs.pop(field_name)) 

+

              for field_name, obj in list(six.iteritems(attrs)) 

+

              if isinstance(obj, Field)] 

+

    fields.sort(key=lambda x: x[1].creation_counter) 

+

 

+

    # If this class is subclassing another Serializer, add that Serializer's 

+

    # fields.  Note that we loop over the bases in *reverse*. This is necessary 

+

    # in order to maintain the correct order of fields. 

+

    for base in bases[::-1]: 

+

        if hasattr(base, 'base_fields'): 

+

            fields = list(base.base_fields.items()) + fields 

+

 

+

    return SortedDict(fields) 

+

 

+

 

+

class SerializerMetaclass(type): 

+

    def __new__(cls, name, bases, attrs): 

+

        attrs['base_fields'] = _get_declared_fields(bases, attrs) 

+

        return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs) 

+

 

+

 

+

class SerializerOptions(object): 

+

    """ 

+

    Meta class options for Serializer 

+

    """ 

+

    def __init__(self, meta): 

+

        self.depth = getattr(meta, 'depth', 0) 

+

        self.fields = getattr(meta, 'fields', ()) 

+

        self.exclude = getattr(meta, 'exclude', ()) 

+

 

+

 

+

class BaseSerializer(WritableField): 

+

    """ 

+

    This is the Serializer implementation. 

+

    We need to implement it as `BaseSerializer` due to metaclass magicks. 

+

    """ 

+

    class Meta(object): 

+

        pass 

+

 

+

    _options_class = SerializerOptions 

+

    _dict_class = SortedDictWithMetadata 

+

 

+

    def __init__(self, instance=None, data=None, files=None, 

+

                 context=None, partial=False, many=None, 

+

                 allow_add_remove=False, **kwargs): 

+

        super(BaseSerializer, self).__init__(**kwargs) 

+

        self.opts = self._options_class(self.Meta) 

+

        self.parent = None 

+

        self.root = None 

+

        self.partial = partial 

+

        self.many = many 

+

        self.allow_add_remove = allow_add_remove 

+

 

+

        self.context = context or {} 

+

 

+

        self.init_data = data 

+

        self.init_files = files 

+

        self.object = instance 

+

        self.fields = self.get_fields() 

+

 

+

        self._data = None 

+

        self._files = None 

+

        self._errors = None 

+

        self._deleted = None 

+

 

+

        if many and instance is not None and not hasattr(instance, '__iter__'): 

+

            raise ValueError('instance should be a queryset or other iterable with many=True') 

+

 

+

        if allow_add_remove and not many: 

+

            raise ValueError('allow_add_remove should only be used for bulk updates, but you have not set many=True') 

+

 

+

    ##### 

+

    # Methods to determine which fields to use when (de)serializing objects. 

+

 

+

    def get_default_fields(self): 

+

        """ 

+

        Return the complete set of default fields for the object, as a dict. 

+

        """ 

+

        return {} 

+

 

+

    def get_fields(self): 

+

        """ 

+

        Returns the complete set of fields for the object as a dict. 

+

 

+

        This will be the set of any explicitly declared fields, 

+

        plus the set of fields returned by get_default_fields(). 

+

        """ 

+

        ret = SortedDict() 

+

 

+

        # Get the explicitly declared fields 

+

        base_fields = copy.deepcopy(self.base_fields) 

+

        for key, field in base_fields.items(): 

+

            ret[key] = field 

+

 

+

        # Add in the default fields 

+

        default_fields = self.get_default_fields() 

+

        for key, val in default_fields.items(): 

+

            if key not in ret: 

+

                ret[key] = val 

+

 

+

        # If 'fields' is specified, use those fields, in that order. 

+

        if self.opts.fields: 

+

            assert isinstance(self.opts.fields, (list, tuple)), '`fields` must be a list or tuple' 

+

            new = SortedDict() 

+

            for key in self.opts.fields: 

+

                new[key] = ret[key] 

+

            ret = new 

+

 

+

        # Remove anything in 'exclude' 

+

        if self.opts.exclude: 

+

            assert isinstance(self.opts.exclude, (list, tuple)), '`exclude` must be a list or tuple' 

+

            for key in self.opts.exclude: 

+

                ret.pop(key, None) 

+

 

+

        for key, field in ret.items(): 

+

            field.initialize(parent=self, field_name=key) 

+

 

+

        return ret 

+

 

+

    ##### 

+

    # Methods to convert or revert from objects <--> primitive representations. 

+

 

+

    def get_field_key(self, field_name): 

+

        """ 

+

        Return the key that should be used for a given field. 

+

        """ 

+

        return field_name 

+

 

+

    def restore_fields(self, data, files): 

+

        """ 

+

        Core of deserialization, together with `restore_object`. 

+

        Converts a dictionary of data into a dictionary of deserialized fields. 

+

        """ 

+

        reverted_data = {} 

+

 

+

        if data is not None and not isinstance(data, dict): 

+

            self._errors['non_field_errors'] = ['Invalid data'] 

+

            return None 

+

 

+

        for field_name, field in self.fields.items(): 

+

            field.initialize(parent=self, field_name=field_name) 

+

            try: 

+

                field.field_from_native(data, files, field_name, reverted_data) 

+

            except ValidationError as err: 

+

                self._errors[field_name] = list(err.messages) 

+

 

+

        return reverted_data 

+

 

+

    def perform_validation(self, attrs): 

+

        """ 

+

        Run `validate_<fieldname>()` and `validate()` methods on the serializer 

+

        """ 

+

        for field_name, field in self.fields.items(): 

+

            if field_name in self._errors: 

+

                continue 

+

            try: 

+

                validate_method = getattr(self, 'validate_%s' % field_name, None) 

+

                if validate_method: 

+

                    source = field.source or field_name 

+

                    attrs = validate_method(attrs, source) 

+

            except ValidationError as err: 

+

                self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages) 

+

 

+

        # If there are already errors, we don't run .validate() because 

+

        # field-validation failed and thus `attrs` may not be complete. 

+

        # which in turn can cause inconsistent validation errors. 

+

        if not self._errors: 

+

            try: 

+

                attrs = self.validate(attrs) 

+

            except ValidationError as err: 

+

                if hasattr(err, 'message_dict'): 

+

                    for field_name, error_messages in err.message_dict.items(): 

+

                        self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) 

+

                elif hasattr(err, 'messages'): 

+

                    self._errors['non_field_errors'] = err.messages 

+

 

+

        return attrs 

+

 

+

    def validate(self, attrs): 

+

        """ 

+

        Stub method, to be overridden in Serializer subclasses 

+

        """ 

+

        return attrs 

+

 

+

    def restore_object(self, attrs, instance=None): 

+

        """ 

+

        Deserialize a dictionary of attributes into an object instance. 

+

        You should override this method to control how deserialized objects 

+

        are instantiated. 

+

        """ 

+

        if instance is not None: 

+

            instance.update(attrs) 

+

            return instance 

+

        return attrs 

+

 

+

    def to_native(self, obj): 

+

        """ 

+

        Serialize objects -> primitives. 

+

        """ 

+

        ret = self._dict_class() 

+

        ret.fields = {} 

+

 

+

        for field_name, field in self.fields.items(): 

+

            field.initialize(parent=self, field_name=field_name) 

+

            key = self.get_field_key(field_name) 

+

            value = field.field_to_native(obj, field_name) 

+

            ret[key] = value 

+

            ret.fields[key] = field 

+

        return ret 

+

 

+

    def from_native(self, data, files): 

+

        """ 

+

        Deserialize primitives -> objects. 

+

        """ 

+

        self._errors = {} 

+

        if data is not None or files is not None: 

+

            attrs = self.restore_fields(data, files) 

+

            if attrs is not None: 

+

                attrs = self.perform_validation(attrs) 

+

        else: 

+

            self._errors['non_field_errors'] = ['No input provided'] 

+

 

+

        if not self._errors: 

+

            return self.restore_object(attrs, instance=getattr(self, 'object', None)) 

+

 

+

    def field_to_native(self, obj, field_name): 

+

        """ 

+

        Override default so that the serializer can be used as a nested field 

+

        across relationships. 

+

        """ 

+

        if self.source == '*': 

+

            return self.to_native(obj) 

+

 

+

        try: 

+

            source = self.source or field_name 

+

            value = obj 

+

 

+

            for component in source.split('.'): 

+

                value = get_component(value, component) 

+

                if value is None: 

+

                    break 

+

        except ObjectDoesNotExist: 

+

            return None 

+

 

+

        if is_simple_callable(getattr(value, 'all', None)): 

+

            return [self.to_native(item) for item in value.all()] 

+

 

+

        if value is None: 

+

            return None 

+

 

+

        if self.many is not None: 

+

            many = self.many 

+

        else: 

+

            many = hasattr(value, '__iter__') and not isinstance(value, (Page, dict, six.text_type)) 

+

 

+

        if many: 

+

            return [self.to_native(item) for item in value] 

+

        return self.to_native(value) 

+

 

+

    def field_from_native(self, data, files, field_name, into): 

+

        """ 

+

        Override default so that the serializer can be used as a writable 

+

        nested field across relationships. 

+

        """ 

+

        if self.read_only: 

+

            return 

+

 

+

        try: 

+

            value = data[field_name] 

+

        except KeyError: 

+

            if self.default is not None and not self.partial: 

+

                # Note: partial updates shouldn't set defaults 

+

                value = copy.deepcopy(self.default) 

+

            else: 

+

                if self.required: 

+

                    raise ValidationError(self.error_messages['required']) 

+

                return 

+

 

+

        # Set the serializer object if it exists 

+

        obj = getattr(self.parent.object, field_name) if self.parent.object else None 

+

 

+

        if self.source == '*': 

+

            if value: 

+

                into.update(value) 

+

        else: 

+

            if value in (None, ''): 

+

                into[(self.source or field_name)] = None 

+

            else: 

+

                kwargs = { 

+

                    'instance': obj, 

+

                    'data': value, 

+

                    'context': self.context, 

+

                    'partial': self.partial, 

+

                    'many': self.many 

+

                } 

+

                serializer = self.__class__(**kwargs) 

+

 

+

                if serializer.is_valid(): 

+

                    into[self.source or field_name] = serializer.object 

+

                else: 

+

                    # Propagate errors up to our parent 

+

                    raise NestedValidationError(serializer.errors) 

+

 

+

    def get_identity(self, data): 

+

        """ 

+

        This hook is required for bulk update. 

+

        It is used to determine the canonical identity of a given object. 

+

 

+

        Note that the data has not been validated at this point, so we need 

+

        to make sure that we catch any cases of incorrect datatypes being 

+

        passed to this method. 

+

        """ 

+

        try: 

+

            return data.get('id', None) 

+

        except AttributeError: 

+

            return None 

+

 

+

    @property 

+

    def errors(self): 

+

        """ 

+

        Run deserialization and return error data, 

+

        setting self.object if no errors occurred. 

+

        """ 

+

        if self._errors is None: 

+

            data, files = self.init_data, self.init_files 

+

 

+

            if self.many is not None: 

+

                many = self.many 

+

            else: 

+

                many = hasattr(data, '__iter__') and not isinstance(data, (Page, dict, six.text_type)) 

+

                if many: 

+

                    warnings.warn('Implict list/queryset serialization is deprecated. ' 

+

                                  'Use the `many=True` flag when instantiating the serializer.', 

+

                                  DeprecationWarning, stacklevel=3) 

+

 

+

            if many: 

+

                ret = [] 

+

                errors = [] 

+

                update = self.object is not None 

+

 

+

                if update: 

+

                    # If this is a bulk update we need to map all the objects 

+

                    # to a canonical identity so we can determine which 

+

                    # individual object is being updated for each item in the 

+

                    # incoming data 

+

                    objects = self.object 

+

                    identities = [self.get_identity(self.to_native(obj)) for obj in objects] 

+

                    identity_to_objects = dict(zip(identities, objects)) 

+

 

+

                if hasattr(data, '__iter__') and not isinstance(data, (dict, six.text_type)): 

+

                    for item in data: 

+

                        if update: 

+

                            # Determine which object we're updating 

+

                            identity = self.get_identity(item) 

+

                            self.object = identity_to_objects.pop(identity, None) 

+

                            if self.object is None and not self.allow_add_remove: 

+

                                ret.append(None) 

+

                                errors.append({'non_field_errors': ['Cannot create a new item, only existing items may be updated.']}) 

+

                                continue 

+

 

+

                        ret.append(self.from_native(item, None)) 

+

                        errors.append(self._errors) 

+

 

+

                    if update: 

+

                        self._deleted = identity_to_objects.values() 

+

 

+

                    self._errors = any(errors) and errors or [] 

+

                else: 

+

                    self._errors = {'non_field_errors': ['Expected a list of items.']} 

+

            else: 

+

                ret = self.from_native(data, files) 

+

 

+

            if not self._errors: 

+

                self.object = ret 

+

 

+

        return self._errors 

+

 

+

    def is_valid(self): 

+

        return not self.errors 

+

 

+

    @property 

+

    def data(self): 

+

        """ 

+

        Returns the serialized data on the serializer. 

+

        """ 

+

        if self._data is None: 

+

            obj = self.object 

+

 

+

            if self.many is not None: 

+

                many = self.many 

+

            else: 

+

                many = hasattr(obj, '__iter__') and not isinstance(obj, (Page, dict)) 

+

                if many: 

+

                    warnings.warn('Implict list/queryset serialization is deprecated. ' 

+

                                  'Use the `many=True` flag when instantiating the serializer.', 

+

                                  DeprecationWarning, stacklevel=2) 

+

 

+

            if many: 

+

                self._data = [self.to_native(item) for item in obj] 

+

            else: 

+

                self._data = self.to_native(obj) 

+

 

+

        return self._data 

+

 

+

    def save_object(self, obj, **kwargs): 

+

        obj.save(**kwargs) 

+

 

+

    def delete_object(self, obj): 

+

        obj.delete() 

+

 

+

    def save(self, **kwargs): 

+

        """ 

+

        Save the deserialized object and return it. 

+

        """ 

+

        if isinstance(self.object, list): 

+

            [self.save_object(item, **kwargs) for item in self.object] 

+

        else: 

+

            self.save_object(self.object, **kwargs) 

+

 

+

        if self.allow_add_remove and self._deleted: 

+

            [self.delete_object(item) for item in self._deleted] 

+

 

+

        return self.object 

+

 

+

    def metadata(self): 

+

        """ 

+

        Return a dictionary of metadata about the fields on the serializer. 

+

        Useful for things like responding to OPTIONS requests, or generating 

+

        API schemas for auto-documentation. 

+

        """ 

+

        return SortedDict( 

+

            [(field_name, field.metadata()) 

+

            for field_name, field in six.iteritems(self.fields)] 

+

        ) 

+

 

+

 

+

class Serializer(six.with_metaclass(SerializerMetaclass, BaseSerializer)): 

+

    pass 

+

 

+

 

+

class ModelSerializerOptions(SerializerOptions): 

+

    """ 

+

    Meta class options for ModelSerializer 

+

    """ 

+

    def __init__(self, meta): 

+

        super(ModelSerializerOptions, self).__init__(meta) 

+

        self.model = getattr(meta, 'model', None) 

+

        self.read_only_fields = getattr(meta, 'read_only_fields', ()) 

+

 

+

 

+

class ModelSerializer(Serializer): 

+

    """ 

+

    A serializer that deals with model instances and querysets. 

+

    """ 

+

    _options_class = ModelSerializerOptions 

+

 

+

    field_mapping = { 

+

        models.AutoField: IntegerField, 

+

        models.FloatField: FloatField, 

+

        models.IntegerField: IntegerField, 

+

        models.PositiveIntegerField: IntegerField, 

+

        models.SmallIntegerField: IntegerField, 

+

        models.PositiveSmallIntegerField: IntegerField, 

+

        models.DateTimeField: DateTimeField, 

+

        models.DateField: DateField, 

+

        models.TimeField: TimeField, 

+

        models.DecimalField: DecimalField, 

+

        models.EmailField: EmailField, 

+

        models.CharField: CharField, 

+

        models.URLField: URLField, 

+

        models.SlugField: SlugField, 

+

        models.TextField: CharField, 

+

        models.CommaSeparatedIntegerField: CharField, 

+

        models.BooleanField: BooleanField, 

+

        models.FileField: FileField, 

+

        models.ImageField: ImageField, 

+

    } 

+

 

+

    def get_default_fields(self): 

+

        """ 

+

        Return all the fields that should be serialized for the model. 

+

        """ 

+

 

+

        cls = self.opts.model 

+

        assert cls is not None, \ 

+

                "Serializer class '%s' is missing 'model' Meta option" % self.__class__.__name__ 

+

        opts = get_concrete_model(cls)._meta 

+

        ret = SortedDict() 

+

        nested = bool(self.opts.depth) 

+

 

+

        # Deal with adding the primary key field 

+

        pk_field = opts.pk 

+

        while pk_field.rel and pk_field.rel.parent_link: 

+

            # If model is a child via multitable inheritance, use parent's pk 

+

            pk_field = pk_field.rel.to._meta.pk 

+

 

+

        field = self.get_pk_field(pk_field) 

+

        if field: 

+

            ret[pk_field.name] = field 

+

 

+

        # Deal with forward relationships 

+

        forward_rels = [field for field in opts.fields if field.serialize] 

+

        forward_rels += [field for field in opts.many_to_many if field.serialize] 

+

 

+

        for model_field in forward_rels: 

+

            has_through_model = False 

+

 

+

            if model_field.rel: 

+

                to_many = isinstance(model_field, 

+

                                     models.fields.related.ManyToManyField) 

+

                related_model = model_field.rel.to 

+

 

+

                if to_many and not model_field.rel.through._meta.auto_created: 

+

                    has_through_model = True 

+

 

+

            if model_field.rel and nested: 

+

                if len(inspect.getargspec(self.get_nested_field).args) == 2: 

+

                    warnings.warn( 

+

                        'The `get_nested_field(model_field)` call signature ' 

+

                        'is due to be deprecated. ' 

+

                        'Use `get_nested_field(model_field, related_model, ' 

+

                        'to_many) instead', 

+

                        PendingDeprecationWarning 

+

                    ) 

+

                    field = self.get_nested_field(model_field) 

+

                else: 

+

                    field = self.get_nested_field(model_field, related_model, to_many) 

+

            elif model_field.rel: 

+

                if len(inspect.getargspec(self.get_nested_field).args) == 3: 

+

                    warnings.warn( 

+

                        'The `get_related_field(model_field, to_many)` call ' 

+

                        'signature is due to be deprecated. ' 

+

                        'Use `get_related_field(model_field, related_model, ' 

+

                        'to_many) instead', 

+

                        PendingDeprecationWarning 

+

                    ) 

+

                    field = self.get_related_field(model_field, to_many=to_many) 

+

                else: 

+

                    field = self.get_related_field(model_field, related_model, to_many) 

+

            else: 

+

                field = self.get_field(model_field) 

+

 

+

            if field: 

+

                if has_through_model: 

+

                    field.read_only = True 

+

 

+

                ret[model_field.name] = field 

+

 

+

        # Deal with reverse relationships 

+

        if not self.opts.fields: 

+

            reverse_rels = [] 

+

        else: 

+

            # Reverse relationships are only included if they are explicitly 

+

            # present in the `fields` option on the serializer 

+

            reverse_rels = opts.get_all_related_objects() 

+

            reverse_rels += opts.get_all_related_many_to_many_objects() 

+

 

+

        for relation in reverse_rels: 

+

            accessor_name = relation.get_accessor_name() 

+

            if not self.opts.fields or accessor_name not in self.opts.fields: 

+

                continue 

+

            related_model = relation.model 

+

            to_many = relation.field.rel.multiple 

+

            has_through_model = False 

+

            is_m2m = isinstance(relation.field, 

+

                                models.fields.related.ManyToManyField) 

+

 

+

            if is_m2m and not relation.field.rel.through._meta.auto_created: 

+

                has_through_model = True 

+

 

+

            if nested: 

+

                field = self.get_nested_field(None, related_model, to_many) 

+

            else: 

+

                field = self.get_related_field(None, related_model, to_many) 

+

 

+

            if field: 

+

                if has_through_model: 

+

                    field.read_only = True 

+

 

+

                ret[accessor_name] = field 

+

 

+

        # Add the `read_only` flag to any fields that have bee specified 

+

        # in the `read_only_fields` option 

+

        for field_name in self.opts.read_only_fields: 

+

            assert field_name not in self.base_fields.keys(), \ 

+

                "field '%s' on serializer '%s' specfied in " \ 

+

                "`read_only_fields`, but also added " \ 

+

                "as an explict field.  Remove it from `read_only_fields`." % \ 

+

                (field_name, self.__class__.__name__) 

+

            assert field_name in ret, \ 

+

                "Noexistant field '%s' specified in `read_only_fields` " \ 

+

                "on serializer '%s'." % \ 

+

                (self.__class__.__name__, field_name) 

+

            ret[field_name].read_only = True 

+

 

+

        return ret 

+

 

+

    def get_pk_field(self, model_field): 

+

        """ 

+

        Returns a default instance of the pk field. 

+

        """ 

+

        return self.get_field(model_field) 

+

 

+

    def get_nested_field(self, model_field, related_model, to_many): 

+

        """ 

+

        Creates a default instance of a nested relational field. 

+

 

+

        Note that model_field will be `None` for reverse relationships. 

+

        """ 

+

        class NestedModelSerializer(ModelSerializer): 

+

            class Meta: 

+

                model = related_model 

+

                depth = self.opts.depth - 1 

+

 

+

        return NestedModelSerializer(many=to_many) 

+

 

+

    def get_related_field(self, model_field, related_model, to_many): 

+

        """ 

+

        Creates a default instance of a flat relational field. 

+

 

+

        Note that model_field will be `None` for reverse relationships. 

+

        """ 

+

        # TODO: filter queryset using: 

+

        # .using(db).complex_filter(self.rel.limit_choices_to) 

+

 

+

        kwargs = { 

+

            'queryset': related_model._default_manager, 

+

            'many': to_many 

+

        } 

+

 

+

        if model_field: 

+

            kwargs['required'] = not(model_field.null or model_field.blank) 

+

 

+

        return PrimaryKeyRelatedField(**kwargs) 

+

 

+

    def get_field(self, model_field): 

+

        """ 

+

        Creates a default instance of a basic non-relational field. 

+

        """ 

+

        kwargs = {} 

+

 

+

        if model_field.null or model_field.blank: 

+

            kwargs['required'] = False 

+

 

+

        if isinstance(model_field, models.AutoField) or not model_field.editable: 

+

            kwargs['read_only'] = True 

+

 

+

        if model_field.has_default(): 

+

            kwargs['default'] = model_field.get_default() 

+

 

+

        if issubclass(model_field.__class__, models.TextField): 

+

            kwargs['widget'] = widgets.Textarea 

+

 

+

        if model_field.verbose_name is not None: 

+

            kwargs['label'] = model_field.verbose_name 

+

 

+

        if model_field.help_text is not None: 

+

            kwargs['help_text'] = model_field.help_text 

+

 

+

        # TODO: TypedChoiceField? 

+

        if model_field.flatchoices:  # This ModelField contains choices 

+

            kwargs['choices'] = model_field.flatchoices 

+

            return ChoiceField(**kwargs) 

+

 

+

        # put this below the ChoiceField because min_value isn't a valid initializer 

+

        if issubclass(model_field.__class__, models.PositiveIntegerField) or\ 

+

                issubclass(model_field.__class__, models.PositiveSmallIntegerField): 

+

            kwargs['min_value'] = 0 

+

 

+

        attribute_dict = { 

+

            models.CharField: ['max_length'], 

+

            models.CommaSeparatedIntegerField: ['max_length'], 

+

            models.DecimalField: ['max_digits', 'decimal_places'], 

+

            models.EmailField: ['max_length'], 

+

            models.FileField: ['max_length'], 

+

            models.ImageField: ['max_length'], 

+

            models.SlugField: ['max_length'], 

+

            models.URLField: ['max_length'], 

+

        } 

+

 

+

        if model_field.__class__ in attribute_dict: 

+

            attributes = attribute_dict[model_field.__class__] 

+

            for attribute in attributes: 

+

                kwargs.update({attribute: getattr(model_field, attribute)}) 

+

 

+

        try: 

+

            return self.field_mapping[model_field.__class__](**kwargs) 

+

        except KeyError: 

+

            return ModelField(model_field=model_field, **kwargs) 

+

 

+

    def get_validation_exclusions(self): 

+

        """ 

+

        Return a list of field names to exclude from model validation. 

+

        """ 

+

        cls = self.opts.model 

+

        opts = get_concrete_model(cls)._meta 

+

        exclusions = [field.name for field in opts.fields + opts.many_to_many] 

+

        for field_name, field in self.fields.items(): 

+

            field_name = field.source or field_name 

+

            if field_name in exclusions and not field.read_only: 

+

                exclusions.remove(field_name) 

+

        return exclusions 

+

 

+

    def full_clean(self, instance): 

+

        """ 

+

        Perform Django's full_clean, and populate the `errors` dictionary 

+

        if any validation errors occur. 

+

 

+

        Note that we don't perform this inside the `.restore_object()` method, 

+

        so that subclasses can override `.restore_object()`, and still get 

+

        the full_clean validation checking. 

+

        """ 

+

        try: 

+

            instance.full_clean(exclude=self.get_validation_exclusions()) 

+

        except ValidationError as err: 

+

            self._errors = err.message_dict 

+

            return None 

+

        return instance 

+

 

+

    def restore_object(self, attrs, instance=None): 

+

        """ 

+

        Restore the model instance. 

+

        """ 

+

        m2m_data = {} 

+

        related_data = {} 

+

        meta = self.opts.model._meta 

+

 

+

        # Reverse fk or one-to-one relations 

+

        for (obj, model) in meta.get_all_related_objects_with_model(): 

+

            field_name = obj.field.related_query_name() 

+

            if field_name in attrs: 

+

                related_data[field_name] = attrs.pop(field_name) 

+

 

+

        # Reverse m2m relations 

+

        for (obj, model) in meta.get_all_related_m2m_objects_with_model(): 

+

            field_name = obj.field.related_query_name() 

+

            if field_name in attrs: 

+

                m2m_data[field_name] = attrs.pop(field_name) 

+

 

+

        # Forward m2m relations 

+

        for field in meta.many_to_many: 

+

            if field.name in attrs: 

+

                m2m_data[field.name] = attrs.pop(field.name) 

+

 

+

        # Update an existing instance... 

+

        if instance is not None: 

+

            for key, val in attrs.items(): 

+

                setattr(instance, key, val) 

+

 

+

        # ...or create a new instance 

+

        else: 

+

            instance = self.opts.model(**attrs) 

+

 

+

        # Any relations that cannot be set until we've 

+

        # saved the model get hidden away on these 

+

        # private attributes, so we can deal with them 

+

        # at the point of save. 

+

        instance._related_data = related_data 

+

        instance._m2m_data = m2m_data 

+

 

+

        return instance 

+

 

+

    def from_native(self, data, files): 

+

        """ 

+

        Override the default method to also include model field validation. 

+

        """ 

+

        instance = super(ModelSerializer, self).from_native(data, files) 

+

        if not self._errors: 

+

            return self.full_clean(instance) 

+

 

+

    def save_object(self, obj, **kwargs): 

+

        """ 

+

        Save the deserialized object and return it. 

+

        """ 

+

        obj.save(**kwargs) 

+

 

+

        if getattr(obj, '_m2m_data', None): 

+

            for accessor_name, object_list in obj._m2m_data.items(): 

+

                setattr(obj, accessor_name, object_list) 

+

            del(obj._m2m_data) 

+

 

+

        if getattr(obj, '_related_data', None): 

+

            for accessor_name, related in obj._related_data.items(): 

+

                setattr(obj, accessor_name, related) 

+

            del(obj._related_data) 

+

 

+

 

+

class HyperlinkedModelSerializerOptions(ModelSerializerOptions): 

+

    """ 

+

    Options for HyperlinkedModelSerializer 

+

    """ 

+

    def __init__(self, meta): 

+

        super(HyperlinkedModelSerializerOptions, self).__init__(meta) 

+

        self.view_name = getattr(meta, 'view_name', None) 

+

        self.lookup_field = getattr(meta, 'lookup_field', None) 

+

 

+

 

+

class HyperlinkedModelSerializer(ModelSerializer): 

+

    """ 

+

    A subclass of ModelSerializer that uses hyperlinked relationships, 

+

    instead of primary key relationships. 

+

    """ 

+

    _options_class = HyperlinkedModelSerializerOptions 

+

    _default_view_name = '%(model_name)s-detail' 

+

    _hyperlink_field_class = HyperlinkedRelatedField 

+

 

+

    def get_default_fields(self): 

+

        fields = super(HyperlinkedModelSerializer, self).get_default_fields() 

+

 

+

        if self.opts.view_name is None: 

+

            self.opts.view_name = self._get_default_view_name(self.opts.model) 

+

 

+

        if 'url' not in fields: 

+

            url_field = HyperlinkedIdentityField( 

+

                view_name=self.opts.view_name, 

+

                lookup_field=self.opts.lookup_field 

+

            ) 

+

            fields.insert(0, 'url', url_field) 

+

 

+

        return fields 

+

 

+

    def get_pk_field(self, model_field): 

+

        if self.opts.fields and model_field.name in self.opts.fields: 

+

            return self.get_field(model_field) 

+

 

+

    def get_related_field(self, model_field, related_model, to_many): 

+

        """ 

+

        Creates a default instance of a flat relational field. 

+

        """ 

+

        # TODO: filter queryset using: 

+

        # .using(db).complex_filter(self.rel.limit_choices_to) 

+

        kwargs = { 

+

            'queryset': related_model._default_manager, 

+

            'view_name': self._get_default_view_name(related_model), 

+

            'many': to_many 

+

        } 

+

 

+

        if model_field: 

+

            kwargs['required'] = not(model_field.null or model_field.blank) 

+

 

+

        if self.opts.lookup_field: 

+

            kwargs['lookup_field'] = self.opts.lookup_field 

+

 

+

        return self._hyperlink_field_class(**kwargs) 

+

 

+

    def get_identity(self, data): 

+

        """ 

+

        This hook is required for bulk update. 

+

        We need to override the default, to use the url as the identity. 

+

        """ 

+

        try: 

+

            return data.get('url', None) 

+

        except AttributeError: 

+

            return None 

+

 

+

    def _get_default_view_name(self, model): 

+

        """ 

+

        Return the view name to use if 'view_name' is not specified in 'Meta' 

+

        """ 

+

        model_meta = model._meta 

+

        format_kwargs = { 

+

            'app_label': model_meta.app_label, 

+

            'model_name': model_meta.object_name.lower() 

+

        } 

+

        return self._default_view_name % format_kwargs 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_settings.html b/htmlcov/rest_framework_settings.html new file mode 100644 index 000000000..ae47b5bc8 --- /dev/null +++ b/htmlcov/rest_framework_settings.html @@ -0,0 +1,465 @@ + + + + + + + + Coverage for rest_framework/settings: 95% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+ +
+

""" 

+

Settings for REST framework are all namespaced in the REST_FRAMEWORK setting. 

+

For example your project's `settings.py` file might look like this: 

+

 

+

REST_FRAMEWORK = { 

+

    'DEFAULT_RENDERER_CLASSES': ( 

+

        'rest_framework.renderers.JSONRenderer', 

+

        'rest_framework.renderers.YAMLRenderer', 

+

    ) 

+

    'DEFAULT_PARSER_CLASSES': ( 

+

        'rest_framework.parsers.JSONParser', 

+

        'rest_framework.parsers.YAMLParser', 

+

    ) 

+

} 

+

 

+

This module provides the `api_setting` object, that is used to access 

+

REST framework settings, checking for user settings first, then falling 

+

back to the defaults. 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

from django.conf import settings 

+

from django.utils import importlib 

+

 

+

from rest_framework import ISO_8601 

+

from rest_framework.compat import six 

+

 

+

 

+

USER_SETTINGS = getattr(settings, 'REST_FRAMEWORK', None) 

+

 

+

DEFAULTS = { 

+

    # Base API policies 

+

    'DEFAULT_RENDERER_CLASSES': ( 

+

        'rest_framework.renderers.JSONRenderer', 

+

        'rest_framework.renderers.BrowsableAPIRenderer', 

+

    ), 

+

    'DEFAULT_PARSER_CLASSES': ( 

+

        'rest_framework.parsers.JSONParser', 

+

        'rest_framework.parsers.FormParser', 

+

        'rest_framework.parsers.MultiPartParser' 

+

    ), 

+

    'DEFAULT_AUTHENTICATION_CLASSES': ( 

+

        'rest_framework.authentication.SessionAuthentication', 

+

        'rest_framework.authentication.BasicAuthentication' 

+

    ), 

+

    'DEFAULT_PERMISSION_CLASSES': ( 

+

        'rest_framework.permissions.AllowAny', 

+

    ), 

+

    'DEFAULT_THROTTLE_CLASSES': ( 

+

    ), 

+

 

+

    'DEFAULT_CONTENT_NEGOTIATION_CLASS': 

+

        'rest_framework.negotiation.DefaultContentNegotiation', 

+

 

+

    # Genric view behavior 

+

    'DEFAULT_MODEL_SERIALIZER_CLASS': 

+

        'rest_framework.serializers.ModelSerializer', 

+

    'DEFAULT_PAGINATION_SERIALIZER_CLASS': 

+

        'rest_framework.pagination.PaginationSerializer', 

+

    'DEFAULT_FILTER_BACKENDS': (), 

+

 

+

    # Throttling 

+

    'DEFAULT_THROTTLE_RATES': { 

+

        'user': None, 

+

        'anon': None, 

+

    }, 

+

 

+

    # Pagination 

+

    'PAGINATE_BY': None, 

+

    'PAGINATE_BY_PARAM': None, 

+

 

+

    # Authentication 

+

    'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser', 

+

    'UNAUTHENTICATED_TOKEN': None, 

+

 

+

    # Browser enhancements 

+

    'FORM_METHOD_OVERRIDE': '_method', 

+

    'FORM_CONTENT_OVERRIDE': '_content', 

+

    'FORM_CONTENTTYPE_OVERRIDE': '_content_type', 

+

    'URL_ACCEPT_OVERRIDE': 'accept', 

+

    'URL_FORMAT_OVERRIDE': 'format', 

+

 

+

    'FORMAT_SUFFIX_KWARG': 'format', 

+

 

+

    # Input and output formats 

+

    'DATE_INPUT_FORMATS': ( 

+

        ISO_8601, 

+

    ), 

+

    'DATE_FORMAT': None, 

+

 

+

    'DATETIME_INPUT_FORMATS': ( 

+

        ISO_8601, 

+

    ), 

+

    'DATETIME_FORMAT': None, 

+

 

+

    'TIME_INPUT_FORMATS': ( 

+

        ISO_8601, 

+

    ), 

+

    'TIME_FORMAT': None, 

+

 

+

    # Pending deprecation 

+

    'FILTER_BACKEND': None, 

+

} 

+

 

+

 

+

# List of settings that may be in string import notation. 

+

IMPORT_STRINGS = ( 

+

    'DEFAULT_RENDERER_CLASSES', 

+

    'DEFAULT_PARSER_CLASSES', 

+

    'DEFAULT_AUTHENTICATION_CLASSES', 

+

    'DEFAULT_PERMISSION_CLASSES', 

+

    'DEFAULT_THROTTLE_CLASSES', 

+

    'DEFAULT_CONTENT_NEGOTIATION_CLASS', 

+

    'DEFAULT_MODEL_SERIALIZER_CLASS', 

+

    'DEFAULT_PAGINATION_SERIALIZER_CLASS', 

+

    'DEFAULT_FILTER_BACKENDS', 

+

    'FILTER_BACKEND', 

+

    'UNAUTHENTICATED_USER', 

+

    'UNAUTHENTICATED_TOKEN', 

+

) 

+

 

+

 

+

def perform_import(val, setting_name): 

+

    """ 

+

    If the given setting is a string import notation, 

+

    then perform the necessary import or imports. 

+

    """ 

+

    if isinstance(val, six.string_types): 

+

        return import_from_string(val, setting_name) 

+

    elif isinstance(val, (list, tuple)): 

+

        return [import_from_string(item, setting_name) for item in val] 

+

    return val 

+

 

+

 

+

def import_from_string(val, setting_name): 

+

    """ 

+

    Attempt to import a class from a string representation. 

+

    """ 

+

    try: 

+

        # Nod to tastypie's use of importlib. 

+

        parts = val.split('.') 

+

        module_path, class_name = '.'.join(parts[:-1]), parts[-1] 

+

        module = importlib.import_module(module_path) 

+

        return getattr(module, class_name) 

+

    except ImportError as e: 

+

        msg = "Could not import '%s' for API setting '%s'. %s: %s." % (val, setting_name, e.__class__.__name__, e) 

+

        raise ImportError(msg) 

+

 

+

 

+

class APISettings(object): 

+

    """ 

+

    A settings object, that allows API settings to be accessed as properties. 

+

    For example: 

+

 

+

        from rest_framework.settings import api_settings 

+

        print api_settings.DEFAULT_RENDERER_CLASSES 

+

 

+

    Any setting with string import paths will be automatically resolved 

+

    and return the class, rather than the string literal. 

+

    """ 

+

    def __init__(self, user_settings=None, defaults=None, import_strings=None): 

+

        self.user_settings = user_settings or {} 

+

        self.defaults = defaults or {} 

+

        self.import_strings = import_strings or () 

+

 

+

    def __getattr__(self, attr): 

+

        if attr not in self.defaults.keys(): 

+

            raise AttributeError("Invalid API setting: '%s'" % attr) 

+

 

+

        try: 

+

            # Check if present in user settings 

+

            val = self.user_settings[attr] 

+

        except KeyError: 

+

            # Fall back to defaults 

+

            val = self.defaults[attr] 

+

 

+

        # Coerce import strings into classes 

+

        if val and attr in self.import_strings: 

+

            val = perform_import(val, attr) 

+

 

+

        self.validate_setting(attr, val) 

+

 

+

        # Cache the result 

+

        setattr(self, attr, val) 

+

        return val 

+

 

+

    def validate_setting(self, attr, val): 

+

        if attr == 'FILTER_BACKEND' and val is not None: 

+

            # Make sure we can initialize the class 

+

            val() 

+

 

+

api_settings = APISettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_status.html b/htmlcov/rest_framework_status.html new file mode 100644 index 000000000..85f919f6b --- /dev/null +++ b/htmlcov/rest_framework_status.html @@ -0,0 +1,187 @@ + + + + + + + + Coverage for rest_framework/status: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+ +
+

""" 

+

Descriptive HTTP status codes, for code readability. 

+

 

+

See RFC 2616 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 

+

And RFC 6585 - http://tools.ietf.org/html/rfc6585 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

HTTP_100_CONTINUE = 100 

+

HTTP_101_SWITCHING_PROTOCOLS = 101 

+

HTTP_200_OK = 200 

+

HTTP_201_CREATED = 201 

+

HTTP_202_ACCEPTED = 202 

+

HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203 

+

HTTP_204_NO_CONTENT = 204 

+

HTTP_205_RESET_CONTENT = 205 

+

HTTP_206_PARTIAL_CONTENT = 206 

+

HTTP_300_MULTIPLE_CHOICES = 300 

+

HTTP_301_MOVED_PERMANENTLY = 301 

+

HTTP_302_FOUND = 302 

+

HTTP_303_SEE_OTHER = 303 

+

HTTP_304_NOT_MODIFIED = 304 

+

HTTP_305_USE_PROXY = 305 

+

HTTP_306_RESERVED = 306 

+

HTTP_307_TEMPORARY_REDIRECT = 307 

+

HTTP_400_BAD_REQUEST = 400 

+

HTTP_401_UNAUTHORIZED = 401 

+

HTTP_402_PAYMENT_REQUIRED = 402 

+

HTTP_403_FORBIDDEN = 403 

+

HTTP_404_NOT_FOUND = 404 

+

HTTP_405_METHOD_NOT_ALLOWED = 405 

+

HTTP_406_NOT_ACCEPTABLE = 406 

+

HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407 

+

HTTP_408_REQUEST_TIMEOUT = 408 

+

HTTP_409_CONFLICT = 409 

+

HTTP_410_GONE = 410 

+

HTTP_411_LENGTH_REQUIRED = 411 

+

HTTP_412_PRECONDITION_FAILED = 412 

+

HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413 

+

HTTP_414_REQUEST_URI_TOO_LONG = 414 

+

HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415 

+

HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416 

+

HTTP_417_EXPECTATION_FAILED = 417 

+

HTTP_428_PRECONDITION_REQUIRED = 428 

+

HTTP_429_TOO_MANY_REQUESTS = 429 

+

HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431 

+

HTTP_500_INTERNAL_SERVER_ERROR = 500 

+

HTTP_501_NOT_IMPLEMENTED = 501 

+

HTTP_502_BAD_GATEWAY = 502 

+

HTTP_503_SERVICE_UNAVAILABLE = 503 

+

HTTP_504_GATEWAY_TIMEOUT = 504 

+

HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505 

+

HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_throttling.html b/htmlcov/rest_framework_throttling.html new file mode 100644 index 000000000..778b0293b --- /dev/null +++ b/htmlcov/rest_framework_throttling.html @@ -0,0 +1,533 @@ + + + + + + + + Coverage for rest_framework/throttling: 81% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+ +
+

""" 

+

Provides various throttling policies. 

+

""" 

+

from __future__ import unicode_literals 

+

from django.core.cache import cache 

+

from django.core.exceptions import ImproperlyConfigured 

+

from rest_framework.settings import api_settings 

+

import time 

+

 

+

 

+

class BaseThrottle(object): 

+

    """ 

+

    Rate throttling of requests. 

+

    """ 

+

    def allow_request(self, request, view): 

+

        """ 

+

        Return `True` if the request should be allowed, `False` otherwise. 

+

        """ 

+

        raise NotImplementedError('.allow_request() must be overridden') 

+

 

+

    def wait(self): 

+

        """ 

+

        Optionally, return a recommended number of seconds to wait before 

+

        the next request. 

+

        """ 

+

        return None 

+

 

+

 

+

class SimpleRateThrottle(BaseThrottle): 

+

    """ 

+

    A simple cache implementation, that only requires `.get_cache_key()` 

+

    to be overridden. 

+

 

+

    The rate (requests / seconds) is set by a `throttle` attribute on the View 

+

    class.  The attribute is a string of the form 'number_of_requests/period'. 

+

 

+

    Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day') 

+

 

+

    Previous request information used for throttling is stored in the cache. 

+

    """ 

+

 

+

    timer = time.time 

+

    cache_format = 'throtte_%(scope)s_%(ident)s' 

+

    scope = None 

+

    THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES 

+

 

+

    def __init__(self): 

+

        if not getattr(self, 'rate', None): 

+

            self.rate = self.get_rate() 

+

        self.num_requests, self.duration = self.parse_rate(self.rate) 

+

 

+

    def get_cache_key(self, request, view): 

+

        """ 

+

        Should return a unique cache-key which can be used for throttling. 

+

        Must be overridden. 

+

 

+

        May return `None` if the request should not be throttled. 

+

        """ 

+

        raise NotImplementedError('.get_cache_key() must be overridden') 

+

 

+

    def get_rate(self): 

+

        """ 

+

        Determine the string representation of the allowed request rate. 

+

        """ 

+

        if not getattr(self, 'scope', None): 

+

            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" % 

+

                   self.__class__.__name__) 

+

            raise ImproperlyConfigured(msg) 

+

 

+

        try: 

+

            return self.THROTTLE_RATES[self.scope] 

+

        except KeyError: 

+

            msg = "No default throttle rate set for '%s' scope" % self.scope 

+

            raise ImproperlyConfigured(msg) 

+

 

+

    def parse_rate(self, rate): 

+

        """ 

+

        Given the request rate string, return a two tuple of: 

+

        <allowed number of requests>, <period of time in seconds> 

+

        """ 

+

        if rate is None: 

+

            return (None, None) 

+

        num, period = rate.split('/') 

+

        num_requests = int(num) 

+

        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]] 

+

        return (num_requests, duration) 

+

 

+

    def allow_request(self, request, view): 

+

        """ 

+

        Implement the check to see if the request should be throttled. 

+

 

+

        On success calls `throttle_success`. 

+

        On failure calls `throttle_failure`. 

+

        """ 

+

        if self.rate is None: 

+

            return True 

+

 

+

        self.key = self.get_cache_key(request, view) 

+

        self.history = cache.get(self.key, []) 

+

        self.now = self.timer() 

+

 

+

        # Drop any requests from the history which have now passed the 

+

        # throttle duration 

+

        while self.history and self.history[-1] <= self.now - self.duration: 

+

            self.history.pop() 

+

        if len(self.history) >= self.num_requests: 

+

            return self.throttle_failure() 

+

        return self.throttle_success() 

+

 

+

    def throttle_success(self): 

+

        """ 

+

        Inserts the current request's timestamp along with the key 

+

        into the cache. 

+

        """ 

+

        self.history.insert(0, self.now) 

+

        cache.set(self.key, self.history, self.duration) 

+

        return True 

+

 

+

    def throttle_failure(self): 

+

        """ 

+

        Called when a request to the API has failed due to throttling. 

+

        """ 

+

        return False 

+

 

+

    def wait(self): 

+

        """ 

+

        Returns the recommended next request time in seconds. 

+

        """ 

+

        if self.history: 

+

            remaining_duration = self.duration - (self.now - self.history[-1]) 

+

        else: 

+

            remaining_duration = self.duration 

+

 

+

        available_requests = self.num_requests - len(self.history) + 1 

+

 

+

        return remaining_duration / float(available_requests) 

+

 

+

 

+

class AnonRateThrottle(SimpleRateThrottle): 

+

    """ 

+

    Limits the rate of API calls that may be made by a anonymous users. 

+

 

+

    The IP address of the request will be used as the unique cache key. 

+

    """ 

+

    scope = 'anon' 

+

 

+

    def get_cache_key(self, request, view): 

+

        if request.user.is_authenticated(): 

+

            return None  # Only throttle unauthenticated requests. 

+

 

+

        ident = request.META.get('REMOTE_ADDR', None) 

+

 

+

        return self.cache_format % { 

+

            'scope': self.scope, 

+

            'ident': ident 

+

        } 

+

 

+

 

+

class UserRateThrottle(SimpleRateThrottle): 

+

    """ 

+

    Limits the rate of API calls that may be made by a given user. 

+

 

+

    The user id will be used as a unique cache key if the user is 

+

    authenticated.  For anonymous requests, the IP address of the request will 

+

    be used. 

+

    """ 

+

    scope = 'user' 

+

 

+

    def get_cache_key(self, request, view): 

+

        if request.user.is_authenticated(): 

+

            ident = request.user.id 

+

        else: 

+

            ident = request.META.get('REMOTE_ADDR', None) 

+

 

+

        return self.cache_format % { 

+

            'scope': self.scope, 

+

            'ident': ident 

+

        } 

+

 

+

 

+

class ScopedRateThrottle(SimpleRateThrottle): 

+

    """ 

+

    Limits the rate of API calls by different amounts for various parts of 

+

    the API.  Any view that has the `throttle_scope` property set will be 

+

    throttled.  The unique cache key will be generated by concatenating the 

+

    user id of the request, and the scope of the view being accessed. 

+

    """ 

+

    scope_attr = 'throttle_scope' 

+

 

+

    def __init__(self): 

+

        # Override the usual SimpleRateThrottle, because we can't determine 

+

        # the rate until called by the view. 

+

        pass 

+

 

+

    def allow_request(self, request, view): 

+

        # We can only determine the scope once we're called by the view. 

+

        self.scope = getattr(view, self.scope_attr, None) 

+

 

+

        # If a view does not have a `throttle_scope` always allow the request 

+

        if not self.scope: 

+

            return True 

+

 

+

        # Determine the allowed request rate as we normally would during 

+

        # the `__init__` call. 

+

        self.rate = self.get_rate() 

+

        self.num_requests, self.duration = self.parse_rate(self.rate) 

+

 

+

        # We can now proceed as normal. 

+

        return super(ScopedRateThrottle, self).allow_request(request, view) 

+

 

+

    def get_cache_key(self, request, view): 

+

        """ 

+

        If `view.throttle_scope` is not set, don't apply this throttle. 

+

 

+

        Otherwise generate the unique cache key by concatenating the user id 

+

        with the '.throttle_scope` property of the view. 

+

        """ 

+

        if request.user.is_authenticated(): 

+

            ident = request.user.id 

+

        else: 

+

            ident = request.META.get('REMOTE_ADDR', None) 

+

 

+

        return self.cache_format % { 

+

            'scope': self.scope, 

+

            'ident': ident 

+

        } 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_urlpatterns.html b/htmlcov/rest_framework_urlpatterns.html new file mode 100644 index 000000000..4c824a770 --- /dev/null +++ b/htmlcov/rest_framework_urlpatterns.html @@ -0,0 +1,205 @@ + + + + + + + + Coverage for rest_framework/urlpatterns: 87% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+ +
+

from __future__ import unicode_literals 

+

from django.core.urlresolvers import RegexURLResolver 

+

from rest_framework.compat import url, include 

+

from rest_framework.settings import api_settings 

+

 

+

 

+

def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required): 

+

    ret = [] 

+

    for urlpattern in urlpatterns: 

+

        if isinstance(urlpattern, RegexURLResolver): 

+

            # Set of included URL patterns 

+

            regex = urlpattern.regex.pattern 

+

            namespace = urlpattern.namespace 

+

            app_name = urlpattern.app_name 

+

            kwargs = urlpattern.default_kwargs 

+

            # Add in the included patterns, after applying the suffixes 

+

            patterns = apply_suffix_patterns(urlpattern.url_patterns, 

+

                                             suffix_pattern, 

+

                                             suffix_required) 

+

            ret.append(url(regex, include(patterns, namespace, app_name), kwargs)) 

+

 

+

        else: 

+

            # Regular URL pattern 

+

            regex = urlpattern.regex.pattern.rstrip('$') + suffix_pattern 

+

            view = urlpattern._callback or urlpattern._callback_str 

+

            kwargs = urlpattern.default_args 

+

            name = urlpattern.name 

+

            # Add in both the existing and the new urlpattern 

+

            if not suffix_required: 

+

                ret.append(urlpattern) 

+

            ret.append(url(regex, view, kwargs, name)) 

+

 

+

    return ret 

+

 

+

 

+

def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None): 

+

    """ 

+

    Supplement existing urlpatterns with corresponding patterns that also 

+

    include a '.format' suffix.  Retains urlpattern ordering. 

+

 

+

    urlpatterns: 

+

        A list of URL patterns. 

+

 

+

    suffix_required: 

+

        If `True`, only suffixed URLs will be generated, and non-suffixed 

+

        URLs will not be used.  Defaults to `False`. 

+

 

+

    allowed: 

+

        An optional tuple/list of allowed suffixes.  eg ['json', 'api'] 

+

        Defaults to `None`, which allows any suffix. 

+

    """ 

+

    suffix_kwarg = api_settings.FORMAT_SUFFIX_KWARG 

+

    if allowed: 

+

        if len(allowed) == 1: 

+

            allowed_pattern = allowed[0] 

+

        else: 

+

            allowed_pattern = '(%s)' % '|'.join(allowed) 

+

        suffix_pattern = r'\.(?P<%s>%s)$' % (suffix_kwarg, allowed_pattern) 

+

    else: 

+

        suffix_pattern = r'\.(?P<%s>[a-z]+)$' % suffix_kwarg 

+

 

+

    return apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_urls.html b/htmlcov/rest_framework_urls.html new file mode 100644 index 000000000..7720a6d40 --- /dev/null +++ b/htmlcov/rest_framework_urls.html @@ -0,0 +1,129 @@ + + + + + + + + Coverage for rest_framework/urls: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+ +
+

""" 

+

Login and logout views for the browsable API. 

+

 

+

Add these to your root URLconf if you're using the browsable API and 

+

your API requires authentication. 

+

 

+

The urls must be namespaced as 'rest_framework', and you should make sure 

+

your authentication settings include `SessionAuthentication`. 

+

 

+

    urlpatterns = patterns('', 

+

        ... 

+

        url(r'^auth', include('rest_framework.urls', namespace='rest_framework')) 

+

    ) 

+

""" 

+

from __future__ import unicode_literals 

+

from rest_framework.compat import patterns, url 

+

 

+

 

+

template_name = {'template_name': 'rest_framework/login.html'} 

+

 

+

urlpatterns = patterns('django.contrib.auth.views', 

+

    url(r'^login/$', 'login', template_name, name='login'), 

+

    url(r'^logout/$', 'logout', template_name, name='logout'), 

+

) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_utils___init__.html b/htmlcov/rest_framework_utils___init__.html new file mode 100644 index 000000000..99eb18c4f --- /dev/null +++ b/htmlcov/rest_framework_utils___init__.html @@ -0,0 +1,81 @@ + + + + + + + + Coverage for rest_framework/utils/__init__: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+ + + +
+
+ + + + + diff --git a/htmlcov/rest_framework_utils_breadcrumbs.html b/htmlcov/rest_framework_utils_breadcrumbs.html new file mode 100644 index 000000000..14fb8955d --- /dev/null +++ b/htmlcov/rest_framework_utils_breadcrumbs.html @@ -0,0 +1,189 @@ + + + + + + + + Coverage for rest_framework/utils/breadcrumbs: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+ +
+

from __future__ import unicode_literals 

+

from django.core.urlresolvers import resolve, get_script_prefix 

+

from rest_framework.utils.formatting import get_view_name 

+

 

+

 

+

def get_breadcrumbs(url): 

+

    """ 

+

    Given a url returns a list of breadcrumbs, which are each a 

+

    tuple of (name, url). 

+

    """ 

+

 

+

    from rest_framework.views import APIView 

+

 

+

    def breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen): 

+

        """ 

+

        Add tuples of (name, url) to the breadcrumbs list, 

+

        progressively chomping off parts of the url. 

+

        """ 

+

 

+

        try: 

+

            (view, unused_args, unused_kwargs) = resolve(url) 

+

        except Exception: 

+

            pass 

+

        else: 

+

            # Check if this is a REST framework view, 

+

            # and if so add it to the breadcrumbs 

+

            cls = getattr(view, 'cls', None) 

+

            if cls is not None and issubclass(cls, APIView): 

+

                # Don't list the same view twice in a row. 

+

                # Probably an optional trailing slash. 

+

                if not seen or seen[-1] != view: 

+

                    suffix = getattr(view, 'suffix', None) 

+

                    name = get_view_name(view.cls, suffix) 

+

                    breadcrumbs_list.insert(0, (name, prefix + url)) 

+

                    seen.append(view) 

+

 

+

        if url == '': 

+

            # All done 

+

            return breadcrumbs_list 

+

 

+

        elif url.endswith('/'): 

+

            # Drop trailing slash off the end and continue to try to 

+

            # resolve more breadcrumbs 

+

            url = url.rstrip('/') 

+

            return breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen) 

+

 

+

        # Drop trailing non-slash off the end and continue to try to 

+

        # resolve more breadcrumbs 

+

        url = url[:url.rfind('/') + 1] 

+

        return breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen) 

+

 

+

    prefix = get_script_prefix().rstrip('/') 

+

    url = url[len(prefix):] 

+

    return breadcrumbs_recursive(url, [], prefix, []) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_utils_encoders.html b/htmlcov/rest_framework_utils_encoders.html new file mode 100644 index 000000000..9f0ca343a --- /dev/null +++ b/htmlcov/rest_framework_utils_encoders.html @@ -0,0 +1,275 @@ + + + + + + + + Coverage for rest_framework/utils/encoders: 73% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+ +
+

""" 

+

Helper classes for parsers. 

+

""" 

+

from __future__ import unicode_literals 

+

from django.utils.datastructures import SortedDict 

+

from django.utils.functional import Promise 

+

from rest_framework.compat import timezone, force_text 

+

from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata 

+

import datetime 

+

import decimal 

+

import types 

+

import json 

+

 

+

 

+

class JSONEncoder(json.JSONEncoder): 

+

    """ 

+

    JSONEncoder subclass that knows how to encode date/time/timedelta, 

+

    decimal types, and generators. 

+

    """ 

+

    def default(self, o): 

+

        # For Date Time string spec, see ECMA 262 

+

        # http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 

+

        if isinstance(o, Promise): 

+

            return force_text(o) 

+

        elif isinstance(o, datetime.datetime): 

+

            r = o.isoformat() 

+

            if o.microsecond: 

+

                r = r[:23] + r[26:] 

+

            if r.endswith('+00:00'): 

+

                r = r[:-6] + 'Z' 

+

            return r 

+

        elif isinstance(o, datetime.date): 

+

            return o.isoformat() 

+

        elif isinstance(o, datetime.time): 

+

            if timezone and timezone.is_aware(o): 

+

                raise ValueError("JSON can't represent timezone-aware times.") 

+

            r = o.isoformat() 

+

            if o.microsecond: 

+

                r = r[:12] 

+

            return r 

+

        elif isinstance(o, datetime.timedelta): 

+

            return str(o.total_seconds()) 

+

        elif isinstance(o, decimal.Decimal): 

+

            return str(o) 

+

        elif hasattr(o, '__iter__'): 

+

            return [i for i in o] 

+

        return super(JSONEncoder, self).default(o) 

+

 

+

 

+

try: 

+

    import yaml 

+

except ImportError: 

+

    SafeDumper = None 

+

else: 

+

    # Adapted from http://pyyaml.org/attachment/ticket/161/use_ordered_dict.py 

+

    class SafeDumper(yaml.SafeDumper): 

+

        """ 

+

        Handles decimals as strings. 

+

        Handles SortedDicts as usual dicts, but preserves field order, rather 

+

        than the usual behaviour of sorting the keys. 

+

        """ 

+

        def represent_decimal(self, data): 

+

            return self.represent_scalar('tag:yaml.org,2002:str', str(data)) 

+

 

+

        def represent_mapping(self, tag, mapping, flow_style=None): 

+

            value = [] 

+

            node = yaml.MappingNode(tag, value, flow_style=flow_style) 

+

            if self.alias_key is not None: 

+

                self.represented_objects[self.alias_key] = node 

+

            best_style = True 

+

            if hasattr(mapping, 'items'): 

+

                mapping = list(mapping.items()) 

+

                if not isinstance(mapping, SortedDict): 

+

                    mapping.sort() 

+

            for item_key, item_value in mapping: 

+

                node_key = self.represent_data(item_key) 

+

                node_value = self.represent_data(item_value) 

+

                if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style): 

+

                    best_style = False 

+

                if not (isinstance(node_value, yaml.ScalarNode) and not node_value.style): 

+

                    best_style = False 

+

                value.append((node_key, node_value)) 

+

            if flow_style is None: 

+

                if self.default_flow_style is not None: 

+

                    node.flow_style = self.default_flow_style 

+

                else: 

+

                    node.flow_style = best_style 

+

            return node 

+

 

+

    SafeDumper.add_representer(SortedDict, 

+

            yaml.representer.SafeRepresenter.represent_dict) 

+

    SafeDumper.add_representer(DictWithMetadata, 

+

            yaml.representer.SafeRepresenter.represent_dict) 

+

    SafeDumper.add_representer(SortedDictWithMetadata, 

+

            yaml.representer.SafeRepresenter.represent_dict) 

+

    SafeDumper.add_representer(types.GeneratorType, 

+

            yaml.representer.SafeRepresenter.represent_list) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_utils_formatting.html b/htmlcov/rest_framework_utils_formatting.html new file mode 100644 index 000000000..54e1570f7 --- /dev/null +++ b/htmlcov/rest_framework_utils_formatting.html @@ -0,0 +1,241 @@ + + + + + + + + Coverage for rest_framework/utils/formatting: 97% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+ +
+

""" 

+

Utility functions to return a formatted name and description for a given view. 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

from django.utils.html import escape 

+

from django.utils.safestring import mark_safe 

+

from rest_framework.compat import apply_markdown 

+

import re 

+

 

+

 

+

def _remove_trailing_string(content, trailing): 

+

    """ 

+

    Strip trailing component `trailing` from `content` if it exists. 

+

    Used when generating names from view classes. 

+

    """ 

+

    if content.endswith(trailing) and content != trailing: 

+

        return content[:-len(trailing)] 

+

    return content 

+

 

+

 

+

def _remove_leading_indent(content): 

+

    """ 

+

    Remove leading indent from a block of text. 

+

    Used when generating descriptions from docstrings. 

+

    """ 

+

    whitespace_counts = [len(line) - len(line.lstrip(' ')) 

+

                         for line in content.splitlines()[1:] if line.lstrip()] 

+

 

+

    # unindent the content if needed 

+

    if whitespace_counts: 

+

        whitespace_pattern = '^' + (' ' * min(whitespace_counts)) 

+

        content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content) 

+

    content = content.strip('\n') 

+

    return content 

+

 

+

 

+

def _camelcase_to_spaces(content): 

+

    """ 

+

    Translate 'CamelCaseNames' to 'Camel Case Names'. 

+

    Used when generating names from view classes. 

+

    """ 

+

    camelcase_boundry = '(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))' 

+

    content = re.sub(camelcase_boundry, ' \\1', content).strip() 

+

    return ' '.join(content.split('_')).title() 

+

 

+

 

+

def get_view_name(cls, suffix=None): 

+

    """ 

+

    Return a formatted name for an `APIView` class or `@api_view` function. 

+

    """ 

+

    name = cls.__name__ 

+

    name = _remove_trailing_string(name, 'View') 

+

    name = _remove_trailing_string(name, 'ViewSet') 

+

    name = _camelcase_to_spaces(name) 

+

    if suffix: 

+

        name += ' ' + suffix 

+

    return name 

+

 

+

 

+

def get_view_description(cls, html=False): 

+

    """ 

+

    Return a description for an `APIView` class or `@api_view` function. 

+

    """ 

+

    description = cls.__doc__ or '' 

+

    description = _remove_leading_indent(description) 

+

    if html: 

+

        return markup_description(description) 

+

    return description 

+

 

+

 

+

def markup_description(description): 

+

    """ 

+

    Apply HTML markup to the given description. 

+

    """ 

+

    if apply_markdown: 

+

        description = apply_markdown(description) 

+

    else: 

+

        description = escape(description).replace('\n', '<br />') 

+

    return mark_safe(description) 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_utils_mediatypes.html b/htmlcov/rest_framework_utils_mediatypes.html new file mode 100644 index 000000000..2ce44ab59 --- /dev/null +++ b/htmlcov/rest_framework_utils_mediatypes.html @@ -0,0 +1,257 @@ + + + + + + + + Coverage for rest_framework/utils/mediatypes: 77% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+ +
+

""" 

+

Handling of media types, as found in HTTP Content-Type and Accept headers. 

+

 

+

See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 

+

""" 

+

from __future__ import unicode_literals 

+

from django.http.multipartparser import parse_header 

+

from rest_framework import HTTP_HEADER_ENCODING 

+

 

+

 

+

def media_type_matches(lhs, rhs): 

+

    """ 

+

    Returns ``True`` if the media type in the first argument <= the 

+

    media type in the second argument.  The media types are strings 

+

    as described by the HTTP spec. 

+

 

+

    Valid media type strings include: 

+

 

+

    'application/json; indent=4' 

+

    'application/json' 

+

    'text/*' 

+

    '*/*' 

+

    """ 

+

    lhs = _MediaType(lhs) 

+

    rhs = _MediaType(rhs) 

+

    return lhs.match(rhs) 

+

 

+

 

+

def order_by_precedence(media_type_lst): 

+

    """ 

+

    Returns a list of sets of media type strings, ordered by precedence. 

+

    Precedence is determined by how specific a media type is: 

+

 

+

    3. 'type/subtype; param=val' 

+

    2. 'type/subtype' 

+

    1. 'type/*' 

+

    0. '*/*' 

+

    """ 

+

    ret = [set(), set(), set(), set()] 

+

    for media_type in media_type_lst: 

+

        precedence = _MediaType(media_type).precedence 

+

        ret[3 - precedence].add(media_type) 

+

    return [media_types for media_types in ret if media_types] 

+

 

+

 

+

class _MediaType(object): 

+

    def __init__(self, media_type_str): 

+

        if media_type_str is None: 

+

            media_type_str = '' 

+

        self.orig = media_type_str 

+

        self.full_type, self.params = parse_header(media_type_str.encode(HTTP_HEADER_ENCODING)) 

+

        self.main_type, sep, self.sub_type = self.full_type.partition('/') 

+

 

+

    def match(self, other): 

+

        """Return true if this MediaType satisfies the given MediaType.""" 

+

        for key in self.params.keys(): 

+

            if key != 'q' and other.params.get(key, None) != self.params.get(key, None): 

+

                return False 

+

 

+

        if self.sub_type != '*' and other.sub_type != '*'  and other.sub_type != self.sub_type: 

+

            return False 

+

 

+

        if self.main_type != '*' and other.main_type != '*' and other.main_type != self.main_type: 

+

            return False 

+

 

+

        return True 

+

 

+

    @property 

+

    def precedence(self): 

+

        """ 

+

        Return a precedence level from 0-3 for the media type given how specific it is. 

+

        """ 

+

        if self.main_type == '*': 

+

            return 0 

+

        elif self.sub_type == '*': 

+

            return 1 

+

        elif not self.params or self.params.keys() == ['q']: 

+

            return 2 

+

        return 3 

+

 

+

    def __str__(self): 

+

        return unicode(self).encode('utf-8') 

+

 

+

    def __unicode__(self): 

+

        ret = "%s/%s" % (self.main_type, self.sub_type) 

+

        for key, val in self.params.items(): 

+

            ret += "; %s=%s" % (key, val) 

+

        return ret 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_views.html b/htmlcov/rest_framework_views.html new file mode 100644 index 000000000..f836e71fb --- /dev/null +++ b/htmlcov/rest_framework_views.html @@ -0,0 +1,793 @@ + + + + + + + + Coverage for rest_framework/views: 100% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+

140

+

141

+

142

+

143

+

144

+

145

+

146

+

147

+

148

+

149

+

150

+

151

+

152

+

153

+

154

+

155

+

156

+

157

+

158

+

159

+

160

+

161

+

162

+

163

+

164

+

165

+

166

+

167

+

168

+

169

+

170

+

171

+

172

+

173

+

174

+

175

+

176

+

177

+

178

+

179

+

180

+

181

+

182

+

183

+

184

+

185

+

186

+

187

+

188

+

189

+

190

+

191

+

192

+

193

+

194

+

195

+

196

+

197

+

198

+

199

+

200

+

201

+

202

+

203

+

204

+

205

+

206

+

207

+

208

+

209

+

210

+

211

+

212

+

213

+

214

+

215

+

216

+

217

+

218

+

219

+

220

+

221

+

222

+

223

+

224

+

225

+

226

+

227

+

228

+

229

+

230

+

231

+

232

+

233

+

234

+

235

+

236

+

237

+

238

+

239

+

240

+

241

+

242

+

243

+

244

+

245

+

246

+

247

+

248

+

249

+

250

+

251

+

252

+

253

+

254

+

255

+

256

+

257

+

258

+

259

+

260

+

261

+

262

+

263

+

264

+

265

+

266

+

267

+

268

+

269

+

270

+

271

+

272

+

273

+

274

+

275

+

276

+

277

+

278

+

279

+

280

+

281

+

282

+

283

+

284

+

285

+

286

+

287

+

288

+

289

+

290

+

291

+

292

+

293

+

294

+

295

+

296

+

297

+

298

+

299

+

300

+

301

+

302

+

303

+

304

+

305

+

306

+

307

+

308

+

309

+

310

+

311

+

312

+

313

+

314

+

315

+

316

+

317

+

318

+

319

+

320

+

321

+

322

+

323

+

324

+

325

+

326

+

327

+

328

+

329

+

330

+

331

+

332

+

333

+

334

+

335

+

336

+

337

+

338

+

339

+

340

+

341

+

342

+

343

+

344

+

345

+

346

+

347

+

348

+

349

+

350

+

351

+

352

+

353

+

354

+

355

+

356

+ +
+

""" 

+

Provides an APIView class that is the base of all views in REST framework. 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

from django.core.exceptions import PermissionDenied 

+

from django.http import Http404, HttpResponse 

+

from django.utils.datastructures import SortedDict 

+

from django.views.decorators.csrf import csrf_exempt 

+

from rest_framework import status, exceptions 

+

from rest_framework.compat import View 

+

from rest_framework.request import Request 

+

from rest_framework.response import Response 

+

from rest_framework.settings import api_settings 

+

from rest_framework.utils.formatting import get_view_name, get_view_description 

+

 

+

 

+

class APIView(View): 

+

    settings = api_settings 

+

 

+

    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES 

+

    parser_classes = api_settings.DEFAULT_PARSER_CLASSES 

+

    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES 

+

    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES 

+

    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES 

+

    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS 

+

 

+

    @classmethod 

+

    def as_view(cls, **initkwargs): 

+

        """ 

+

        Store the original class on the view function. 

+

 

+

        This allows us to discover information about the view when we do URL 

+

        reverse lookups.  Used for breadcrumb generation. 

+

        """ 

+

        view = super(APIView, cls).as_view(**initkwargs) 

+

        view.cls = cls 

+

        return view 

+

 

+

    @property 

+

    def allowed_methods(self): 

+

        """ 

+

        Wrap Django's private `_allowed_methods` interface in a public property. 

+

        """ 

+

        return self._allowed_methods() 

+

 

+

    @property 

+

    def default_response_headers(self): 

+

        # TODO: deprecate? 

+

        # TODO: Only vary by accept if multiple renderers 

+

        return { 

+

            'Allow': ', '.join(self.allowed_methods), 

+

            'Vary': 'Accept' 

+

        } 

+

 

+

    def http_method_not_allowed(self, request, *args, **kwargs): 

+

        """ 

+

        If `request.method` does not correspond to a handler method, 

+

        determine what kind of exception to raise. 

+

        """ 

+

        raise exceptions.MethodNotAllowed(request.method) 

+

 

+

    def permission_denied(self, request): 

+

        """ 

+

        If request is not permitted, determine what kind of exception to raise. 

+

        """ 

+

        if not self.request.successful_authenticator: 

+

            raise exceptions.NotAuthenticated() 

+

        raise exceptions.PermissionDenied() 

+

 

+

    def throttled(self, request, wait): 

+

        """ 

+

        If request is throttled, determine what kind of exception to raise. 

+

        """ 

+

        raise exceptions.Throttled(wait) 

+

 

+

    def get_authenticate_header(self, request): 

+

        """ 

+

        If a request is unauthenticated, determine the WWW-Authenticate 

+

        header to use for 401 responses, if any. 

+

        """ 

+

        authenticators = self.get_authenticators() 

+

        if authenticators: 

+

            return authenticators[0].authenticate_header(request) 

+

 

+

    def get_parser_context(self, http_request): 

+

        """ 

+

        Returns a dict that is passed through to Parser.parse(), 

+

        as the `parser_context` keyword argument. 

+

        """ 

+

        # Note: Additionally `request` will also be added to the context 

+

        #       by the Request object. 

+

        return { 

+

            'view': self, 

+

            'args': getattr(self, 'args', ()), 

+

            'kwargs': getattr(self, 'kwargs', {}) 

+

        } 

+

 

+

    def get_renderer_context(self): 

+

        """ 

+

        Returns a dict that is passed through to Renderer.render(), 

+

        as the `renderer_context` keyword argument. 

+

        """ 

+

        # Note: Additionally 'response' will also be added to the context, 

+

        #       by the Response object. 

+

        return { 

+

            'view': self, 

+

            'args': getattr(self, 'args', ()), 

+

            'kwargs': getattr(self, 'kwargs', {}), 

+

            'request': getattr(self, 'request', None) 

+

        } 

+

 

+

    # API policy instantiation methods 

+

 

+

    def get_format_suffix(self, **kwargs): 

+

        """ 

+

        Determine if the request includes a '.json' style format suffix 

+

        """ 

+

        if self.settings.FORMAT_SUFFIX_KWARG: 

+

            return kwargs.get(self.settings.FORMAT_SUFFIX_KWARG) 

+

 

+

    def get_renderers(self): 

+

        """ 

+

        Instantiates and returns the list of renderers that this view can use. 

+

        """ 

+

        return [renderer() for renderer in self.renderer_classes] 

+

 

+

    def get_parsers(self): 

+

        """ 

+

        Instantiates and returns the list of parsers that this view can use. 

+

        """ 

+

        return [parser() for parser in self.parser_classes] 

+

 

+

    def get_authenticators(self): 

+

        """ 

+

        Instantiates and returns the list of authenticators that this view can use. 

+

        """ 

+

        return [auth() for auth in self.authentication_classes] 

+

 

+

    def get_permissions(self): 

+

        """ 

+

        Instantiates and returns the list of permissions that this view requires. 

+

        """ 

+

        return [permission() for permission in self.permission_classes] 

+

 

+

    def get_throttles(self): 

+

        """ 

+

        Instantiates and returns the list of throttles that this view uses. 

+

        """ 

+

        return [throttle() for throttle in self.throttle_classes] 

+

 

+

    def get_content_negotiator(self): 

+

        """ 

+

        Instantiate and return the content negotiation class to use. 

+

        """ 

+

        if not getattr(self, '_negotiator', None): 

+

            self._negotiator = self.content_negotiation_class() 

+

        return self._negotiator 

+

 

+

    # API policy implementation methods 

+

 

+

    def perform_content_negotiation(self, request, force=False): 

+

        """ 

+

        Determine which renderer and media type to use render the response. 

+

        """ 

+

        renderers = self.get_renderers() 

+

        conneg = self.get_content_negotiator() 

+

 

+

        try: 

+

            return conneg.select_renderer(request, renderers, self.format_kwarg) 

+

        except Exception: 

+

            if force: 

+

                return (renderers[0], renderers[0].media_type) 

+

            raise 

+

 

+

    def perform_authentication(self, request): 

+

        """ 

+

        Perform authentication on the incoming request. 

+

 

+

        Note that if you override this and simply 'pass', then authentication 

+

        will instead be performed lazily, the first time either 

+

        `request.user` or `request.auth` is accessed. 

+

        """ 

+

        request.user 

+

 

+

    def check_permissions(self, request): 

+

        """ 

+

        Check if the request should be permitted. 

+

        Raises an appropriate exception if the request is not permitted. 

+

        """ 

+

        for permission in self.get_permissions(): 

+

            if not permission.has_permission(request, self): 

+

                self.permission_denied(request) 

+

 

+

    def check_object_permissions(self, request, obj): 

+

        """ 

+

        Check if the request should be permitted for a given object. 

+

        Raises an appropriate exception if the request is not permitted. 

+

        """ 

+

        for permission in self.get_permissions(): 

+

            if not permission.has_object_permission(request, self, obj): 

+

                self.permission_denied(request) 

+

 

+

    def check_throttles(self, request): 

+

        """ 

+

        Check if request should be throttled. 

+

        Raises an appropriate exception if the request is throttled. 

+

        """ 

+

        for throttle in self.get_throttles(): 

+

            if not throttle.allow_request(request, self): 

+

                self.throttled(request, throttle.wait()) 

+

 

+

    # Dispatch methods 

+

 

+

    def initialize_request(self, request, *args, **kargs): 

+

        """ 

+

        Returns the initial request object. 

+

        """ 

+

        parser_context = self.get_parser_context(request) 

+

 

+

        return Request(request, 

+

                       parsers=self.get_parsers(), 

+

                       authenticators=self.get_authenticators(), 

+

                       negotiator=self.get_content_negotiator(), 

+

                       parser_context=parser_context) 

+

 

+

    def initial(self, request, *args, **kwargs): 

+

        """ 

+

        Runs anything that needs to occur prior to calling the method handler. 

+

        """ 

+

        self.format_kwarg = self.get_format_suffix(**kwargs) 

+

 

+

        # Ensure that the incoming request is permitted 

+

        self.perform_authentication(request) 

+

        self.check_permissions(request) 

+

        self.check_throttles(request) 

+

 

+

        # Perform content negotiation and store the accepted info on the request 

+

        neg = self.perform_content_negotiation(request) 

+

        request.accepted_renderer, request.accepted_media_type = neg 

+

 

+

    def finalize_response(self, request, response, *args, **kwargs): 

+

        """ 

+

        Returns the final response object. 

+

        """ 

+

        # Make the error obvious if a proper response is not returned 

+

        assert isinstance(response, HttpResponse), ( 

+

            'Expected a `Response` to be returned from the view, ' 

+

            'but received a `%s`' % type(response) 

+

        ) 

+

 

+

        if isinstance(response, Response): 

+

            if not getattr(request, 'accepted_renderer', None): 

+

                neg = self.perform_content_negotiation(request, force=True) 

+

                request.accepted_renderer, request.accepted_media_type = neg 

+

 

+

            response.accepted_renderer = request.accepted_renderer 

+

            response.accepted_media_type = request.accepted_media_type 

+

            response.renderer_context = self.get_renderer_context() 

+

 

+

        for key, value in self.headers.items(): 

+

            response[key] = value 

+

 

+

        return response 

+

 

+

    def handle_exception(self, exc): 

+

        """ 

+

        Handle any exception that occurs, by returning an appropriate response, 

+

        or re-raising the error. 

+

        """ 

+

        if isinstance(exc, exceptions.Throttled): 

+

            # Throttle wait header 

+

            self.headers['X-Throttle-Wait-Seconds'] = '%d' % exc.wait 

+

 

+

        if isinstance(exc, (exceptions.NotAuthenticated, 

+

                            exceptions.AuthenticationFailed)): 

+

            # WWW-Authenticate header for 401 responses, else coerce to 403 

+

            auth_header = self.get_authenticate_header(self.request) 

+

 

+

            if auth_header: 

+

                self.headers['WWW-Authenticate'] = auth_header 

+

            else: 

+

                exc.status_code = status.HTTP_403_FORBIDDEN 

+

 

+

        if isinstance(exc, exceptions.APIException): 

+

            return Response({'detail': exc.detail}, 

+

                            status=exc.status_code, 

+

                            exception=True) 

+

        elif isinstance(exc, Http404): 

+

            return Response({'detail': 'Not found'}, 

+

                            status=status.HTTP_404_NOT_FOUND, 

+

                            exception=True) 

+

        elif isinstance(exc, PermissionDenied): 

+

            return Response({'detail': 'Permission denied'}, 

+

                            status=status.HTTP_403_FORBIDDEN, 

+

                            exception=True) 

+

        raise 

+

 

+

    # Note: session based authentication is explicitly CSRF validated, 

+

    # all other authentication is CSRF exempt. 

+

    @csrf_exempt 

+

    def dispatch(self, request, *args, **kwargs): 

+

        """ 

+

        `.dispatch()` is pretty much the same as Django's regular dispatch, 

+

        but with extra hooks for startup, finalize, and exception handling. 

+

        """ 

+

        self.args = args 

+

        self.kwargs = kwargs 

+

        request = self.initialize_request(request, *args, **kwargs) 

+

        self.request = request 

+

        self.headers = self.default_response_headers  # deprecate? 

+

 

+

        try: 

+

            self.initial(request, *args, **kwargs) 

+

 

+

            # Get the appropriate handler method 

+

            if request.method.lower() in self.http_method_names: 

+

                handler = getattr(self, request.method.lower(), 

+

                                  self.http_method_not_allowed) 

+

            else: 

+

                handler = self.http_method_not_allowed 

+

 

+

            response = handler(request, *args, **kwargs) 

+

 

+

        except Exception as exc: 

+

            response = self.handle_exception(exc) 

+

 

+

        self.response = self.finalize_response(request, response, *args, **kwargs) 

+

        return self.response 

+

 

+

    def options(self, request, *args, **kwargs): 

+

        """ 

+

        Handler method for HTTP 'OPTIONS' request. 

+

        We may as well implement this as Django will otherwise provide 

+

        a less useful default implementation. 

+

        """ 

+

        return Response(self.metadata(request), status=status.HTTP_200_OK) 

+

 

+

    def metadata(self, request): 

+

        """ 

+

        Return a dictionary of metadata about the view. 

+

        Used to return responses for OPTIONS requests. 

+

        """ 

+

 

+

        # This is used by ViewSets to disambiguate instance vs list views 

+

        view_name_suffix = getattr(self, 'suffix', None) 

+

 

+

        # By default we can't provide any form-like information, however the 

+

        # generic views override this implementation and add additional 

+

        # information for POST and PUT methods, based on the serializer. 

+

        ret = SortedDict() 

+

        ret['name'] = get_view_name(self.__class__, view_name_suffix) 

+

        ret['description'] = get_view_description(self.__class__) 

+

        ret['renders'] = [renderer.media_type for renderer in self.renderer_classes] 

+

        ret['parses'] = [parser.media_type for parser in self.parser_classes] 

+

        return ret 

+ +
+
+ + + + + diff --git a/htmlcov/rest_framework_viewsets.html b/htmlcov/rest_framework_viewsets.html new file mode 100644 index 000000000..8264ddc0c --- /dev/null +++ b/htmlcov/rest_framework_viewsets.html @@ -0,0 +1,359 @@ + + + + + + + + Coverage for rest_framework/viewsets: 95% + + + + + + + + + + + +
+ +

Hot-keys on this page

+
+

+ r + m + x + p   toggle line displays +

+

+ j + k   next/prev highlighted chunk +

+

+ 0   (zero) top of page +

+

+ 1   (one) first highlighted chunk +

+
+
+ +
+ + + + + +
+

1

+

2

+

3

+

4

+

5

+

6

+

7

+

8

+

9

+

10

+

11

+

12

+

13

+

14

+

15

+

16

+

17

+

18

+

19

+

20

+

21

+

22

+

23

+

24

+

25

+

26

+

27

+

28

+

29

+

30

+

31

+

32

+

33

+

34

+

35

+

36

+

37

+

38

+

39

+

40

+

41

+

42

+

43

+

44

+

45

+

46

+

47

+

48

+

49

+

50

+

51

+

52

+

53

+

54

+

55

+

56

+

57

+

58

+

59

+

60

+

61

+

62

+

63

+

64

+

65

+

66

+

67

+

68

+

69

+

70

+

71

+

72

+

73

+

74

+

75

+

76

+

77

+

78

+

79

+

80

+

81

+

82

+

83

+

84

+

85

+

86

+

87

+

88

+

89

+

90

+

91

+

92

+

93

+

94

+

95

+

96

+

97

+

98

+

99

+

100

+

101

+

102

+

103

+

104

+

105

+

106

+

107

+

108

+

109

+

110

+

111

+

112

+

113

+

114

+

115

+

116

+

117

+

118

+

119

+

120

+

121

+

122

+

123

+

124

+

125

+

126

+

127

+

128

+

129

+

130

+

131

+

132

+

133

+

134

+

135

+

136

+

137

+

138

+

139

+ +
+

""" 

+

ViewSets are essentially just a type of class based view, that doesn't provide 

+

any method handlers, such as `get()`, `post()`, etc... but instead has actions, 

+

such as `list()`, `retrieve()`, `create()`, etc... 

+

 

+

Actions are only bound to methods at the point of instantiating the views. 

+

 

+

    user_list = UserViewSet.as_view({'get': 'list'}) 

+

    user_detail = UserViewSet.as_view({'get': 'retrieve'}) 

+

 

+

Typically, rather than instantiate views from viewsets directly, you'll 

+

regsiter the viewset with a router and let the URL conf be determined 

+

automatically. 

+

 

+

    router = DefaultRouter() 

+

    router.register(r'users', UserViewSet, 'user') 

+

    urlpatterns = router.urls 

+

""" 

+

from __future__ import unicode_literals 

+

 

+

from functools import update_wrapper 

+

from django.utils.decorators import classonlymethod 

+

from rest_framework import views, generics, mixins 

+

 

+

 

+

class ViewSetMixin(object): 

+

    """ 

+

    This is the magic. 

+

 

+

    Overrides `.as_view()` so that it takes an `actions` keyword that performs 

+

    the binding of HTTP methods to actions on the Resource. 

+

 

+

    For example, to create a concrete view binding the 'GET' and 'POST' methods 

+

    to the 'list' and 'create' actions... 

+

 

+

    view = MyViewSet.as_view({'get': 'list', 'post': 'create'}) 

+

    """ 

+

 

+

    @classonlymethod 

+

    def as_view(cls, actions=None, **initkwargs): 

+

        """ 

+

        Because of the way class based views create a closure around the 

+

        instantiated view, we need to totally reimplement `.as_view`, 

+

        and slightly modify the view function that is created and returned. 

+

        """ 

+

        # The suffix initkwarg is reserved for identifing the viewset type 

+

        # eg. 'List' or 'Instance'. 

+

        cls.suffix = None 

+

 

+

        # sanitize keyword arguments 

+

        for key in initkwargs: 

+

            if key in cls.http_method_names: 

+

                raise TypeError("You tried to pass in the %s method name as a " 

+

                                "keyword argument to %s(). Don't do that." 

+

                                % (key, cls.__name__)) 

+

            if not hasattr(cls, key): 

+

                raise TypeError("%s() received an invalid keyword %r" % ( 

+

                    cls.__name__, key)) 

+

 

+

        def view(request, *args, **kwargs): 

+

            self = cls(**initkwargs) 

+

            # We also store the mapping of request methods to actions, 

+

            # so that we can later set the action attribute. 

+

            # eg. `self.action = 'list'` on an incoming GET request. 

+

            self.action_map = actions 

+

 

+

            # Bind methods to actions 

+

            # This is the bit that's different to a standard view 

+

            for method, action in actions.items(): 

+

                handler = getattr(self, action) 

+

                setattr(self, method, handler) 

+

 

+

            # Patch this in as it's otherwise only present from 1.5 onwards 

+

            if hasattr(self, 'get') and not hasattr(self, 'head'): 

+

                self.head = self.get 

+

 

+

            # And continue as usual 

+

            return self.dispatch(request, *args, **kwargs) 

+

 

+

        # take name and docstring from class 

+

        update_wrapper(view, cls, updated=()) 

+

 

+

        # and possible attributes set by decorators 

+

        # like csrf_exempt from dispatch 

+

        update_wrapper(view, cls.dispatch, assigned=()) 

+

 

+

        # We need to set these on the view function, so that breadcrumb 

+

        # generation can pick out these bits of information from a 

+

        # resolved URL. 

+

        view.cls = cls 

+

        view.suffix = initkwargs.get('suffix', None) 

+

        return view 

+

 

+

    def initialize_request(self, request, *args, **kargs): 

+

        """ 

+

        Set the `.action` attribute on the view, 

+

        depending on the request method. 

+

        """ 

+

        request = super(ViewSetMixin, self).initialize_request(request, *args, **kargs) 

+

        self.action = self.action_map.get(request.method.lower()) 

+

        return request 

+

 

+

 

+

class ViewSet(ViewSetMixin, views.APIView): 

+

    """ 

+

    The base ViewSet class does not provide any actions by default. 

+

    """ 

+

    pass 

+

 

+

 

+

class GenericViewSet(ViewSetMixin, generics.GenericAPIView): 

+

    """ 

+

    The GenericViewSet class does not provide any actions by default, 

+

    but does include the base set of generic view behavior, such as 

+

    the `get_object` and `get_queryset` methods. 

+

    """ 

+

    pass 

+

 

+

 

+

class ReadOnlyModelViewSet(mixins.RetrieveModelMixin, 

+

                           mixins.ListModelMixin, 

+

                           GenericViewSet): 

+

    """ 

+

    A viewset that provides default `list()` and `retrieve()` actions. 

+

    """ 

+

    pass 

+

 

+

 

+

class ModelViewSet(mixins.CreateModelMixin, 

+

                    mixins.RetrieveModelMixin, 

+

                    mixins.UpdateModelMixin, 

+

                    mixins.DestroyModelMixin, 

+

                    mixins.ListModelMixin, 

+

                    GenericViewSet): 

+

    """ 

+

    A viewset that provides default `create()`, `retrieve()`, `update()`, 

+

    `partial_update()`, `destroy()` and `list()` actions. 

+

    """ 

+

    pass 

+ +
+
+ + + + + diff --git a/htmlcov/status.dat b/htmlcov/status.dat new file mode 100644 index 000000000..9e448e377 --- /dev/null +++ b/htmlcov/status.dat @@ -0,0 +1,1258 @@ +(dp1 +S'files' +p2 +(dp3 +S'rest_framework_utils_encoders' +p4 +(dp5 +S'index' +p6 +(dp7 +S'par' +p8 +I0 +sS'html_filename' +p9 +S'rest_framework_utils_encoders.html' +p10 +sS'name' +p11 +S'rest_framework/utils/encoders' +p12 +sS'nums' +p13 +ccopy_reg +_reconstructor +p14 +(ccoverage.results +Numbers +p15 +c__builtin__ +object +p16 +NtRp17 +(dp18 +S'n_files' +p19 +I1 +sS'n_branches' +p20 +I0 +sS'n_statements' +p21 +I70 +sS'n_excluded' +p22 +I0 +sS'n_missing' +p23 +I19 +sS'n_missing_branches' +p24 +I0 +sbssS'hash' +p25 +S'\x9d|\xea|-p\x0c#\xef\x82\x8c\x91\xf1\xcd}f' +p26 +ssS'rest_framework___init__' +p27 +(dp28 +g6 +(dp29 +g8 +I0 +sg9 +S'rest_framework___init__.html' +p30 +sg11 +S'rest_framework/__init__' +p31 +sg13 +g14 +(g15 +g16 +NtRp32 +(dp33 +g19 +I1 +sg20 +I0 +sg21 +I4 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'\xbc\xf1f\xd9[\xd4\xcf\x9cQ\x94H\xfd3)\xea[' +p34 +ssS'rest_framework_urlpatterns' +p35 +(dp36 +g6 +(dp37 +g8 +I0 +sg9 +S'rest_framework_urlpatterns.html' +p38 +sg11 +S'rest_framework/urlpatterns' +p39 +sg13 +g14 +(g15 +g16 +NtRp40 +(dp41 +g19 +I1 +sg20 +I0 +sg21 +I31 +sg22 +I0 +sg23 +I4 +sg24 +I0 +sbssg25 +S"\x84\xb0-\xc6Y\x7f\xebA'\x8c5+\xcf\xf6\xcf\xda" +p42 +ssS'rest_framework_permissions' +p43 +(dp44 +g6 +(dp45 +g8 +I0 +sg9 +S'rest_framework_permissions.html' +p46 +sg11 +S'rest_framework/permissions' +p47 +sg13 +g14 +(g15 +g16 +NtRp48 +(dp49 +g19 +I1 +sg20 +I0 +sg21 +I63 +sg22 +I0 +sg23 +I12 +sg24 +I0 +sbssg25 +S",\xc4,\xda\x05\x86\x17\xe8u2~ls*'\xc1" +p50 +ssS'rest_framework_fields' +p51 +(dp52 +g6 +(dp53 +g8 +I0 +sg9 +S'rest_framework_fields.html' +p54 +sg11 +S'rest_framework/fields' +p55 +sg13 +g14 +(g15 +g16 +NtRp56 +(dp57 +g19 +I1 +sg20 +I0 +sg21 +I594 +sg22 +I0 +sg23 +I80 +sg24 +I0 +sbssg25 +S'\x08\x1b\xd2m\x91l\x14e\x97CDA\x1c&k\xf9' +p58 +ssS'rest_framework_models' +p59 +(dp60 +g6 +(dp61 +g8 +I0 +sg9 +S'rest_framework_models.html' +p62 +sg11 +S'rest_framework/models' +p63 +sg13 +g14 +(g15 +g16 +NtRp64 +(dp65 +g19 +I1 +sg20 +I0 +sg21 +I0 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S' E\xaf\xdd\xe7\xbb\xc4\x11z\xf8\x80\x18v.\xec\xf6' +p66 +ssS'rest_framework_utils_breadcrumbs' +p67 +(dp68 +g6 +(dp69 +g8 +I0 +sg9 +S'rest_framework_utils_breadcrumbs.html' +p70 +sg11 +S'rest_framework/utils/breadcrumbs' +p71 +sg13 +g14 +(g15 +g16 +NtRp72 +(dp73 +g19 +I1 +sg20 +I0 +sg21 +I27 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'V"\xf6\xbc\\m)\x12R4>c\xff\xea\xde\x8b' +p74 +ssS'rest_framework_urls' +p75 +(dp76 +g6 +(dp77 +g8 +I0 +sg9 +S'rest_framework_urls.html' +p78 +sg11 +S'rest_framework/urls' +p79 +sg13 +g14 +(g15 +g16 +NtRp80 +(dp81 +g19 +I1 +sg20 +I0 +sg21 +I4 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'\xba\x9b\xdaeu\x17\x8b\xe0e\xc6-\xc5R\xba\xa2\xd5' +p82 +ssS'rest_framework_serializers' +p83 +(dp84 +g6 +(dp85 +g8 +I0 +sg9 +S'rest_framework_serializers.html' +p86 +sg11 +S'rest_framework/serializers' +p87 +sg13 +g14 +(g15 +g16 +NtRp88 +(dp89 +g19 +I1 +sg20 +I0 +sg21 +I464 +sg22 +I0 +sg23 +I27 +sg24 +I0 +sbssg25 +S'O\\\xf6\x81y\x95\xae\x9a)\xe9~\xb8\xab\t\x88#' +p90 +ssS'rest_framework_exceptions' +p91 +(dp92 +g6 +(dp93 +g8 +I0 +sg9 +S'rest_framework_exceptions.html' +p94 +sg11 +S'rest_framework/exceptions' +p95 +sg13 +g14 +(g15 +g16 +NtRp96 +(dp97 +g19 +I1 +sg20 +I0 +sg21 +I51 +sg22 +I0 +sg23 +I2 +sg24 +I0 +sbssg25 +S'\xdd\xcaE\x12\x1f4V\xe6\x91\x11\xef:T\xe1r\xca' +p98 +ssS'rest_framework_status' +p99 +(dp100 +g6 +(dp101 +g8 +I0 +sg9 +S'rest_framework_status.html' +p102 +sg11 +S'rest_framework/status' +p103 +sg13 +g14 +(g15 +g16 +NtRp104 +(dp105 +g19 +I1 +sg20 +I0 +sg21 +I46 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'\x97z\xcd\xfd\xdc\x0c\xe3\xa9j\x04\xab\x13]\x98\xbf\x80' +p106 +ssS'rest_framework_relations' +p107 +(dp108 +g6 +(dp109 +g8 +I0 +sg9 +S'rest_framework_relations.html' +p110 +sg11 +S'rest_framework/relations' +p111 +sg13 +g14 +(g15 +g16 +NtRp112 +(dp113 +g19 +I1 +sg20 +I0 +sg21 +I365 +sg22 +I0 +sg23 +I88 +sg24 +I0 +sbssg25 +S'\xdb"\xfe\xc2\xb3\x8a\xe2(\xbeoNk\x1b\xd3H9' +p114 +ssS'rest_framework_authtoken_views' +p115 +(dp116 +g6 +(dp117 +g8 +I0 +sg9 +S'rest_framework_authtoken_views.html' +p118 +sg11 +S'rest_framework/authtoken/views' +p119 +sg13 +g14 +(g15 +g16 +NtRp120 +(dp121 +g19 +I1 +sg20 +I0 +sg21 +I21 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'\xb8A\x13\xee\xfc9\x8b\x1eY-\xad\x00\xa7\x9dH]' +p122 +ssS'rest_framework_mixins' +p123 +(dp124 +g6 +(dp125 +g8 +I0 +sg9 +S'rest_framework_mixins.html' +p126 +sg11 +S'rest_framework/mixins' +p127 +sg13 +g14 +(g15 +g16 +NtRp128 +(dp129 +g19 +I1 +sg20 +I0 +sg21 +I97 +sg22 +I0 +sg23 +I7 +sg24 +I0 +sbssg25 +S'\xcd\xe5\x9f\xc2\xbb\xd9\xcb\x14*\x88\x99\xe8\xdf\xd2\xa8\xd6' +p130 +ssS'rest_framework_views' +p131 +(dp132 +g6 +(dp133 +g8 +I0 +sg9 +S'rest_framework_views.html' +p134 +sg11 +S'rest_framework/views' +p135 +sg13 +g14 +(g15 +g16 +NtRp136 +(dp137 +g19 +I1 +sg20 +I0 +sg21 +I146 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'ZBo\x84oh^\x1f\x8c\x94Mp$\xf3\xd2\xa1' +p138 +ssS'rest_framework_generics' +p139 +(dp140 +g6 +(dp141 +g8 +I0 +sg9 +S'rest_framework_generics.html' +p142 +sg11 +S'rest_framework/generics' +p143 +sg13 +g14 +(g15 +g16 +NtRp144 +(dp145 +g19 +I1 +sg20 +I0 +sg21 +I196 +sg22 +I0 +sg23 +I34 +sg24 +I0 +sbssg25 +S'@\x1c\x97\x176\x18\x9c\xfc"| |\xb8^\xbb\x83' +p146 +ssS'rest_framework_utils___init__' +p147 +(dp148 +g6 +(dp149 +g8 +I0 +sg9 +S'rest_framework_utils___init__.html' +p150 +sg11 +S'rest_framework/utils/__init__' +p151 +sg13 +g14 +(g15 +g16 +NtRp152 +(dp153 +g19 +I1 +sg20 +I0 +sg21 +I0 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'\xb0\xc8pN\xaf>\xa0\xbaz\x144\xe0A9\xb8?' +p154 +ssS'rest_framework_renderers' +p155 +(dp156 +g6 +(dp157 +g8 +I0 +sg9 +S'rest_framework_renderers.html' +p158 +sg11 +S'rest_framework/renderers' +p159 +sg13 +g14 +(g15 +g16 +NtRp160 +(dp161 +g19 +I1 +sg20 +I0 +sg21 +I282 +sg22 +I0 +sg23 +I23 +sg24 +I0 +sbssg25 +S'\t\x11\xd4\xafO\xae\\*\x8d\xaf\xa4f\xde\x86\xe8N' +p162 +ssS'rest_framework_negotiation' +p163 +(dp164 +g6 +(dp165 +g8 +I0 +sg9 +S'rest_framework_negotiation.html' +p166 +sg11 +S'rest_framework/negotiation' +p167 +sg13 +g14 +(g15 +g16 +NtRp168 +(dp169 +g19 +I1 +sg20 +I0 +sg21 +I41 +sg22 +I0 +sg23 +I4 +sg24 +I0 +sbssg25 +S'\xd2\xa2\x94\xc8}y\xba\x9eZE\xe5M\xa5>\x9f\x8d' +p170 +ssS'rest_framework_throttling' +p171 +(dp172 +g6 +(dp173 +g8 +I0 +sg9 +S'rest_framework_throttling.html' +p174 +sg11 +S'rest_framework/throttling' +p175 +sg13 +g14 +(g15 +g16 +NtRp176 +(dp177 +g19 +I1 +sg20 +I0 +sg21 +I90 +sg22 +I0 +sg23 +I17 +sg24 +I0 +sbssg25 +S'a\xbcT\xe7\xff\x1an\xb5\x886\xa3\xa2e\x90PZ' +p178 +ssS'rest_framework_reverse' +p179 +(dp180 +g6 +(dp181 +g8 +I0 +sg9 +S'rest_framework_reverse.html' +p182 +sg11 +S'rest_framework/reverse' +p183 +sg13 +g14 +(g15 +g16 +NtRp184 +(dp185 +g19 +I1 +sg20 +I0 +sg21 +I12 +sg22 +I0 +sg23 +I3 +sg24 +I0 +sbssg25 +S"#\xe7D\x01\x10\xe8'1\x9c\xc9yX4\xb4\xef\x19" +p186 +ssS'rest_framework_request' +p187 +(dp188 +g6 +(dp189 +g8 +I0 +sg9 +S'rest_framework_request.html' +p190 +sg11 +S'rest_framework/request' +p191 +sg13 +g14 +(g15 +g16 +NtRp192 +(dp193 +g19 +I1 +sg20 +I0 +sg21 +I161 +sg22 +I0 +sg23 +I8 +sg24 +I0 +sbssg25 +S'C\xd4v\x9b\xf2Z\xe47\xe8\xc8\x03\xf4\xf8\xac\xefs' +p194 +ssS'rest_framework_parsers' +p195 +(dp196 +g6 +(dp197 +g8 +I0 +sg9 +S'rest_framework_parsers.html' +p198 +sg11 +S'rest_framework/parsers' +p199 +sg13 +g14 +(g15 +g16 +NtRp200 +(dp201 +g19 +I1 +sg20 +I0 +sg21 +I153 +sg22 +I0 +sg23 +I13 +sg24 +I0 +sbssg25 +S'\x11o\x05[\x99{\x9c\x8bj\xa8\xd0t\xe8\x16\\\xae' +p202 +ssS'rest_framework_settings' +p203 +(dp204 +g6 +(dp205 +g8 +I0 +sg9 +S'rest_framework_settings.html' +p206 +sg11 +S'rest_framework/settings' +p207 +sg13 +g14 +(g15 +g16 +NtRp208 +(dp209 +g19 +I1 +sg20 +I0 +sg21 +I44 +sg22 +I0 +sg23 +I2 +sg24 +I0 +sbssg25 +S'\n\xb8|\x03\xa7d|\xfc9\xda\xb5\xb9\x1a\x00@\xc3' +p210 +ssS'rest_framework_authtoken_models' +p211 +(dp212 +g6 +(dp213 +g8 +I0 +sg9 +S'rest_framework_authtoken_models.html' +p214 +sg11 +S'rest_framework/authtoken/models' +p215 +sg13 +g14 +(g15 +g16 +NtRp216 +(dp217 +g19 +I1 +sg20 +I0 +sg21 +I21 +sg22 +I0 +sg23 +I1 +sg24 +I0 +sbssg25 +S'U;\xc7\xf5{\xf6r\xc7]\x95\xffF\xde\x8caE' +p218 +ssS'rest_framework_decorators' +p219 +(dp220 +g6 +(dp221 +g8 +I0 +sg9 +S'rest_framework_decorators.html' +p222 +sg11 +S'rest_framework/decorators' +p223 +sg13 +g14 +(g15 +g16 +NtRp224 +(dp225 +g19 +I1 +sg20 +I0 +sg21 +I60 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S"\xd4\x88\xa2\x16\xf4#X\xb4X\xe97Lj\xeb\x16'" +p226 +ssS'rest_framework_authentication' +p227 +(dp228 +g6 +(dp229 +g8 +I0 +sg9 +S'rest_framework_authentication.html' +p230 +sg11 +S'rest_framework/authentication' +p231 +sg13 +g14 +(g15 +g16 +NtRp232 +(dp233 +g19 +I1 +sg20 +I0 +sg21 +I169 +sg22 +I0 +sg23 +I33 +sg24 +I0 +sbssg25 +S'^\x80:,\x1cL\xde\t\xc1\x93\xe0\x8b\x11\xf4\xb8\x06' +p234 +ssS'rest_framework_utils_formatting' +p235 +(dp236 +g6 +(dp237 +g8 +I0 +sg9 +S'rest_framework_utils_formatting.html' +p238 +sg11 +S'rest_framework/utils/formatting' +p239 +sg13 +g14 +(g15 +g16 +NtRp240 +(dp241 +g19 +I1 +sg20 +I0 +sg21 +I39 +sg22 +I0 +sg23 +I1 +sg24 +I0 +sbssg25 +S'\xdd\x05M\xeb\xfe\tl\xe6\xdd\xc5k\xae\xa8\xf9um' +p242 +ssS'rest_framework_pagination' +p243 +(dp244 +g6 +(dp245 +g8 +I0 +sg9 +S'rest_framework_pagination.html' +p246 +sg11 +S'rest_framework/pagination' +p247 +sg13 +g14 +(g15 +g16 +NtRp248 +(dp249 +g19 +I1 +sg20 +I0 +sg21 +I43 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'y\xa8f\rv\x8c\x9b\x9a:9\xdc\x89\t>\x0c' +p282 +ssS'rest_framework_viewsets' +p283 +(dp284 +g6 +(dp285 +g8 +I0 +sg9 +S'rest_framework_viewsets.html' +p286 +sg11 +S'rest_framework/viewsets' +p287 +sg13 +g14 +(g15 +g16 +NtRp288 +(dp289 +g19 +I1 +sg20 +I0 +sg21 +I39 +sg22 +I0 +sg23 +I2 +sg24 +I0 +sbssg25 +S'ic\x82\xc6e\x93$\x1b\x0c\x8bK\x10\x0f9\xe8\n' +p290 +ssS'rest_framework_authtoken___init__' +p291 +(dp292 +g6 +(dp293 +g8 +I0 +sg9 +S'rest_framework_authtoken___init__.html' +p294 +sg11 +S'rest_framework/authtoken/__init__' +p295 +sg13 +g14 +(g15 +g16 +NtRp296 +(dp297 +g19 +I1 +sg20 +I0 +sg21 +I0 +sg22 +I0 +sg23 +I0 +sg24 +I0 +sbssg25 +S'\xb0\xc8pN\xaf>\xa0\xbaz\x144\xe0A9\xb8?' +p298 +ssS'rest_framework_routers' +p299 +(dp300 +g6 +(dp301 +g8 +I0 +sg9 +S'rest_framework_routers.html' +p302 +sg11 +S'rest_framework/routers' +p303 +sg13 +g14 +(g15 +g16 +NtRp304 +(dp305 +g19 +I1 +sg20 +I0 +sg21 +I108 +sg22 +I0 +sg23 +I7 +sg24 +I0 +sbssg25 +S'i\xa8[\x1f\x0f|\xd6\xa0R\x98\xa9\xecs\xe53\xb3' +p306 +sssS'version' +p307 +S'3.5.1' +p308 +sS'settings' +p309 +S'\xfe\xa4\x01e\x06\x8a\x97H\x97\xaf\xbf\xcd\xfez\xe4\xbf' +p310 +sS'format' +p311 +I1 +s. \ No newline at end of file diff --git a/htmlcov/style.css b/htmlcov/style.css new file mode 100644 index 000000000..c40357b8b --- /dev/null +++ b/htmlcov/style.css @@ -0,0 +1,275 @@ +/* CSS styles for Coverage. */ +/* Page-wide styles */ +html, body, h1, h2, h3, p, td, th { + margin: 0; + padding: 0; + border: 0; + outline: 0; + font-weight: inherit; + font-style: inherit; + font-size: 100%; + font-family: inherit; + vertical-align: baseline; + } + +/* Set baseline grid to 16 pt. */ +body { + font-family: georgia, serif; + font-size: 1em; + } + +html>body { + font-size: 16px; + } + +/* Set base font size to 12/16 */ +p { + font-size: .75em; /* 12/16 */ + line-height: 1.3333em; /* 16/12 */ + } + +table { + border-collapse: collapse; + } + +a.nav { + text-decoration: none; + color: inherit; + } +a.nav:hover { + text-decoration: underline; + color: inherit; + } + +/* Page structure */ +#header { + background: #f8f8f8; + width: 100%; + border-bottom: 1px solid #eee; + } + +#source { + padding: 1em; + font-family: "courier new", monospace; + } + +#indexfile #footer { + margin: 1em 3em; + } + +#pyfile #footer { + margin: 1em 1em; + } + +#footer .content { + padding: 0; + font-size: 85%; + font-family: verdana, sans-serif; + color: #666666; + font-style: italic; + } + +#index { + margin: 1em 0 0 3em; + } + +/* Header styles */ +#header .content { + padding: 1em 3em; + } + +h1 { + font-size: 1.25em; +} + +h2.stats { + margin-top: .5em; + font-size: 1em; +} +.stats span { + border: 1px solid; + padding: .1em .25em; + margin: 0 .1em; + cursor: pointer; + border-color: #999 #ccc #ccc #999; +} +.stats span.hide_run, .stats span.hide_exc, +.stats span.hide_mis, .stats span.hide_par, +.stats span.par.hide_run.hide_par { + border-color: #ccc #999 #999 #ccc; +} +.stats span.par.hide_run { + border-color: #999 #ccc #ccc #999; +} + +/* Help panel */ +#keyboard_icon { + float: right; + cursor: pointer; +} + +.help_panel { + position: absolute; + background: #ffc; + padding: .5em; + border: 1px solid #883; + display: none; +} + +#indexfile .help_panel { + width: 20em; height: 4em; +} + +#pyfile .help_panel { + width: 16em; height: 8em; +} + +.help_panel .legend { + font-style: italic; + margin-bottom: 1em; +} + +#panel_icon { + float: right; + cursor: pointer; +} + +.keyhelp { + margin: .75em; +} + +.keyhelp .key { + border: 1px solid black; + border-color: #888 #333 #333 #888; + padding: .1em .35em; + font-family: monospace; + font-weight: bold; + background: #eee; +} + +/* Source file styles */ +.linenos p { + text-align: right; + margin: 0; + padding: 0 .5em; + color: #999999; + font-family: verdana, sans-serif; + font-size: .625em; /* 10/16 */ + line-height: 1.6em; /* 16/10 */ + } +.linenos p.highlight { + background: #ffdd00; + } +.linenos p a { + text-decoration: none; + color: #999999; + } +.linenos p a:hover { + text-decoration: underline; + color: #999999; + } + +td.text { + width: 100%; + } +.text p { + margin: 0; + padding: 0 0 0 .5em; + border-left: 2px solid #ffffff; + white-space: nowrap; + } + +.text p.mis { + background: #ffdddd; + border-left: 2px solid #ff0000; + } +.text p.run, .text p.run.hide_par { + background: #ddffdd; + border-left: 2px solid #00ff00; + } +.text p.exc { + background: #eeeeee; + border-left: 2px solid #808080; + } +.text p.par, .text p.par.hide_run { + background: #ffffaa; + border-left: 2px solid #eeee99; + } +.text p.hide_run, .text p.hide_exc, .text p.hide_mis, .text p.hide_par, +.text p.hide_run.hide_par { + background: inherit; + } + +.text span.annotate { + font-family: georgia; + font-style: italic; + color: #666; + float: right; + padding-right: .5em; + } +.text p.hide_par span.annotate { + display: none; + } + +/* Syntax coloring */ +.text .com { + color: green; + font-style: italic; + line-height: 1px; + } +.text .key { + font-weight: bold; + line-height: 1px; + } +.text .str { + color: #000080; + } + +/* index styles */ +#index td, #index th { + text-align: right; + width: 5em; + padding: .25em .5em; + border-bottom: 1px solid #eee; + } +#index th { + font-style: italic; + color: #333; + border-bottom: 1px solid #ccc; + cursor: pointer; + } +#index th:hover { + background: #eee; + border-bottom: 1px solid #999; + } +#index td.left, #index th.left { + padding-left: 0; + } +#index td.right, #index th.right { + padding-right: 0; + } +#index th.headerSortDown, #index th.headerSortUp { + border-bottom: 1px solid #000; + } +#index td.name, #index th.name { + text-align: left; + width: auto; + } +#index td.name a { + text-decoration: none; + color: #000; + } +#index td.name a:hover { + text-decoration: underline; + color: #000; + } +#index tr.total { + } +#index tr.total td { + font-weight: bold; + border-top: 1px solid #ccc; + border-bottom: none; + } +#index tr.file:hover { + background: #eeeeee; + } diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py index 291142cf9..fe0711fa2 100644 --- a/rest_framework/tests/test_routers.py +++ b/rest_framework/tests/test_routers.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.db import models from django.test import TestCase from django.test.client import RequestFactory -from rest_framework import serializers, viewsets +from rest_framework import serializers, viewsets, permissions from rest_framework.compat import include, patterns, url from rest_framework.decorators import link, action from rest_framework.response import Response @@ -120,7 +120,7 @@ class TestCustomLookupFields(TestCase): ) -class TestTrailingSlash(TestCase): +class TestTrailingSlashIncluded(TestCase): def setUp(self): class NoteViewSet(viewsets.ModelViewSet): model = RouterTestModel @@ -135,7 +135,7 @@ class TestTrailingSlash(TestCase): self.assertEqual(expected[idx], self.urls[idx].regex.pattern) -class TestTrailingSlash(TestCase): +class TestTrailingSlashRemoved(TestCase): def setUp(self): class NoteViewSet(viewsets.ModelViewSet): model = RouterTestModel @@ -149,6 +149,7 @@ class TestTrailingSlash(TestCase): for idx in range(len(expected)): self.assertEqual(expected[idx], self.urls[idx].regex.pattern) + class TestNameableRoot(TestCase): def setUp(self): class NoteViewSet(viewsets.ModelViewSet): @@ -162,3 +163,31 @@ class TestNameableRoot(TestCase): expected = 'nameable-root' self.assertEqual(expected, self.urls[0].name) + +class TestActionKeywordArgs(TestCase): + """ + Ensure keyword arguments passed in the `@action` decorator + are properly handled. Refs #940. + """ + + def setUp(self): + class TestViewSet(viewsets.ModelViewSet): + permission_classes = [] + + @action(permission_classes=[permissions.AllowAny]) + def custom(self, request, *args, **kwargs): + return Response({ + 'permission_classes': self.permission_classes + }) + + self.router = SimpleRouter() + self.router.register(r'test', TestViewSet, base_name='test') + self.view = self.router.urls[-1].callback + + def test_action_kwargs(self): + request = factory.post('/test/0/custom/') + response = self.view(request) + self.assertEqual( + response.data, + {'permission_classes': [permissions.AllowAny]} + ) From f2e6af89755c34083acb1a5fcd843a480037293f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 22:04:38 +0100 Subject: [PATCH 02/38] Remove erronous htmlcov files --- htmlcov/coverage_html.js | 372 --- htmlcov/index.html | 404 ---- htmlcov/jquery-1.4.3.min.js | 166 -- htmlcov/jquery.hotkeys.js | 99 - htmlcov/jquery.isonscreen.js | 53 - htmlcov/jquery.tablesorter.min.js | 2 - htmlcov/keybd_closed.png | Bin 264 -> 0 bytes htmlcov/keybd_open.png | Bin 267 -> 0 bytes htmlcov/rest_framework___init__.html | 99 - htmlcov/rest_framework_authentication.html | 767 ------- .../rest_framework_authtoken___init__.html | 81 - htmlcov/rest_framework_authtoken_models.html | 151 -- .../rest_framework_authtoken_serializers.html | 129 -- htmlcov/rest_framework_authtoken_views.html | 133 -- htmlcov/rest_framework_decorators.html | 339 --- htmlcov/rest_framework_exceptions.html | 257 --- htmlcov/rest_framework_fields.html | 1991 ---------------- htmlcov/rest_framework_filters.html | 367 --- htmlcov/rest_framework_generics.html | 1079 --------- htmlcov/rest_framework_mixins.html | 449 ---- htmlcov/rest_framework_models.html | 83 - htmlcov/rest_framework_negotiation.html | 259 --- htmlcov/rest_framework_pagination.html | 269 --- htmlcov/rest_framework_parsers.html | 671 ------ htmlcov/rest_framework_permissions.html | 429 ---- htmlcov/rest_framework_relations.html | 1347 ----------- htmlcov/rest_framework_renderers.html | 1227 ---------- htmlcov/rest_framework_request.html | 819 ------- htmlcov/rest_framework_response.html | 249 -- htmlcov/rest_framework_reverse.html | 127 -- htmlcov/rest_framework_routers.html | 595 ----- htmlcov/rest_framework_serializers.html | 2011 ----------------- htmlcov/rest_framework_settings.html | 465 ---- htmlcov/rest_framework_status.html | 187 -- htmlcov/rest_framework_throttling.html | 533 ----- htmlcov/rest_framework_urlpatterns.html | 205 -- htmlcov/rest_framework_urls.html | 129 -- htmlcov/rest_framework_utils___init__.html | 81 - htmlcov/rest_framework_utils_breadcrumbs.html | 189 -- htmlcov/rest_framework_utils_encoders.html | 275 --- htmlcov/rest_framework_utils_formatting.html | 241 -- htmlcov/rest_framework_utils_mediatypes.html | 257 --- htmlcov/rest_framework_views.html | 793 ------- htmlcov/rest_framework_viewsets.html | 359 --- htmlcov/status.dat | 1258 ----------- htmlcov/style.css | 275 --- 46 files changed, 20271 deletions(-) delete mode 100644 htmlcov/coverage_html.js delete mode 100644 htmlcov/index.html delete mode 100644 htmlcov/jquery-1.4.3.min.js delete mode 100644 htmlcov/jquery.hotkeys.js delete mode 100644 htmlcov/jquery.isonscreen.js delete mode 100644 htmlcov/jquery.tablesorter.min.js delete mode 100644 htmlcov/keybd_closed.png delete mode 100644 htmlcov/keybd_open.png delete mode 100644 htmlcov/rest_framework___init__.html delete mode 100644 htmlcov/rest_framework_authentication.html delete mode 100644 htmlcov/rest_framework_authtoken___init__.html delete mode 100644 htmlcov/rest_framework_authtoken_models.html delete mode 100644 htmlcov/rest_framework_authtoken_serializers.html delete mode 100644 htmlcov/rest_framework_authtoken_views.html delete mode 100644 htmlcov/rest_framework_decorators.html delete mode 100644 htmlcov/rest_framework_exceptions.html delete mode 100644 htmlcov/rest_framework_fields.html delete mode 100644 htmlcov/rest_framework_filters.html delete mode 100644 htmlcov/rest_framework_generics.html delete mode 100644 htmlcov/rest_framework_mixins.html delete mode 100644 htmlcov/rest_framework_models.html delete mode 100644 htmlcov/rest_framework_negotiation.html delete mode 100644 htmlcov/rest_framework_pagination.html delete mode 100644 htmlcov/rest_framework_parsers.html delete mode 100644 htmlcov/rest_framework_permissions.html delete mode 100644 htmlcov/rest_framework_relations.html delete mode 100644 htmlcov/rest_framework_renderers.html delete mode 100644 htmlcov/rest_framework_request.html delete mode 100644 htmlcov/rest_framework_response.html delete mode 100644 htmlcov/rest_framework_reverse.html delete mode 100644 htmlcov/rest_framework_routers.html delete mode 100644 htmlcov/rest_framework_serializers.html delete mode 100644 htmlcov/rest_framework_settings.html delete mode 100644 htmlcov/rest_framework_status.html delete mode 100644 htmlcov/rest_framework_throttling.html delete mode 100644 htmlcov/rest_framework_urlpatterns.html delete mode 100644 htmlcov/rest_framework_urls.html delete mode 100644 htmlcov/rest_framework_utils___init__.html delete mode 100644 htmlcov/rest_framework_utils_breadcrumbs.html delete mode 100644 htmlcov/rest_framework_utils_encoders.html delete mode 100644 htmlcov/rest_framework_utils_formatting.html delete mode 100644 htmlcov/rest_framework_utils_mediatypes.html delete mode 100644 htmlcov/rest_framework_views.html delete mode 100644 htmlcov/rest_framework_viewsets.html delete mode 100644 htmlcov/status.dat delete mode 100644 htmlcov/style.css diff --git a/htmlcov/coverage_html.js b/htmlcov/coverage_html.js deleted file mode 100644 index da3e22c81..000000000 --- a/htmlcov/coverage_html.js +++ /dev/null @@ -1,372 +0,0 @@ -// Coverage.py HTML report browser code. -/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */ -/*global coverage: true, document, window, $ */ - -coverage = {}; - -// Find all the elements with shortkey_* class, and use them to assign a shotrtcut key. -coverage.assign_shortkeys = function () { - $("*[class*='shortkey_']").each(function (i, e) { - $.each($(e).attr("class").split(" "), function (i, c) { - if (/^shortkey_/.test(c)) { - $(document).bind('keydown', c.substr(9), function () { - $(e).click(); - }); - } - }); - }); -}; - -// Create the events for the help panel. -coverage.wire_up_help_panel = function () { - $("#keyboard_icon").click(function () { - // Show the help panel, and position it so the keyboard icon in the - // panel is in the same place as the keyboard icon in the header. - $(".help_panel").show(); - var koff = $("#keyboard_icon").offset(); - var poff = $("#panel_icon").position(); - $(".help_panel").offset({ - top: koff.top-poff.top, - left: koff.left-poff.left - }); - }); - $("#panel_icon").click(function () { - $(".help_panel").hide(); - }); -}; - -// Loaded on index.html -coverage.index_ready = function ($) { - // Look for a cookie containing previous sort settings: - var sort_list = []; - var cookie_name = "COVERAGE_INDEX_SORT"; - var i; - - // This almost makes it worth installing the jQuery cookie plugin: - if (document.cookie.indexOf(cookie_name) > -1) { - var cookies = document.cookie.split(";"); - for (i = 0; i < cookies.length; i++) { - var parts = cookies[i].split("="); - - if ($.trim(parts[0]) === cookie_name && parts[1]) { - sort_list = eval("[[" + parts[1] + "]]"); - break; - } - } - } - - // Create a new widget which exists only to save and restore - // the sort order: - $.tablesorter.addWidget({ - id: "persistentSort", - - // Format is called by the widget before displaying: - format: function (table) { - if (table.config.sortList.length === 0 && sort_list.length > 0) { - // This table hasn't been sorted before - we'll use - // our stored settings: - $(table).trigger('sorton', [sort_list]); - } - else { - // This is not the first load - something has - // already defined sorting so we'll just update - // our stored value to match: - sort_list = table.config.sortList; - } - } - }); - - // Configure our tablesorter to handle the variable number of - // columns produced depending on report options: - var headers = []; - var col_count = $("table.index > thead > tr > th").length; - - headers[0] = { sorter: 'text' }; - for (i = 1; i < col_count-1; i++) { - headers[i] = { sorter: 'digit' }; - } - headers[col_count-1] = { sorter: 'percent' }; - - // Enable the table sorter: - $("table.index").tablesorter({ - widgets: ['persistentSort'], - headers: headers - }); - - coverage.assign_shortkeys(); - coverage.wire_up_help_panel(); - - // Watch for page unload events so we can save the final sort settings: - $(window).unload(function () { - document.cookie = cookie_name + "=" + sort_list.toString() + "; path=/"; - }); -}; - -// -- pyfile stuff -- - -coverage.pyfile_ready = function ($) { - // If we're directed to a particular line number, highlight the line. - var frag = location.hash; - if (frag.length > 2 && frag[1] === 'n') { - $(frag).addClass('highlight'); - coverage.set_sel(parseInt(frag.substr(2), 10)); - } - else { - coverage.set_sel(0); - } - - $(document) - .bind('keydown', 'j', coverage.to_next_chunk_nicely) - .bind('keydown', 'k', coverage.to_prev_chunk_nicely) - .bind('keydown', '0', coverage.to_top) - .bind('keydown', '1', coverage.to_first_chunk) - ; - - coverage.assign_shortkeys(); - coverage.wire_up_help_panel(); -}; - -coverage.toggle_lines = function (btn, cls) { - btn = $(btn); - var hide = "hide_"+cls; - if (btn.hasClass(hide)) { - $("#source ."+cls).removeClass(hide); - btn.removeClass(hide); - } - else { - $("#source ."+cls).addClass(hide); - btn.addClass(hide); - } -}; - -// Return the nth line div. -coverage.line_elt = function (n) { - return $("#t" + n); -}; - -// Return the nth line number div. -coverage.num_elt = function (n) { - return $("#n" + n); -}; - -// Return the container of all the code. -coverage.code_container = function () { - return $(".linenos"); -}; - -// Set the selection. b and e are line numbers. -coverage.set_sel = function (b, e) { - // The first line selected. - coverage.sel_begin = b; - // The next line not selected. - coverage.sel_end = (e === undefined) ? b+1 : e; -}; - -coverage.to_top = function () { - coverage.set_sel(0, 1); - coverage.scroll_window(0); -}; - -coverage.to_first_chunk = function () { - coverage.set_sel(0, 1); - coverage.to_next_chunk(); -}; - -coverage.is_transparent = function (color) { - // Different browsers return different colors for "none". - return color === "transparent" || color === "rgba(0, 0, 0, 0)"; -}; - -coverage.to_next_chunk = function () { - var c = coverage; - - // Find the start of the next colored chunk. - var probe = c.sel_end; - while (true) { - var probe_line = c.line_elt(probe); - if (probe_line.length === 0) { - return; - } - var color = probe_line.css("background-color"); - if (!c.is_transparent(color)) { - break; - } - probe++; - } - - // There's a next chunk, `probe` points to it. - var begin = probe; - - // Find the end of this chunk. - var next_color = color; - while (next_color === color) { - probe++; - probe_line = c.line_elt(probe); - next_color = probe_line.css("background-color"); - } - c.set_sel(begin, probe); - c.show_selection(); -}; - -coverage.to_prev_chunk = function () { - var c = coverage; - - // Find the end of the prev colored chunk. - var probe = c.sel_begin-1; - var probe_line = c.line_elt(probe); - if (probe_line.length === 0) { - return; - } - var color = probe_line.css("background-color"); - while (probe > 0 && c.is_transparent(color)) { - probe--; - probe_line = c.line_elt(probe); - if (probe_line.length === 0) { - return; - } - color = probe_line.css("background-color"); - } - - // There's a prev chunk, `probe` points to its last line. - var end = probe+1; - - // Find the beginning of this chunk. - var prev_color = color; - while (prev_color === color) { - probe--; - probe_line = c.line_elt(probe); - prev_color = probe_line.css("background-color"); - } - c.set_sel(probe+1, end); - c.show_selection(); -}; - -// Return the line number of the line nearest pixel position pos -coverage.line_at_pos = function (pos) { - var l1 = coverage.line_elt(1), - l2 = coverage.line_elt(2), - result; - if (l1.length && l2.length) { - var l1_top = l1.offset().top, - line_height = l2.offset().top - l1_top, - nlines = (pos - l1_top) / line_height; - if (nlines < 1) { - result = 1; - } - else { - result = Math.ceil(nlines); - } - } - else { - result = 1; - } - return result; -}; - -// Returns 0, 1, or 2: how many of the two ends of the selection are on -// the screen right now? -coverage.selection_ends_on_screen = function () { - if (coverage.sel_begin === 0) { - return 0; - } - - var top = coverage.line_elt(coverage.sel_begin); - var next = coverage.line_elt(coverage.sel_end-1); - - return ( - (top.isOnScreen() ? 1 : 0) + - (next.isOnScreen() ? 1 : 0) - ); -}; - -coverage.to_next_chunk_nicely = function () { - coverage.finish_scrolling(); - if (coverage.selection_ends_on_screen() === 0) { - // The selection is entirely off the screen: select the top line on - // the screen. - var win = $(window); - coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop())); - } - coverage.to_next_chunk(); -}; - -coverage.to_prev_chunk_nicely = function () { - coverage.finish_scrolling(); - if (coverage.selection_ends_on_screen() === 0) { - var win = $(window); - coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop() + win.height())); - } - coverage.to_prev_chunk(); -}; - -// Select line number lineno, or if it is in a colored chunk, select the -// entire chunk -coverage.select_line_or_chunk = function (lineno) { - var c = coverage; - var probe_line = c.line_elt(lineno); - if (probe_line.length === 0) { - return; - } - var the_color = probe_line.css("background-color"); - if (!c.is_transparent(the_color)) { - // The line is in a highlighted chunk. - // Search backward for the first line. - var probe = lineno; - var color = the_color; - while (probe > 0 && color === the_color) { - probe--; - probe_line = c.line_elt(probe); - if (probe_line.length === 0) { - break; - } - color = probe_line.css("background-color"); - } - var begin = probe + 1; - - // Search forward for the last line. - probe = lineno; - color = the_color; - while (color === the_color) { - probe++; - probe_line = c.line_elt(probe); - color = probe_line.css("background-color"); - } - - coverage.set_sel(begin, probe); - } - else { - coverage.set_sel(lineno); - } -}; - -coverage.show_selection = function () { - var c = coverage; - - // Highlight the lines in the chunk - c.code_container().find(".highlight").removeClass("highlight"); - for (var probe = c.sel_begin; probe > 0 && probe < c.sel_end; probe++) { - c.num_elt(probe).addClass("highlight"); - } - - c.scroll_to_selection(); -}; - -coverage.scroll_to_selection = function () { - // Scroll the page if the chunk isn't fully visible. - if (coverage.selection_ends_on_screen() < 2) { - // Need to move the page. The html,body trick makes it scroll in all - // browsers, got it from http://stackoverflow.com/questions/3042651 - var top = coverage.line_elt(coverage.sel_begin); - var top_pos = parseInt(top.offset().top, 10); - coverage.scroll_window(top_pos - 30); - } -}; - -coverage.scroll_window = function (to_pos) { - $("html,body").animate({scrollTop: to_pos}, 200); -}; - -coverage.finish_scrolling = function () { - $("html,body").stop(true, true); -}; - diff --git a/htmlcov/index.html b/htmlcov/index.html deleted file mode 100644 index 983451658..000000000 --- a/htmlcov/index.html +++ /dev/null @@ -1,404 +0,0 @@ - - - - - Coverage report - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- n - s - m - x - - c   change column sorting -

-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Modulestatementsmissingexcludedcoverage
Total3621406089%
rest_framework/__init__400100%
rest_framework/authentication16933080%
rest_framework/authtoken/__init__000100%
rest_framework/authtoken/models211095%
rest_framework/authtoken/serializers172088%
rest_framework/authtoken/views2100100%
rest_framework/decorators6000100%
rest_framework/exceptions512096%
rest_framework/fields59480087%
rest_framework/filters776092%
rest_framework/generics19634083%
rest_framework/mixins977093%
rest_framework/models000100%
rest_framework/negotiation414090%
rest_framework/pagination4300100%
rest_framework/parsers15313092%
rest_framework/permissions6312081%
rest_framework/relations36588076%
rest_framework/renderers28223092%
rest_framework/request1618095%
rest_framework/response421098%
rest_framework/reverse123075%
rest_framework/routers1087094%
rest_framework/serializers46427094%
rest_framework/settings442095%
rest_framework/status4600100%
rest_framework/throttling9017081%
rest_framework/urlpatterns314087%
rest_framework/urls400100%
rest_framework/utils/__init__000100%
rest_framework/utils/breadcrumbs2700100%
rest_framework/utils/encoders7019073%
rest_framework/utils/formatting391097%
rest_framework/utils/mediatypes4410077%
rest_framework/views14600100%
rest_framework/viewsets392095%
-
- - - - - diff --git a/htmlcov/jquery-1.4.3.min.js b/htmlcov/jquery-1.4.3.min.js deleted file mode 100644 index c941a5f7a..000000000 --- a/htmlcov/jquery-1.4.3.min.js +++ /dev/null @@ -1,166 +0,0 @@ -/*! - * jQuery JavaScript Library v1.4.3 - * http://jquery.com/ - * - * Copyright 2010, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2010, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Thu Oct 14 23:10:06 2010 -0400 - */ -(function(E,A){function U(){return false}function ba(){return true}function ja(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function Ga(a){var b,d,e=[],f=[],h,k,l,n,s,v,B,D;k=c.data(this,this.nodeType?"events":"__events__");if(typeof k==="function")k=k.events;if(!(a.liveFired===this||!k||!k.live||a.button&&a.type==="click")){if(a.namespace)D=RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)");a.liveFired=this;var H=k.live.slice(0);for(n=0;nd)break;a.currentTarget=f.elem;a.data=f.handleObj.data; -a.handleObj=f.handleObj;D=f.handleObj.origHandler.apply(f.elem,arguments);if(D===false||a.isPropagationStopped()){d=f.level;if(D===false)b=false}}return b}}function Y(a,b){return(a&&a!=="*"?a+".":"")+b.replace(Ha,"`").replace(Ia,"&")}function ka(a,b,d){if(c.isFunction(b))return c.grep(a,function(f,h){return!!b.call(f,h,f)===d});else if(b.nodeType)return c.grep(a,function(f){return f===b===d});else if(typeof b==="string"){var e=c.grep(a,function(f){return f.nodeType===1});if(Ja.test(b))return c.filter(b, -e,!d);else b=c.filter(b,e)}return c.grep(a,function(f){return c.inArray(f,b)>=0===d})}function la(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var e=c.data(a[d++]),f=c.data(this,e);if(e=e&&e.events){delete f.handle;f.events={};for(var h in e)for(var k in e[h])c.event.add(this,h,e[h][k],e[h][k].data)}}})}function Ka(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)} -function ma(a,b,d){var e=b==="width"?a.offsetWidth:a.offsetHeight;if(d==="border")return e;c.each(b==="width"?La:Ma,function(){d||(e-=parseFloat(c.css(a,"padding"+this))||0);if(d==="margin")e+=parseFloat(c.css(a,"margin"+this))||0;else e-=parseFloat(c.css(a,"border"+this+"Width"))||0});return e}function ca(a,b,d,e){if(c.isArray(b)&&b.length)c.each(b,function(f,h){d||Na.test(a)?e(a,h):ca(a+"["+(typeof h==="object"||c.isArray(h)?f:"")+"]",h,d,e)});else if(!d&&b!=null&&typeof b==="object")c.isEmptyObject(b)? -e(a,""):c.each(b,function(f,h){ca(a+"["+f+"]",h,d,e)});else e(a,b)}function S(a,b){var d={};c.each(na.concat.apply([],na.slice(0,b)),function(){d[this]=a});return d}function oa(a){if(!da[a]){var b=c("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d==="")d="block";da[a]=d}return da[a]}function ea(a){return c.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var u=E.document,c=function(){function a(){if(!b.isReady){try{u.documentElement.doScroll("left")}catch(i){setTimeout(a, -1);return}b.ready()}}var b=function(i,r){return new b.fn.init(i,r)},d=E.jQuery,e=E.$,f,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,k=/\S/,l=/^\s+/,n=/\s+$/,s=/\W/,v=/\d/,B=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,D=/^[\],:{}\s]*$/,H=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,w=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,G=/(?:^|:|,)(?:\s*\[)+/g,M=/(webkit)[ \/]([\w.]+)/,g=/(opera)(?:.*version)?[ \/]([\w.]+)/,j=/(msie) ([\w.]+)/,o=/(mozilla)(?:.*? rv:([\w.]+))?/,m=navigator.userAgent,p=false, -q=[],t,x=Object.prototype.toString,C=Object.prototype.hasOwnProperty,P=Array.prototype.push,N=Array.prototype.slice,R=String.prototype.trim,Q=Array.prototype.indexOf,L={};b.fn=b.prototype={init:function(i,r){var y,z,F;if(!i)return this;if(i.nodeType){this.context=this[0]=i;this.length=1;return this}if(i==="body"&&!r&&u.body){this.context=u;this[0]=u.body;this.selector="body";this.length=1;return this}if(typeof i==="string")if((y=h.exec(i))&&(y[1]||!r))if(y[1]){F=r?r.ownerDocument||r:u;if(z=B.exec(i))if(b.isPlainObject(r)){i= -[u.createElement(z[1])];b.fn.attr.call(i,r,true)}else i=[F.createElement(z[1])];else{z=b.buildFragment([y[1]],[F]);i=(z.cacheable?z.fragment.cloneNode(true):z.fragment).childNodes}return b.merge(this,i)}else{if((z=u.getElementById(y[2]))&&z.parentNode){if(z.id!==y[2])return f.find(i);this.length=1;this[0]=z}this.context=u;this.selector=i;return this}else if(!r&&!s.test(i)){this.selector=i;this.context=u;i=u.getElementsByTagName(i);return b.merge(this,i)}else return!r||r.jquery?(r||f).find(i):b(r).find(i); -else if(b.isFunction(i))return f.ready(i);if(i.selector!==A){this.selector=i.selector;this.context=i.context}return b.makeArray(i,this)},selector:"",jquery:"1.4.3",length:0,size:function(){return this.length},toArray:function(){return N.call(this,0)},get:function(i){return i==null?this.toArray():i<0?this.slice(i)[0]:this[i]},pushStack:function(i,r,y){var z=b();b.isArray(i)?P.apply(z,i):b.merge(z,i);z.prevObject=this;z.context=this.context;if(r==="find")z.selector=this.selector+(this.selector?" ": -"")+y;else if(r)z.selector=this.selector+"."+r+"("+y+")";return z},each:function(i,r){return b.each(this,i,r)},ready:function(i){b.bindReady();if(b.isReady)i.call(u,b);else q&&q.push(i);return this},eq:function(i){return i===-1?this.slice(i):this.slice(i,+i+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(N.apply(this,arguments),"slice",N.call(arguments).join(","))},map:function(i){return this.pushStack(b.map(this,function(r,y){return i.call(r, -y,r)}))},end:function(){return this.prevObject||b(null)},push:P,sort:[].sort,splice:[].splice};b.fn.init.prototype=b.fn;b.extend=b.fn.extend=function(){var i=arguments[0]||{},r=1,y=arguments.length,z=false,F,I,K,J,fa;if(typeof i==="boolean"){z=i;i=arguments[1]||{};r=2}if(typeof i!=="object"&&!b.isFunction(i))i={};if(y===r){i=this;--r}for(;r0)){if(q){for(var r=0;i=q[r++];)i.call(u,b);q=null}b.fn.triggerHandler&&b(u).triggerHandler("ready")}}},bindReady:function(){if(!p){p=true;if(u.readyState==="complete")return setTimeout(b.ready, -1);if(u.addEventListener){u.addEventListener("DOMContentLoaded",t,false);E.addEventListener("load",b.ready,false)}else if(u.attachEvent){u.attachEvent("onreadystatechange",t);E.attachEvent("onload",b.ready);var i=false;try{i=E.frameElement==null}catch(r){}u.documentElement.doScroll&&i&&a()}}},isFunction:function(i){return b.type(i)==="function"},isArray:Array.isArray||function(i){return b.type(i)==="array"},isWindow:function(i){return i&&typeof i==="object"&&"setInterval"in i},isNaN:function(i){return i== -null||!v.test(i)||isNaN(i)},type:function(i){return i==null?String(i):L[x.call(i)]||"object"},isPlainObject:function(i){if(!i||b.type(i)!=="object"||i.nodeType||b.isWindow(i))return false;if(i.constructor&&!C.call(i,"constructor")&&!C.call(i.constructor.prototype,"isPrototypeOf"))return false;for(var r in i);return r===A||C.call(i,r)},isEmptyObject:function(i){for(var r in i)return false;return true},error:function(i){throw i;},parseJSON:function(i){if(typeof i!=="string"||!i)return null;i=b.trim(i); -if(D.test(i.replace(H,"@").replace(w,"]").replace(G,"")))return E.JSON&&E.JSON.parse?E.JSON.parse(i):(new Function("return "+i))();else b.error("Invalid JSON: "+i)},noop:function(){},globalEval:function(i){if(i&&k.test(i)){var r=u.getElementsByTagName("head")[0]||u.documentElement,y=u.createElement("script");y.type="text/javascript";if(b.support.scriptEval)y.appendChild(u.createTextNode(i));else y.text=i;r.insertBefore(y,r.firstChild);r.removeChild(y)}},nodeName:function(i,r){return i.nodeName&&i.nodeName.toUpperCase()=== -r.toUpperCase()},each:function(i,r,y){var z,F=0,I=i.length,K=I===A||b.isFunction(i);if(y)if(K)for(z in i){if(r.apply(i[z],y)===false)break}else for(;F";a=u.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var s=u.createElement("div"); -s.style.width=s.style.paddingLeft="1px";u.body.appendChild(s);c.boxModel=c.support.boxModel=s.offsetWidth===2;if("zoom"in s.style){s.style.display="inline";s.style.zoom=1;c.support.inlineBlockNeedsLayout=s.offsetWidth===2;s.style.display="";s.innerHTML="
";c.support.shrinkWrapBlocks=s.offsetWidth!==2}s.innerHTML="
t
";var v=s.getElementsByTagName("td");c.support.reliableHiddenOffsets=v[0].offsetHeight=== -0;v[0].style.display="";v[1].style.display="none";c.support.reliableHiddenOffsets=c.support.reliableHiddenOffsets&&v[0].offsetHeight===0;s.innerHTML="";u.body.removeChild(s).style.display="none"});a=function(s){var v=u.createElement("div");s="on"+s;var B=s in v;if(!B){v.setAttribute(s,"return;");B=typeof v[s]==="function"}return B};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=f=h=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength", -cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var pa={},Oa=/^(?:\{.*\}|\[.*\])$/;c.extend({cache:{},uuid:0,expando:"jQuery"+c.now(),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},data:function(a,b,d){if(c.acceptData(a)){a=a==E?pa:a;var e=a.nodeType,f=e?a[c.expando]:null,h=c.cache;if(!(e&&!f&&typeof b==="string"&&d===A)){if(e)f||(a[c.expando]=f=++c.uuid);else h=a;if(typeof b==="object")if(e)h[f]= -c.extend(h[f],b);else c.extend(h,b);else if(e&&!h[f])h[f]={};a=e?h[f]:h;if(d!==A)a[b]=d;return typeof b==="string"?a[b]:a}}},removeData:function(a,b){if(c.acceptData(a)){a=a==E?pa:a;var d=a.nodeType,e=d?a[c.expando]:a,f=c.cache,h=d?f[e]:e;if(b){if(h){delete h[b];d&&c.isEmptyObject(h)&&c.removeData(a)}}else if(d&&c.support.deleteExpando)delete a[c.expando];else if(a.removeAttribute)a.removeAttribute(c.expando);else if(d)delete f[e];else for(var k in a)delete a[k]}},acceptData:function(a){if(a.nodeName){var b= -c.noData[a.nodeName.toLowerCase()];if(b)return!(b===true||a.getAttribute("classid")!==b)}return true}});c.fn.extend({data:function(a,b){if(typeof a==="undefined")return this.length?c.data(this[0]):null;else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===A){var e=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(e===A&&this.length){e=c.data(this[0],a);if(e===A&&this[0].nodeType===1){e=this[0].getAttribute("data-"+a);if(typeof e=== -"string")try{e=e==="true"?true:e==="false"?false:e==="null"?null:!c.isNaN(e)?parseFloat(e):Oa.test(e)?c.parseJSON(e):e}catch(f){}else e=A}}return e===A&&d[1]?this.data(d[0]):e}else return this.each(function(){var h=c(this),k=[d[0],b];h.triggerHandler("setData"+d[1]+"!",k);c.data(this,a,b);h.triggerHandler("changeData"+d[1]+"!",k)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var e=c.data(a,b);if(!d)return e|| -[];if(!e||c.isArray(d))e=c.data(a,b,c.makeArray(d));else e.push(d);return e}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),e=d.shift();if(e==="inprogress")e=d.shift();if(e){b==="fx"&&d.unshift("inprogress");e.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===A)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this, -a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var qa=/[\n\t]/g,ga=/\s+/,Pa=/\r/g,Qa=/^(?:href|src|style)$/,Ra=/^(?:button|input)$/i,Sa=/^(?:button|input|object|select|textarea)$/i,Ta=/^a(?:rea)?$/i,ra=/^(?:radio|checkbox)$/i;c.fn.extend({attr:function(a,b){return c.access(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this, -a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(s){var v=c(this);v.addClass(a.call(this,s,v.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ga),d=0,e=this.length;d-1)return true;return false}, -val:function(a){if(!arguments.length){var b=this[0];if(b){if(c.nodeName(b,"option")){var d=b.attributes.value;return!d||d.specified?b.value:b.text}if(c.nodeName(b,"select")){var e=b.selectedIndex;d=[];var f=b.options;b=b.type==="select-one";if(e<0)return null;var h=b?e:0;for(e=b?e+1:f.length;h=0;else if(c.nodeName(this,"select")){var B=c.makeArray(v);c("option",this).each(function(){this.selected= -c.inArray(c(this).val(),B)>=0});if(!B.length)this.selectedIndex=-1}else this.value=v}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,e){if(!a||a.nodeType===3||a.nodeType===8)return A;if(e&&b in c.attrFn)return c(a)[b](d);e=a.nodeType!==1||!c.isXMLDoc(a);var f=d!==A;b=e&&c.props[b]||b;if(a.nodeType===1){var h=Qa.test(b);if((b in a||a[b]!==A)&&e&&!h){if(f){b==="type"&&Ra.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); -if(d===null)a.nodeType===1&&a.removeAttribute(b);else a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:Sa.test(a.nodeName)||Ta.test(a.nodeName)&&a.href?0:A;return a[b]}if(!c.support.style&&e&&b==="style"){if(f)a.style.cssText=""+d;return a.style.cssText}f&&a.setAttribute(b,""+d);if(!a.attributes[b]&&a.hasAttribute&&!a.hasAttribute(b))return A;a=!c.support.hrefNormalized&&e&& -h?a.getAttribute(b,2):a.getAttribute(b);return a===null?A:a}}});var X=/\.(.*)$/,ha=/^(?:textarea|input|select)$/i,Ha=/\./g,Ia=/ /g,Ua=/[^\w\s.|`]/g,Va=function(a){return a.replace(Ua,"\\$&")},sa={focusin:0,focusout:0};c.event={add:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(c.isWindow(a)&&a!==E&&!a.frameElement)a=E;if(d===false)d=U;var f,h;if(d.handler){f=d;d=f.handler}if(!d.guid)d.guid=c.guid++;if(h=c.data(a)){var k=a.nodeType?"events":"__events__",l=h[k],n=h.handle;if(typeof l=== -"function"){n=l.handle;l=l.events}else if(!l){a.nodeType||(h[k]=h=function(){});h.events=l={}}if(!n)h.handle=n=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(n.elem,arguments):A};n.elem=a;b=b.split(" ");for(var s=0,v;k=b[s++];){h=f?c.extend({},f):{handler:d,data:e};if(k.indexOf(".")>-1){v=k.split(".");k=v.shift();h.namespace=v.slice(0).sort().join(".")}else{v=[];h.namespace=""}h.type=k;if(!h.guid)h.guid=d.guid;var B=l[k],D=c.event.special[k]||{};if(!B){B=l[k]=[]; -if(!D.setup||D.setup.call(a,e,v,n)===false)if(a.addEventListener)a.addEventListener(k,n,false);else a.attachEvent&&a.attachEvent("on"+k,n)}if(D.add){D.add.call(a,h);if(!h.handler.guid)h.handler.guid=d.guid}B.push(h);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(d===false)d=U;var f,h,k=0,l,n,s,v,B,D,H=a.nodeType?"events":"__events__",w=c.data(a),G=w&&w[H];if(w&&G){if(typeof G==="function"){w=G;G=G.events}if(b&&b.type){d=b.handler;b=b.type}if(!b|| -typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(f in G)c.event.remove(a,f+b)}else{for(b=b.split(" ");f=b[k++];){v=f;l=f.indexOf(".")<0;n=[];if(!l){n=f.split(".");f=n.shift();s=RegExp("(^|\\.)"+c.map(n.slice(0).sort(),Va).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(B=G[f])if(d){v=c.event.special[f]||{};for(h=e||0;h=0){a.type= -f=f.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[f]&&c.each(c.cache,function(){this.events&&this.events[f]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return A;a.result=A;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(e=d.nodeType?c.data(d,"handle"):(c.data(d,"__events__")||{}).handle)&&e.apply(d,b);e=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+f]&&d["on"+f].apply(d,b)=== -false){a.result=false;a.preventDefault()}}catch(h){}if(!a.isPropagationStopped()&&e)c.event.trigger(a,b,e,true);else if(!a.isDefaultPrevented()){e=a.target;var k,l=f.replace(X,""),n=c.nodeName(e,"a")&&l==="click",s=c.event.special[l]||{};if((!s._default||s._default.call(d,a)===false)&&!n&&!(e&&e.nodeName&&c.noData[e.nodeName.toLowerCase()])){try{if(e[l]){if(k=e["on"+l])e["on"+l]=null;c.event.triggered=true;e[l]()}}catch(v){}if(k)e["on"+l]=k;c.event.triggered=false}}},handle:function(a){var b,d,e; -d=[];var f,h=c.makeArray(arguments);a=h[0]=c.event.fix(a||E.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;if(!b){e=a.type.split(".");a.type=e.shift();d=e.slice(0).sort();e=RegExp("(^|\\.)"+d.join("\\.(?:.*\\.)?")+"(\\.|$)")}a.namespace=a.namespace||d.join(".");f=c.data(this,this.nodeType?"events":"__events__");if(typeof f==="function")f=f.events;d=(f||{})[a.type];if(f&&d){d=d.slice(0);f=0;for(var k=d.length;f-1?c.map(a.options,function(e){return e.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},Z=function(a,b){var d=a.target,e,f;if(!(!ha.test(d.nodeName)||d.readOnly)){e=c.data(d,"_change_data");f=va(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",f);if(!(e===A||f===e))if(e!=null||f){a.type="change";a.liveFired= -A;return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:Z,beforedeactivate:Z,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return Z.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return Z.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,"_change_data",va(a))}},setup:function(){if(this.type=== -"file")return false;for(var a in V)c.event.add(this,a+".specialChange",V[a]);return ha.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return ha.test(this.nodeName)}};V=c.event.special.change.filters;V.focus=V.beforeactivate}u.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(e){e=c.event.fix(e);e.type=b;return c.event.trigger(e,null,e.target)}c.event.special[b]={setup:function(){sa[b]++===0&&u.addEventListener(a,d,true)},teardown:function(){--sa[b]=== -0&&u.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,e,f){if(typeof d==="object"){for(var h in d)this[b](h,e,d[h],f);return this}if(c.isFunction(e)||e===false){f=e;e=A}var k=b==="one"?c.proxy(f,function(n){c(this).unbind(n,k);return f.apply(this,arguments)}):f;if(d==="unload"&&b!=="one")this.one(d,e,f);else{h=0;for(var l=this.length;h0?this.bind(b,d,e):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});E.attachEvent&&!E.addEventListener&&c(E).bind("unload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}}); -(function(){function a(g,j,o,m,p,q){p=0;for(var t=m.length;p0){C=x;break}}x=x[g]}m[p]=C}}}var d=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,h=false,k=true;[0,0].sort(function(){k=false;return 0});var l=function(g,j,o,m){o=o||[];var p=j=j||u;if(j.nodeType!==1&&j.nodeType!==9)return[];if(!g||typeof g!=="string")return o;var q=[],t,x,C,P,N=true,R=l.isXML(j),Q=g,L;do{d.exec("");if(t=d.exec(Q)){Q=t[3];q.push(t[1]);if(t[2]){P=t[3]; -break}}}while(t);if(q.length>1&&s.exec(g))if(q.length===2&&n.relative[q[0]])x=M(q[0]+q[1],j);else for(x=n.relative[q[0]]?[j]:l(q.shift(),j);q.length;){g=q.shift();if(n.relative[g])g+=q.shift();x=M(g,x)}else{if(!m&&q.length>1&&j.nodeType===9&&!R&&n.match.ID.test(q[0])&&!n.match.ID.test(q[q.length-1])){t=l.find(q.shift(),j,R);j=t.expr?l.filter(t.expr,t.set)[0]:t.set[0]}if(j){t=m?{expr:q.pop(),set:D(m)}:l.find(q.pop(),q.length===1&&(q[0]==="~"||q[0]==="+")&&j.parentNode?j.parentNode:j,R);x=t.expr?l.filter(t.expr, -t.set):t.set;if(q.length>0)C=D(x);else N=false;for(;q.length;){t=L=q.pop();if(n.relative[L])t=q.pop();else L="";if(t==null)t=j;n.relative[L](C,t,R)}}else C=[]}C||(C=x);C||l.error(L||g);if(f.call(C)==="[object Array]")if(N)if(j&&j.nodeType===1)for(g=0;C[g]!=null;g++){if(C[g]&&(C[g]===true||C[g].nodeType===1&&l.contains(j,C[g])))o.push(x[g])}else for(g=0;C[g]!=null;g++)C[g]&&C[g].nodeType===1&&o.push(x[g]);else o.push.apply(o,C);else D(C,o);if(P){l(P,p,o,m);l.uniqueSort(o)}return o};l.uniqueSort=function(g){if(w){h= -k;g.sort(w);if(h)for(var j=1;j0};l.find=function(g,j,o){var m;if(!g)return[];for(var p=0,q=n.order.length;p":function(g,j){var o=typeof j==="string",m,p=0,q=g.length;if(o&&!/\W/.test(j))for(j=j.toLowerCase();p=0))o||m.push(t);else if(o)j[q]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},CHILD:function(g){if(g[1]==="nth"){var j=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=j[1]+(j[2]||1)-0;g[3]=j[3]-0}g[0]=e++;return g},ATTR:function(g,j,o, -m,p,q){j=g[1].replace(/\\/g,"");if(!q&&n.attrMap[j])g[1]=n.attrMap[j];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,j,o,m,p){if(g[1]==="not")if((d.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=l(g[3],null,null,j);else{g=l.filter(g[3],j,o,true^p);o||m.push.apply(m,g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled=== -true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,j,o){return!!l(o[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"=== -g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},setFilters:{first:function(g,j){return j===0},last:function(g,j,o,m){return j===m.length-1},even:function(g,j){return j%2===0},odd:function(g,j){return j%2===1},lt:function(g,j,o){return jo[3]-0},nth:function(g,j,o){return o[3]- -0===j},eq:function(g,j,o){return o[3]-0===j}},filter:{PSEUDO:function(g,j,o,m){var p=j[1],q=n.filters[p];if(q)return q(g,o,j,m);else if(p==="contains")return(g.textContent||g.innerText||l.getText([g])||"").indexOf(j[3])>=0;else if(p==="not"){j=j[3];o=0;for(m=j.length;o=0}},ID:function(g,j){return g.nodeType===1&&g.getAttribute("id")===j},TAG:function(g,j){return j==="*"&&g.nodeType===1||g.nodeName.toLowerCase()=== -j},CLASS:function(g,j){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(j)>-1},ATTR:function(g,j){var o=j[1];o=n.attrHandle[o]?n.attrHandle[o](g):g[o]!=null?g[o]:g.getAttribute(o);var m=o+"",p=j[2],q=j[4];return o==null?p==="!=":p==="="?m===q:p==="*="?m.indexOf(q)>=0:p==="~="?(" "+m+" ").indexOf(q)>=0:!q?m&&o!==false:p==="!="?m!==q:p==="^="?m.indexOf(q)===0:p==="$="?m.substr(m.length-q.length)===q:p==="|="?m===q||m.substr(0,q.length+1)===q+"-":false},POS:function(g,j,o,m){var p=n.setFilters[j[2]]; -if(p)return p(g,o,j,m)}}},s=n.match.POS,v=function(g,j){return"\\"+(j-0+1)},B;for(B in n.match){n.match[B]=RegExp(n.match[B].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[B]=RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[B].source.replace(/\\(\d+)/g,v))}var D=function(g,j){g=Array.prototype.slice.call(g,0);if(j){j.push.apply(j,g);return j}return g};try{Array.prototype.slice.call(u.documentElement.childNodes,0)}catch(H){D=function(g,j){var o=j||[],m=0;if(f.call(g)==="[object Array]")Array.prototype.push.apply(o, -g);else if(typeof g.length==="number")for(var p=g.length;m";var o=u.documentElement;o.insertBefore(g,o.firstChild);if(u.getElementById(j)){n.find.ID=function(m,p,q){if(typeof p.getElementById!=="undefined"&&!q)return(p=p.getElementById(m[1]))?p.id===m[1]||typeof p.getAttributeNode!=="undefined"&&p.getAttributeNode("id").nodeValue===m[1]?[p]:A:[]};n.filter.ID=function(m,p){var q=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&q&&q.nodeValue===p}}o.removeChild(g); -o=g=null})();(function(){var g=u.createElement("div");g.appendChild(u.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(j,o){var m=o.getElementsByTagName(j[1]);if(j[1]==="*"){for(var p=[],q=0;m[q];q++)m[q].nodeType===1&&p.push(m[q]);m=p}return m};g.innerHTML="";if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(j){return j.getAttribute("href",2)};g=null})();u.querySelectorAll&& -function(){var g=l,j=u.createElement("div");j.innerHTML="

";if(!(j.querySelectorAll&&j.querySelectorAll(".TEST").length===0)){l=function(m,p,q,t){p=p||u;if(!t&&!l.isXML(p))if(p.nodeType===9)try{return D(p.querySelectorAll(m),q)}catch(x){}else if(p.nodeType===1&&p.nodeName.toLowerCase()!=="object"){var C=p.id,P=p.id="__sizzle__";try{return D(p.querySelectorAll("#"+P+" "+m),q)}catch(N){}finally{if(C)p.id=C;else p.removeAttribute("id")}}return g(m,p,q,t)};for(var o in g)l[o]=g[o]; -j=null}}();(function(){var g=u.documentElement,j=g.matchesSelector||g.mozMatchesSelector||g.webkitMatchesSelector||g.msMatchesSelector,o=false;try{j.call(u.documentElement,":sizzle")}catch(m){o=true}if(j)l.matchesSelector=function(p,q){try{if(o||!n.match.PSEUDO.test(q))return j.call(p,q)}catch(t){}return l(q,null,null,[p]).length>0}})();(function(){var g=u.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length=== -0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(j,o,m){if(typeof o.getElementsByClassName!=="undefined"&&!m)return o.getElementsByClassName(j[1])};g=null}}})();l.contains=u.documentElement.contains?function(g,j){return g!==j&&(g.contains?g.contains(j):true)}:function(g,j){return!!(g.compareDocumentPosition(j)&16)};l.isXML=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false};var M=function(g, -j){for(var o=[],m="",p,q=j.nodeType?[j]:j;p=n.match.PSEUDO.exec(g);){m+=p[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;p=0;for(var t=q.length;p0)for(var h=d;h0},closest:function(a, -b){var d=[],e,f,h=this[0];if(c.isArray(a)){var k={},l,n=1;if(h&&a.length){e=0;for(f=a.length;e-1:c(h).is(e))d.push({selector:l,elem:h,level:n})}h=h.parentNode;n++}}return d}k=$a.test(a)?c(a,b||this.context):null;e=0;for(f=this.length;e-1:c.find.matchesSelector(h,a)){d.push(h);break}else{h=h.parentNode;if(!h|| -!h.ownerDocument||h===b)break}d=d.length>1?c.unique(d):d;return this.pushStack(d,"closest",a)},index:function(a){if(!a||typeof a==="string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var d=typeof a==="string"?c(a,b||this.context):c.makeArray(a),e=c.merge(this.get(),d);return this.pushStack(!d[0]||!d[0].parentNode||d[0].parentNode.nodeType===11||!e[0]||!e[0].parentNode||e[0].parentNode.nodeType===11?e:c.unique(e))},andSelf:function(){return this.add(this.prevObject)}}); -c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling", -d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,e){var f=c.map(this,b,d);Wa.test(a)||(e=d);if(e&&typeof e==="string")f=c.filter(e,f);f=this.length>1?c.unique(f):f;if((this.length>1||Ya.test(e))&&Xa.test(a))f=f.reverse();return this.pushStack(f,a,Za.call(arguments).join(","))}}); -c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return b.length===1?c.find.matchesSelector(b[0],a)?[b[0]]:[]:c.find.matches(a,b)},dir:function(a,b,d){var e=[];for(a=a[b];a&&a.nodeType!==9&&(d===A||a.nodeType!==1||!c(a).is(d));){a.nodeType===1&&e.push(a);a=a[b]}return e},nth:function(a,b,d){b=b||1;for(var e=0;a;a=a[d])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var xa=/ jQuery\d+="(?:\d+|null)"/g, -$=/^\s+/,ya=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,za=/<([\w:]+)/,ab=/\s]+\/)>/g,O={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"], -area:[1,"",""],_default:[0,"",""]};O.optgroup=O.option;O.tbody=O.tfoot=O.colgroup=O.caption=O.thead;O.th=O.td;if(!c.support.htmlSerialize)O._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==A)return this.empty().append((this[0]&&this[0].ownerDocument||u).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this, -d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})}, -unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a= -c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,e;(e=this[d])!=null;d++)if(!a||c.filter(a,[e]).length){if(!b&&e.nodeType===1){c.cleanData(e.getElementsByTagName("*")); -c.cleanData([e])}e.parentNode&&e.parentNode.removeChild(e)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,e=this.ownerDocument;if(!d){d=e.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(xa,"").replace(cb,'="$1">').replace($, -"")],e)[0]}else return this.cloneNode(true)});if(a===true){la(this,b);la(this.find("*"),b.find("*"))}return b},html:function(a){if(a===A)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(xa,""):null;else if(typeof a==="string"&&!Aa.test(a)&&(c.support.leadingWhitespace||!$.test(a))&&!O[(za.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ya,"<$1>");try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?l.cloneNode(true):l)}k.length&&c.each(k,Ka)}return this}});c.buildFragment=function(a,b,d){var e,f,h;b=b&&b[0]?b[0].ownerDocument||b[0]:u;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===u&&!Aa.test(a[0])&&(c.support.checkClone|| -!Ba.test(a[0]))){f=true;if(h=c.fragments[a[0]])if(h!==1)e=h}if(!e){e=b.createDocumentFragment();c.clean(a,b,e,d)}if(f)c.fragments[a[0]]=h?e:1;return{fragment:e,cacheable:f}};c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var e=[];d=c(d);var f=this.length===1&&this[0].parentNode;if(f&&f.nodeType===11&&f.childNodes.length===1&&d.length===1){d[b](this[0]);return this}else{f=0;for(var h= -d.length;f0?this.clone(true):this).get();c(d[f])[b](k);e=e.concat(k)}return this.pushStack(e,a,d.selector)}}});c.extend({clean:function(a,b,d,e){b=b||u;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||u;for(var f=[],h=0,k;(k=a[h])!=null;h++){if(typeof k==="number")k+="";if(k){if(typeof k==="string"&&!bb.test(k))k=b.createTextNode(k);else if(typeof k==="string"){k=k.replace(ya,"<$1>");var l=(za.exec(k)||["",""])[1].toLowerCase(),n=O[l]||O._default, -s=n[0],v=b.createElement("div");for(v.innerHTML=n[1]+k+n[2];s--;)v=v.lastChild;if(!c.support.tbody){s=ab.test(k);l=l==="table"&&!s?v.firstChild&&v.firstChild.childNodes:n[1]===""&&!s?v.childNodes:[];for(n=l.length-1;n>=0;--n)c.nodeName(l[n],"tbody")&&!l[n].childNodes.length&&l[n].parentNode.removeChild(l[n])}!c.support.leadingWhitespace&&$.test(k)&&v.insertBefore(b.createTextNode($.exec(k)[0]),v.firstChild);k=v.childNodes}if(k.nodeType)f.push(k);else f=c.merge(f,k)}}if(d)for(h=0;f[h];h++)if(e&& -c.nodeName(f[h],"script")&&(!f[h].type||f[h].type.toLowerCase()==="text/javascript"))e.push(f[h].parentNode?f[h].parentNode.removeChild(f[h]):f[h]);else{f[h].nodeType===1&&f.splice.apply(f,[h+1,0].concat(c.makeArray(f[h].getElementsByTagName("script"))));d.appendChild(f[h])}return f},cleanData:function(a){for(var b,d,e=c.cache,f=c.event.special,h=c.support.deleteExpando,k=0,l;(l=a[k])!=null;k++)if(!(l.nodeName&&c.noData[l.nodeName.toLowerCase()]))if(d=l[c.expando]){if((b=e[d])&&b.events)for(var n in b.events)f[n]? -c.event.remove(l,n):c.removeEvent(l,n,b.handle);if(h)delete l[c.expando];else l.removeAttribute&&l.removeAttribute(c.expando);delete e[d]}}});var Ca=/alpha\([^)]*\)/i,db=/opacity=([^)]*)/,eb=/-([a-z])/ig,fb=/([A-Z])/g,Da=/^-?\d+(?:px)?$/i,gb=/^-?\d/,hb={position:"absolute",visibility:"hidden",display:"block"},La=["Left","Right"],Ma=["Top","Bottom"],W,ib=u.defaultView&&u.defaultView.getComputedStyle,jb=function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){if(arguments.length===2&&b===A)return this; -return c.access(this,a,b,true,function(d,e,f){return f!==A?c.style(d,e,f):c.css(d,e)})};c.extend({cssHooks:{opacity:{get:function(a,b){if(b){var d=W(a,"opacity","opacity");return d===""?"1":d}else return a.style.opacity}}},cssNumber:{zIndex:true,fontWeight:true,opacity:true,zoom:true,lineHeight:true},cssProps:{"float":c.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,d,e){if(!(!a||a.nodeType===3||a.nodeType===8||!a.style)){var f,h=c.camelCase(b),k=a.style,l=c.cssHooks[h];b=c.cssProps[h]|| -h;if(d!==A){if(!(typeof d==="number"&&isNaN(d)||d==null)){if(typeof d==="number"&&!c.cssNumber[h])d+="px";if(!l||!("set"in l)||(d=l.set(a,d))!==A)try{k[b]=d}catch(n){}}}else{if(l&&"get"in l&&(f=l.get(a,false,e))!==A)return f;return k[b]}}},css:function(a,b,d){var e,f=c.camelCase(b),h=c.cssHooks[f];b=c.cssProps[f]||f;if(h&&"get"in h&&(e=h.get(a,true,d))!==A)return e;else if(W)return W(a,b,f)},swap:function(a,b,d){var e={},f;for(f in b){e[f]=a.style[f];a.style[f]=b[f]}d.call(a);for(f in b)a.style[f]= -e[f]},camelCase:function(a){return a.replace(eb,jb)}});c.curCSS=c.css;c.each(["height","width"],function(a,b){c.cssHooks[b]={get:function(d,e,f){var h;if(e){if(d.offsetWidth!==0)h=ma(d,b,f);else c.swap(d,hb,function(){h=ma(d,b,f)});return h+"px"}},set:function(d,e){if(Da.test(e)){e=parseFloat(e);if(e>=0)return e+"px"}else return e}}});if(!c.support.opacity)c.cssHooks.opacity={get:function(a,b){return db.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"": -b?"1":""},set:function(a,b){var d=a.style;d.zoom=1;var e=c.isNaN(b)?"":"alpha(opacity="+b*100+")",f=d.filter||"";d.filter=Ca.test(f)?f.replace(Ca,e):d.filter+" "+e}};if(ib)W=function(a,b,d){var e;d=d.replace(fb,"-$1").toLowerCase();if(!(b=a.ownerDocument.defaultView))return A;if(b=b.getComputedStyle(a,null)){e=b.getPropertyValue(d);if(e===""&&!c.contains(a.ownerDocument.documentElement,a))e=c.style(a,d)}return e};else if(u.documentElement.currentStyle)W=function(a,b){var d,e,f=a.currentStyle&&a.currentStyle[b], -h=a.style;if(!Da.test(f)&&gb.test(f)){d=h.left;e=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;h.left=b==="fontSize"?"1em":f||0;f=h.pixelLeft+"px";h.left=d;a.runtimeStyle.left=e}return f};if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=a.offsetHeight;return a.offsetWidth===0&&b===0||!c.support.reliableHiddenOffsets&&(a.style.display||c.css(a,"display"))==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var kb=c.now(),lb=/)<[^<]*)*<\/script>/gi, -mb=/^(?:select|textarea)/i,nb=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ob=/^(?:GET|HEAD|DELETE)$/,Na=/\[\]$/,T=/\=\?(&|$)/,ia=/\?/,pb=/([?&])_=[^&]*/,qb=/^(\w+:)?\/\/([^\/?#]+)/,rb=/%20/g,sb=/#.*$/,Ea=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!=="string"&&Ea)return Ea.apply(this,arguments);else if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var f=a.slice(e,a.length);a=a.slice(0,e)}e="GET";if(b)if(c.isFunction(b)){d= -b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);e="POST"}var h=this;c.ajax({url:a,type:e,dataType:"html",data:b,complete:function(k,l){if(l==="success"||l==="notmodified")h.html(f?c("
").append(k.responseText.replace(lb,"")).find(f):k.responseText);d&&h.each(d,[k.responseText,l,k])}});return this},serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&& -!this.disabled&&(this.checked||mb.test(this.nodeName)||nb.test(this.type))}).map(function(a,b){var d=c(this).val();return d==null?null:c.isArray(d)?c.map(d,function(e){return{name:b.name,value:e}}):{name:b.name,value:d}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:e})}, -getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:e})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return new E.XMLHttpRequest},accepts:{xml:"application/xml, text/xml",html:"text/html", -script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},ajax:function(a){var b=c.extend(true,{},c.ajaxSettings,a),d,e,f,h=b.type.toUpperCase(),k=ob.test(h);b.url=b.url.replace(sb,"");b.context=a&&a.context!=null?a.context:b;if(b.data&&b.processData&&typeof b.data!=="string")b.data=c.param(b.data,b.traditional);if(b.dataType==="jsonp"){if(h==="GET")T.test(b.url)||(b.url+=(ia.test(b.url)?"&":"?")+(b.jsonp||"callback")+"=?");else if(!b.data|| -!T.test(b.data))b.data=(b.data?b.data+"&":"")+(b.jsonp||"callback")+"=?";b.dataType="json"}if(b.dataType==="json"&&(b.data&&T.test(b.data)||T.test(b.url))){d=b.jsonpCallback||"jsonp"+kb++;if(b.data)b.data=(b.data+"").replace(T,"="+d+"$1");b.url=b.url.replace(T,"="+d+"$1");b.dataType="script";var l=E[d];E[d]=function(m){f=m;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);if(c.isFunction(l))l(m);else{E[d]=A;try{delete E[d]}catch(p){}}v&&v.removeChild(B)}}if(b.dataType==="script"&&b.cache===null)b.cache= -false;if(b.cache===false&&h==="GET"){var n=c.now(),s=b.url.replace(pb,"$1_="+n);b.url=s+(s===b.url?(ia.test(b.url)?"&":"?")+"_="+n:"")}if(b.data&&h==="GET")b.url+=(ia.test(b.url)?"&":"?")+b.data;b.global&&c.active++===0&&c.event.trigger("ajaxStart");n=(n=qb.exec(b.url))&&(n[1]&&n[1]!==location.protocol||n[2]!==location.host);if(b.dataType==="script"&&h==="GET"&&n){var v=u.getElementsByTagName("head")[0]||u.documentElement,B=u.createElement("script");if(b.scriptCharset)B.charset=b.scriptCharset;B.src= -b.url;if(!d){var D=false;B.onload=B.onreadystatechange=function(){if(!D&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){D=true;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);B.onload=B.onreadystatechange=null;v&&B.parentNode&&v.removeChild(B)}}}v.insertBefore(B,v.firstChild);return A}var H=false,w=b.xhr();if(w){b.username?w.open(h,b.url,b.async,b.username,b.password):w.open(h,b.url,b.async);try{if(b.data!=null&&!k||a&&a.contentType)w.setRequestHeader("Content-Type", -b.contentType);if(b.ifModified){c.lastModified[b.url]&&w.setRequestHeader("If-Modified-Since",c.lastModified[b.url]);c.etag[b.url]&&w.setRequestHeader("If-None-Match",c.etag[b.url])}n||w.setRequestHeader("X-Requested-With","XMLHttpRequest");w.setRequestHeader("Accept",b.dataType&&b.accepts[b.dataType]?b.accepts[b.dataType]+", */*; q=0.01":b.accepts._default)}catch(G){}if(b.beforeSend&&b.beforeSend.call(b.context,w,b)===false){b.global&&c.active--===1&&c.event.trigger("ajaxStop");w.abort();return false}b.global&& -c.triggerGlobal(b,"ajaxSend",[w,b]);var M=w.onreadystatechange=function(m){if(!w||w.readyState===0||m==="abort"){H||c.handleComplete(b,w,e,f);H=true;if(w)w.onreadystatechange=c.noop}else if(!H&&w&&(w.readyState===4||m==="timeout")){H=true;w.onreadystatechange=c.noop;e=m==="timeout"?"timeout":!c.httpSuccess(w)?"error":b.ifModified&&c.httpNotModified(w,b.url)?"notmodified":"success";var p;if(e==="success")try{f=c.httpData(w,b.dataType,b)}catch(q){e="parsererror";p=q}if(e==="success"||e==="notmodified")d|| -c.handleSuccess(b,w,e,f);else c.handleError(b,w,e,p);d||c.handleComplete(b,w,e,f);m==="timeout"&&w.abort();if(b.async)w=null}};try{var g=w.abort;w.abort=function(){w&&g.call&&g.call(w);M("abort")}}catch(j){}b.async&&b.timeout>0&&setTimeout(function(){w&&!H&&M("timeout")},b.timeout);try{w.send(k||b.data==null?null:b.data)}catch(o){c.handleError(b,w,null,o);c.handleComplete(b,w,e,f)}b.async||M();return w}},param:function(a,b){var d=[],e=function(h,k){k=c.isFunction(k)?k():k;d[d.length]=encodeURIComponent(h)+ -"="+encodeURIComponent(k)};if(b===A)b=c.ajaxSettings.traditional;if(c.isArray(a)||a.jquery)c.each(a,function(){e(this.name,this.value)});else for(var f in a)ca(f,a[f],b,e);return d.join("&").replace(rb,"+")}});c.extend({active:0,lastModified:{},etag:{},handleError:function(a,b,d,e){a.error&&a.error.call(a.context,b,d,e);a.global&&c.triggerGlobal(a,"ajaxError",[b,a,e])},handleSuccess:function(a,b,d,e){a.success&&a.success.call(a.context,e,d,b);a.global&&c.triggerGlobal(a,"ajaxSuccess",[b,a])},handleComplete:function(a, -b,d){a.complete&&a.complete.call(a.context,b,d);a.global&&c.triggerGlobal(a,"ajaxComplete",[b,a]);a.global&&c.active--===1&&c.event.trigger("ajaxStop")},triggerGlobal:function(a,b,d){(a.context&&a.context.url==null?c(a.context):c.event).trigger(b,d)},httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===1223}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),e=a.getResponseHeader("Etag"); -if(d)c.lastModified[b]=d;if(e)c.etag[b]=e;return a.status===304},httpData:function(a,b,d){var e=a.getResponseHeader("content-type")||"",f=b==="xml"||!b&&e.indexOf("xml")>=0;a=f?a.responseXML:a.responseText;f&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b==="json"||!b&&e.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&e.indexOf("javascript")>=0)c.globalEval(a);return a}});if(E.ActiveXObject)c.ajaxSettings.xhr= -function(){if(E.location.protocol!=="file:")try{return new E.XMLHttpRequest}catch(a){}try{return new E.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}};c.support.ajax=!!c.ajaxSettings.xhr();var da={},tb=/^(?:toggle|show|hide)$/,ub=/^([+\-]=)?([\d+.\-]+)(.*)$/,aa,na=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b,d){if(a||a===0)return this.animate(S("show",3),a,b,d);else{a= -0;for(b=this.length;a=0;e--)if(d[e].elem===this){b&&d[e](true);d.splice(e,1)}});b||this.dequeue();return this}});c.each({slideDown:S("show",1),slideUp:S("hide",1),slideToggle:S("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,e,f){return this.animate(b, -d,e,f)}});c.extend({speed:function(a,b,d){var e=a&&typeof a==="object"?c.extend({},a):{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};e.duration=c.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in c.fx.speeds?c.fx.speeds[e.duration]:c.fx.speeds._default;e.old=e.complete;e.complete=function(){e.queue!==false&&c(this).dequeue();c.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,d,e){return d+e*a},swing:function(a,b,d,e){return(-Math.cos(a* -Math.PI)/2+0.5)*e+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||c.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a=parseFloat(c.css(this.elem,this.prop));return a&&a>-1E4?a:0},custom:function(a,b,d){function e(h){return f.step(h)} -this.startTime=c.now();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;this.pos=this.state=0;var f=this;a=c.fx;e.elem=this.elem;if(e()&&c.timers.push(e)&&!aa)aa=setInterval(a.tick,a.interval)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true; -this.custom(this.cur(),0)},step:function(a){var b=c.now(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var e in this.options.curAnim)if(this.options.curAnim[e]!==true)d=false;if(d){if(this.options.overflow!=null&&!c.support.shrinkWrapBlocks){var f=this.elem,h=this.options;c.each(["","X","Y"],function(l,n){f.style["overflow"+n]=h.overflow[l]})}this.options.hide&&c(this.elem).hide();if(this.options.hide|| -this.options.show)for(var k in this.options.curAnim)c.style(this.elem,k,this.options.orig[k]);this.options.complete.call(this.elem)}return false}else{a=b-this.startTime;this.state=a/this.options.duration;b=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||b](this.state,a,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a= -c.timers,b=0;b-1;e={};var s={};if(n)s=f.position();k=n?s.top:parseInt(k,10)||0;l=n?s.left:parseInt(l,10)||0;if(c.isFunction(b))b=b.call(a,d,h);if(b.top!=null)e.top=b.top-h.top+k;if(b.left!=null)e.left=b.left-h.left+l;"using"in b?b.using.call(a, -e):f.css(e)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),e=Fa.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.css(a,"marginTop"))||0;d.left-=parseFloat(c.css(a,"marginLeft"))||0;e.top+=parseFloat(c.css(b[0],"borderTopWidth"))||0;e.left+=parseFloat(c.css(b[0],"borderLeftWidth"))||0;return{top:d.top-e.top,left:d.left-e.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||u.body;a&&!Fa.test(a.nodeName)&& -c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(e){var f=this[0],h;if(!f)return null;if(e!==A)return this.each(function(){if(h=ea(this))h.scrollTo(!a?e:c(h).scrollLeft(),a?e:c(h).scrollTop());else this[d]=e});else return(h=ea(f))?"pageXOffset"in h?h[a?"pageYOffset":"pageXOffset"]:c.support.boxModel&&h.document.documentElement[d]||h.document.body[d]:f[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase(); -c.fn["inner"+b]=function(){return this[0]?parseFloat(c.css(this[0],d,"padding")):null};c.fn["outer"+b]=function(e){return this[0]?parseFloat(c.css(this[0],d,e?"margin":"border")):null};c.fn[d]=function(e){var f=this[0];if(!f)return e==null?null:this;if(c.isFunction(e))return this.each(function(h){var k=c(this);k[d](e.call(this,h,k[d]()))});return c.isWindow(f)?f.document.compatMode==="CSS1Compat"&&f.document.documentElement["client"+b]||f.document.body["client"+b]:f.nodeType===9?Math.max(f.documentElement["client"+ -b],f.body["scroll"+b],f.documentElement["scroll"+b],f.body["offset"+b],f.documentElement["offset"+b]):e===A?parseFloat(c.css(f,d)):this.css(d,typeof e==="string"?e:e+"px")}})})(window); diff --git a/htmlcov/jquery.hotkeys.js b/htmlcov/jquery.hotkeys.js deleted file mode 100644 index 09b21e03c..000000000 --- a/htmlcov/jquery.hotkeys.js +++ /dev/null @@ -1,99 +0,0 @@ -/* - * jQuery Hotkeys Plugin - * Copyright 2010, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * - * Based upon the plugin by Tzury Bar Yochay: - * http://github.com/tzuryby/hotkeys - * - * Original idea by: - * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ -*/ - -(function(jQuery){ - - jQuery.hotkeys = { - version: "0.8", - - specialKeys: { - 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", - 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", - 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", - 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", - 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", - 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", - 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta" - }, - - shiftNums: { - "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", - "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", - ".": ">", "/": "?", "\\": "|" - } - }; - - function keyHandler( handleObj ) { - // Only care when a possible input has been specified - if ( typeof handleObj.data !== "string" ) { - return; - } - - var origHandler = handleObj.handler, - keys = handleObj.data.toLowerCase().split(" "); - - handleObj.handler = function( event ) { - // Don't fire in text-accepting inputs that we didn't directly bind to - if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || - event.target.type === "text") ) { - return; - } - - // Keypress represents characters, not special keys - var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], - character = String.fromCharCode( event.which ).toLowerCase(), - key, modif = "", possible = {}; - - // check combinations (alt|ctrl|shift+anything) - if ( event.altKey && special !== "alt" ) { - modif += "alt+"; - } - - if ( event.ctrlKey && special !== "ctrl" ) { - modif += "ctrl+"; - } - - // TODO: Need to make sure this works consistently across platforms - if ( event.metaKey && !event.ctrlKey && special !== "meta" ) { - modif += "meta+"; - } - - if ( event.shiftKey && special !== "shift" ) { - modif += "shift+"; - } - - if ( special ) { - possible[ modif + special ] = true; - - } else { - possible[ modif + character ] = true; - possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true; - - // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" - if ( modif === "shift+" ) { - possible[ jQuery.hotkeys.shiftNums[ character ] ] = true; - } - } - - for ( var i = 0, l = keys.length; i < l; i++ ) { - if ( possible[ keys[i] ] ) { - return origHandler.apply( this, arguments ); - } - } - }; - } - - jQuery.each([ "keydown", "keyup", "keypress" ], function() { - jQuery.event.special[ this ] = { add: keyHandler }; - }); - -})( jQuery ); diff --git a/htmlcov/jquery.isonscreen.js b/htmlcov/jquery.isonscreen.js deleted file mode 100644 index 0182ebd21..000000000 --- a/htmlcov/jquery.isonscreen.js +++ /dev/null @@ -1,53 +0,0 @@ -/* Copyright (c) 2010 - * @author Laurence Wheway - * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) - * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. - * - * @version 1.2.0 - */ -(function($) { - jQuery.extend({ - isOnScreen: function(box, container) { - //ensure numbers come in as intgers (not strings) and remove 'px' is it's there - for(var i in box){box[i] = parseFloat(box[i])}; - for(var i in container){container[i] = parseFloat(container[i])}; - - if(!container){ - container = { - left: $(window).scrollLeft(), - top: $(window).scrollTop(), - width: $(window).width(), - height: $(window).height() - } - } - - if( box.left+box.width-container.left > 0 && - box.left < container.width+container.left && - box.top+box.height-container.top > 0 && - box.top < container.height+container.top - ) return true; - return false; - } - }) - - - jQuery.fn.isOnScreen = function (container) { - for(var i in container){container[i] = parseFloat(container[i])}; - - if(!container){ - container = { - left: $(window).scrollLeft(), - top: $(window).scrollTop(), - width: $(window).width(), - height: $(window).height() - } - } - - if( $(this).offset().left+$(this).width()-container.left > 0 && - $(this).offset().left < container.width+container.left && - $(this).offset().top+$(this).height()-container.top > 0 && - $(this).offset().top < container.height+container.top - ) return true; - return false; - } -})(jQuery); diff --git a/htmlcov/jquery.tablesorter.min.js b/htmlcov/jquery.tablesorter.min.js deleted file mode 100644 index 64c700712..000000000 --- a/htmlcov/jquery.tablesorter.min.js +++ /dev/null @@ -1,2 +0,0 @@ - -(function($){$.extend({tablesorter:new function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'.',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}var rows=table.tBodies[0].rows;if(table.tBodies[0].rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('
').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;ib)?1:0));};function sortTextDesc(a,b){return((ba)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){$this.trigger("sortStart");var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){var $cell=$(this);var i=this.column;this.order=this.count++%2;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;iD6{MWQjEnx?oJHr&dIz4a@dl*-CY>| zgW!U_%O?XxI14-?iy0WWg+Z8+Vb&Z8pdfpRr>`sfZ8lau9@bl*u7(4JIy_w*Lo808 zo$Afkpupp@{Fv_bobxQ#pD>iB3oNa1d9=pM`D99*FvsH{pKJfpB1-4UD;=6}F=+gKX>Gx9b=!>PY1_pdfo@{(boFyt=akR{ E04sl8JOBUy diff --git a/htmlcov/keybd_open.png b/htmlcov/keybd_open.png deleted file mode 100644 index a77961db5424cfff43a63d399972ee85fc0dfdb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 267 zcmeAS@N?(olHy`uVBq!ia0vp^%0SG+!3HE>D6{MWQjEnx?oJHr&dIz4a@dl*-CY>| zgW!U_%O?XxI14-?iy0WWg+Z8+Vb&Z8pdfpRr>`sfZ8lau9%kc-1xY}mZci7-5R21$ zCp+>TR^VYdE*ieC^FGV{Cyeh_21=Rotz3KNq=!VmdK II;Vst00jnQH~;_u diff --git a/htmlcov/rest_framework___init__.html b/htmlcov/rest_framework___init__.html deleted file mode 100644 index 9cb0c53ac..000000000 --- a/htmlcov/rest_framework___init__.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - Coverage for rest_framework/__init__: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
-
- - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

- -
-

__version__ = '2.3.5' 

-

 

-

VERSION = __version__  # synonym 

-

 

-

# Header encoding (see RFC5987) 

-

HTTP_HEADER_ENCODING = 'iso-8859-1' 

-

 

-

# Default datetime input and output formats 

-

ISO_8601 = 'iso-8601' 

- -
- - - - - - diff --git a/htmlcov/rest_framework_authentication.html b/htmlcov/rest_framework_authentication.html deleted file mode 100644 index 899d06777..000000000 --- a/htmlcov/rest_framework_authentication.html +++ /dev/null @@ -1,767 +0,0 @@ - - - - - - - - Coverage for rest_framework/authentication: 80% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

-

296

-

297

-

298

-

299

-

300

-

301

-

302

-

303

-

304

-

305

-

306

-

307

-

308

-

309

-

310

-

311

-

312

-

313

-

314

-

315

-

316

-

317

-

318

-

319

-

320

-

321

-

322

-

323

-

324

-

325

-

326

-

327

-

328

-

329

-

330

-

331

-

332

-

333

-

334

-

335

-

336

-

337

-

338

-

339

-

340

-

341

-

342

-

343

- -
-

""" 

-

Provides various authentication policies. 

-

""" 

-

from __future__ import unicode_literals 

-

import base64 

-

from datetime import datetime 

-

 

-

from django.contrib.auth import authenticate 

-

from django.core.exceptions import ImproperlyConfigured 

-

from rest_framework import exceptions, HTTP_HEADER_ENCODING 

-

from rest_framework.compat import CsrfViewMiddleware 

-

from rest_framework.compat import oauth, oauth_provider, oauth_provider_store 

-

from rest_framework.compat import oauth2_provider 

-

from rest_framework.authtoken.models import Token 

-

 

-

 

-

def get_authorization_header(request): 

-

    """ 

-

    Return request's 'Authorization:' header, as a bytestring. 

-

 

-

    Hide some test client ickyness where the header can be unicode. 

-

    """ 

-

    auth = request.META.get('HTTP_AUTHORIZATION', b'') 

-

    if type(auth) == type(''): 

-

        # Work around django test client oddness 

-

        auth = auth.encode(HTTP_HEADER_ENCODING) 

-

    return auth 

-

 

-

 

-

class BaseAuthentication(object): 

-

    """ 

-

    All authentication classes should extend BaseAuthentication. 

-

    """ 

-

 

-

    def authenticate(self, request): 

-

        """ 

-

        Authenticate the request and return a two-tuple of (user, token). 

-

        """ 

-

        raise NotImplementedError(".authenticate() must be overridden.") 

-

 

-

    def authenticate_header(self, request): 

-

        """ 

-

        Return a string to be used as the value of the `WWW-Authenticate` 

-

        header in a `401 Unauthenticated` response, or `None` if the 

-

        authentication scheme should return `403 Permission Denied` responses. 

-

        """ 

-

        pass 

-

 

-

 

-

class BasicAuthentication(BaseAuthentication): 

-

    """ 

-

    HTTP Basic authentication against username/password. 

-

    """ 

-

    www_authenticate_realm = 'api' 

-

 

-

    def authenticate(self, request): 

-

        """ 

-

        Returns a `User` if a correct username and password have been supplied 

-

        using HTTP Basic authentication.  Otherwise returns `None`. 

-

        """ 

-

        auth = get_authorization_header(request).split() 

-

 

-

        if not auth or auth[0].lower() != b'basic': 

-

            return None 

-

 

-

        if len(auth) == 1: 

-

            msg = 'Invalid basic header. No credentials provided.' 

-

            raise exceptions.AuthenticationFailed(msg) 

-

        elif len(auth) > 2: 

-

            msg = 'Invalid basic header. Credentials string should not contain spaces.' 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        try: 

-

            auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':') 

-

        except (TypeError, UnicodeDecodeError): 

-

            msg = 'Invalid basic header. Credentials not correctly base64 encoded' 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        userid, password = auth_parts[0], auth_parts[2] 

-

        return self.authenticate_credentials(userid, password) 

-

 

-

    def authenticate_credentials(self, userid, password): 

-

        """ 

-

        Authenticate the userid and password against username and password. 

-

        """ 

-

        user = authenticate(username=userid, password=password) 

-

        if user is None or not user.is_active: 

-

            raise exceptions.AuthenticationFailed('Invalid username/password') 

-

        return (user, None) 

-

 

-

    def authenticate_header(self, request): 

-

        return 'Basic realm="%s"' % self.www_authenticate_realm 

-

 

-

 

-

class SessionAuthentication(BaseAuthentication): 

-

    """ 

-

    Use Django's session framework for authentication. 

-

    """ 

-

 

-

    def authenticate(self, request): 

-

        """ 

-

        Returns a `User` if the request session currently has a logged in user. 

-

        Otherwise returns `None`. 

-

        """ 

-

 

-

        # Get the underlying HttpRequest object 

-

        http_request = request._request 

-

        user = getattr(http_request, 'user', None) 

-

 

-

        # Unauthenticated, CSRF validation not required 

-

        if not user or not user.is_active: 

-

            return None 

-

 

-

        # Enforce CSRF validation for session based authentication. 

-

        class CSRFCheck(CsrfViewMiddleware): 

-

            def _reject(self, request, reason): 

-

                # Return the failure reason instead of an HttpResponse 

-

                return reason 

-

 

-

        reason = CSRFCheck().process_view(http_request, None, (), {}) 

-

        if reason: 

-

            # CSRF failed, bail with explicit error message 

-

            raise exceptions.AuthenticationFailed('CSRF Failed: %s' % reason) 

-

 

-

        # CSRF passed with authenticated user 

-

        return (user, None) 

-

 

-

 

-

class TokenAuthentication(BaseAuthentication): 

-

    """ 

-

    Simple token based authentication. 

-

 

-

    Clients should authenticate by passing the token key in the "Authorization" 

-

    HTTP header, prepended with the string "Token ".  For example: 

-

 

-

        Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a 

-

    """ 

-

 

-

    model = Token 

-

    """ 

-

    A custom token model may be used, but must have the following properties. 

-

 

-

    * key -- The string identifying the token 

-

    * user -- The user to which the token belongs 

-

    """ 

-

 

-

    def authenticate(self, request): 

-

        auth = get_authorization_header(request).split() 

-

 

-

        if not auth or auth[0].lower() != b'token': 

-

            return None 

-

 

-

        if len(auth) == 1: 

-

            msg = 'Invalid token header. No credentials provided.' 

-

            raise exceptions.AuthenticationFailed(msg) 

-

        elif len(auth) > 2: 

-

            msg = 'Invalid token header. Token string should not contain spaces.' 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        return self.authenticate_credentials(auth[1]) 

-

 

-

    def authenticate_credentials(self, key): 

-

        try: 

-

            token = self.model.objects.get(key=key) 

-

        except self.model.DoesNotExist: 

-

            raise exceptions.AuthenticationFailed('Invalid token') 

-

 

-

        if not token.user.is_active: 

-

            raise exceptions.AuthenticationFailed('User inactive or deleted') 

-

 

-

        return (token.user, token) 

-

 

-

    def authenticate_header(self, request): 

-

        return 'Token' 

-

 

-

 

-

class OAuthAuthentication(BaseAuthentication): 

-

    """ 

-

    OAuth 1.0a authentication backend using `django-oauth-plus` and `oauth2`. 

-

 

-

    Note: The `oauth2` package actually provides oauth1.0a support.  Urg. 

-

          We import it from the `compat` module as `oauth`. 

-

    """ 

-

    www_authenticate_realm = 'api' 

-

 

-

    def __init__(self, *args, **kwargs): 

-

        super(OAuthAuthentication, self).__init__(*args, **kwargs) 

-

 

-

        if oauth is None: 

-

            raise ImproperlyConfigured( 

-

                "The 'oauth2' package could not be imported." 

-

                "It is required for use with the 'OAuthAuthentication' class.") 

-

 

-

        if oauth_provider is None: 

-

            raise ImproperlyConfigured( 

-

                "The 'django-oauth-plus' package could not be imported." 

-

                "It is required for use with the 'OAuthAuthentication' class.") 

-

 

-

    def authenticate(self, request): 

-

        """ 

-

        Returns two-tuple of (user, token) if authentication succeeds, 

-

        or None otherwise. 

-

        """ 

-

        try: 

-

            oauth_request = oauth_provider.utils.get_oauth_request(request) 

-

        except oauth.Error as err: 

-

            raise exceptions.AuthenticationFailed(err.message) 

-

 

-

        if not oauth_request: 

-

            return None 

-

 

-

        oauth_params = oauth_provider.consts.OAUTH_PARAMETERS_NAMES 

-

 

-

        found = any(param for param in oauth_params if param in oauth_request) 

-

        missing = list(param for param in oauth_params if param not in oauth_request) 

-

 

-

        if not found: 

-

            # OAuth authentication was not attempted. 

-

            return None 

-

 

-

        if missing: 

-

            # OAuth was attempted but missing parameters. 

-

            msg = 'Missing parameters: %s' % (', '.join(missing)) 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        if not self.check_nonce(request, oauth_request): 

-

            msg = 'Nonce check failed' 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        try: 

-

            consumer_key = oauth_request.get_parameter('oauth_consumer_key') 

-

            consumer = oauth_provider_store.get_consumer(request, oauth_request, consumer_key) 

-

        except oauth_provider.store.InvalidConsumerError: 

-

            msg = 'Invalid consumer token: %s' % oauth_request.get_parameter('oauth_consumer_key') 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        if consumer.status != oauth_provider.consts.ACCEPTED: 

-

            msg = 'Invalid consumer key status: %s' % consumer.get_status_display() 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        try: 

-

            token_param = oauth_request.get_parameter('oauth_token') 

-

            token = oauth_provider_store.get_access_token(request, oauth_request, consumer, token_param) 

-

        except oauth_provider.store.InvalidTokenError: 

-

            msg = 'Invalid access token: %s' % oauth_request.get_parameter('oauth_token') 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        try: 

-

            self.validate_token(request, consumer, token) 

-

        except oauth.Error as err: 

-

            raise exceptions.AuthenticationFailed(err.message) 

-

 

-

        user = token.user 

-

 

-

        if not user.is_active: 

-

            msg = 'User inactive or deleted: %s' % user.username 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        return (token.user, token) 

-

 

-

    def authenticate_header(self, request): 

-

        """ 

-

        If permission is denied, return a '401 Unauthorized' response, 

-

        with an appropraite 'WWW-Authenticate' header. 

-

        """ 

-

        return 'OAuth realm="%s"' % self.www_authenticate_realm 

-

 

-

    def validate_token(self, request, consumer, token): 

-

        """ 

-

        Check the token and raise an `oauth.Error` exception if invalid. 

-

        """ 

-

        oauth_server, oauth_request = oauth_provider.utils.initialize_server_request(request) 

-

        oauth_server.verify_request(oauth_request, consumer, token) 

-

 

-

    def check_nonce(self, request, oauth_request): 

-

        """ 

-

        Checks nonce of request, and return True if valid. 

-

        """ 

-

        return oauth_provider_store.check_nonce(request, oauth_request, oauth_request['oauth_nonce']) 

-

 

-

 

-

class OAuth2Authentication(BaseAuthentication): 

-

    """ 

-

    OAuth 2 authentication backend using `django-oauth2-provider` 

-

    """ 

-

    www_authenticate_realm = 'api' 

-

 

-

    def __init__(self, *args, **kwargs): 

-

        super(OAuth2Authentication, self).__init__(*args, **kwargs) 

-

 

-

        if oauth2_provider is None: 

-

            raise ImproperlyConfigured( 

-

                "The 'django-oauth2-provider' package could not be imported. " 

-

                "It is required for use with the 'OAuth2Authentication' class.") 

-

 

-

    def authenticate(self, request): 

-

        """ 

-

        Returns two-tuple of (user, token) if authentication succeeds, 

-

        or None otherwise. 

-

        """ 

-

 

-

        auth = get_authorization_header(request).split() 

-

 

-

        if not auth or auth[0].lower() != b'bearer': 

-

            return None 

-

 

-

        if len(auth) == 1: 

-

            msg = 'Invalid bearer header. No credentials provided.' 

-

            raise exceptions.AuthenticationFailed(msg) 

-

        elif len(auth) > 2: 

-

            msg = 'Invalid bearer header. Token string should not contain spaces.' 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        return self.authenticate_credentials(request, auth[1]) 

-

 

-

    def authenticate_credentials(self, request, access_token): 

-

        """ 

-

        Authenticate the request, given the access token. 

-

        """ 

-

 

-

        try: 

-

            token = oauth2_provider.models.AccessToken.objects.select_related('user') 

-

            # TODO: Change to timezone aware datetime when oauth2_provider add 

-

            # support to it. 

-

            token = token.get(token=access_token, expires__gt=datetime.now()) 

-

        except oauth2_provider.models.AccessToken.DoesNotExist: 

-

            raise exceptions.AuthenticationFailed('Invalid token') 

-

 

-

        user = token.user 

-

 

-

        if not user.is_active: 

-

            msg = 'User inactive or deleted: %s' % user.username 

-

            raise exceptions.AuthenticationFailed(msg) 

-

 

-

        return (user, token) 

-

 

-

    def authenticate_header(self, request): 

-

        """ 

-

        Bearer is the only finalized type currently 

-

 

-

        Check details on the `OAuth2Authentication.authenticate` method 

-

        """ 

-

        return 'Bearer realm="%s"' % self.www_authenticate_realm 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_authtoken___init__.html b/htmlcov/rest_framework_authtoken___init__.html deleted file mode 100644 index f72574937..000000000 --- a/htmlcov/rest_framework_authtoken___init__.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - Coverage for rest_framework/authtoken/__init__: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
- - - -
-
- - - - - diff --git a/htmlcov/rest_framework_authtoken_models.html b/htmlcov/rest_framework_authtoken_models.html deleted file mode 100644 index 27d2fff1d..000000000 --- a/htmlcov/rest_framework_authtoken_models.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - Coverage for rest_framework/authtoken/models: 95% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

- -
-

import uuid 

-

import hmac 

-

from hashlib import sha1 

-

from rest_framework.compat import User 

-

from django.conf import settings 

-

from django.db import models 

-

 

-

 

-

class Token(models.Model): 

-

    """ 

-

    The default authorization token model. 

-

    """ 

-

    key = models.CharField(max_length=40, primary_key=True) 

-

    user = models.OneToOneField(User, related_name='auth_token') 

-

    created = models.DateTimeField(auto_now_add=True) 

-

 

-

    class Meta: 

-

        # Work around for a bug in Django: 

-

        # https://code.djangoproject.com/ticket/19422 

-

        # 

-

        # Also see corresponding ticket: 

-

        # https://github.com/tomchristie/django-rest-framework/issues/705 

-

        abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS 

-

 

-

    def save(self, *args, **kwargs): 

-

        if not self.key: 

-

            self.key = self.generate_key() 

-

        return super(Token, self).save(*args, **kwargs) 

-

 

-

    def generate_key(self): 

-

        unique = uuid.uuid4() 

-

        return hmac.new(unique.bytes, digestmod=sha1).hexdigest() 

-

 

-

    def __unicode__(self): 

-

        return self.key 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_authtoken_serializers.html b/htmlcov/rest_framework_authtoken_serializers.html deleted file mode 100644 index 8997d9a7b..000000000 --- a/htmlcov/rest_framework_authtoken_serializers.html +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - Coverage for rest_framework/authtoken/serializers: 88% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

- -
-

from django.contrib.auth import authenticate 

-

from rest_framework import serializers 

-

 

-

 

-

class AuthTokenSerializer(serializers.Serializer): 

-

    username = serializers.CharField() 

-

    password = serializers.CharField() 

-

 

-

    def validate(self, attrs): 

-

        username = attrs.get('username') 

-

        password = attrs.get('password') 

-

 

-

        if username and password: 

-

            user = authenticate(username=username, password=password) 

-

 

-

            if user: 

-

                if not user.is_active: 

-

                    raise serializers.ValidationError('User account is disabled.') 

-

                attrs['user'] = user 

-

                return attrs 

-

            else: 

-

                raise serializers.ValidationError('Unable to login with provided credentials.') 

-

        else: 

-

            raise serializers.ValidationError('Must include "username" and "password"') 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_authtoken_views.html b/htmlcov/rest_framework_authtoken_views.html deleted file mode 100644 index d13746ea8..000000000 --- a/htmlcov/rest_framework_authtoken_views.html +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - - - Coverage for rest_framework/authtoken/views: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

- -
-

from rest_framework.views import APIView 

-

from rest_framework import status 

-

from rest_framework import parsers 

-

from rest_framework import renderers 

-

from rest_framework.response import Response 

-

from rest_framework.authtoken.models import Token 

-

from rest_framework.authtoken.serializers import AuthTokenSerializer 

-

 

-

 

-

class ObtainAuthToken(APIView): 

-

    throttle_classes = () 

-

    permission_classes = () 

-

    parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,) 

-

    renderer_classes = (renderers.JSONRenderer,) 

-

    serializer_class = AuthTokenSerializer 

-

    model = Token 

-

 

-

    def post(self, request): 

-

        serializer = self.serializer_class(data=request.DATA) 

-

        if serializer.is_valid(): 

-

            token, created = Token.objects.get_or_create(user=serializer.object['user']) 

-

            return Response({'token': token.key}) 

-

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 

-

 

-

 

-

obtain_auth_token = ObtainAuthToken.as_view() 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_decorators.html b/htmlcov/rest_framework_decorators.html deleted file mode 100644 index 6ad6f6b51..000000000 --- a/htmlcov/rest_framework_decorators.html +++ /dev/null @@ -1,339 +0,0 @@ - - - - - - - - Coverage for rest_framework/decorators: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

- -
-

""" 

-

The most important decorator in this module is `@api_view`, which is used 

-

for writing function-based views with REST framework. 

-

 

-

There are also various decorators for setting the API policies on function 

-

based views, as well as the `@action` and `@link` decorators, which are 

-

used to annotate methods on viewsets that should be included by routers. 

-

""" 

-

from __future__ import unicode_literals 

-

from rest_framework.compat import six 

-

from rest_framework.views import APIView 

-

import types 

-

 

-

 

-

def api_view(http_method_names): 

-

 

-

    """ 

-

    Decorator that converts a function-based view into an APIView subclass. 

-

    Takes a list of allowed methods for the view as an argument. 

-

    """ 

-

 

-

    def decorator(func): 

-

 

-

        WrappedAPIView = type( 

-

            six.PY3 and 'WrappedAPIView' or b'WrappedAPIView', 

-

            (APIView,), 

-

            {'__doc__': func.__doc__} 

-

        ) 

-

 

-

        # Note, the above allows us to set the docstring. 

-

        # It is the equivalent of: 

-

        # 

-

        #     class WrappedAPIView(APIView): 

-

        #         pass 

-

        #     WrappedAPIView.__doc__ = func.doc    <--- Not possible to do this 

-

 

-

        # api_view applied without (method_names) 

-

        assert not(isinstance(http_method_names, types.FunctionType)), \ 

-

            '@api_view missing list of allowed HTTP methods' 

-

 

-

        # api_view applied with eg. string instead of list of strings 

-

        assert isinstance(http_method_names, (list, tuple)), \ 

-

            '@api_view expected a list of strings, received %s' % type(http_method_names).__name__ 

-

 

-

        allowed_methods = set(http_method_names) | set(('options',)) 

-

        WrappedAPIView.http_method_names = [method.lower() for method in allowed_methods] 

-

 

-

        def handler(self, *args, **kwargs): 

-

            return func(*args, **kwargs) 

-

 

-

        for method in http_method_names: 

-

            setattr(WrappedAPIView, method.lower(), handler) 

-

 

-

        WrappedAPIView.__name__ = func.__name__ 

-

 

-

        WrappedAPIView.renderer_classes = getattr(func, 'renderer_classes', 

-

                                                  APIView.renderer_classes) 

-

 

-

        WrappedAPIView.parser_classes = getattr(func, 'parser_classes', 

-

                                                APIView.parser_classes) 

-

 

-

        WrappedAPIView.authentication_classes = getattr(func, 'authentication_classes', 

-

                                                        APIView.authentication_classes) 

-

 

-

        WrappedAPIView.throttle_classes = getattr(func, 'throttle_classes', 

-

                                                  APIView.throttle_classes) 

-

 

-

        WrappedAPIView.permission_classes = getattr(func, 'permission_classes', 

-

                                                    APIView.permission_classes) 

-

 

-

        return WrappedAPIView.as_view() 

-

    return decorator 

-

 

-

 

-

def renderer_classes(renderer_classes): 

-

    def decorator(func): 

-

        func.renderer_classes = renderer_classes 

-

        return func 

-

    return decorator 

-

 

-

 

-

def parser_classes(parser_classes): 

-

    def decorator(func): 

-

        func.parser_classes = parser_classes 

-

        return func 

-

    return decorator 

-

 

-

 

-

def authentication_classes(authentication_classes): 

-

    def decorator(func): 

-

        func.authentication_classes = authentication_classes 

-

        return func 

-

    return decorator 

-

 

-

 

-

def throttle_classes(throttle_classes): 

-

    def decorator(func): 

-

        func.throttle_classes = throttle_classes 

-

        return func 

-

    return decorator 

-

 

-

 

-

def permission_classes(permission_classes): 

-

    def decorator(func): 

-

        func.permission_classes = permission_classes 

-

        return func 

-

    return decorator 

-

 

-

 

-

def link(**kwargs): 

-

    """ 

-

    Used to mark a method on a ViewSet that should be routed for GET requests. 

-

    """ 

-

    def decorator(func): 

-

        func.bind_to_methods = ['get'] 

-

        func.kwargs = kwargs 

-

        return func 

-

    return decorator 

-

 

-

 

-

def action(methods=['post'], **kwargs): 

-

    """ 

-

    Used to mark a method on a ViewSet that should be routed for POST requests. 

-

    """ 

-

    def decorator(func): 

-

        func.bind_to_methods = methods 

-

        func.kwargs = kwargs 

-

        return func 

-

    return decorator 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_exceptions.html b/htmlcov/rest_framework_exceptions.html deleted file mode 100644 index d975a8481..000000000 --- a/htmlcov/rest_framework_exceptions.html +++ /dev/null @@ -1,257 +0,0 @@ - - - - - - - - Coverage for rest_framework/exceptions: 96% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

- -
-

""" 

-

Handled exceptions raised by REST framework. 

-

 

-

In addition Django's built in 403 and 404 exceptions are handled. 

-

(`django.http.Http404` and `django.core.exceptions.PermissionDenied`) 

-

""" 

-

from __future__ import unicode_literals 

-

from rest_framework import status 

-

 

-

 

-

class APIException(Exception): 

-

    """ 

-

    Base class for REST framework exceptions. 

-

    Subclasses should provide `.status_code` and `.detail` properties. 

-

    """ 

-

    pass 

-

 

-

 

-

class ParseError(APIException): 

-

    status_code = status.HTTP_400_BAD_REQUEST 

-

    default_detail = 'Malformed request.' 

-

 

-

    def __init__(self, detail=None): 

-

        self.detail = detail or self.default_detail 

-

 

-

 

-

class AuthenticationFailed(APIException): 

-

    status_code = status.HTTP_401_UNAUTHORIZED 

-

    default_detail = 'Incorrect authentication credentials.' 

-

 

-

    def __init__(self, detail=None): 

-

        self.detail = detail or self.default_detail 

-

 

-

 

-

class NotAuthenticated(APIException): 

-

    status_code = status.HTTP_401_UNAUTHORIZED 

-

    default_detail = 'Authentication credentials were not provided.' 

-

 

-

    def __init__(self, detail=None): 

-

        self.detail = detail or self.default_detail 

-

 

-

 

-

class PermissionDenied(APIException): 

-

    status_code = status.HTTP_403_FORBIDDEN 

-

    default_detail = 'You do not have permission to perform this action.' 

-

 

-

    def __init__(self, detail=None): 

-

        self.detail = detail or self.default_detail 

-

 

-

 

-

class MethodNotAllowed(APIException): 

-

    status_code = status.HTTP_405_METHOD_NOT_ALLOWED 

-

    default_detail = "Method '%s' not allowed." 

-

 

-

    def __init__(self, method, detail=None): 

-

        self.detail = (detail or self.default_detail) % method 

-

 

-

 

-

class NotAcceptable(APIException): 

-

    status_code = status.HTTP_406_NOT_ACCEPTABLE 

-

    default_detail = "Could not satisfy the request's Accept header" 

-

 

-

    def __init__(self, detail=None, available_renderers=None): 

-

        self.detail = detail or self.default_detail 

-

        self.available_renderers = available_renderers 

-

 

-

 

-

class UnsupportedMediaType(APIException): 

-

    status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE 

-

    default_detail = "Unsupported media type '%s' in request." 

-

 

-

    def __init__(self, media_type, detail=None): 

-

        self.detail = (detail or self.default_detail) % media_type 

-

 

-

 

-

class Throttled(APIException): 

-

    status_code = status.HTTP_429_TOO_MANY_REQUESTS 

-

    default_detail = "Request was throttled." 

-

    extra_detail = "Expected available in %d second%s." 

-

 

-

    def __init__(self, wait=None, detail=None): 

-

        import math 

-

        self.wait = wait and math.ceil(wait) or None 

-

        if wait is not None: 

-

            format = detail or self.default_detail + self.extra_detail 

-

            self.detail = format % (self.wait, self.wait != 1 and 's' or '') 

-

        else: 

-

            self.detail = detail or self.default_detail 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_fields.html b/htmlcov/rest_framework_fields.html deleted file mode 100644 index cf2731d25..000000000 --- a/htmlcov/rest_framework_fields.html +++ /dev/null @@ -1,1991 +0,0 @@ - - - - - - - - Coverage for rest_framework/fields: 87% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

-

296

-

297

-

298

-

299

-

300

-

301

-

302

-

303

-

304

-

305

-

306

-

307

-

308

-

309

-

310

-

311

-

312

-

313

-

314

-

315

-

316

-

317

-

318

-

319

-

320

-

321

-

322

-

323

-

324

-

325

-

326

-

327

-

328

-

329

-

330

-

331

-

332

-

333

-

334

-

335

-

336

-

337

-

338

-

339

-

340

-

341

-

342

-

343

-

344

-

345

-

346

-

347

-

348

-

349

-

350

-

351

-

352

-

353

-

354

-

355

-

356

-

357

-

358

-

359

-

360

-

361

-

362

-

363

-

364

-

365

-

366

-

367

-

368

-

369

-

370

-

371

-

372

-

373

-

374

-

375

-

376

-

377

-

378

-

379

-

380

-

381

-

382

-

383

-

384

-

385

-

386

-

387

-

388

-

389

-

390

-

391

-

392

-

393

-

394

-

395

-

396

-

397

-

398

-

399

-

400

-

401

-

402

-

403

-

404

-

405

-

406

-

407

-

408

-

409

-

410

-

411

-

412

-

413

-

414

-

415

-

416

-

417

-

418

-

419

-

420

-

421

-

422

-

423

-

424

-

425

-

426

-

427

-

428

-

429

-

430

-

431

-

432

-

433

-

434

-

435

-

436

-

437

-

438

-

439

-

440

-

441

-

442

-

443

-

444

-

445

-

446

-

447

-

448

-

449

-

450

-

451

-

452

-

453

-

454

-

455

-

456

-

457

-

458

-

459

-

460

-

461

-

462

-

463

-

464

-

465

-

466

-

467

-

468

-

469

-

470

-

471

-

472

-

473

-

474

-

475

-

476

-

477

-

478

-

479

-

480

-

481

-

482

-

483

-

484

-

485

-

486

-

487

-

488

-

489

-

490

-

491

-

492

-

493

-

494

-

495

-

496

-

497

-

498

-

499

-

500

-

501

-

502

-

503

-

504

-

505

-

506

-

507

-

508

-

509

-

510

-

511

-

512

-

513

-

514

-

515

-

516

-

517

-

518

-

519

-

520

-

521

-

522

-

523

-

524

-

525

-

526

-

527

-

528

-

529

-

530

-

531

-

532

-

533

-

534

-

535

-

536

-

537

-

538

-

539

-

540

-

541

-

542

-

543

-

544

-

545

-

546

-

547

-

548

-

549

-

550

-

551

-

552

-

553

-

554

-

555

-

556

-

557

-

558

-

559

-

560

-

561

-

562

-

563

-

564

-

565

-

566

-

567

-

568

-

569

-

570

-

571

-

572

-

573

-

574

-

575

-

576

-

577

-

578

-

579

-

580

-

581

-

582

-

583

-

584

-

585

-

586

-

587

-

588

-

589

-

590

-

591

-

592

-

593

-

594

-

595

-

596

-

597

-

598

-

599

-

600

-

601

-

602

-

603

-

604

-

605

-

606

-

607

-

608

-

609

-

610

-

611

-

612

-

613

-

614

-

615

-

616

-

617

-

618

-

619

-

620

-

621

-

622

-

623

-

624

-

625

-

626

-

627

-

628

-

629

-

630

-

631

-

632

-

633

-

634

-

635

-

636

-

637

-

638

-

639

-

640

-

641

-

642

-

643

-

644

-

645

-

646

-

647

-

648

-

649

-

650

-

651

-

652

-

653

-

654

-

655

-

656

-

657

-

658

-

659

-

660

-

661

-

662

-

663

-

664

-

665

-

666

-

667

-

668

-

669

-

670

-

671

-

672

-

673

-

674

-

675

-

676

-

677

-

678

-

679

-

680

-

681

-

682

-

683

-

684

-

685

-

686

-

687

-

688

-

689

-

690

-

691

-

692

-

693

-

694

-

695

-

696

-

697

-

698

-

699

-

700

-

701

-

702

-

703

-

704

-

705

-

706

-

707

-

708

-

709

-

710

-

711

-

712

-

713

-

714

-

715

-

716

-

717

-

718

-

719

-

720

-

721

-

722

-

723

-

724

-

725

-

726

-

727

-

728

-

729

-

730

-

731

-

732

-

733

-

734

-

735

-

736

-

737

-

738

-

739

-

740

-

741

-

742

-

743

-

744

-

745

-

746

-

747

-

748

-

749

-

750

-

751

-

752

-

753

-

754

-

755

-

756

-

757

-

758

-

759

-

760

-

761

-

762

-

763

-

764

-

765

-

766

-

767

-

768

-

769

-

770

-

771

-

772

-

773

-

774

-

775

-

776

-

777

-

778

-

779

-

780

-

781

-

782

-

783

-

784

-

785

-

786

-

787

-

788

-

789

-

790

-

791

-

792

-

793

-

794

-

795

-

796

-

797

-

798

-

799

-

800

-

801

-

802

-

803

-

804

-

805

-

806

-

807

-

808

-

809

-

810

-

811

-

812

-

813

-

814

-

815

-

816

-

817

-

818

-

819

-

820

-

821

-

822

-

823

-

824

-

825

-

826

-

827

-

828

-

829

-

830

-

831

-

832

-

833

-

834

-

835

-

836

-

837

-

838

-

839

-

840

-

841

-

842

-

843

-

844

-

845

-

846

-

847

-

848

-

849

-

850

-

851

-

852

-

853

-

854

-

855

-

856

-

857

-

858

-

859

-

860

-

861

-

862

-

863

-

864

-

865

-

866

-

867

-

868

-

869

-

870

-

871

-

872

-

873

-

874

-

875

-

876

-

877

-

878

-

879

-

880

-

881

-

882

-

883

-

884

-

885

-

886

-

887

-

888

-

889

-

890

-

891

-

892

-

893

-

894

-

895

-

896

-

897

-

898

-

899

-

900

-

901

-

902

-

903

-

904

-

905

-

906

-

907

-

908

-

909

-

910

-

911

-

912

-

913

-

914

-

915

-

916

-

917

-

918

-

919

-

920

-

921

-

922

-

923

-

924

-

925

-

926

-

927

-

928

-

929

-

930

-

931

-

932

-

933

-

934

-

935

-

936

-

937

-

938

-

939

-

940

-

941

-

942

-

943

-

944

-

945

-

946

-

947

-

948

-

949

-

950

-

951

-

952

-

953

-

954

-

955

- -
-

""" 

-

Serializer fields perform validation on incoming data. 

-

 

-

They are very similar to Django's form fields. 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

import copy 

-

import datetime 

-

import inspect 

-

import re 

-

import warnings 

-

from decimal import Decimal, DecimalException 

-

from django import forms 

-

from django.core import validators 

-

from django.core.exceptions import ValidationError 

-

from django.conf import settings 

-

from django.db.models.fields import BLANK_CHOICE_DASH 

-

from django.forms import widgets 

-

from django.utils.encoding import is_protected_type 

-

from django.utils.translation import ugettext_lazy as _ 

-

from django.utils.datastructures import SortedDict 

-

from rest_framework import ISO_8601 

-

from rest_framework.compat import ( 

-

    timezone, parse_date, parse_datetime, parse_time, BytesIO, six, smart_text, 

-

    force_text, is_non_str_iterable 

-

) 

-

from rest_framework.settings import api_settings 

-

 

-

 

-

def is_simple_callable(obj): 

-

    """ 

-

    True if the object is a callable that takes no arguments. 

-

    """ 

-

    function = inspect.isfunction(obj) 

-

    method = inspect.ismethod(obj) 

-

 

-

    if not (function or method): 

-

        return False 

-

 

-

    args, _, _, defaults = inspect.getargspec(obj) 

-

    len_args = len(args) if function else len(args) - 1 

-

    len_defaults = len(defaults) if defaults else 0 

-

    return len_args <= len_defaults 

-

 

-

 

-

def get_component(obj, attr_name): 

-

    """ 

-

    Given an object, and an attribute name, 

-

    return that attribute on the object. 

-

    """ 

-

    if isinstance(obj, dict): 

-

        val = obj.get(attr_name) 

-

    else: 

-

        val = getattr(obj, attr_name) 

-

 

-

    if is_simple_callable(val): 

-

        return val() 

-

    return val 

-

 

-

 

-

def readable_datetime_formats(formats): 

-

    format = ', '.join(formats).replace(ISO_8601, 

-

             'YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HHMM|-HHMM|Z]') 

-

    return humanize_strptime(format) 

-

 

-

 

-

def readable_date_formats(formats): 

-

    format = ', '.join(formats).replace(ISO_8601, 'YYYY[-MM[-DD]]') 

-

    return humanize_strptime(format) 

-

 

-

 

-

def readable_time_formats(formats): 

-

    format = ', '.join(formats).replace(ISO_8601, 'hh:mm[:ss[.uuuuuu]]') 

-

    return humanize_strptime(format) 

-

 

-

 

-

def humanize_strptime(format_string): 

-

    # Note that we're missing some of the locale specific mappings that 

-

    # don't really make sense. 

-

    mapping = { 

-

        "%Y": "YYYY", 

-

        "%y": "YY", 

-

        "%m": "MM", 

-

        "%b": "[Jan-Dec]", 

-

        "%B": "[January-December]", 

-

        "%d": "DD", 

-

        "%H": "hh", 

-

        "%I": "hh",  # Requires '%p' to differentiate from '%H'. 

-

        "%M": "mm", 

-

        "%S": "ss", 

-

        "%f": "uuuuuu", 

-

        "%a": "[Mon-Sun]", 

-

        "%A": "[Monday-Sunday]", 

-

        "%p": "[AM|PM]", 

-

        "%z": "[+HHMM|-HHMM]" 

-

    } 

-

    for key, val in mapping.items(): 

-

        format_string = format_string.replace(key, val) 

-

    return format_string 

-

 

-

 

-

class Field(object): 

-

    read_only = True 

-

    creation_counter = 0 

-

    empty = '' 

-

    type_name = None 

-

    partial = False 

-

    use_files = False 

-

    form_field_class = forms.CharField 

-

    type_label = 'field' 

-

 

-

    def __init__(self, source=None, label=None, help_text=None): 

-

        self.parent = None 

-

 

-

        self.creation_counter = Field.creation_counter 

-

        Field.creation_counter += 1 

-

 

-

        self.source = source 

-

 

-

        if label is not None: 

-

            self.label = smart_text(label) 

-

 

-

        if help_text is not None: 

-

            self.help_text = smart_text(help_text) 

-

 

-

    def initialize(self, parent, field_name): 

-

        """ 

-

        Called to set up a field prior to field_to_native or field_from_native. 

-

 

-

        parent - The parent serializer. 

-

        model_field - The model field this field corresponds to, if one exists. 

-

        """ 

-

        self.parent = parent 

-

        self.root = parent.root or parent 

-

        self.context = self.root.context 

-

        self.partial = self.root.partial 

-

        if self.partial: 

-

            self.required = False 

-

 

-

    def field_from_native(self, data, files, field_name, into): 

-

        """ 

-

        Given a dictionary and a field name, updates the dictionary `into`, 

-

        with the field and it's deserialized value. 

-

        """ 

-

        return 

-

 

-

    def field_to_native(self, obj, field_name): 

-

        """ 

-

        Given and object and a field name, returns the value that should be 

-

        serialized for that field. 

-

        """ 

-

        if obj is None: 

-

            return self.empty 

-

 

-

        if self.source == '*': 

-

            return self.to_native(obj) 

-

 

-

        source = self.source or field_name 

-

        value = obj 

-

 

-

        for component in source.split('.'): 

-

            value = get_component(value, component) 

-

            if value is None: 

-

                break 

-

 

-

        return self.to_native(value) 

-

 

-

    def to_native(self, value): 

-

        """ 

-

        Converts the field's value into it's simple representation. 

-

        """ 

-

        if is_simple_callable(value): 

-

            value = value() 

-

 

-

        if is_protected_type(value): 

-

            return value 

-

        elif (is_non_str_iterable(value) and 

-

              not isinstance(value, (dict, six.string_types))): 

-

            return [self.to_native(item) for item in value] 

-

        elif isinstance(value, dict): 

-

            # Make sure we preserve field ordering, if it exists 

-

            ret = SortedDict() 

-

            for key, val in value.items(): 

-

                ret[key] = self.to_native(val) 

-

            return ret 

-

        return force_text(value) 

-

 

-

    def attributes(self): 

-

        """ 

-

        Returns a dictionary of attributes to be used when serializing to xml. 

-

        """ 

-

        if self.type_name: 

-

            return {'type': self.type_name} 

-

        return {} 

-

 

-

    def metadata(self): 

-

        metadata = SortedDict() 

-

        metadata['type'] = self.type_label 

-

        metadata['required'] = getattr(self, 'required', False) 

-

        optional_attrs = ['read_only', 'label', 'help_text', 

-

                          'min_length', 'max_length'] 

-

        for attr in optional_attrs: 

-

            value = getattr(self, attr, None) 

-

            if value is not None and value != '': 

-

                metadata[attr] = force_text(value, strings_only=True) 

-

        return metadata 

-

 

-

 

-

class WritableField(Field): 

-

    """ 

-

    Base for read/write fields. 

-

    """ 

-

    default_validators = [] 

-

    default_error_messages = { 

-

        'required': _('This field is required.'), 

-

        'invalid': _('Invalid value.'), 

-

    } 

-

    widget = widgets.TextInput 

-

    default = None 

-

 

-

    def __init__(self, source=None, label=None, help_text=None, 

-

                 read_only=False, required=None, 

-

                 validators=[], error_messages=None, widget=None, 

-

                 default=None, blank=None): 

-

 

-

        # 'blank' is to be deprecated in favor of 'required' 

-

        if blank is not None: 

-

            warnings.warn('The `blank` keyword argument is deprecated. ' 

-

                          'Use the `required` keyword argument instead.', 

-

                          DeprecationWarning, stacklevel=2) 

-

            required = not(blank) 

-

 

-

        super(WritableField, self).__init__(source=source, label=label, help_text=help_text) 

-

 

-

        self.read_only = read_only 

-

        if required is None: 

-

            self.required = not(read_only) 

-

        else: 

-

            assert not (read_only and required), "Cannot set required=True and read_only=True" 

-

            self.required = required 

-

 

-

        messages = {} 

-

        for c in reversed(self.__class__.__mro__): 

-

            messages.update(getattr(c, 'default_error_messages', {})) 

-

        messages.update(error_messages or {}) 

-

        self.error_messages = messages 

-

 

-

        self.validators = self.default_validators + validators 

-

        self.default = default if default is not None else self.default 

-

 

-

        # Widgets are ony used for HTML forms. 

-

        widget = widget or self.widget 

-

        if isinstance(widget, type): 

-

            widget = widget() 

-

        self.widget = widget 

-

 

-

    def __deepcopy__(self, memo): 

-

        result = copy.copy(self) 

-

        memo[id(self)] = result 

-

        result.validators = self.validators[:] 

-

        return result 

-

 

-

    def validate(self, value): 

-

        if value in validators.EMPTY_VALUES and self.required: 

-

            raise ValidationError(self.error_messages['required']) 

-

 

-

    def run_validators(self, value): 

-

        if value in validators.EMPTY_VALUES: 

-

            return 

-

        errors = [] 

-

        for v in self.validators: 

-

            try: 

-

                v(value) 

-

            except ValidationError as e: 

-

                if hasattr(e, 'code') and e.code in self.error_messages: 

-

                    message = self.error_messages[e.code] 

-

                    if e.params: 

-

                        message = message % e.params 

-

                    errors.append(message) 

-

                else: 

-

                    errors.extend(e.messages) 

-

        if errors: 

-

            raise ValidationError(errors) 

-

 

-

    def field_from_native(self, data, files, field_name, into): 

-

        """ 

-

        Given a dictionary and a field name, updates the dictionary `into`, 

-

        with the field and it's deserialized value. 

-

        """ 

-

        if self.read_only: 

-

            return 

-

 

-

        try: 

-

            if self.use_files: 

-

                files = files or {} 

-

                native = files[field_name] 

-

            else: 

-

                native = data[field_name] 

-

        except KeyError: 

-

            if self.default is not None and not self.partial: 

-

                # Note: partial updates shouldn't set defaults 

-

                if is_simple_callable(self.default): 

-

                    native = self.default() 

-

                else: 

-

                    native = self.default 

-

            else: 

-

                if self.required: 

-

                    raise ValidationError(self.error_messages['required']) 

-

                return 

-

 

-

        value = self.from_native(native) 

-

        if self.source == '*': 

-

            if value: 

-

                into.update(value) 

-

        else: 

-

            self.validate(value) 

-

            self.run_validators(value) 

-

            into[self.source or field_name] = value 

-

 

-

    def from_native(self, value): 

-

        """ 

-

        Reverts a simple representation back to the field's value. 

-

        """ 

-

        return value 

-

 

-

 

-

class ModelField(WritableField): 

-

    """ 

-

    A generic field that can be used against an arbitrary model field. 

-

    """ 

-

    def __init__(self, *args, **kwargs): 

-

        try: 

-

            self.model_field = kwargs.pop('model_field') 

-

        except KeyError: 

-

            raise ValueError("ModelField requires 'model_field' kwarg") 

-

 

-

        self.min_length = kwargs.pop('min_length', 

-

                                     getattr(self.model_field, 'min_length', None)) 

-

        self.max_length = kwargs.pop('max_length', 

-

                                     getattr(self.model_field, 'max_length', None)) 

-

        self.min_value = kwargs.pop('min_value', 

-

                                    getattr(self.model_field, 'min_value', None)) 

-

        self.max_value = kwargs.pop('max_value', 

-

                                    getattr(self.model_field, 'max_value', None)) 

-

 

-

        super(ModelField, self).__init__(*args, **kwargs) 

-

 

-

        if self.min_length is not None: 

-

            self.validators.append(validators.MinLengthValidator(self.min_length)) 

-

        if self.max_length is not None: 

-

            self.validators.append(validators.MaxLengthValidator(self.max_length)) 

-

        if self.min_value is not None: 

-

            self.validators.append(validators.MinValueValidator(self.min_value)) 

-

        if self.max_value is not None: 

-

            self.validators.append(validators.MaxValueValidator(self.max_value)) 

-

 

-

    def from_native(self, value): 

-

        rel = getattr(self.model_field, "rel", None) 

-

        if rel is not None: 

-

            return rel.to._meta.get_field(rel.field_name).to_python(value) 

-

        else: 

-

            return self.model_field.to_python(value) 

-

 

-

    def field_to_native(self, obj, field_name): 

-

        value = self.model_field._get_val_from_obj(obj) 

-

        if is_protected_type(value): 

-

            return value 

-

        return self.model_field.value_to_string(obj) 

-

 

-

    def attributes(self): 

-

        return { 

-

            "type": self.model_field.get_internal_type() 

-

        } 

-

 

-

 

-

##### Typed Fields ##### 

-

 

-

class BooleanField(WritableField): 

-

    type_name = 'BooleanField' 

-

    type_label = 'boolean' 

-

    form_field_class = forms.BooleanField 

-

    widget = widgets.CheckboxInput 

-

    default_error_messages = { 

-

        'invalid': _("'%s' value must be either True or False."), 

-

    } 

-

    empty = False 

-

 

-

    # Note: we set default to `False` in order to fill in missing value not 

-

    # supplied by html form.  TODO: Fix so that only html form input gets 

-

    # this behavior. 

-

    default = False 

-

 

-

    def from_native(self, value): 

-

        if value in ('true', 't', 'True', '1'): 

-

            return True 

-

        if value in ('false', 'f', 'False', '0'): 

-

            return False 

-

        return bool(value) 

-

 

-

 

-

class CharField(WritableField): 

-

    type_name = 'CharField' 

-

    type_label = 'string' 

-

    form_field_class = forms.CharField 

-

 

-

    def __init__(self, max_length=None, min_length=None, *args, **kwargs): 

-

        self.max_length, self.min_length = max_length, min_length 

-

        super(CharField, self).__init__(*args, **kwargs) 

-

        if min_length is not None: 

-

            self.validators.append(validators.MinLengthValidator(min_length)) 

-

        if max_length is not None: 

-

            self.validators.append(validators.MaxLengthValidator(max_length)) 

-

 

-

    def from_native(self, value): 

-

        if isinstance(value, six.string_types) or value is None: 

-

            return value 

-

        return smart_text(value) 

-

 

-

 

-

class URLField(CharField): 

-

    type_name = 'URLField' 

-

    type_label = 'url' 

-

 

-

    def __init__(self, **kwargs): 

-

        kwargs['validators'] = [validators.URLValidator()] 

-

        super(URLField, self).__init__(**kwargs) 

-

 

-

 

-

class SlugField(CharField): 

-

    type_name = 'SlugField' 

-

    type_label = 'slug' 

-

    form_field_class = forms.SlugField 

-

 

-

    default_error_messages = { 

-

        'invalid': _("Enter a valid 'slug' consisting of letters, numbers," 

-

                     " underscores or hyphens."), 

-

    } 

-

    default_validators = [validators.validate_slug] 

-

 

-

    def __init__(self, *args, **kwargs): 

-

        super(SlugField, self).__init__(*args, **kwargs) 

-

 

-

 

-

class ChoiceField(WritableField): 

-

    type_name = 'ChoiceField' 

-

    type_label = 'multiple choice' 

-

    form_field_class = forms.ChoiceField 

-

    widget = widgets.Select 

-

    default_error_messages = { 

-

        'invalid_choice': _('Select a valid choice. %(value)s is not one of ' 

-

                            'the available choices.'), 

-

    } 

-

 

-

    def __init__(self, choices=(), *args, **kwargs): 

-

        super(ChoiceField, self).__init__(*args, **kwargs) 

-

        self.choices = choices 

-

        if not self.required: 

-

            self.choices = BLANK_CHOICE_DASH + self.choices 

-

 

-

    def _get_choices(self): 

-

        return self._choices 

-

 

-

    def _set_choices(self, value): 

-

        # Setting choices also sets the choices on the widget. 

-

        # choices can be any iterable, but we call list() on it because 

-

        # it will be consumed more than once. 

-

        self._choices = self.widget.choices = list(value) 

-

 

-

    choices = property(_get_choices, _set_choices) 

-

 

-

    def validate(self, value): 

-

        """ 

-

        Validates that the input is in self.choices. 

-

        """ 

-

        super(ChoiceField, self).validate(value) 

-

        if value and not self.valid_value(value): 

-

            raise ValidationError(self.error_messages['invalid_choice'] % {'value': value}) 

-

 

-

    def valid_value(self, value): 

-

        """ 

-

        Check to see if the provided value is a valid choice. 

-

        """ 

-

        for k, v in self.choices: 

-

            if isinstance(v, (list, tuple)): 

-

                # This is an optgroup, so look inside the group for options 

-

                for k2, v2 in v: 

-

                    if value == smart_text(k2): 

-

                        return True 

-

            else: 

-

                if value == smart_text(k) or value == k: 

-

                    return True 

-

        return False 

-

 

-

 

-

class EmailField(CharField): 

-

    type_name = 'EmailField' 

-

    type_label = 'email' 

-

    form_field_class = forms.EmailField 

-

 

-

    default_error_messages = { 

-

        'invalid': _('Enter a valid e-mail address.'), 

-

    } 

-

    default_validators = [validators.validate_email] 

-

 

-

    def from_native(self, value): 

-

        ret = super(EmailField, self).from_native(value) 

-

        if ret is None: 

-

            return None 

-

        return ret.strip() 

-

 

-

 

-

class RegexField(CharField): 

-

    type_name = 'RegexField' 

-

    type_label = 'regex' 

-

    form_field_class = forms.RegexField 

-

 

-

    def __init__(self, regex, max_length=None, min_length=None, *args, **kwargs): 

-

        super(RegexField, self).__init__(max_length, min_length, *args, **kwargs) 

-

        self.regex = regex 

-

 

-

    def _get_regex(self): 

-

        return self._regex 

-

 

-

    def _set_regex(self, regex): 

-

        if isinstance(regex, six.string_types): 

-

            regex = re.compile(regex) 

-

        self._regex = regex 

-

        if hasattr(self, '_regex_validator') and self._regex_validator in self.validators: 

-

            self.validators.remove(self._regex_validator) 

-

        self._regex_validator = validators.RegexValidator(regex=regex) 

-

        self.validators.append(self._regex_validator) 

-

 

-

    regex = property(_get_regex, _set_regex) 

-

 

-

 

-

class DateField(WritableField): 

-

    type_name = 'DateField' 

-

    type_label = 'date' 

-

    widget = widgets.DateInput 

-

    form_field_class = forms.DateField 

-

 

-

    default_error_messages = { 

-

        'invalid': _("Date has wrong format. Use one of these formats instead: %s"), 

-

    } 

-

    empty = None 

-

    input_formats = api_settings.DATE_INPUT_FORMATS 

-

    format = api_settings.DATE_FORMAT 

-

 

-

    def __init__(self, input_formats=None, format=None, *args, **kwargs): 

-

        self.input_formats = input_formats if input_formats is not None else self.input_formats 

-

        self.format = format if format is not None else self.format 

-

        super(DateField, self).__init__(*args, **kwargs) 

-

 

-

    def from_native(self, value): 

-

        if value in validators.EMPTY_VALUES: 

-

            return None 

-

 

-

        if isinstance(value, datetime.datetime): 

-

            if timezone and settings.USE_TZ and timezone.is_aware(value): 

-

                # Convert aware datetimes to the default time zone 

-

                # before casting them to dates (#17742). 

-

                default_timezone = timezone.get_default_timezone() 

-

                value = timezone.make_naive(value, default_timezone) 

-

            return value.date() 

-

        if isinstance(value, datetime.date): 

-

            return value 

-

 

-

        for format in self.input_formats: 

-

            if format.lower() == ISO_8601: 

-

                try: 

-

                    parsed = parse_date(value) 

-

                except (ValueError, TypeError): 

-

                    pass 

-

                else: 

-

                    if parsed is not None: 

-

                        return parsed 

-

            else: 

-

                try: 

-

                    parsed = datetime.datetime.strptime(value, format) 

-

                except (ValueError, TypeError): 

-

                    pass 

-

                else: 

-

                    return parsed.date() 

-

 

-

        msg = self.error_messages['invalid'] % readable_date_formats(self.input_formats) 

-

        raise ValidationError(msg) 

-

 

-

    def to_native(self, value): 

-

        if value is None or self.format is None: 

-

            return value 

-

 

-

        if isinstance(value, datetime.datetime): 

-

            value = value.date() 

-

 

-

        if self.format.lower() == ISO_8601: 

-

            return value.isoformat() 

-

        return value.strftime(self.format) 

-

 

-

 

-

class DateTimeField(WritableField): 

-

    type_name = 'DateTimeField' 

-

    type_label = 'datetime' 

-

    widget = widgets.DateTimeInput 

-

    form_field_class = forms.DateTimeField 

-

 

-

    default_error_messages = { 

-

        'invalid': _("Datetime has wrong format. Use one of these formats instead: %s"), 

-

    } 

-

    empty = None 

-

    input_formats = api_settings.DATETIME_INPUT_FORMATS 

-

    format = api_settings.DATETIME_FORMAT 

-

 

-

    def __init__(self, input_formats=None, format=None, *args, **kwargs): 

-

        self.input_formats = input_formats if input_formats is not None else self.input_formats 

-

        self.format = format if format is not None else self.format 

-

        super(DateTimeField, self).__init__(*args, **kwargs) 

-

 

-

    def from_native(self, value): 

-

        if value in validators.EMPTY_VALUES: 

-

            return None 

-

 

-

        if isinstance(value, datetime.datetime): 

-

            return value 

-

        if isinstance(value, datetime.date): 

-

            value = datetime.datetime(value.year, value.month, value.day) 

-

            if settings.USE_TZ: 

-

                # For backwards compatibility, interpret naive datetimes in 

-

                # local time. This won't work during DST change, but we can't 

-

                # do much about it, so we let the exceptions percolate up the 

-

                # call stack. 

-

                warnings.warn("DateTimeField received a naive datetime (%s)" 

-

                              " while time zone support is active." % value, 

-

                              RuntimeWarning) 

-

                default_timezone = timezone.get_default_timezone() 

-

                value = timezone.make_aware(value, default_timezone) 

-

            return value 

-

 

-

        for format in self.input_formats: 

-

            if format.lower() == ISO_8601: 

-

                try: 

-

                    parsed = parse_datetime(value) 

-

                except (ValueError, TypeError): 

-

                    pass 

-

                else: 

-

                    if parsed is not None: 

-

                        return parsed 

-

            else: 

-

                try: 

-

                    parsed = datetime.datetime.strptime(value, format) 

-

                except (ValueError, TypeError): 

-

                    pass 

-

                else: 

-

                    return parsed 

-

 

-

        msg = self.error_messages['invalid'] % readable_datetime_formats(self.input_formats) 

-

        raise ValidationError(msg) 

-

 

-

    def to_native(self, value): 

-

        if value is None or self.format is None: 

-

            return value 

-

 

-

        if self.format.lower() == ISO_8601: 

-

            ret = value.isoformat() 

-

            if ret.endswith('+00:00'): 

-

                ret = ret[:-6] + 'Z' 

-

            return ret 

-

        return value.strftime(self.format) 

-

 

-

 

-

class TimeField(WritableField): 

-

    type_name = 'TimeField' 

-

    type_label = 'time' 

-

    widget = widgets.TimeInput 

-

    form_field_class = forms.TimeField 

-

 

-

    default_error_messages = { 

-

        'invalid': _("Time has wrong format. Use one of these formats instead: %s"), 

-

    } 

-

    empty = None 

-

    input_formats = api_settings.TIME_INPUT_FORMATS 

-

    format = api_settings.TIME_FORMAT 

-

 

-

    def __init__(self, input_formats=None, format=None, *args, **kwargs): 

-

        self.input_formats = input_formats if input_formats is not None else self.input_formats 

-

        self.format = format if format is not None else self.format 

-

        super(TimeField, self).__init__(*args, **kwargs) 

-

 

-

    def from_native(self, value): 

-

        if value in validators.EMPTY_VALUES: 

-

            return None 

-

 

-

        if isinstance(value, datetime.time): 

-

            return value 

-

 

-

        for format in self.input_formats: 

-

            if format.lower() == ISO_8601: 

-

                try: 

-

                    parsed = parse_time(value) 

-

                except (ValueError, TypeError): 

-

                    pass 

-

                else: 

-

                    if parsed is not None: 

-

                        return parsed 

-

            else: 

-

                try: 

-

                    parsed = datetime.datetime.strptime(value, format) 

-

                except (ValueError, TypeError): 

-

                    pass 

-

                else: 

-

                    return parsed.time() 

-

 

-

        msg = self.error_messages['invalid'] % readable_time_formats(self.input_formats) 

-

        raise ValidationError(msg) 

-

 

-

    def to_native(self, value): 

-

        if value is None or self.format is None: 

-

            return value 

-

 

-

        if isinstance(value, datetime.datetime): 

-

            value = value.time() 

-

 

-

        if self.format.lower() == ISO_8601: 

-

            return value.isoformat() 

-

        return value.strftime(self.format) 

-

 

-

 

-

class IntegerField(WritableField): 

-

    type_name = 'IntegerField' 

-

    type_label = 'integer' 

-

    form_field_class = forms.IntegerField 

-

 

-

    default_error_messages = { 

-

        'invalid': _('Enter a whole number.'), 

-

        'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), 

-

        'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), 

-

    } 

-

 

-

    def __init__(self, max_value=None, min_value=None, *args, **kwargs): 

-

        self.max_value, self.min_value = max_value, min_value 

-

        super(IntegerField, self).__init__(*args, **kwargs) 

-

 

-

        if max_value is not None: 

-

            self.validators.append(validators.MaxValueValidator(max_value)) 

-

        if min_value is not None: 

-

            self.validators.append(validators.MinValueValidator(min_value)) 

-

 

-

    def from_native(self, value): 

-

        if value in validators.EMPTY_VALUES: 

-

            return None 

-

 

-

        try: 

-

            value = int(str(value)) 

-

        except (ValueError, TypeError): 

-

            raise ValidationError(self.error_messages['invalid']) 

-

        return value 

-

 

-

 

-

class FloatField(WritableField): 

-

    type_name = 'FloatField' 

-

    type_label = 'float' 

-

    form_field_class = forms.FloatField 

-

 

-

    default_error_messages = { 

-

        'invalid': _("'%s' value must be a float."), 

-

    } 

-

 

-

    def from_native(self, value): 

-

        if value in validators.EMPTY_VALUES: 

-

            return None 

-

 

-

        try: 

-

            return float(value) 

-

        except (TypeError, ValueError): 

-

            msg = self.error_messages['invalid'] % value 

-

            raise ValidationError(msg) 

-

 

-

 

-

class DecimalField(WritableField): 

-

    type_name = 'DecimalField' 

-

    type_label = 'decimal' 

-

    form_field_class = forms.DecimalField 

-

 

-

    default_error_messages = { 

-

        'invalid': _('Enter a number.'), 

-

        'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'), 

-

        'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'), 

-

        'max_digits': _('Ensure that there are no more than %s digits in total.'), 

-

        'max_decimal_places': _('Ensure that there are no more than %s decimal places.'), 

-

        'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.') 

-

    } 

-

 

-

    def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs): 

-

        self.max_value, self.min_value = max_value, min_value 

-

        self.max_digits, self.decimal_places = max_digits, decimal_places 

-

        super(DecimalField, self).__init__(*args, **kwargs) 

-

 

-

        if max_value is not None: 

-

            self.validators.append(validators.MaxValueValidator(max_value)) 

-

        if min_value is not None: 

-

            self.validators.append(validators.MinValueValidator(min_value)) 

-

 

-

    def from_native(self, value): 

-

        """ 

-

        Validates that the input is a decimal number. Returns a Decimal 

-

        instance. Returns None for empty values. Ensures that there are no more 

-

        than max_digits in the number, and no more than decimal_places digits 

-

        after the decimal point. 

-

        """ 

-

        if value in validators.EMPTY_VALUES: 

-

            return None 

-

        value = smart_text(value).strip() 

-

        try: 

-

            value = Decimal(value) 

-

        except DecimalException: 

-

            raise ValidationError(self.error_messages['invalid']) 

-

        return value 

-

 

-

    def validate(self, value): 

-

        super(DecimalField, self).validate(value) 

-

        if value in validators.EMPTY_VALUES: 

-

            return 

-

        # Check for NaN, Inf and -Inf values. We can't compare directly for NaN, 

-

        # since it is never equal to itself. However, NaN is the only value that 

-

        # isn't equal to itself, so we can use this to identify NaN 

-

        if value != value or value == Decimal("Inf") or value == Decimal("-Inf"): 

-

            raise ValidationError(self.error_messages['invalid']) 

-

        sign, digittuple, exponent = value.as_tuple() 

-

        decimals = abs(exponent) 

-

        # digittuple doesn't include any leading zeros. 

-

        digits = len(digittuple) 

-

        if decimals > digits: 

-

            # We have leading zeros up to or past the decimal point.  Count 

-

            # everything past the decimal point as a digit.  We do not count 

-

            # 0 before the decimal point as a digit since that would mean 

-

            # we would not allow max_digits = decimal_places. 

-

            digits = decimals 

-

        whole_digits = digits - decimals 

-

 

-

        if self.max_digits is not None and digits > self.max_digits: 

-

            raise ValidationError(self.error_messages['max_digits'] % self.max_digits) 

-

        if self.decimal_places is not None and decimals > self.decimal_places: 

-

            raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places) 

-

        if self.max_digits is not None and self.decimal_places is not None and whole_digits > (self.max_digits - self.decimal_places): 

-

            raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places)) 

-

        return value 

-

 

-

 

-

class FileField(WritableField): 

-

    use_files = True 

-

    type_name = 'FileField' 

-

    type_label = 'file upload' 

-

    form_field_class = forms.FileField 

-

    widget = widgets.FileInput 

-

 

-

    default_error_messages = { 

-

        'invalid': _("No file was submitted. Check the encoding type on the form."), 

-

        'missing': _("No file was submitted."), 

-

        'empty': _("The submitted file is empty."), 

-

        'max_length': _('Ensure this filename has at most %(max)d characters (it has %(length)d).'), 

-

        'contradiction': _('Please either submit a file or check the clear checkbox, not both.') 

-

    } 

-

 

-

    def __init__(self, *args, **kwargs): 

-

        self.max_length = kwargs.pop('max_length', None) 

-

        self.allow_empty_file = kwargs.pop('allow_empty_file', False) 

-

        super(FileField, self).__init__(*args, **kwargs) 

-

 

-

    def from_native(self, data): 

-

        if data in validators.EMPTY_VALUES: 

-

            return None 

-

 

-

        # UploadedFile objects should have name and size attributes. 

-

        try: 

-

            file_name = data.name 

-

            file_size = data.size 

-

        except AttributeError: 

-

            raise ValidationError(self.error_messages['invalid']) 

-

 

-

        if self.max_length is not None and len(file_name) > self.max_length: 

-

            error_values = {'max': self.max_length, 'length': len(file_name)} 

-

            raise ValidationError(self.error_messages['max_length'] % error_values) 

-

        if not file_name: 

-

            raise ValidationError(self.error_messages['invalid']) 

-

        if not self.allow_empty_file and not file_size: 

-

            raise ValidationError(self.error_messages['empty']) 

-

 

-

        return data 

-

 

-

    def to_native(self, value): 

-

        return value.name 

-

 

-

 

-

class ImageField(FileField): 

-

    use_files = True 

-

    type_name = 'ImageField' 

-

    type_label = 'image upload' 

-

    form_field_class = forms.ImageField 

-

 

-

    default_error_messages = { 

-

        'invalid_image': _("Upload a valid image. The file you uploaded was " 

-

                           "either not an image or a corrupted image."), 

-

    } 

-

 

-

    def from_native(self, data): 

-

        """ 

-

        Checks that the file-upload field data contains a valid image (GIF, JPG, 

-

        PNG, possibly others -- whatever the Python Imaging Library supports). 

-

        """ 

-

        f = super(ImageField, self).from_native(data) 

-

        if f is None: 

-

            return None 

-

 

-

        from compat import Image 

-

        assert Image is not None, 'PIL must be installed for ImageField support' 

-

 

-

        # We need to get a file object for PIL. We might have a path or we might 

-

        # have to read the data into memory. 

-

        if hasattr(data, 'temporary_file_path'): 

-

            file = data.temporary_file_path() 

-

        else: 

-

            if hasattr(data, 'read'): 

-

                file = BytesIO(data.read()) 

-

            else: 

-

                file = BytesIO(data['content']) 

-

 

-

        try: 

-

            # load() could spot a truncated JPEG, but it loads the entire 

-

            # image in memory, which is a DoS vector. See #3848 and #18520. 

-

            # verify() must be called immediately after the constructor. 

-

            Image.open(file).verify() 

-

        except ImportError: 

-

            # Under PyPy, it is possible to import PIL. However, the underlying 

-

            # _imaging C module isn't available, so an ImportError will be 

-

            # raised. Catch and re-raise. 

-

            raise 

-

        except Exception:  # Python Imaging Library doesn't recognize it as an image 

-

            raise ValidationError(self.error_messages['invalid_image']) 

-

        if hasattr(f, 'seek') and callable(f.seek): 

-

            f.seek(0) 

-

        return f 

-

 

-

 

-

class SerializerMethodField(Field): 

-

    """ 

-

    A field that gets its value by calling a method on the serializer it's attached to. 

-

    """ 

-

 

-

    def __init__(self, method_name): 

-

        self.method_name = method_name 

-

        super(SerializerMethodField, self).__init__() 

-

 

-

    def field_to_native(self, obj, field_name): 

-

        value = getattr(self.parent, self.method_name)(obj) 

-

        return self.to_native(value) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_filters.html b/htmlcov/rest_framework_filters.html deleted file mode 100644 index 28b6eaae5..000000000 --- a/htmlcov/rest_framework_filters.html +++ /dev/null @@ -1,367 +0,0 @@ - - - - - - - - Coverage for rest_framework/filters: 92% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

- -
-

""" 

-

Provides generic filtering backends that can be used to filter the results 

-

returned by list views. 

-

""" 

-

from __future__ import unicode_literals 

-

from django.db import models 

-

from rest_framework.compat import django_filters, six 

-

from functools import reduce 

-

import operator 

-

 

-

FilterSet = django_filters and django_filters.FilterSet or None 

-

 

-

 

-

class BaseFilterBackend(object): 

-

    """ 

-

    A base class from which all filter backend classes should inherit. 

-

    """ 

-

 

-

    def filter_queryset(self, request, queryset, view): 

-

        """ 

-

        Return a filtered queryset. 

-

        """ 

-

        raise NotImplementedError(".filter_queryset() must be overridden.") 

-

 

-

 

-

class DjangoFilterBackend(BaseFilterBackend): 

-

    """ 

-

    A filter backend that uses django-filter. 

-

    """ 

-

    default_filter_set = FilterSet 

-

 

-

    def __init__(self): 

-

        assert django_filters, 'Using DjangoFilterBackend, but django-filter is not installed' 

-

 

-

    def get_filter_class(self, view, queryset=None): 

-

        """ 

-

        Return the django-filters `FilterSet` used to filter the queryset. 

-

        """ 

-

        filter_class = getattr(view, 'filter_class', None) 

-

        filter_fields = getattr(view, 'filter_fields', None) 

-

 

-

        if filter_class: 

-

            filter_model = filter_class.Meta.model 

-

 

-

            assert issubclass(filter_model, queryset.model), \ 

-

                'FilterSet model %s does not match queryset model %s' % \ 

-

                (filter_model, queryset.model) 

-

 

-

            return filter_class 

-

 

-

        if filter_fields: 

-

            class AutoFilterSet(self.default_filter_set): 

-

                class Meta: 

-

                    model = queryset.model 

-

                    fields = filter_fields 

-

            return AutoFilterSet 

-

 

-

        return None 

-

 

-

    def filter_queryset(self, request, queryset, view): 

-

        filter_class = self.get_filter_class(view, queryset) 

-

 

-

        if filter_class: 

-

            return filter_class(request.QUERY_PARAMS, queryset=queryset).qs 

-

 

-

        return queryset 

-

 

-

 

-

class SearchFilter(BaseFilterBackend): 

-

    search_param = 'search'  # The URL query parameter used for the search. 

-

 

-

    def get_search_terms(self, request): 

-

        """ 

-

        Search terms are set by a ?search=... query parameter, 

-

        and may be comma and/or whitespace delimited. 

-

        """ 

-

        params = request.QUERY_PARAMS.get(self.search_param, '') 

-

        return params.replace(',', ' ').split() 

-

 

-

    def construct_search(self, field_name): 

-

        if field_name.startswith('^'): 

-

            return "%s__istartswith" % field_name[1:] 

-

        elif field_name.startswith('='): 

-

            return "%s__iexact" % field_name[1:] 

-

        elif field_name.startswith('@'): 

-

            return "%s__search" % field_name[1:] 

-

        else: 

-

            return "%s__icontains" % field_name 

-

 

-

    def filter_queryset(self, request, queryset, view): 

-

        search_fields = getattr(view, 'search_fields', None) 

-

 

-

        if not search_fields: 

-

            return queryset 

-

 

-

        orm_lookups = [self.construct_search(str(search_field)) 

-

                       for search_field in search_fields] 

-

 

-

        for search_term in self.get_search_terms(request): 

-

            or_queries = [models.Q(**{orm_lookup: search_term}) 

-

                          for orm_lookup in orm_lookups] 

-

            queryset = queryset.filter(reduce(operator.or_, or_queries)) 

-

 

-

        return queryset 

-

 

-

 

-

class OrderingFilter(BaseFilterBackend): 

-

    ordering_param = 'ordering'  # The URL query parameter used for the ordering. 

-

 

-

    def get_ordering(self, request): 

-

        """ 

-

        Search terms are set by a ?search=... query parameter, 

-

        and may be comma and/or whitespace delimited. 

-

        """ 

-

        params = request.QUERY_PARAMS.get(self.ordering_param) 

-

        if params: 

-

            return [param.strip() for param in params.split(',')] 

-

 

-

    def get_default_ordering(self, view): 

-

        ordering = getattr(view, 'ordering', None) 

-

        if isinstance(ordering, six.string_types): 

-

            return (ordering,) 

-

        return ordering 

-

 

-

    def remove_invalid_fields(self, queryset, ordering): 

-

        field_names = [field.name for field in queryset.model._meta.fields] 

-

        return [term for term in ordering if term.lstrip('-') in field_names] 

-

 

-

    def filter_queryset(self, request, queryset, view): 

-

        ordering = self.get_ordering(request) 

-

 

-

        if ordering: 

-

            # Skip any incorrect parameters 

-

            ordering = self.remove_invalid_fields(queryset, ordering) 

-

 

-

        if not ordering: 

-

            # Use 'ordering' attribtue by default 

-

            ordering = self.get_default_ordering(view) 

-

 

-

        if ordering: 

-

            return queryset.order_by(*ordering) 

-

 

-

        return queryset 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_generics.html b/htmlcov/rest_framework_generics.html deleted file mode 100644 index 5f5851cbd..000000000 --- a/htmlcov/rest_framework_generics.html +++ /dev/null @@ -1,1079 +0,0 @@ - - - - - - - - Coverage for rest_framework/generics: 83% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

-

296

-

297

-

298

-

299

-

300

-

301

-

302

-

303

-

304

-

305

-

306

-

307

-

308

-

309

-

310

-

311

-

312

-

313

-

314

-

315

-

316

-

317

-

318

-

319

-

320

-

321

-

322

-

323

-

324

-

325

-

326

-

327

-

328

-

329

-

330

-

331

-

332

-

333

-

334

-

335

-

336

-

337

-

338

-

339

-

340

-

341

-

342

-

343

-

344

-

345

-

346

-

347

-

348

-

349

-

350

-

351

-

352

-

353

-

354

-

355

-

356

-

357

-

358

-

359

-

360

-

361

-

362

-

363

-

364

-

365

-

366

-

367

-

368

-

369

-

370

-

371

-

372

-

373

-

374

-

375

-

376

-

377

-

378

-

379

-

380

-

381

-

382

-

383

-

384

-

385

-

386

-

387

-

388

-

389

-

390

-

391

-

392

-

393

-

394

-

395

-

396

-

397

-

398

-

399

-

400

-

401

-

402

-

403

-

404

-

405

-

406

-

407

-

408

-

409

-

410

-

411

-

412

-

413

-

414

-

415

-

416

-

417

-

418

-

419

-

420

-

421

-

422

-

423

-

424

-

425

-

426

-

427

-

428

-

429

-

430

-

431

-

432

-

433

-

434

-

435

-

436

-

437

-

438

-

439

-

440

-

441

-

442

-

443

-

444

-

445

-

446

-

447

-

448

-

449

-

450

-

451

-

452

-

453

-

454

-

455

-

456

-

457

-

458

-

459

-

460

-

461

-

462

-

463

-

464

-

465

-

466

-

467

-

468

-

469

-

470

-

471

-

472

-

473

-

474

-

475

-

476

-

477

-

478

-

479

-

480

-

481

-

482

-

483

-

484

-

485

-

486

-

487

-

488

-

489

-

490

-

491

-

492

-

493

-

494

-

495

-

496

-

497

-

498

-

499

- -
-

""" 

-

Generic views that provide commonly needed behaviour. 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

from django.core.exceptions import ImproperlyConfigured, PermissionDenied 

-

from django.core.paginator import Paginator, InvalidPage 

-

from django.http import Http404 

-

from django.shortcuts import get_object_or_404 as _get_object_or_404 

-

from django.utils.translation import ugettext as _ 

-

from rest_framework import views, mixins, exceptions 

-

from rest_framework.request import clone_request 

-

from rest_framework.settings import api_settings 

-

import warnings 

-

 

-

 

-

def get_object_or_404(queryset, **filter_kwargs): 

-

    """ 

-

    Same as Django's standard shortcut, but make sure to raise 404 

-

    if the filter_kwargs don't match the required types. 

-

    """ 

-

    try: 

-

        return _get_object_or_404(queryset, **filter_kwargs) 

-

    except (TypeError, ValueError): 

-

        raise Http404 

-

 

-

 

-

class GenericAPIView(views.APIView): 

-

    """ 

-

    Base class for all other generic views. 

-

    """ 

-

 

-

    # You'll need to either set these attributes, 

-

    # or override `get_queryset()`/`get_serializer_class()`. 

-

    queryset = None 

-

    serializer_class = None 

-

 

-

    # This shortcut may be used instead of setting either or both 

-

    # of the `queryset`/`serializer_class` attributes, although using 

-

    # the explicit style is generally preferred. 

-

    model = None 

-

 

-

    # If you want to use object lookups other than pk, set this attribute. 

-

    # For more complex lookup requirements override `get_object()`. 

-

    lookup_field = 'pk' 

-

 

-

    # Pagination settings 

-

    paginate_by = api_settings.PAGINATE_BY 

-

    paginate_by_param = api_settings.PAGINATE_BY_PARAM 

-

    pagination_serializer_class = api_settings.DEFAULT_PAGINATION_SERIALIZER_CLASS 

-

    page_kwarg = 'page' 

-

 

-

    # The filter backend classes to use for queryset filtering 

-

    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS 

-

 

-

    # The following attributes may be subject to change, 

-

    # and should be considered private API. 

-

    model_serializer_class = api_settings.DEFAULT_MODEL_SERIALIZER_CLASS 

-

    paginator_class = Paginator 

-

 

-

    ###################################### 

-

    # These are pending deprecation... 

-

 

-

    pk_url_kwarg = 'pk' 

-

    slug_url_kwarg = 'slug' 

-

    slug_field = 'slug' 

-

    allow_empty = True 

-

    filter_backend = api_settings.FILTER_BACKEND 

-

 

-

    def get_serializer_context(self): 

-

        """ 

-

        Extra context provided to the serializer class. 

-

        """ 

-

        return { 

-

            'request': self.request, 

-

            'format': self.format_kwarg, 

-

            'view': self 

-

        } 

-

 

-

    def get_serializer(self, instance=None, data=None, 

-

                       files=None, many=False, partial=False): 

-

        """ 

-

        Return the serializer instance that should be used for validating and 

-

        deserializing input, and for serializing output. 

-

        """ 

-

        serializer_class = self.get_serializer_class() 

-

        context = self.get_serializer_context() 

-

        return serializer_class(instance, data=data, files=files, 

-

                                many=many, partial=partial, context=context) 

-

 

-

    def get_pagination_serializer(self, page): 

-

        """ 

-

        Return a serializer instance to use with paginated data. 

-

        """ 

-

        class SerializerClass(self.pagination_serializer_class): 

-

            class Meta: 

-

                object_serializer_class = self.get_serializer_class() 

-

 

-

        pagination_serializer_class = SerializerClass 

-

        context = self.get_serializer_context() 

-

        return pagination_serializer_class(instance=page, context=context) 

-

 

-

    def paginate_queryset(self, queryset, page_size=None): 

-

        """ 

-

        Paginate a queryset if required, either returning a page object, 

-

        or `None` if pagination is not configured for this view. 

-

        """ 

-

        deprecated_style = False 

-

        if page_size is not None: 

-

            warnings.warn('The `page_size` parameter to `paginate_queryset()` ' 

-

                          'is due to be deprecated. ' 

-

                          'Note that the return style of this method is also ' 

-

                          'changed, and will simply return a page object ' 

-

                          'when called without a `page_size` argument.', 

-

                          PendingDeprecationWarning, stacklevel=2) 

-

            deprecated_style = True 

-

        else: 

-

            # Determine the required page size. 

-

            # If pagination is not configured, simply return None. 

-

            page_size = self.get_paginate_by() 

-

            if not page_size: 

-

                return None 

-

 

-

        if not self.allow_empty: 

-

            warnings.warn( 

-

                'The `allow_empty` parameter is due to be deprecated. ' 

-

                'To use `allow_empty=False` style behavior, You should override ' 

-

                '`get_queryset()` and explicitly raise a 404 on empty querysets.', 

-

                PendingDeprecationWarning, stacklevel=2 

-

            ) 

-

 

-

        paginator = self.paginator_class(queryset, page_size, 

-

                                         allow_empty_first_page=self.allow_empty) 

-

        page_kwarg = self.kwargs.get(self.page_kwarg) 

-

        page_query_param = self.request.QUERY_PARAMS.get(self.page_kwarg) 

-

        page = page_kwarg or page_query_param or 1 

-

        try: 

-

            page_number = int(page) 

-

        except ValueError: 

-

            if page == 'last': 

-

                page_number = paginator.num_pages 

-

            else: 

-

                raise Http404(_("Page is not 'last', nor can it be converted to an int.")) 

-

        try: 

-

            page = paginator.page(page_number) 

-

        except InvalidPage as e: 

-

            raise Http404(_('Invalid page (%(page_number)s): %(message)s') % { 

-

                                'page_number': page_number, 

-

                                'message': str(e) 

-

            }) 

-

 

-

        if deprecated_style: 

-

            return (paginator, page, page.object_list, page.has_other_pages()) 

-

        return page 

-

 

-

    def filter_queryset(self, queryset): 

-

        """ 

-

        Given a queryset, filter it with whichever filter backend is in use. 

-

 

-

        You are unlikely to want to override this method, although you may need 

-

        to call it either from a list view, or from a custom `get_object` 

-

        method if you want to apply the configured filtering backend to the 

-

        default queryset. 

-

        """ 

-

        filter_backends = self.filter_backends or [] 

-

        if not filter_backends and self.filter_backend: 

-

            warnings.warn( 

-

                'The `filter_backend` attribute and `FILTER_BACKEND` setting ' 

-

                'are due to be deprecated in favor of a `filter_backends` ' 

-

                'attribute and `DEFAULT_FILTER_BACKENDS` setting, that take ' 

-

                'a *list* of filter backend classes.', 

-

                PendingDeprecationWarning, stacklevel=2 

-

            ) 

-

            filter_backends = [self.filter_backend] 

-

 

-

        for backend in filter_backends: 

-

            queryset = backend().filter_queryset(self.request, queryset, self) 

-

        return queryset 

-

 

-

    ######################## 

-

    ### The following methods provide default implementations 

-

    ### that you may want to override for more complex cases. 

-

 

-

    def get_paginate_by(self, queryset=None): 

-

        """ 

-

        Return the size of pages to use with pagination. 

-

 

-

        If `PAGINATE_BY_PARAM` is set it will attempt to get the page size 

-

        from a named query parameter in the url, eg. ?page_size=100 

-

 

-

        Otherwise defaults to using `self.paginate_by`. 

-

        """ 

-

        if queryset is not None: 

-

            warnings.warn('The `queryset` parameter to `get_paginate_by()` ' 

-

                          'is due to be deprecated.', 

-

                          PendingDeprecationWarning, stacklevel=2) 

-

 

-

        if self.paginate_by_param: 

-

            query_params = self.request.QUERY_PARAMS 

-

            try: 

-

                return int(query_params[self.paginate_by_param]) 

-

            except (KeyError, ValueError): 

-

                pass 

-

 

-

        return self.paginate_by 

-

 

-

    def get_serializer_class(self): 

-

        """ 

-

        Return the class to use for the serializer. 

-

        Defaults to using `self.serializer_class`. 

-

 

-

        You may want to override this if you need to provide different 

-

        serializations depending on the incoming request. 

-

 

-

        (Eg. admins get full serialization, others get basic serialization) 

-

        """ 

-

        serializer_class = self.serializer_class 

-

        if serializer_class is not None: 

-

            return serializer_class 

-

 

-

        assert self.model is not None, \ 

-

            "'%s' should either include a 'serializer_class' attribute, " \ 

-

            "or use the 'model' attribute as a shortcut for " \ 

-

            "automatically generating a serializer class." \ 

-

            % self.__class__.__name__ 

-

 

-

        class DefaultSerializer(self.model_serializer_class): 

-

            class Meta: 

-

                model = self.model 

-

        return DefaultSerializer 

-

 

-

    def get_queryset(self): 

-

        """ 

-

        Get the list of items for this view. 

-

        This must be an iterable, and may be a queryset. 

-

        Defaults to using `self.queryset`. 

-

 

-

        You may want to override this if you need to provide different 

-

        querysets depending on the incoming request. 

-

 

-

        (Eg. return a list of items that is specific to the user) 

-

        """ 

-

        if self.queryset is not None: 

-

            return self.queryset._clone() 

-

 

-

        if self.model is not None: 

-

            return self.model._default_manager.all() 

-

 

-

        raise ImproperlyConfigured("'%s' must define 'queryset' or 'model'" 

-

                                    % self.__class__.__name__) 

-

 

-

    def get_object(self, queryset=None): 

-

        """ 

-

        Returns the object the view is displaying. 

-

 

-

        You may want to override this if you need to provide non-standard 

-

        queryset lookups.  Eg if objects are referenced using multiple 

-

        keyword arguments in the url conf. 

-

        """ 

-

        # Determine the base queryset to use. 

-

        if queryset is None: 

-

            queryset = self.filter_queryset(self.get_queryset()) 

-

        else: 

-

            pass  # Deprecation warning 

-

 

-

        # Perform the lookup filtering. 

-

        pk = self.kwargs.get(self.pk_url_kwarg, None) 

-

        slug = self.kwargs.get(self.slug_url_kwarg, None) 

-

        lookup = self.kwargs.get(self.lookup_field, None) 

-

 

-

        if lookup is not None: 

-

            filter_kwargs = {self.lookup_field: lookup} 

-

        elif pk is not None and self.lookup_field == 'pk': 

-

            warnings.warn( 

-

                'The `pk_url_kwarg` attribute is due to be deprecated. ' 

-

                'Use the `lookup_field` attribute instead', 

-

                PendingDeprecationWarning 

-

            ) 

-

            filter_kwargs = {'pk': pk} 

-

        elif slug is not None and self.lookup_field == 'pk': 

-

            warnings.warn( 

-

                'The `slug_url_kwarg` attribute is due to be deprecated. ' 

-

                'Use the `lookup_field` attribute instead', 

-

                PendingDeprecationWarning 

-

            ) 

-

            filter_kwargs = {self.slug_field: slug} 

-

        else: 

-

            raise ImproperlyConfigured( 

-

                'Expected view %s to be called with a URL keyword argument ' 

-

                'named "%s". Fix your URL conf, or set the `.lookup_field` ' 

-

                'attribute on the view correctly.' % 

-

                (self.__class__.__name__, self.lookup_field) 

-

            ) 

-

 

-

        obj = get_object_or_404(queryset, **filter_kwargs) 

-

 

-

        # May raise a permission denied 

-

        self.check_object_permissions(self.request, obj) 

-

 

-

        return obj 

-

 

-

    ######################## 

-

    ### The following are placeholder methods, 

-

    ### and are intended to be overridden. 

-

    ### 

-

    ### The are not called by GenericAPIView directly, 

-

    ### but are used by the mixin methods. 

-

 

-

    def pre_save(self, obj): 

-

        """ 

-

        Placeholder method for calling before saving an object. 

-

 

-

        May be used to set attributes on the object that are implicit 

-

        in either the request, or the url. 

-

        """ 

-

        pass 

-

 

-

    def post_save(self, obj, created=False): 

-

        """ 

-

        Placeholder method for calling after saving an object. 

-

        """ 

-

        pass 

-

 

-

    def metadata(self, request): 

-

        """ 

-

        Return a dictionary of metadata about the view. 

-

        Used to return responses for OPTIONS requests. 

-

 

-

        We override the default behavior, and add some extra information 

-

        about the required request body for POST and PUT operations. 

-

        """ 

-

        ret = super(GenericAPIView, self).metadata(request) 

-

 

-

        actions = {} 

-

        for method in ('PUT', 'POST'): 

-

            if method not in self.allowed_methods: 

-

                continue 

-

 

-

            cloned_request = clone_request(request, method) 

-

            try: 

-

                # Test global permissions 

-

                self.check_permissions(cloned_request) 

-

                # Test object permissions 

-

                if method == 'PUT': 

-

                    self.get_object() 

-

            except (exceptions.APIException, PermissionDenied, Http404): 

-

                pass 

-

            else: 

-

                # If user has appropriate permissions for the view, include 

-

                # appropriate metadata about the fields that should be supplied. 

-

                serializer = self.get_serializer() 

-

                actions[method] = serializer.metadata() 

-

 

-

        if actions: 

-

            ret['actions'] = actions 

-

 

-

        return ret 

-

 

-

 

-

########################################################## 

-

### Concrete view classes that provide method handlers ### 

-

### by composing the mixin classes with the base view. ### 

-

########################################################## 

-

 

-

class CreateAPIView(mixins.CreateModelMixin, 

-

                    GenericAPIView): 

-

 

-

    """ 

-

    Concrete view for creating a model instance. 

-

    """ 

-

    def post(self, request, *args, **kwargs): 

-

        return self.create(request, *args, **kwargs) 

-

 

-

 

-

class ListAPIView(mixins.ListModelMixin, 

-

                  GenericAPIView): 

-

    """ 

-

    Concrete view for listing a queryset. 

-

    """ 

-

    def get(self, request, *args, **kwargs): 

-

        return self.list(request, *args, **kwargs) 

-

 

-

 

-

class RetrieveAPIView(mixins.RetrieveModelMixin, 

-

                      GenericAPIView): 

-

    """ 

-

    Concrete view for retrieving a model instance. 

-

    """ 

-

    def get(self, request, *args, **kwargs): 

-

        return self.retrieve(request, *args, **kwargs) 

-

 

-

 

-

class DestroyAPIView(mixins.DestroyModelMixin, 

-

                     GenericAPIView): 

-

 

-

    """ 

-

    Concrete view for deleting a model instance. 

-

    """ 

-

    def delete(self, request, *args, **kwargs): 

-

        return self.destroy(request, *args, **kwargs) 

-

 

-

 

-

class UpdateAPIView(mixins.UpdateModelMixin, 

-

                    GenericAPIView): 

-

 

-

    """ 

-

    Concrete view for updating a model instance. 

-

    """ 

-

    def put(self, request, *args, **kwargs): 

-

        return self.update(request, *args, **kwargs) 

-

 

-

    def patch(self, request, *args, **kwargs): 

-

        return self.partial_update(request, *args, **kwargs) 

-

 

-

 

-

class ListCreateAPIView(mixins.ListModelMixin, 

-

                        mixins.CreateModelMixin, 

-

                        GenericAPIView): 

-

    """ 

-

    Concrete view for listing a queryset or creating a model instance. 

-

    """ 

-

    def get(self, request, *args, **kwargs): 

-

        return self.list(request, *args, **kwargs) 

-

 

-

    def post(self, request, *args, **kwargs): 

-

        return self.create(request, *args, **kwargs) 

-

 

-

 

-

class RetrieveUpdateAPIView(mixins.RetrieveModelMixin, 

-

                            mixins.UpdateModelMixin, 

-

                            GenericAPIView): 

-

    """ 

-

    Concrete view for retrieving, updating a model instance. 

-

    """ 

-

    def get(self, request, *args, **kwargs): 

-

        return self.retrieve(request, *args, **kwargs) 

-

 

-

    def put(self, request, *args, **kwargs): 

-

        return self.update(request, *args, **kwargs) 

-

 

-

    def patch(self, request, *args, **kwargs): 

-

        return self.partial_update(request, *args, **kwargs) 

-

 

-

 

-

class RetrieveDestroyAPIView(mixins.RetrieveModelMixin, 

-

                             mixins.DestroyModelMixin, 

-

                             GenericAPIView): 

-

    """ 

-

    Concrete view for retrieving or deleting a model instance. 

-

    """ 

-

    def get(self, request, *args, **kwargs): 

-

        return self.retrieve(request, *args, **kwargs) 

-

 

-

    def delete(self, request, *args, **kwargs): 

-

        return self.destroy(request, *args, **kwargs) 

-

 

-

 

-

class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, 

-

                                   mixins.UpdateModelMixin, 

-

                                   mixins.DestroyModelMixin, 

-

                                   GenericAPIView): 

-

    """ 

-

    Concrete view for retrieving, updating or deleting a model instance. 

-

    """ 

-

    def get(self, request, *args, **kwargs): 

-

        return self.retrieve(request, *args, **kwargs) 

-

 

-

    def put(self, request, *args, **kwargs): 

-

        return self.update(request, *args, **kwargs) 

-

 

-

    def patch(self, request, *args, **kwargs): 

-

        return self.partial_update(request, *args, **kwargs) 

-

 

-

    def delete(self, request, *args, **kwargs): 

-

        return self.destroy(request, *args, **kwargs) 

-

 

-

 

-

########################## 

-

### Deprecated classes ### 

-

########################## 

-

 

-

class MultipleObjectAPIView(GenericAPIView): 

-

    def __init__(self, *args, **kwargs): 

-

        warnings.warn( 

-

            'Subclassing `MultipleObjectAPIView` is due to be deprecated. ' 

-

            'You should simply subclass `GenericAPIView` instead.', 

-

            PendingDeprecationWarning, stacklevel=2 

-

        ) 

-

        super(MultipleObjectAPIView, self).__init__(*args, **kwargs) 

-

 

-

 

-

class SingleObjectAPIView(GenericAPIView): 

-

    def __init__(self, *args, **kwargs): 

-

        warnings.warn( 

-

            'Subclassing `SingleObjectAPIView` is due to be deprecated. ' 

-

            'You should simply subclass `GenericAPIView` instead.', 

-

            PendingDeprecationWarning, stacklevel=2 

-

        ) 

-

        super(SingleObjectAPIView, self).__init__(*args, **kwargs) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_mixins.html b/htmlcov/rest_framework_mixins.html deleted file mode 100644 index fa62f2ae8..000000000 --- a/htmlcov/rest_framework_mixins.html +++ /dev/null @@ -1,449 +0,0 @@ - - - - - - - - Coverage for rest_framework/mixins: 93% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

- -
-

""" 

-

Basic building blocks for generic class based views. 

-

 

-

We don't bind behaviour to http method handlers yet, 

-

which allows mixin classes to be composed in interesting ways. 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

from django.http import Http404 

-

from rest_framework import status 

-

from rest_framework.response import Response 

-

from rest_framework.request import clone_request 

-

import warnings 

-

 

-

 

-

def _get_validation_exclusions(obj, pk=None, slug_field=None, lookup_field=None): 

-

    """ 

-

    Given a model instance, and an optional pk and slug field, 

-

    return the full list of all other field names on that model. 

-

 

-

    For use when performing full_clean on a model instance, 

-

    so we only clean the required fields. 

-

    """ 

-

    include = [] 

-

 

-

    if pk: 

-

        # Pending deprecation 

-

        pk_field = obj._meta.pk 

-

        while pk_field.rel: 

-

            pk_field = pk_field.rel.to._meta.pk 

-

        include.append(pk_field.name) 

-

 

-

    if slug_field: 

-

        # Pending deprecation 

-

        include.append(slug_field) 

-

 

-

    if lookup_field and lookup_field != 'pk': 

-

        include.append(lookup_field) 

-

 

-

    return [field.name for field in obj._meta.fields if field.name not in include] 

-

 

-

 

-

class CreateModelMixin(object): 

-

    """ 

-

    Create a model instance. 

-

    """ 

-

    def create(self, request, *args, **kwargs): 

-

        serializer = self.get_serializer(data=request.DATA, files=request.FILES) 

-

 

-

        if serializer.is_valid(): 

-

            self.pre_save(serializer.object) 

-

            self.object = serializer.save(force_insert=True) 

-

            self.post_save(self.object, created=True) 

-

            headers = self.get_success_headers(serializer.data) 

-

            return Response(serializer.data, status=status.HTTP_201_CREATED, 

-

                            headers=headers) 

-

 

-

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 

-

 

-

    def get_success_headers(self, data): 

-

        try: 

-

            return {'Location': data['url']} 

-

        except (TypeError, KeyError): 

-

            return {} 

-

 

-

 

-

class ListModelMixin(object): 

-

    """ 

-

    List a queryset. 

-

    """ 

-

    empty_error = "Empty list and '%(class_name)s.allow_empty' is False." 

-

 

-

    def list(self, request, *args, **kwargs): 

-

        self.object_list = self.filter_queryset(self.get_queryset()) 

-

 

-

        # Default is to allow empty querysets.  This can be altered by setting 

-

        # `.allow_empty = False`, to raise 404 errors on empty querysets. 

-

        if not self.allow_empty and not self.object_list: 

-

            warnings.warn( 

-

                'The `allow_empty` parameter is due to be deprecated. ' 

-

                'To use `allow_empty=False` style behavior, You should override ' 

-

                '`get_queryset()` and explicitly raise a 404 on empty querysets.', 

-

                PendingDeprecationWarning 

-

            ) 

-

            class_name = self.__class__.__name__ 

-

            error_msg = self.empty_error % {'class_name': class_name} 

-

            raise Http404(error_msg) 

-

 

-

        # Switch between paginated or standard style responses 

-

        page = self.paginate_queryset(self.object_list) 

-

        if page is not None: 

-

            serializer = self.get_pagination_serializer(page) 

-

        else: 

-

            serializer = self.get_serializer(self.object_list, many=True) 

-

 

-

        return Response(serializer.data) 

-

 

-

 

-

class RetrieveModelMixin(object): 

-

    """ 

-

    Retrieve a model instance. 

-

    """ 

-

    def retrieve(self, request, *args, **kwargs): 

-

        self.object = self.get_object() 

-

        serializer = self.get_serializer(self.object) 

-

        return Response(serializer.data) 

-

 

-

 

-

class UpdateModelMixin(object): 

-

    """ 

-

    Update a model instance. 

-

    """ 

-

    def update(self, request, *args, **kwargs): 

-

        partial = kwargs.pop('partial', False) 

-

        self.object = self.get_object_or_none() 

-

 

-

        if self.object is None: 

-

            created = True 

-

            save_kwargs = {'force_insert': True} 

-

            success_status_code = status.HTTP_201_CREATED 

-

        else: 

-

            created = False 

-

            save_kwargs = {'force_update': True} 

-

            success_status_code = status.HTTP_200_OK 

-

 

-

        serializer = self.get_serializer(self.object, data=request.DATA, 

-

                                         files=request.FILES, partial=partial) 

-

 

-

        if serializer.is_valid(): 

-

            self.pre_save(serializer.object) 

-

            self.object = serializer.save(**save_kwargs) 

-

            self.post_save(self.object, created=created) 

-

            return Response(serializer.data, status=success_status_code) 

-

 

-

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 

-

 

-

    def partial_update(self, request, *args, **kwargs): 

-

        kwargs['partial'] = True 

-

        return self.update(request, *args, **kwargs) 

-

 

-

    def get_object_or_none(self): 

-

        try: 

-

            return self.get_object() 

-

        except Http404: 

-

            # If this is a PUT-as-create operation, we need to ensure that 

-

            # we have relevant permissions, as if this was a POST request. 

-

            # This will either raise a PermissionDenied exception, 

-

            # or simply return None 

-

            self.check_permissions(clone_request(self.request, 'POST')) 

-

 

-

    def pre_save(self, obj): 

-

        """ 

-

        Set any attributes on the object that are implicit in the request. 

-

        """ 

-

        # pk and/or slug attributes are implicit in the URL. 

-

        lookup = self.kwargs.get(self.lookup_field, None) 

-

        pk = self.kwargs.get(self.pk_url_kwarg, None) 

-

        slug = self.kwargs.get(self.slug_url_kwarg, None) 

-

        slug_field = slug and self.slug_field or None 

-

 

-

        if lookup: 

-

            setattr(obj, self.lookup_field, lookup) 

-

 

-

        if pk: 

-

            setattr(obj, 'pk', pk) 

-

 

-

        if slug: 

-

            setattr(obj, slug_field, slug) 

-

 

-

        # Ensure we clean the attributes so that we don't eg return integer 

-

        # pk using a string representation, as provided by the url conf kwarg. 

-

        if hasattr(obj, 'full_clean'): 

-

            exclude = _get_validation_exclusions(obj, pk, slug_field, self.lookup_field) 

-

            obj.full_clean(exclude) 

-

 

-

 

-

class DestroyModelMixin(object): 

-

    """ 

-

    Destroy a model instance. 

-

    """ 

-

    def destroy(self, request, *args, **kwargs): 

-

        obj = self.get_object() 

-

        obj.delete() 

-

        return Response(status=status.HTTP_204_NO_CONTENT) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_models.html b/htmlcov/rest_framework_models.html deleted file mode 100644 index 6786c620a..000000000 --- a/htmlcov/rest_framework_models.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - Coverage for rest_framework/models: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

- -
-

# Just to keep things like ./manage.py test happy 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_negotiation.html b/htmlcov/rest_framework_negotiation.html deleted file mode 100644 index 7ed526c97..000000000 --- a/htmlcov/rest_framework_negotiation.html +++ /dev/null @@ -1,259 +0,0 @@ - - - - - - - - Coverage for rest_framework/negotiation: 90% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

- -
-

""" 

-

Content negotiation deals with selecting an appropriate renderer given the 

-

incoming request.  Typically this will be based on the request's Accept header. 

-

""" 

-

from __future__ import unicode_literals 

-

from django.http import Http404 

-

from rest_framework import exceptions 

-

from rest_framework.settings import api_settings 

-

from rest_framework.utils.mediatypes import order_by_precedence, media_type_matches 

-

from rest_framework.utils.mediatypes import _MediaType 

-

 

-

 

-

class BaseContentNegotiation(object): 

-

    def select_parser(self, request, parsers): 

-

        raise NotImplementedError('.select_parser() must be implemented') 

-

 

-

    def select_renderer(self, request, renderers, format_suffix=None): 

-

        raise NotImplementedError('.select_renderer() must be implemented') 

-

 

-

 

-

class DefaultContentNegotiation(BaseContentNegotiation): 

-

    settings = api_settings 

-

 

-

    def select_parser(self, request, parsers): 

-

        """ 

-

        Given a list of parsers and a media type, return the appropriate 

-

        parser to handle the incoming request. 

-

        """ 

-

        for parser in parsers: 

-

            if media_type_matches(parser.media_type, request.content_type): 

-

                return parser 

-

        return None 

-

 

-

    def select_renderer(self, request, renderers, format_suffix=None): 

-

        """ 

-

        Given a request and a list of renderers, return a two-tuple of: 

-

        (renderer, media type). 

-

        """ 

-

        # Allow URL style format override.  eg. "?format=json 

-

        format_query_param = self.settings.URL_FORMAT_OVERRIDE 

-

        format = format_suffix or request.QUERY_PARAMS.get(format_query_param) 

-

 

-

        if format: 

-

            renderers = self.filter_renderers(renderers, format) 

-

 

-

        accepts = self.get_accept_list(request) 

-

 

-

        # Check the acceptable media types against each renderer, 

-

        # attempting more specific media types first 

-

        # NB. The inner loop here isn't as bad as it first looks :) 

-

        #     Worst case is we're looping over len(accept_list) * len(self.renderers) 

-

        for media_type_set in order_by_precedence(accepts): 

-

            for renderer in renderers: 

-

                for media_type in media_type_set: 

-

                    if media_type_matches(renderer.media_type, media_type): 

-

                        # Return the most specific media type as accepted. 

-

                        if (_MediaType(renderer.media_type).precedence > 

-

                            _MediaType(media_type).precedence): 

-

                            # Eg client requests '*/*' 

-

                            # Accepted media type is 'application/json' 

-

                            return renderer, renderer.media_type 

-

                        else: 

-

                            # Eg client requests 'application/json; indent=8' 

-

                            # Accepted media type is 'application/json; indent=8' 

-

                            return renderer, media_type 

-

 

-

        raise exceptions.NotAcceptable(available_renderers=renderers) 

-

 

-

    def filter_renderers(self, renderers, format): 

-

        """ 

-

        If there is a '.json' style format suffix, filter the renderers 

-

        so that we only negotiation against those that accept that format. 

-

        """ 

-

        renderers = [renderer for renderer in renderers 

-

                     if renderer.format == format] 

-

        if not renderers: 

-

            raise Http404 

-

        return renderers 

-

 

-

    def get_accept_list(self, request): 

-

        """ 

-

        Given the incoming request, return a tokenised list of media 

-

        type strings. 

-

 

-

        Allows URL style accept override.  eg. "?accept=application/json" 

-

        """ 

-

        header = request.META.get('HTTP_ACCEPT', '*/*') 

-

        header = request.QUERY_PARAMS.get(self.settings.URL_ACCEPT_OVERRIDE, header) 

-

        return [token.strip() for token in header.split(',')] 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_pagination.html b/htmlcov/rest_framework_pagination.html deleted file mode 100644 index 5a3f76d82..000000000 --- a/htmlcov/rest_framework_pagination.html +++ /dev/null @@ -1,269 +0,0 @@ - - - - - - - - Coverage for rest_framework/pagination: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

- -
-

""" 

-

Pagination serializers determine the structure of the output that should 

-

be used for paginated responses. 

-

""" 

-

from __future__ import unicode_literals 

-

from rest_framework import serializers 

-

from rest_framework.templatetags.rest_framework import replace_query_param 

-

 

-

 

-

class NextPageField(serializers.Field): 

-

    """ 

-

    Field that returns a link to the next page in paginated results. 

-

    """ 

-

    page_field = 'page' 

-

 

-

    def to_native(self, value): 

-

        if not value.has_next(): 

-

            return None 

-

        page = value.next_page_number() 

-

        request = self.context.get('request') 

-

        url = request and request.build_absolute_uri() or '' 

-

        return replace_query_param(url, self.page_field, page) 

-

 

-

 

-

class PreviousPageField(serializers.Field): 

-

    """ 

-

    Field that returns a link to the previous page in paginated results. 

-

    """ 

-

    page_field = 'page' 

-

 

-

    def to_native(self, value): 

-

        if not value.has_previous(): 

-

            return None 

-

        page = value.previous_page_number() 

-

        request = self.context.get('request') 

-

        url = request and request.build_absolute_uri() or '' 

-

        return replace_query_param(url, self.page_field, page) 

-

 

-

 

-

class DefaultObjectSerializer(serializers.Field): 

-

    """ 

-

    If no object serializer is specified, then this serializer will be applied 

-

    as the default. 

-

    """ 

-

 

-

    def __init__(self, source=None, context=None): 

-

        # Note: Swallow context kwarg - only required for eg. ModelSerializer. 

-

        super(DefaultObjectSerializer, self).__init__(source=source) 

-

 

-

 

-

class PaginationSerializerOptions(serializers.SerializerOptions): 

-

    """ 

-

    An object that stores the options that may be provided to a 

-

    pagination serializer by using the inner `Meta` class. 

-

 

-

    Accessible on the instance as `serializer.opts`. 

-

    """ 

-

    def __init__(self, meta): 

-

        super(PaginationSerializerOptions, self).__init__(meta) 

-

        self.object_serializer_class = getattr(meta, 'object_serializer_class', 

-

                                               DefaultObjectSerializer) 

-

 

-

 

-

class BasePaginationSerializer(serializers.Serializer): 

-

    """ 

-

    A base class for pagination serializers to inherit from, 

-

    to make implementing custom serializers more easy. 

-

    """ 

-

    _options_class = PaginationSerializerOptions 

-

    results_field = 'results' 

-

 

-

    def __init__(self, *args, **kwargs): 

-

        """ 

-

        Override init to add in the object serializer field on-the-fly. 

-

        """ 

-

        super(BasePaginationSerializer, self).__init__(*args, **kwargs) 

-

        results_field = self.results_field 

-

        object_serializer = self.opts.object_serializer_class 

-

 

-

        if 'context' in kwargs: 

-

            context_kwarg = {'context': kwargs['context']} 

-

        else: 

-

            context_kwarg = {} 

-

 

-

        self.fields[results_field] = object_serializer(source='object_list', **context_kwarg) 

-

 

-

 

-

class PaginationSerializer(BasePaginationSerializer): 

-

    """ 

-

    A default implementation of a pagination serializer. 

-

    """ 

-

    count = serializers.Field(source='paginator.count') 

-

    next = NextPageField(source='*') 

-

    previous = PreviousPageField(source='*') 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_parsers.html b/htmlcov/rest_framework_parsers.html deleted file mode 100644 index 92f1db62d..000000000 --- a/htmlcov/rest_framework_parsers.html +++ /dev/null @@ -1,671 +0,0 @@ - - - - - - - - Coverage for rest_framework/parsers: 92% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

- -
-

""" 

-

Parsers are used to parse the content of incoming HTTP requests. 

-

 

-

They give us a generic way of being able to handle various media types 

-

on the request, such as form content or json encoded data. 

-

""" 

-

from __future__ import unicode_literals 

-

from django.conf import settings 

-

from django.core.files.uploadhandler import StopFutureHandlers 

-

from django.http import QueryDict 

-

from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser 

-

from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter 

-

from rest_framework.compat import yaml, etree 

-

from rest_framework.exceptions import ParseError 

-

from rest_framework.compat import six 

-

import json 

-

import datetime 

-

import decimal 

-

 

-

 

-

class DataAndFiles(object): 

-

    def __init__(self, data, files): 

-

        self.data = data 

-

        self.files = files 

-

 

-

 

-

class BaseParser(object): 

-

    """ 

-

    All parsers should extend `BaseParser`, specifying a `media_type` 

-

    attribute, and overriding the `.parse()` method. 

-

    """ 

-

 

-

    media_type = None 

-

 

-

    def parse(self, stream, media_type=None, parser_context=None): 

-

        """ 

-

        Given a stream to read from, return the parsed representation. 

-

        Should return parsed data, or a `DataAndFiles` object consisting of the 

-

        parsed data and files. 

-

        """ 

-

        raise NotImplementedError(".parse() must be overridden.") 

-

 

-

 

-

class JSONParser(BaseParser): 

-

    """ 

-

    Parses JSON-serialized data. 

-

    """ 

-

 

-

    media_type = 'application/json' 

-

 

-

    def parse(self, stream, media_type=None, parser_context=None): 

-

        """ 

-

        Returns a 2-tuple of `(data, files)`. 

-

 

-

        `data` will be an object which is the parsed content of the response. 

-

        `files` will always be `None`. 

-

        """ 

-

        parser_context = parser_context or {} 

-

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

-

 

-

        try: 

-

            data = stream.read().decode(encoding) 

-

            return json.loads(data) 

-

        except ValueError as exc: 

-

            raise ParseError('JSON parse error - %s' % six.text_type(exc)) 

-

 

-

 

-

class YAMLParser(BaseParser): 

-

    """ 

-

    Parses YAML-serialized data. 

-

    """ 

-

 

-

    media_type = 'application/yaml' 

-

 

-

    def parse(self, stream, media_type=None, parser_context=None): 

-

        """ 

-

        Returns a 2-tuple of `(data, files)`. 

-

 

-

        `data` will be an object which is the parsed content of the response. 

-

        `files` will always be `None`. 

-

        """ 

-

        assert yaml, 'YAMLParser requires pyyaml to be installed' 

-

 

-

        parser_context = parser_context or {} 

-

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

-

 

-

        try: 

-

            data = stream.read().decode(encoding) 

-

            return yaml.safe_load(data) 

-

        except (ValueError, yaml.parser.ParserError) as exc: 

-

            raise ParseError('YAML parse error - %s' % six.u(exc)) 

-

 

-

 

-

class FormParser(BaseParser): 

-

    """ 

-

    Parser for form data. 

-

    """ 

-

 

-

    media_type = 'application/x-www-form-urlencoded' 

-

 

-

    def parse(self, stream, media_type=None, parser_context=None): 

-

        """ 

-

        Returns a 2-tuple of `(data, files)`. 

-

 

-

        `data` will be a :class:`QueryDict` containing all the form parameters. 

-

        `files` will always be :const:`None`. 

-

        """ 

-

        parser_context = parser_context or {} 

-

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

-

        data = QueryDict(stream.read(), encoding=encoding) 

-

        return data 

-

 

-

 

-

class MultiPartParser(BaseParser): 

-

    """ 

-

    Parser for multipart form data, which may include file data. 

-

    """ 

-

 

-

    media_type = 'multipart/form-data' 

-

 

-

    def parse(self, stream, media_type=None, parser_context=None): 

-

        """ 

-

        Returns a DataAndFiles object. 

-

 

-

        `.data` will be a `QueryDict` containing all the form parameters. 

-

        `.files` will be a `QueryDict` containing all the form files. 

-

        """ 

-

        parser_context = parser_context or {} 

-

        request = parser_context['request'] 

-

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

-

        meta = request.META 

-

        upload_handlers = request.upload_handlers 

-

 

-

        try: 

-

            parser = DjangoMultiPartParser(meta, stream, upload_handlers, encoding) 

-

            data, files = parser.parse() 

-

            return DataAndFiles(data, files) 

-

        except MultiPartParserError as exc: 

-

            raise ParseError('Multipart form parse error - %s' % six.u(exc)) 

-

 

-

 

-

class XMLParser(BaseParser): 

-

    """ 

-

    XML parser. 

-

    """ 

-

 

-

    media_type = 'application/xml' 

-

 

-

    def parse(self, stream, media_type=None, parser_context=None): 

-

        assert etree, 'XMLParser requires defusedxml to be installed' 

-

 

-

        parser_context = parser_context or {} 

-

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

-

        parser = etree.DefusedXMLParser(encoding=encoding) 

-

        try: 

-

            tree = etree.parse(stream, parser=parser, forbid_dtd=True) 

-

        except (etree.ParseError, ValueError) as exc: 

-

            raise ParseError('XML parse error - %s' % six.u(exc)) 

-

        data = self._xml_convert(tree.getroot()) 

-

 

-

        return data 

-

 

-

    def _xml_convert(self, element): 

-

        """ 

-

        convert the xml `element` into the corresponding python object 

-

        """ 

-

 

-

        children = list(element) 

-

 

-

        if len(children) == 0: 

-

            return self._type_convert(element.text) 

-

        else: 

-

            # if the fist child tag is list-item means all children are list-item 

-

            if children[0].tag == "list-item": 

-

                data = [] 

-

                for child in children: 

-

                    data.append(self._xml_convert(child)) 

-

            else: 

-

                data = {} 

-

                for child in children: 

-

                    data[child.tag] = self._xml_convert(child) 

-

 

-

            return data 

-

 

-

    def _type_convert(self, value): 

-

        """ 

-

        Converts the value returned by the XMl parse into the equivalent 

-

        Python type 

-

        """ 

-

        if value is None: 

-

            return value 

-

 

-

        try: 

-

            return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S') 

-

        except ValueError: 

-

            pass 

-

 

-

        try: 

-

            return int(value) 

-

        except ValueError: 

-

            pass 

-

 

-

        try: 

-

            return decimal.Decimal(value) 

-

        except decimal.InvalidOperation: 

-

            pass 

-

 

-

        return value 

-

 

-

 

-

class FileUploadParser(BaseParser): 

-

    """ 

-

    Parser for file upload data. 

-

    """ 

-

    media_type = '*/*' 

-

 

-

    def parse(self, stream, media_type=None, parser_context=None): 

-

        """ 

-

        Returns a DataAndFiles object. 

-

 

-

        `.data` will be None (we expect request body to be a file content). 

-

        `.files` will be a `QueryDict` containing one 'file' element. 

-

        """ 

-

 

-

        parser_context = parser_context or {} 

-

        request = parser_context['request'] 

-

        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) 

-

        meta = request.META 

-

        upload_handlers = request.upload_handlers 

-

        filename = self.get_filename(stream, media_type, parser_context) 

-

 

-

        # Note that this code is extracted from Django's handling of 

-

        # file uploads in MultiPartParser. 

-

        content_type = meta.get('HTTP_CONTENT_TYPE', 

-

                                meta.get('CONTENT_TYPE', '')) 

-

        try: 

-

            content_length = int(meta.get('HTTP_CONTENT_LENGTH', 

-

                                          meta.get('CONTENT_LENGTH', 0))) 

-

        except (ValueError, TypeError): 

-

            content_length = None 

-

 

-

        # See if the handler will want to take care of the parsing. 

-

        for handler in upload_handlers: 

-

            result = handler.handle_raw_input(None, 

-

                                              meta, 

-

                                              content_length, 

-

                                              None, 

-

                                              encoding) 

-

            if result is not None: 

-

                return DataAndFiles(None, {'file': result[1]}) 

-

 

-

        # This is the standard case. 

-

        possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size] 

-

        chunk_size = min([2 ** 31 - 4] + possible_sizes) 

-

        chunks = ChunkIter(stream, chunk_size) 

-

        counters = [0] * len(upload_handlers) 

-

 

-

        for handler in upload_handlers: 

-

            try: 

-

                handler.new_file(None, filename, content_type, 

-

                                 content_length, encoding) 

-

            except StopFutureHandlers: 

-

                break 

-

 

-

        for chunk in chunks: 

-

            for i, handler in enumerate(upload_handlers): 

-

                chunk_length = len(chunk) 

-

                chunk = handler.receive_data_chunk(chunk, counters[i]) 

-

                counters[i] += chunk_length 

-

                if chunk is None: 

-

                    break 

-

 

-

        for i, handler in enumerate(upload_handlers): 

-

            file_obj = handler.file_complete(counters[i]) 

-

            if file_obj: 

-

                return DataAndFiles(None, {'file': file_obj}) 

-

        raise ParseError("FileUpload parse error - " 

-

                         "none of upload handlers can handle the stream") 

-

 

-

    def get_filename(self, stream, media_type, parser_context): 

-

        """ 

-

        Detects the uploaded file name. First searches a 'filename' url kwarg. 

-

        Then tries to parse Content-Disposition header. 

-

        """ 

-

        try: 

-

            return parser_context['kwargs']['filename'] 

-

        except KeyError: 

-

            pass 

-

 

-

        try: 

-

            meta = parser_context['request'].META 

-

            disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION']) 

-

            return disposition[1]['filename'] 

-

        except (AttributeError, KeyError): 

-

            pass 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_permissions.html b/htmlcov/rest_framework_permissions.html deleted file mode 100644 index 20a29522b..000000000 --- a/htmlcov/rest_framework_permissions.html +++ /dev/null @@ -1,429 +0,0 @@ - - - - - - - - Coverage for rest_framework/permissions: 81% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

- -
-

""" 

-

Provides a set of pluggable permission policies. 

-

""" 

-

from __future__ import unicode_literals 

-

import inspect 

-

import warnings 

-

 

-

SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS'] 

-

 

-

from rest_framework.compat import oauth2_provider_scope, oauth2_constants 

-

 

-

 

-

class BasePermission(object): 

-

    """ 

-

    A base class from which all permission classes should inherit. 

-

    """ 

-

 

-

    def has_permission(self, request, view): 

-

        """ 

-

        Return `True` if permission is granted, `False` otherwise. 

-

        """ 

-

        return True 

-

 

-

    def has_object_permission(self, request, view, obj): 

-

        """ 

-

        Return `True` if permission is granted, `False` otherwise. 

-

        """ 

-

        if len(inspect.getargspec(self.has_permission).args) == 4: 

-

            warnings.warn( 

-

                'The `obj` argument in `has_permission` is deprecated. ' 

-

                'Use `has_object_permission()` instead for object permissions.', 

-

                DeprecationWarning, stacklevel=2 

-

            ) 

-

            return self.has_permission(request, view, obj) 

-

        return True 

-

 

-

 

-

class AllowAny(BasePermission): 

-

    """ 

-

    Allow any access. 

-

    This isn't strictly required, since you could use an empty 

-

    permission_classes list, but it's useful because it makes the intention 

-

    more explicit. 

-

    """ 

-

    def has_permission(self, request, view): 

-

        return True 

-

 

-

 

-

class IsAuthenticated(BasePermission): 

-

    """ 

-

    Allows access only to authenticated users. 

-

    """ 

-

 

-

    def has_permission(self, request, view): 

-

        if request.user and request.user.is_authenticated(): 

-

            return True 

-

        return False 

-

 

-

 

-

class IsAdminUser(BasePermission): 

-

    """ 

-

    Allows access only to admin users. 

-

    """ 

-

 

-

    def has_permission(self, request, view): 

-

        if request.user and request.user.is_staff: 

-

            return True 

-

        return False 

-

 

-

 

-

class IsAuthenticatedOrReadOnly(BasePermission): 

-

    """ 

-

    The request is authenticated as a user, or is a read-only request. 

-

    """ 

-

 

-

    def has_permission(self, request, view): 

-

        if (request.method in SAFE_METHODS or 

-

            request.user and 

-

            request.user.is_authenticated()): 

-

            return True 

-

        return False 

-

 

-

 

-

class DjangoModelPermissions(BasePermission): 

-

    """ 

-

    The request is authenticated using `django.contrib.auth` permissions. 

-

    See: https://docs.djangoproject.com/en/dev/topics/auth/#permissions 

-

 

-

    It ensures that the user is authenticated, and has the appropriate 

-

    `add`/`change`/`delete` permissions on the model. 

-

 

-

    This permission can only be applied against view classes that 

-

    provide a `.model` or `.queryset` attribute. 

-

    """ 

-

 

-

    # Map methods into required permission codes. 

-

    # Override this if you need to also provide 'view' permissions, 

-

    # or if you want to provide custom permission codes. 

-

    perms_map = { 

-

        'GET': [], 

-

        'OPTIONS': [], 

-

        'HEAD': [], 

-

        'POST': ['%(app_label)s.add_%(model_name)s'], 

-

        'PUT': ['%(app_label)s.change_%(model_name)s'], 

-

        'PATCH': ['%(app_label)s.change_%(model_name)s'], 

-

        'DELETE': ['%(app_label)s.delete_%(model_name)s'], 

-

    } 

-

 

-

    authenticated_users_only = True 

-

 

-

    def get_required_permissions(self, method, model_cls): 

-

        """ 

-

        Given a model and an HTTP method, return the list of permission 

-

        codes that the user is required to have. 

-

        """ 

-

        kwargs = { 

-

            'app_label': model_cls._meta.app_label, 

-

            'model_name': model_cls._meta.module_name 

-

        } 

-

        return [perm % kwargs for perm in self.perms_map[method]] 

-

 

-

    def has_permission(self, request, view): 

-

        model_cls = getattr(view, 'model', None) 

-

        queryset = getattr(view, 'queryset', None) 

-

 

-

        if model_cls is None and queryset is not None: 

-

            model_cls = queryset.model 

-

 

-

        # Workaround to ensure DjangoModelPermissions are not applied 

-

        # to the root view when using DefaultRouter. 

-

        if model_cls is None and getattr(view, '_ignore_model_permissions', False): 

-

            return True 

-

 

-

        assert model_cls, ('Cannot apply DjangoModelPermissions on a view that' 

-

                           ' does not have `.model` or `.queryset` property.') 

-

 

-

        perms = self.get_required_permissions(request.method, model_cls) 

-

 

-

        if (request.user and 

-

            (request.user.is_authenticated() or not self.authenticated_users_only) and 

-

            request.user.has_perms(perms)): 

-

            return True 

-

        return False 

-

 

-

 

-

class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions): 

-

    """ 

-

    Similar to DjangoModelPermissions, except that anonymous users are 

-

    allowed read-only access. 

-

    """ 

-

    authenticated_users_only = False 

-

 

-

 

-

class TokenHasReadWriteScope(BasePermission): 

-

    """ 

-

    The request is authenticated as a user and the token used has the right scope 

-

    """ 

-

 

-

    def has_permission(self, request, view): 

-

        token = request.auth 

-

        read_only = request.method in SAFE_METHODS 

-

 

-

        if not token: 

-

            return False 

-

 

-

        if hasattr(token, 'resource'):  # OAuth 1 

-

            return read_only or not request.auth.resource.is_readonly 

-

        elif hasattr(token, 'scope'):  # OAuth 2 

-

            required = oauth2_constants.READ if read_only else oauth2_constants.WRITE 

-

            return oauth2_provider_scope.check(required, request.auth.scope) 

-

 

-

        assert False, ('TokenHasReadWriteScope requires either the' 

-

        '`OAuthAuthentication` or `OAuth2Authentication` authentication ' 

-

        'class to be used.') 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_relations.html b/htmlcov/rest_framework_relations.html deleted file mode 100644 index 29ad3cf65..000000000 --- a/htmlcov/rest_framework_relations.html +++ /dev/null @@ -1,1347 +0,0 @@ - - - - - - - - Coverage for rest_framework/relations: 76% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

-

296

-

297

-

298

-

299

-

300

-

301

-

302

-

303

-

304

-

305

-

306

-

307

-

308

-

309

-

310

-

311

-

312

-

313

-

314

-

315

-

316

-

317

-

318

-

319

-

320

-

321

-

322

-

323

-

324

-

325

-

326

-

327

-

328

-

329

-

330

-

331

-

332

-

333

-

334

-

335

-

336

-

337

-

338

-

339

-

340

-

341

-

342

-

343

-

344

-

345

-

346

-

347

-

348

-

349

-

350

-

351

-

352

-

353

-

354

-

355

-

356

-

357

-

358

-

359

-

360

-

361

-

362

-

363

-

364

-

365

-

366

-

367

-

368

-

369

-

370

-

371

-

372

-

373

-

374

-

375

-

376

-

377

-

378

-

379

-

380

-

381

-

382

-

383

-

384

-

385

-

386

-

387

-

388

-

389

-

390

-

391

-

392

-

393

-

394

-

395

-

396

-

397

-

398

-

399

-

400

-

401

-

402

-

403

-

404

-

405

-

406

-

407

-

408

-

409

-

410

-

411

-

412

-

413

-

414

-

415

-

416

-

417

-

418

-

419

-

420

-

421

-

422

-

423

-

424

-

425

-

426

-

427

-

428

-

429

-

430

-

431

-

432

-

433

-

434

-

435

-

436

-

437

-

438

-

439

-

440

-

441

-

442

-

443

-

444

-

445

-

446

-

447

-

448

-

449

-

450

-

451

-

452

-

453

-

454

-

455

-

456

-

457

-

458

-

459

-

460

-

461

-

462

-

463

-

464

-

465

-

466

-

467

-

468

-

469

-

470

-

471

-

472

-

473

-

474

-

475

-

476

-

477

-

478

-

479

-

480

-

481

-

482

-

483

-

484

-

485

-

486

-

487

-

488

-

489

-

490

-

491

-

492

-

493

-

494

-

495

-

496

-

497

-

498

-

499

-

500

-

501

-

502

-

503

-

504

-

505

-

506

-

507

-

508

-

509

-

510

-

511

-

512

-

513

-

514

-

515

-

516

-

517

-

518

-

519

-

520

-

521

-

522

-

523

-

524

-

525

-

526

-

527

-

528

-

529

-

530

-

531

-

532

-

533

-

534

-

535

-

536

-

537

-

538

-

539

-

540

-

541

-

542

-

543

-

544

-

545

-

546

-

547

-

548

-

549

-

550

-

551

-

552

-

553

-

554

-

555

-

556

-

557

-

558

-

559

-

560

-

561

-

562

-

563

-

564

-

565

-

566

-

567

-

568

-

569

-

570

-

571

-

572

-

573

-

574

-

575

-

576

-

577

-

578

-

579

-

580

-

581

-

582

-

583

-

584

-

585

-

586

-

587

-

588

-

589

-

590

-

591

-

592

-

593

-

594

-

595

-

596

-

597

-

598

-

599

-

600

-

601

-

602

-

603

-

604

-

605

-

606

-

607

-

608

-

609

-

610

-

611

-

612

-

613

-

614

-

615

-

616

-

617

-

618

-

619

-

620

-

621

-

622

-

623

-

624

-

625

-

626

-

627

-

628

-

629

-

630

-

631

-

632

-

633

- -
-

""" 

-

Serializer fields that deal with relationships. 

-

 

-

These fields allow you to specify the style that should be used to represent 

-

model relationships, including hyperlinks, primary keys, or slugs. 

-

""" 

-

from __future__ import unicode_literals 

-

from django.core.exceptions import ObjectDoesNotExist, ValidationError 

-

from django.core.urlresolvers import resolve, get_script_prefix, NoReverseMatch 

-

from django import forms 

-

from django.db.models.fields import BLANK_CHOICE_DASH 

-

from django.forms import widgets 

-

from django.forms.models import ModelChoiceIterator 

-

from django.utils.translation import ugettext_lazy as _ 

-

from rest_framework.fields import Field, WritableField, get_component, is_simple_callable 

-

from rest_framework.reverse import reverse 

-

from rest_framework.compat import urlparse 

-

from rest_framework.compat import smart_text 

-

import warnings 

-

 

-

 

-

##### Relational fields ##### 

-

 

-

 

-

# Not actually Writable, but subclasses may need to be. 

-

class RelatedField(WritableField): 

-

    """ 

-

    Base class for related model fields. 

-

 

-

    This represents a relationship using the unicode representation of the target. 

-

    """ 

-

    widget = widgets.Select 

-

    many_widget = widgets.SelectMultiple 

-

    form_field_class = forms.ChoiceField 

-

    many_form_field_class = forms.MultipleChoiceField 

-

 

-

    cache_choices = False 

-

    empty_label = None 

-

    read_only = True 

-

    many = False 

-

 

-

    def __init__(self, *args, **kwargs): 

-

 

-

        # 'null' is to be deprecated in favor of 'required' 

-

        if 'null' in kwargs: 

-

            warnings.warn('The `null` keyword argument is deprecated. ' 

-

                          'Use the `required` keyword argument instead.', 

-

                          DeprecationWarning, stacklevel=2) 

-

            kwargs['required'] = not kwargs.pop('null') 

-

 

-

        queryset = kwargs.pop('queryset', None) 

-

        self.many = kwargs.pop('many', self.many) 

-

        if self.many: 

-

            self.widget = self.many_widget 

-

            self.form_field_class = self.many_form_field_class 

-

 

-

        kwargs['read_only'] = kwargs.pop('read_only', self.read_only) 

-

        super(RelatedField, self).__init__(*args, **kwargs) 

-

 

-

        if not self.required: 

-

            self.empty_label = BLANK_CHOICE_DASH[0][1] 

-

 

-

        self.queryset = queryset 

-

 

-

    def initialize(self, parent, field_name): 

-

        super(RelatedField, self).initialize(parent, field_name) 

-

        if self.queryset is None and not self.read_only: 

-

            try: 

-

                manager = getattr(self.parent.opts.model, self.source or field_name) 

-

                if hasattr(manager, 'related'):  # Forward 

-

                    self.queryset = manager.related.model._default_manager.all() 

-

                else:  # Reverse 

-

                    self.queryset = manager.field.rel.to._default_manager.all() 

-

            except Exception: 

-

                msg = ('Serializer related fields must include a `queryset`' + 

-

                       ' argument or set `read_only=True') 

-

                raise Exception(msg) 

-

 

-

    ### We need this stuff to make form choices work... 

-

 

-

    def prepare_value(self, obj): 

-

        return self.to_native(obj) 

-

 

-

    def label_from_instance(self, obj): 

-

        """ 

-

        Return a readable representation for use with eg. select widgets. 

-

        """ 

-

        desc = smart_text(obj) 

-

        ident = smart_text(self.to_native(obj)) 

-

        if desc == ident: 

-

            return desc 

-

        return "%s - %s" % (desc, ident) 

-

 

-

    def _get_queryset(self): 

-

        return self._queryset 

-

 

-

    def _set_queryset(self, queryset): 

-

        self._queryset = queryset 

-

        self.widget.choices = self.choices 

-

 

-

    queryset = property(_get_queryset, _set_queryset) 

-

 

-

    def _get_choices(self): 

-

        # If self._choices is set, then somebody must have manually set 

-

        # the property self.choices. In this case, just return self._choices. 

-

        if hasattr(self, '_choices'): 

-

            return self._choices 

-

 

-

        # Otherwise, execute the QuerySet in self.queryset to determine the 

-

        # choices dynamically. Return a fresh ModelChoiceIterator that has not been 

-

        # consumed. Note that we're instantiating a new ModelChoiceIterator *each* 

-

        # time _get_choices() is called (and, thus, each time self.choices is 

-

        # accessed) so that we can ensure the QuerySet has not been consumed. This 

-

        # construct might look complicated but it allows for lazy evaluation of 

-

        # the queryset. 

-

        return ModelChoiceIterator(self) 

-

 

-

    def _set_choices(self, value): 

-

        # Setting choices also sets the choices on the widget. 

-

        # choices can be any iterable, but we call list() on it because 

-

        # it will be consumed more than once. 

-

        self._choices = self.widget.choices = list(value) 

-

 

-

    choices = property(_get_choices, _set_choices) 

-

 

-

    ### Regular serializer stuff... 

-

 

-

    def field_to_native(self, obj, field_name): 

-

        try: 

-

            if self.source == '*': 

-

                return self.to_native(obj) 

-

 

-

            source = self.source or field_name 

-

            value = obj 

-

 

-

            for component in source.split('.'): 

-

                value = get_component(value, component) 

-

                if value is None: 

-

                    break 

-

        except ObjectDoesNotExist: 

-

            return None 

-

 

-

        if value is None: 

-

            return None 

-

 

-

        if self.many: 

-

            if is_simple_callable(getattr(value, 'all', None)): 

-

                return [self.to_native(item) for item in value.all()] 

-

            else: 

-

                # Also support non-queryset iterables. 

-

                # This allows us to also support plain lists of related items. 

-

                return [self.to_native(item) for item in value] 

-

        return self.to_native(value) 

-

 

-

    def field_from_native(self, data, files, field_name, into): 

-

        if self.read_only: 

-

            return 

-

 

-

        try: 

-

            if self.many: 

-

                try: 

-

                    # Form data 

-

                    value = data.getlist(field_name) 

-

                    if value == [''] or value == []: 

-

                        raise KeyError 

-

                except AttributeError: 

-

                    # Non-form data 

-

                    value = data[field_name] 

-

            else: 

-

                value = data[field_name] 

-

        except KeyError: 

-

            if self.partial: 

-

                return 

-

            value = [] if self.many else None 

-

 

-

        if value in (None, '') and self.required: 

-

            raise ValidationError(self.error_messages['required']) 

-

        elif value in (None, ''): 

-

            into[(self.source or field_name)] = None 

-

        elif self.many: 

-

            into[(self.source or field_name)] = [self.from_native(item) for item in value] 

-

        else: 

-

            into[(self.source or field_name)] = self.from_native(value) 

-

 

-

 

-

### PrimaryKey relationships 

-

 

-

class PrimaryKeyRelatedField(RelatedField): 

-

    """ 

-

    Represents a relationship as a pk value. 

-

    """ 

-

    read_only = False 

-

 

-

    default_error_messages = { 

-

        'does_not_exist': _("Invalid pk '%s' - object does not exist."), 

-

        'incorrect_type': _('Incorrect type.  Expected pk value, received %s.'), 

-

    } 

-

 

-

    # TODO: Remove these field hacks... 

-

    def prepare_value(self, obj): 

-

        return self.to_native(obj.pk) 

-

 

-

    def label_from_instance(self, obj): 

-

        """ 

-

        Return a readable representation for use with eg. select widgets. 

-

        """ 

-

        desc = smart_text(obj) 

-

        ident = smart_text(self.to_native(obj.pk)) 

-

        if desc == ident: 

-

            return desc 

-

        return "%s - %s" % (desc, ident) 

-

 

-

    # TODO: Possibly change this to just take `obj`, through prob less performant 

-

    def to_native(self, pk): 

-

        return pk 

-

 

-

    def from_native(self, data): 

-

        if self.queryset is None: 

-

            raise Exception('Writable related fields must include a `queryset` argument') 

-

 

-

        try: 

-

            return self.queryset.get(pk=data) 

-

        except ObjectDoesNotExist: 

-

            msg = self.error_messages['does_not_exist'] % smart_text(data) 

-

            raise ValidationError(msg) 

-

        except (TypeError, ValueError): 

-

            received = type(data).__name__ 

-

            msg = self.error_messages['incorrect_type'] % received 

-

            raise ValidationError(msg) 

-

 

-

    def field_to_native(self, obj, field_name): 

-

        if self.many: 

-

            # To-many relationship 

-

 

-

            queryset = None 

-

            if not self.source: 

-

                # Prefer obj.serializable_value for performance reasons 

-

                try: 

-

                    queryset = obj.serializable_value(field_name) 

-

                except AttributeError: 

-

                    pass 

-

            if queryset is None: 

-

                # RelatedManager (reverse relationship) 

-

                source = self.source or field_name 

-

                queryset = obj 

-

                for component in source.split('.'): 

-

                    queryset = get_component(queryset, component) 

-

 

-

            # Forward relationship 

-

            if is_simple_callable(getattr(queryset, 'all', None)): 

-

                return [self.to_native(item.pk) for item in queryset.all()] 

-

            else: 

-

                # Also support non-queryset iterables. 

-

                # This allows us to also support plain lists of related items. 

-

                return [self.to_native(item.pk) for item in queryset] 

-

 

-

        # To-one relationship 

-

        try: 

-

            # Prefer obj.serializable_value for performance reasons 

-

            pk = obj.serializable_value(self.source or field_name) 

-

        except AttributeError: 

-

            # RelatedObject (reverse relationship) 

-

            try: 

-

                pk = getattr(obj, self.source or field_name).pk 

-

            except ObjectDoesNotExist: 

-

                return None 

-

 

-

        # Forward relationship 

-

        return self.to_native(pk) 

-

 

-

 

-

### Slug relationships 

-

 

-

 

-

class SlugRelatedField(RelatedField): 

-

    """ 

-

    Represents a relationship using a unique field on the target. 

-

    """ 

-

    read_only = False 

-

 

-

    default_error_messages = { 

-

        'does_not_exist': _("Object with %s=%s does not exist."), 

-

        'invalid': _('Invalid value.'), 

-

    } 

-

 

-

    def __init__(self, *args, **kwargs): 

-

        self.slug_field = kwargs.pop('slug_field', None) 

-

        assert self.slug_field, 'slug_field is required' 

-

        super(SlugRelatedField, self).__init__(*args, **kwargs) 

-

 

-

    def to_native(self, obj): 

-

        return getattr(obj, self.slug_field) 

-

 

-

    def from_native(self, data): 

-

        if self.queryset is None: 

-

            raise Exception('Writable related fields must include a `queryset` argument') 

-

 

-

        try: 

-

            return self.queryset.get(**{self.slug_field: data}) 

-

        except ObjectDoesNotExist: 

-

            raise ValidationError(self.error_messages['does_not_exist'] % 

-

                                  (self.slug_field, smart_text(data))) 

-

        except (TypeError, ValueError): 

-

            msg = self.error_messages['invalid'] 

-

            raise ValidationError(msg) 

-

 

-

 

-

### Hyperlinked relationships 

-

 

-

class HyperlinkedRelatedField(RelatedField): 

-

    """ 

-

    Represents a relationship using hyperlinking. 

-

    """ 

-

    read_only = False 

-

    lookup_field = 'pk' 

-

 

-

    default_error_messages = { 

-

        'no_match': _('Invalid hyperlink - No URL match'), 

-

        'incorrect_match': _('Invalid hyperlink - Incorrect URL match'), 

-

        'configuration_error': _('Invalid hyperlink due to configuration error'), 

-

        'does_not_exist': _("Invalid hyperlink - object does not exist."), 

-

        'incorrect_type': _('Incorrect type.  Expected url string, received %s.'), 

-

    } 

-

 

-

    # These are all pending deprecation 

-

    pk_url_kwarg = 'pk' 

-

    slug_field = 'slug' 

-

    slug_url_kwarg = None  # Defaults to same as `slug_field` unless overridden 

-

 

-

    def __init__(self, *args, **kwargs): 

-

        try: 

-

            self.view_name = kwargs.pop('view_name') 

-

        except KeyError: 

-

            raise ValueError("Hyperlinked field requires 'view_name' kwarg") 

-

 

-

        self.lookup_field = kwargs.pop('lookup_field', self.lookup_field) 

-

        self.format = kwargs.pop('format', None) 

-

 

-

        # These are pending deprecation 

-

        if 'pk_url_kwarg' in kwargs: 

-

            msg = 'pk_url_kwarg is pending deprecation. Use lookup_field instead.' 

-

            warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

-

        if 'slug_url_kwarg' in kwargs: 

-

            msg = 'slug_url_kwarg is pending deprecation. Use lookup_field instead.' 

-

            warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

-

        if 'slug_field' in kwargs: 

-

            msg = 'slug_field is pending deprecation. Use lookup_field instead.' 

-

            warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

-

 

-

        self.pk_url_kwarg = kwargs.pop('pk_url_kwarg', self.pk_url_kwarg) 

-

        self.slug_field = kwargs.pop('slug_field', self.slug_field) 

-

        default_slug_kwarg = self.slug_url_kwarg or self.slug_field 

-

        self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', default_slug_kwarg) 

-

 

-

        super(HyperlinkedRelatedField, self).__init__(*args, **kwargs) 

-

 

-

    def get_url(self, obj, view_name, request, format): 

-

        """ 

-

        Given an object, return the URL that hyperlinks to the object. 

-

 

-

        May raise a `NoReverseMatch` if the `view_name` and `lookup_field` 

-

        attributes are not configured to correctly match the URL conf. 

-

        """ 

-

        lookup_field = getattr(obj, self.lookup_field) 

-

        kwargs = {self.lookup_field: lookup_field} 

-

        try: 

-

            return reverse(view_name, kwargs=kwargs, request=request, format=format) 

-

        except NoReverseMatch: 

-

            pass 

-

 

-

        if self.pk_url_kwarg != 'pk': 

-

            # Only try pk if it has been explicitly set. 

-

            # Otherwise, the default `lookup_field = 'pk'` has us covered. 

-

            pk = obj.pk 

-

            kwargs = {self.pk_url_kwarg: pk} 

-

            try: 

-

                return reverse(view_name, kwargs=kwargs, request=request, format=format) 

-

            except NoReverseMatch: 

-

                pass 

-

 

-

        slug = getattr(obj, self.slug_field, None) 

-

        if slug is not None: 

-

            # Only try slug if it corresponds to an attribute on the object. 

-

            kwargs = {self.slug_url_kwarg: slug} 

-

            try: 

-

                ret = reverse(view_name, kwargs=kwargs, request=request, format=format) 

-

                if self.slug_field == 'slug' and self.slug_url_kwarg == 'slug': 

-

                    # If the lookup succeeds using the default slug params, 

-

                    # then `slug_field` is being used implicitly, and we 

-

                    # we need to warn about the pending deprecation. 

-

                    msg = 'Implicit slug field hyperlinked fields are pending deprecation.' \ 

-

                          'You should set `lookup_field=slug` on the HyperlinkedRelatedField.' 

-

                    warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

-

                return ret 

-

            except NoReverseMatch: 

-

                pass 

-

 

-

        raise NoReverseMatch() 

-

 

-

    def get_object(self, queryset, view_name, view_args, view_kwargs): 

-

        """ 

-

        Return the object corresponding to a matched URL. 

-

 

-

        Takes the matched URL conf arguments, and the queryset, and should 

-

        return an object instance, or raise an `ObjectDoesNotExist` exception. 

-

        """ 

-

        lookup = view_kwargs.get(self.lookup_field, None) 

-

        pk = view_kwargs.get(self.pk_url_kwarg, None) 

-

        slug = view_kwargs.get(self.slug_url_kwarg, None) 

-

 

-

        if lookup is not None: 

-

            filter_kwargs = {self.lookup_field: lookup} 

-

        elif pk is not None: 

-

            filter_kwargs = {'pk': pk} 

-

        elif slug is not None: 

-

            filter_kwargs = {self.slug_field: slug} 

-

        else: 

-

            raise ObjectDoesNotExist() 

-

 

-

        return queryset.get(**filter_kwargs) 

-

 

-

    def to_native(self, obj): 

-

        view_name = self.view_name 

-

        request = self.context.get('request', None) 

-

        format = self.format or self.context.get('format', None) 

-

 

-

        if request is None: 

-

            msg = ( 

-

                "Using `HyperlinkedRelatedField` without including the request " 

-

                "in the serializer context is deprecated. " 

-

                "Add `context={'request': request}` when instantiating " 

-

                "the serializer." 

-

            ) 

-

            warnings.warn(msg, DeprecationWarning, stacklevel=4) 

-

 

-

        # If the object has not yet been saved then we cannot hyperlink to it. 

-

        if getattr(obj, 'pk', None) is None: 

-

            return 

-

 

-

        # Return the hyperlink, or error if incorrectly configured. 

-

        try: 

-

            return self.get_url(obj, view_name, request, format) 

-

        except NoReverseMatch: 

-

            msg = ( 

-

                'Could not resolve URL for hyperlinked relationship using ' 

-

                'view name "%s". You may have failed to include the related ' 

-

                'model in your API, or incorrectly configured the ' 

-

                '`lookup_field` attribute on this field.' 

-

            ) 

-

            raise Exception(msg % view_name) 

-

 

-

    def from_native(self, value): 

-

        # Convert URL -> model instance pk 

-

        # TODO: Use values_list 

-

        queryset = self.queryset 

-

        if queryset is None: 

-

            raise Exception('Writable related fields must include a `queryset` argument') 

-

 

-

        try: 

-

            http_prefix = value.startswith(('http:', 'https:')) 

-

        except AttributeError: 

-

            msg = self.error_messages['incorrect_type'] 

-

            raise ValidationError(msg % type(value).__name__) 

-

 

-

        if http_prefix: 

-

            # If needed convert absolute URLs to relative path 

-

            value = urlparse.urlparse(value).path 

-

            prefix = get_script_prefix() 

-

            if value.startswith(prefix): 

-

                value = '/' + value[len(prefix):] 

-

 

-

        try: 

-

            match = resolve(value) 

-

        except Exception: 

-

            raise ValidationError(self.error_messages['no_match']) 

-

 

-

        if match.view_name != self.view_name: 

-

            raise ValidationError(self.error_messages['incorrect_match']) 

-

 

-

        try: 

-

            return self.get_object(queryset, match.view_name, 

-

                                   match.args, match.kwargs) 

-

        except (ObjectDoesNotExist, TypeError, ValueError): 

-

            raise ValidationError(self.error_messages['does_not_exist']) 

-

 

-

 

-

class HyperlinkedIdentityField(Field): 

-

    """ 

-

    Represents the instance, or a property on the instance, using hyperlinking. 

-

    """ 

-

    lookup_field = 'pk' 

-

    read_only = True 

-

 

-

    # These are all pending deprecation 

-

    pk_url_kwarg = 'pk' 

-

    slug_field = 'slug' 

-

    slug_url_kwarg = None  # Defaults to same as `slug_field` unless overridden 

-

 

-

    def __init__(self, *args, **kwargs): 

-

        try: 

-

            self.view_name = kwargs.pop('view_name') 

-

        except KeyError: 

-

            msg = "HyperlinkedIdentityField requires 'view_name' argument" 

-

            raise ValueError(msg) 

-

 

-

        self.format = kwargs.pop('format', None) 

-

        lookup_field = kwargs.pop('lookup_field', None) 

-

        self.lookup_field = lookup_field or self.lookup_field 

-

 

-

        # These are pending deprecation 

-

        if 'pk_url_kwarg' in kwargs: 

-

            msg = 'pk_url_kwarg is pending deprecation. Use lookup_field instead.' 

-

            warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

-

        if 'slug_url_kwarg' in kwargs: 

-

            msg = 'slug_url_kwarg is pending deprecation. Use lookup_field instead.' 

-

            warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

-

        if 'slug_field' in kwargs: 

-

            msg = 'slug_field is pending deprecation. Use lookup_field instead.' 

-

            warnings.warn(msg, PendingDeprecationWarning, stacklevel=2) 

-

 

-

        self.slug_field = kwargs.pop('slug_field', self.slug_field) 

-

        default_slug_kwarg = self.slug_url_kwarg or self.slug_field 

-

        self.pk_url_kwarg = kwargs.pop('pk_url_kwarg', self.pk_url_kwarg) 

-

        self.slug_url_kwarg = kwargs.pop('slug_url_kwarg', default_slug_kwarg) 

-

 

-

        super(HyperlinkedIdentityField, self).__init__(*args, **kwargs) 

-

 

-

    def field_to_native(self, obj, field_name): 

-

        request = self.context.get('request', None) 

-

        format = self.context.get('format', None) 

-

        view_name = self.view_name 

-

 

-

        if request is None: 

-

            warnings.warn("Using `HyperlinkedIdentityField` without including the " 

-

                          "request in the serializer context is deprecated. " 

-

                          "Add `context={'request': request}` when instantiating the serializer.", 

-

                          DeprecationWarning, stacklevel=4) 

-

 

-

        # By default use whatever format is given for the current context 

-

        # unless the target is a different type to the source. 

-

        # 

-

        # Eg. Consider a HyperlinkedIdentityField pointing from a json 

-

        # representation to an html property of that representation... 

-

        # 

-

        # '/snippets/1/' should link to '/snippets/1/highlight/' 

-

        # ...but... 

-

        # '/snippets/1/.json' should link to '/snippets/1/highlight/.html' 

-

        if format and self.format and self.format != format: 

-

            format = self.format 

-

 

-

        # Return the hyperlink, or error if incorrectly configured. 

-

        try: 

-

            return self.get_url(obj, view_name, request, format) 

-

        except NoReverseMatch: 

-

            msg = ( 

-

                'Could not resolve URL for hyperlinked relationship using ' 

-

                'view name "%s". You may have failed to include the related ' 

-

                'model in your API, or incorrectly configured the ' 

-

                '`lookup_field` attribute on this field.' 

-

            ) 

-

            raise Exception(msg % view_name) 

-

 

-

    def get_url(self, obj, view_name, request, format): 

-

        """ 

-

        Given an object, return the URL that hyperlinks to the object. 

-

 

-

        May raise a `NoReverseMatch` if the `view_name` and `lookup_field` 

-

        attributes are not configured to correctly match the URL conf. 

-

        """ 

-

        lookup_field = getattr(obj, self.lookup_field) 

-

        kwargs = {self.lookup_field: lookup_field} 

-

        try: 

-

            return reverse(view_name, kwargs=kwargs, request=request, format=format) 

-

        except NoReverseMatch: 

-

            pass 

-

 

-

        if self.pk_url_kwarg != 'pk': 

-

            # Only try pk lookup if it has been explicitly set. 

-

            # Otherwise, the default `lookup_field = 'pk'` has us covered. 

-

            kwargs = {self.pk_url_kwarg: obj.pk} 

-

            try: 

-

                return reverse(view_name, kwargs=kwargs, request=request, format=format) 

-

            except NoReverseMatch: 

-

                pass 

-

 

-

        slug = getattr(obj, self.slug_field, None) 

-

        if slug: 

-

            # Only use slug lookup if a slug field exists on the model 

-

            kwargs = {self.slug_url_kwarg: slug} 

-

            try: 

-

                return reverse(view_name, kwargs=kwargs, request=request, format=format) 

-

            except NoReverseMatch: 

-

                pass 

-

 

-

        raise NoReverseMatch() 

-

 

-

 

-

### Old-style many classes for backwards compat 

-

 

-

class ManyRelatedField(RelatedField): 

-

    def __init__(self, *args, **kwargs): 

-

        warnings.warn('`ManyRelatedField()` is deprecated. ' 

-

                      'Use `RelatedField(many=True)` instead.', 

-

                       DeprecationWarning, stacklevel=2) 

-

        kwargs['many'] = True 

-

        super(ManyRelatedField, self).__init__(*args, **kwargs) 

-

 

-

 

-

class ManyPrimaryKeyRelatedField(PrimaryKeyRelatedField): 

-

    def __init__(self, *args, **kwargs): 

-

        warnings.warn('`ManyPrimaryKeyRelatedField()` is deprecated. ' 

-

                      'Use `PrimaryKeyRelatedField(many=True)` instead.', 

-

                       DeprecationWarning, stacklevel=2) 

-

        kwargs['many'] = True 

-

        super(ManyPrimaryKeyRelatedField, self).__init__(*args, **kwargs) 

-

 

-

 

-

class ManySlugRelatedField(SlugRelatedField): 

-

    def __init__(self, *args, **kwargs): 

-

        warnings.warn('`ManySlugRelatedField()` is deprecated. ' 

-

                      'Use `SlugRelatedField(many=True)` instead.', 

-

                       DeprecationWarning, stacklevel=2) 

-

        kwargs['many'] = True 

-

        super(ManySlugRelatedField, self).__init__(*args, **kwargs) 

-

 

-

 

-

class ManyHyperlinkedRelatedField(HyperlinkedRelatedField): 

-

    def __init__(self, *args, **kwargs): 

-

        warnings.warn('`ManyHyperlinkedRelatedField()` is deprecated. ' 

-

                      'Use `HyperlinkedRelatedField(many=True)` instead.', 

-

                       DeprecationWarning, stacklevel=2) 

-

        kwargs['many'] = True 

-

        super(ManyHyperlinkedRelatedField, self).__init__(*args, **kwargs) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_renderers.html b/htmlcov/rest_framework_renderers.html deleted file mode 100644 index 58c71b855..000000000 --- a/htmlcov/rest_framework_renderers.html +++ /dev/null @@ -1,1227 +0,0 @@ - - - - - - - - Coverage for rest_framework/renderers: 92% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

-

296

-

297

-

298

-

299

-

300

-

301

-

302

-

303

-

304

-

305

-

306

-

307

-

308

-

309

-

310

-

311

-

312

-

313

-

314

-

315

-

316

-

317

-

318

-

319

-

320

-

321

-

322

-

323

-

324

-

325

-

326

-

327

-

328

-

329

-

330

-

331

-

332

-

333

-

334

-

335

-

336

-

337

-

338

-

339

-

340

-

341

-

342

-

343

-

344

-

345

-

346

-

347

-

348

-

349

-

350

-

351

-

352

-

353

-

354

-

355

-

356

-

357

-

358

-

359

-

360

-

361

-

362

-

363

-

364

-

365

-

366

-

367

-

368

-

369

-

370

-

371

-

372

-

373

-

374

-

375

-

376

-

377

-

378

-

379

-

380

-

381

-

382

-

383

-

384

-

385

-

386

-

387

-

388

-

389

-

390

-

391

-

392

-

393

-

394

-

395

-

396

-

397

-

398

-

399

-

400

-

401

-

402

-

403

-

404

-

405

-

406

-

407

-

408

-

409

-

410

-

411

-

412

-

413

-

414

-

415

-

416

-

417

-

418

-

419

-

420

-

421

-

422

-

423

-

424

-

425

-

426

-

427

-

428

-

429

-

430

-

431

-

432

-

433

-

434

-

435

-

436

-

437

-

438

-

439

-

440

-

441

-

442

-

443

-

444

-

445

-

446

-

447

-

448

-

449

-

450

-

451

-

452

-

453

-

454

-

455

-

456

-

457

-

458

-

459

-

460

-

461

-

462

-

463

-

464

-

465

-

466

-

467

-

468

-

469

-

470

-

471

-

472

-

473

-

474

-

475

-

476

-

477

-

478

-

479

-

480

-

481

-

482

-

483

-

484

-

485

-

486

-

487

-

488

-

489

-

490

-

491

-

492

-

493

-

494

-

495

-

496

-

497

-

498

-

499

-

500

-

501

-

502

-

503

-

504

-

505

-

506

-

507

-

508

-

509

-

510

-

511

-

512

-

513

-

514

-

515

-

516

-

517

-

518

-

519

-

520

-

521

-

522

-

523

-

524

-

525

-

526

-

527

-

528

-

529

-

530

-

531

-

532

-

533

-

534

-

535

-

536

-

537

-

538

-

539

-

540

-

541

-

542

-

543

-

544

-

545

-

546

-

547

-

548

-

549

-

550

-

551

-

552

-

553

-

554

-

555

-

556

-

557

-

558

-

559

-

560

-

561

-

562

-

563

-

564

-

565

-

566

-

567

-

568

-

569

-

570

-

571

-

572

-

573

- -
-

""" 

-

Renderers are used to serialize a response into specific media types. 

-

 

-

They give us a generic way of being able to handle various media types 

-

on the response, such as JSON encoded data or HTML output. 

-

 

-

REST framework also provides an HTML renderer the renders the browsable API. 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

import copy 

-

import json 

-

from django import forms 

-

from django.core.exceptions import ImproperlyConfigured 

-

from django.http.multipartparser import parse_header 

-

from django.template import RequestContext, loader, Template 

-

from django.utils.xmlutils import SimplerXMLGenerator 

-

from rest_framework.compat import StringIO 

-

from rest_framework.compat import six 

-

from rest_framework.compat import smart_text 

-

from rest_framework.compat import yaml 

-

from rest_framework.settings import api_settings 

-

from rest_framework.request import clone_request 

-

from rest_framework.utils import encoders 

-

from rest_framework.utils.breadcrumbs import get_breadcrumbs 

-

from rest_framework.utils.formatting import get_view_name, get_view_description 

-

from rest_framework import exceptions, parsers, status, VERSION 

-

 

-

 

-

class BaseRenderer(object): 

-

    """ 

-

    All renderers should extend this class, setting the `media_type` 

-

    and `format` attributes, and override the `.render()` method. 

-

    """ 

-

 

-

    media_type = None 

-

    format = None 

-

    charset = 'utf-8' 

-

 

-

    def render(self, data, accepted_media_type=None, renderer_context=None): 

-

        raise NotImplemented('Renderer class requires .render() to be implemented') 

-

 

-

 

-

class JSONRenderer(BaseRenderer): 

-

    """ 

-

    Renderer which serializes to JSON. 

-

    Applies JSON's backslash-u character escaping for non-ascii characters. 

-

    """ 

-

 

-

    media_type = 'application/json' 

-

    format = 'json' 

-

    encoder_class = encoders.JSONEncoder 

-

    ensure_ascii = True 

-

    charset = 'utf-8' 

-

    # Note that JSON encodings must be utf-8, utf-16 or utf-32. 

-

    # See: http://www.ietf.org/rfc/rfc4627.txt 

-

 

-

    def render(self, data, accepted_media_type=None, renderer_context=None): 

-

        """ 

-

        Render `data` into JSON. 

-

        """ 

-

        if data is None: 

-

            return '' 

-

 

-

        # If 'indent' is provided in the context, then pretty print the result. 

-

        # E.g. If we're being called by the BrowsableAPIRenderer. 

-

        renderer_context = renderer_context or {} 

-

        indent = renderer_context.get('indent', None) 

-

 

-

        if accepted_media_type: 

-

            # If the media type looks like 'application/json; indent=4', 

-

            # then pretty print the result. 

-

            base_media_type, params = parse_header(accepted_media_type.encode('ascii')) 

-

            indent = params.get('indent', indent) 

-

            try: 

-

                indent = max(min(int(indent), 8), 0) 

-

            except (ValueError, TypeError): 

-

                indent = None 

-

 

-

        ret = json.dumps(data, cls=self.encoder_class, 

-

            indent=indent, ensure_ascii=self.ensure_ascii) 

-

 

-

        # On python 2.x json.dumps() returns bytestrings if ensure_ascii=True, 

-

        # but if ensure_ascii=False, the return type is underspecified, 

-

        # and may (or may not) be unicode. 

-

        # On python 3.x json.dumps() returns unicode strings. 

-

        if isinstance(ret, six.text_type): 

-

            return bytes(ret.encode(self.charset)) 

-

        return ret 

-

 

-

 

-

class UnicodeJSONRenderer(JSONRenderer): 

-

    ensure_ascii = False 

-

    charset = 'utf-8' 

-

    """ 

-

    Renderer which serializes to JSON. 

-

    Does *not* apply JSON's character escaping for non-ascii characters. 

-

    """ 

-

 

-

 

-

class JSONPRenderer(JSONRenderer): 

-

    """ 

-

    Renderer which serializes to json, 

-

    wrapping the json output in a callback function. 

-

    """ 

-

 

-

    media_type = 'application/javascript' 

-

    format = 'jsonp' 

-

    callback_parameter = 'callback' 

-

    default_callback = 'callback' 

-

 

-

    def get_callback(self, renderer_context): 

-

        """ 

-

        Determine the name of the callback to wrap around the json output. 

-

        """ 

-

        request = renderer_context.get('request', None) 

-

        params = request and request.QUERY_PARAMS or {} 

-

        return params.get(self.callback_parameter, self.default_callback) 

-

 

-

    def render(self, data, accepted_media_type=None, renderer_context=None): 

-

        """ 

-

        Renders into jsonp, wrapping the json output in a callback function. 

-

 

-

        Clients may set the callback function name using a query parameter 

-

        on the URL, for example: ?callback=exampleCallbackName 

-

        """ 

-

        renderer_context = renderer_context or {} 

-

        callback = self.get_callback(renderer_context) 

-

        json = super(JSONPRenderer, self).render(data, accepted_media_type, 

-

                                                 renderer_context) 

-

        return callback.encode(self.charset) + b'(' + json + b');' 

-

 

-

 

-

class XMLRenderer(BaseRenderer): 

-

    """ 

-

    Renderer which serializes to XML. 

-

    """ 

-

 

-

    media_type = 'application/xml' 

-

    format = 'xml' 

-

    charset = 'utf-8' 

-

 

-

    def render(self, data, accepted_media_type=None, renderer_context=None): 

-

        """ 

-

        Renders *obj* into serialized XML. 

-

        """ 

-

        if data is None: 

-

            return '' 

-

 

-

        stream = StringIO() 

-

 

-

        xml = SimplerXMLGenerator(stream, self.charset) 

-

        xml.startDocument() 

-

        xml.startElement("root", {}) 

-

 

-

        self._to_xml(xml, data) 

-

 

-

        xml.endElement("root") 

-

        xml.endDocument() 

-

        return stream.getvalue() 

-

 

-

    def _to_xml(self, xml, data): 

-

        if isinstance(data, (list, tuple)): 

-

            for item in data: 

-

                xml.startElement("list-item", {}) 

-

                self._to_xml(xml, item) 

-

                xml.endElement("list-item") 

-

 

-

        elif isinstance(data, dict): 

-

            for key, value in six.iteritems(data): 

-

                xml.startElement(key, {}) 

-

                self._to_xml(xml, value) 

-

                xml.endElement(key) 

-

 

-

        elif data is None: 

-

            # Don't output any value 

-

            pass 

-

 

-

        else: 

-

            xml.characters(smart_text(data)) 

-

 

-

 

-

class YAMLRenderer(BaseRenderer): 

-

    """ 

-

    Renderer which serializes to YAML. 

-

    """ 

-

 

-

    media_type = 'application/yaml' 

-

    format = 'yaml' 

-

    encoder = encoders.SafeDumper 

-

    charset = 'utf-8' 

-

 

-

    def render(self, data, accepted_media_type=None, renderer_context=None): 

-

        """ 

-

        Renders *obj* into serialized YAML. 

-

        """ 

-

        assert yaml, 'YAMLRenderer requires pyyaml to be installed' 

-

 

-

        if data is None: 

-

            return '' 

-

 

-

        return yaml.dump(data, stream=None, encoding=self.charset, Dumper=self.encoder) 

-

 

-

 

-

class TemplateHTMLRenderer(BaseRenderer): 

-

    """ 

-

    An HTML renderer for use with templates. 

-

 

-

    The data supplied to the Response object should be a dictionary that will 

-

    be used as context for the template. 

-

 

-

    The template name is determined by (in order of preference): 

-

 

-

    1. An explicit `.template_name` attribute set on the response. 

-

    2. An explicit `.template_name` attribute set on this class. 

-

    3. The return result of calling `view.get_template_names()`. 

-

 

-

    For example: 

-

        data = {'users': User.objects.all()} 

-

        return Response(data, template_name='users.html') 

-

 

-

    For pre-rendered HTML, see StaticHTMLRenderer. 

-

    """ 

-

 

-

    media_type = 'text/html' 

-

    format = 'html' 

-

    template_name = None 

-

    exception_template_names = [ 

-

        '%(status_code)s.html', 

-

        'api_exception.html' 

-

    ] 

-

    charset = 'utf-8' 

-

 

-

    def render(self, data, accepted_media_type=None, renderer_context=None): 

-

        """ 

-

        Renders data to HTML, using Django's standard template rendering. 

-

 

-

        The template name is determined by (in order of preference): 

-

 

-

        1. An explicit .template_name set on the response. 

-

        2. An explicit .template_name set on this class. 

-

        3. The return result of calling view.get_template_names(). 

-

        """ 

-

        renderer_context = renderer_context or {} 

-

        view = renderer_context['view'] 

-

        request = renderer_context['request'] 

-

        response = renderer_context['response'] 

-

 

-

        if response.exception: 

-

            template = self.get_exception_template(response) 

-

        else: 

-

            template_names = self.get_template_names(response, view) 

-

            template = self.resolve_template(template_names) 

-

 

-

        context = self.resolve_context(data, request, response) 

-

        return template.render(context) 

-

 

-

    def resolve_template(self, template_names): 

-

        return loader.select_template(template_names) 

-

 

-

    def resolve_context(self, data, request, response): 

-

        if response.exception: 

-

            data['status_code'] = response.status_code 

-

        return RequestContext(request, data) 

-

 

-

    def get_template_names(self, response, view): 

-

        if response.template_name: 

-

            return [response.template_name] 

-

        elif self.template_name: 

-

            return [self.template_name] 

-

        elif hasattr(view, 'get_template_names'): 

-

            return view.get_template_names() 

-

        raise ImproperlyConfigured('Returned a template response with no template_name') 

-

 

-

    def get_exception_template(self, response): 

-

        template_names = [name % {'status_code': response.status_code} 

-

                          for name in self.exception_template_names] 

-

 

-

        try: 

-

            # Try to find an appropriate error template 

-

            return self.resolve_template(template_names) 

-

        except Exception: 

-

            # Fall back to using eg '404 Not Found' 

-

            return Template('%d %s' % (response.status_code, 

-

                                       response.status_text.title())) 

-

 

-

 

-

# Note, subclass TemplateHTMLRenderer simply for the exception behavior 

-

class StaticHTMLRenderer(TemplateHTMLRenderer): 

-

    """ 

-

    An HTML renderer class that simply returns pre-rendered HTML. 

-

 

-

    The data supplied to the Response object should be a string representing 

-

    the pre-rendered HTML content. 

-

 

-

    For example: 

-

        data = '<html><body>example</body></html>' 

-

        return Response(data) 

-

 

-

    For template rendered HTML, see TemplateHTMLRenderer. 

-

    """ 

-

    media_type = 'text/html' 

-

    format = 'html' 

-

    charset = 'utf-8' 

-

 

-

    def render(self, data, accepted_media_type=None, renderer_context=None): 

-

        renderer_context = renderer_context or {} 

-

        response = renderer_context['response'] 

-

 

-

        if response and response.exception: 

-

            request = renderer_context['request'] 

-

            template = self.get_exception_template(response) 

-

            context = self.resolve_context(data, request, response) 

-

            return template.render(context) 

-

 

-

        return data 

-

 

-

 

-

class BrowsableAPIRenderer(BaseRenderer): 

-

    """ 

-

    HTML renderer used to self-document the API. 

-

    """ 

-

    media_type = 'text/html' 

-

    format = 'api' 

-

    template = 'rest_framework/api.html' 

-

    charset = 'utf-8' 

-

 

-

    def get_default_renderer(self, view): 

-

        """ 

-

        Return an instance of the first valid renderer. 

-

        (Don't use another documenting renderer.) 

-

        """ 

-

        renderers = [renderer for renderer in view.renderer_classes 

-

                     if not issubclass(renderer, BrowsableAPIRenderer)] 

-

        if not renderers: 

-

            return None 

-

        return renderers[0]() 

-

 

-

    def get_content(self, renderer, data, 

-

                    accepted_media_type, renderer_context): 

-

        """ 

-

        Get the content as if it had been rendered by the default 

-

        non-documenting renderer. 

-

        """ 

-

        if not renderer: 

-

            return '[No renderers were found]' 

-

 

-

        renderer_context['indent'] = 4 

-

        content = renderer.render(data, accepted_media_type, renderer_context) 

-

 

-

        if renderer.charset is None: 

-

            return '[%d bytes of binary content]' % len(content) 

-

 

-

        return content 

-

 

-

    def show_form_for_method(self, view, method, request, obj): 

-

        """ 

-

        Returns True if a form should be shown for this method. 

-

        """ 

-

        if not method in view.allowed_methods: 

-

            return  # Not a valid method 

-

 

-

        if not api_settings.FORM_METHOD_OVERRIDE: 

-

            return  # Cannot use form overloading 

-

 

-

        try: 

-

            view.check_permissions(request) 

-

            if obj is not None: 

-

                view.check_object_permissions(request, obj) 

-

        except exceptions.APIException: 

-

            return False  # Doesn't have permissions 

-

        return True 

-

 

-

    def serializer_to_form_fields(self, serializer): 

-

        fields = {} 

-

        for k, v in serializer.get_fields().items(): 

-

            if getattr(v, 'read_only', True): 

-

                continue 

-

 

-

            kwargs = {} 

-

            kwargs['required'] = v.required 

-

 

-

            #if getattr(v, 'queryset', None): 

-

            #    kwargs['queryset'] = v.queryset 

-

 

-

            if getattr(v, 'choices', None) is not None: 

-

                kwargs['choices'] = v.choices 

-

 

-

            if getattr(v, 'regex', None) is not None: 

-

                kwargs['regex'] = v.regex 

-

 

-

            if getattr(v, 'widget', None): 

-

                widget = copy.deepcopy(v.widget) 

-

                kwargs['widget'] = widget 

-

 

-

            if getattr(v, 'default', None) is not None: 

-

                kwargs['initial'] = v.default 

-

 

-

            if getattr(v, 'label', None) is not None: 

-

                kwargs['label'] = v.label 

-

 

-

            if getattr(v, 'help_text', None) is not None: 

-

                kwargs['help_text'] = v.help_text 

-

 

-

            fields[k] = v.form_field_class(**kwargs) 

-

 

-

        return fields 

-

 

-

    def _get_form(self, view, method, request): 

-

        # We need to impersonate a request with the correct method, 

-

        # so that eg. any dynamic get_serializer_class methods return the 

-

        # correct form for each method. 

-

        restore = view.request 

-

        request = clone_request(request, method) 

-

        view.request = request 

-

        try: 

-

            return self.get_form(view, method, request) 

-

        finally: 

-

            view.request = restore 

-

 

-

    def _get_raw_data_form(self, view, method, request, media_types): 

-

        # We need to impersonate a request with the correct method, 

-

        # so that eg. any dynamic get_serializer_class methods return the 

-

        # correct form for each method. 

-

        restore = view.request 

-

        request = clone_request(request, method) 

-

        view.request = request 

-

        try: 

-

            return self.get_raw_data_form(view, method, request, media_types) 

-

        finally: 

-

            view.request = restore 

-

 

-

    def get_form(self, view, method, request): 

-

        """ 

-

        Get a form, possibly bound to either the input or output data. 

-

        In the absence on of the Resource having an associated form then 

-

        provide a form that can be used to submit arbitrary content. 

-

        """ 

-

        obj = getattr(view, 'object', None) 

-

        if not self.show_form_for_method(view, method, request, obj): 

-

            return 

-

 

-

        if method in ('DELETE', 'OPTIONS'): 

-

            return True  # Don't actually need to return a form 

-

 

-

        if not getattr(view, 'get_serializer', None) or not parsers.FormParser in view.parser_classes: 

-

            return 

-

 

-

        serializer = view.get_serializer(instance=obj) 

-

        fields = self.serializer_to_form_fields(serializer) 

-

 

-

        # Creating an on the fly form see: 

-

        # http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python 

-

        OnTheFlyForm = type(str("OnTheFlyForm"), (forms.Form,), fields) 

-

        data = (obj is not None) and serializer.data or None 

-

        form_instance = OnTheFlyForm(data) 

-

        return form_instance 

-

 

-

    def get_raw_data_form(self, view, method, request, media_types): 

-

        """ 

-

        Returns a form that allows for arbitrary content types to be tunneled 

-

        via standard HTML forms. 

-

        (Which are typically application/x-www-form-urlencoded) 

-

        """ 

-

 

-

        # If we're not using content overloading there's no point in supplying a generic form, 

-

        # as the view won't treat the form's value as the content of the request. 

-

        if not (api_settings.FORM_CONTENT_OVERRIDE 

-

                and api_settings.FORM_CONTENTTYPE_OVERRIDE): 

-

            return None 

-

 

-

        # Check permissions 

-

        obj = getattr(view, 'object', None) 

-

        if not self.show_form_for_method(view, method, request, obj): 

-

            return 

-

 

-

        content_type_field = api_settings.FORM_CONTENTTYPE_OVERRIDE 

-

        content_field = api_settings.FORM_CONTENT_OVERRIDE 

-

        choices = [(media_type, media_type) for media_type in media_types] 

-

        initial = media_types[0] 

-

 

-

        # NB. http://jacobian.org/writing/dynamic-form-generation/ 

-

        class GenericContentForm(forms.Form): 

-

            def __init__(self): 

-

                super(GenericContentForm, self).__init__() 

-

 

-

                self.fields[content_type_field] = forms.ChoiceField( 

-

                    label='Media type', 

-

                    choices=choices, 

-

                    initial=initial 

-

                ) 

-

                self.fields[content_field] = forms.CharField( 

-

                    label='Content', 

-

                    widget=forms.Textarea 

-

                ) 

-

 

-

        return GenericContentForm() 

-

 

-

    def get_name(self, view): 

-

        return get_view_name(view.__class__, getattr(view, 'suffix', None)) 

-

 

-

    def get_description(self, view): 

-

        return get_view_description(view.__class__, html=True) 

-

 

-

    def get_breadcrumbs(self, request): 

-

        return get_breadcrumbs(request.path) 

-

 

-

    def render(self, data, accepted_media_type=None, renderer_context=None): 

-

        """ 

-

        Render the HTML for the browsable API representation. 

-

        """ 

-

        accepted_media_type = accepted_media_type or '' 

-

        renderer_context = renderer_context or {} 

-

 

-

        view = renderer_context['view'] 

-

        request = renderer_context['request'] 

-

        response = renderer_context['response'] 

-

        media_types = [parser.media_type for parser in view.parser_classes] 

-

 

-

        renderer = self.get_default_renderer(view) 

-

        content = self.get_content(renderer, data, accepted_media_type, renderer_context) 

-

 

-

        put_form = self._get_form(view, 'PUT', request) 

-

        post_form = self._get_form(view, 'POST', request) 

-

        patch_form = self._get_form(view, 'PATCH', request) 

-

        delete_form = self._get_form(view, 'DELETE', request) 

-

        options_form = self._get_form(view, 'OPTIONS', request) 

-

 

-

        raw_data_put_form = self._get_raw_data_form(view, 'PUT', request, media_types) 

-

        raw_data_post_form = self._get_raw_data_form(view, 'POST', request, media_types) 

-

        raw_data_patch_form = self._get_raw_data_form(view, 'PATCH', request, media_types) 

-

        raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form 

-

 

-

        name = self.get_name(view) 

-

        description = self.get_description(view) 

-

        breadcrumb_list = self.get_breadcrumbs(request) 

-

 

-

        template = loader.get_template(self.template) 

-

        context = RequestContext(request, { 

-

            'content': content, 

-

            'view': view, 

-

            'request': request, 

-

            'response': response, 

-

            'description': description, 

-

            'name': name, 

-

            'version': VERSION, 

-

            'breadcrumblist': breadcrumb_list, 

-

            'allowed_methods': view.allowed_methods, 

-

            'available_formats': [renderer.format for renderer in view.renderer_classes], 

-

 

-

            'put_form': put_form, 

-

            'post_form': post_form, 

-

            'patch_form': patch_form, 

-

            'delete_form': delete_form, 

-

            'options_form': options_form, 

-

 

-

            'raw_data_put_form': raw_data_put_form, 

-

            'raw_data_post_form': raw_data_post_form, 

-

            'raw_data_patch_form': raw_data_patch_form, 

-

            'raw_data_put_or_patch_form': raw_data_put_or_patch_form, 

-

 

-

            'api_settings': api_settings 

-

        }) 

-

 

-

        ret = template.render(context) 

-

 

-

        # Munge DELETE Response code to allow us to return content 

-

        # (Do this *after* we've rendered the template so that we include 

-

        # the normal deletion response code in the output) 

-

        if response.status_code == status.HTTP_204_NO_CONTENT: 

-

            response.status_code = status.HTTP_200_OK 

-

 

-

        return ret 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_request.html b/htmlcov/rest_framework_request.html deleted file mode 100644 index 03f2c3e34..000000000 --- a/htmlcov/rest_framework_request.html +++ /dev/null @@ -1,819 +0,0 @@ - - - - - - - - Coverage for rest_framework/request: 95% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

-

296

-

297

-

298

-

299

-

300

-

301

-

302

-

303

-

304

-

305

-

306

-

307

-

308

-

309

-

310

-

311

-

312

-

313

-

314

-

315

-

316

-

317

-

318

-

319

-

320

-

321

-

322

-

323

-

324

-

325

-

326

-

327

-

328

-

329

-

330

-

331

-

332

-

333

-

334

-

335

-

336

-

337

-

338

-

339

-

340

-

341

-

342

-

343

-

344

-

345

-

346

-

347

-

348

-

349

-

350

-

351

-

352

-

353

-

354

-

355

-

356

-

357

-

358

-

359

-

360

-

361

-

362

-

363

-

364

-

365

-

366

-

367

-

368

-

369

- -
-

""" 

-

The Request class is used as a wrapper around the standard request object. 

-

 

-

The wrapped request then offers a richer API, in particular : 

-

 

-

    - content automatically parsed according to `Content-Type` header, 

-

      and available as `request.DATA` 

-

    - full support of PUT method, including support for file uploads 

-

    - form overloading of HTTP method, content type and content 

-

""" 

-

from __future__ import unicode_literals 

-

from django.conf import settings 

-

from django.http import QueryDict 

-

from django.http.multipartparser import parse_header 

-

from django.utils.datastructures import MultiValueDict 

-

from rest_framework import HTTP_HEADER_ENCODING 

-

from rest_framework import exceptions 

-

from rest_framework.compat import BytesIO 

-

from rest_framework.settings import api_settings 

-

 

-

 

-

def is_form_media_type(media_type): 

-

    """ 

-

    Return True if the media type is a valid form media type. 

-

    """ 

-

    base_media_type, params = parse_header(media_type.encode(HTTP_HEADER_ENCODING)) 

-

    return (base_media_type == 'application/x-www-form-urlencoded' or 

-

            base_media_type == 'multipart/form-data') 

-

 

-

 

-

class Empty(object): 

-

    """ 

-

    Placeholder for unset attributes. 

-

    Cannot use `None`, as that may be a valid value. 

-

    """ 

-

    pass 

-

 

-

 

-

def _hasattr(obj, name): 

-

    return not getattr(obj, name) is Empty 

-

 

-

 

-

def clone_request(request, method): 

-

    """ 

-

    Internal helper method to clone a request, replacing with a different 

-

    HTTP method.  Used for checking permissions against other methods. 

-

    """ 

-

    ret = Request(request=request._request, 

-

                  parsers=request.parsers, 

-

                  authenticators=request.authenticators, 

-

                  negotiator=request.negotiator, 

-

                  parser_context=request.parser_context) 

-

    ret._data = request._data 

-

    ret._files = request._files 

-

    ret._content_type = request._content_type 

-

    ret._stream = request._stream 

-

    ret._method = method 

-

    if hasattr(request, '_user'): 

-

        ret._user = request._user 

-

    if hasattr(request, '_auth'): 

-

        ret._auth = request._auth 

-

    if hasattr(request, '_authenticator'): 

-

        ret._authenticator = request._authenticator 

-

    return ret 

-

 

-

 

-

class Request(object): 

-

    """ 

-

    Wrapper allowing to enhance a standard `HttpRequest` instance. 

-

 

-

    Kwargs: 

-

        - request(HttpRequest). The original request instance. 

-

        - parsers_classes(list/tuple). The parsers to use for parsing the 

-

          request content. 

-

        - authentication_classes(list/tuple). The authentications used to try 

-

          authenticating the request's user. 

-

    """ 

-

 

-

    _METHOD_PARAM = api_settings.FORM_METHOD_OVERRIDE 

-

    _CONTENT_PARAM = api_settings.FORM_CONTENT_OVERRIDE 

-

    _CONTENTTYPE_PARAM = api_settings.FORM_CONTENTTYPE_OVERRIDE 

-

 

-

    def __init__(self, request, parsers=None, authenticators=None, 

-

                 negotiator=None, parser_context=None): 

-

        self._request = request 

-

        self.parsers = parsers or () 

-

        self.authenticators = authenticators or () 

-

        self.negotiator = negotiator or self._default_negotiator() 

-

        self.parser_context = parser_context 

-

        self._data = Empty 

-

        self._files = Empty 

-

        self._method = Empty 

-

        self._content_type = Empty 

-

        self._stream = Empty 

-

 

-

        if self.parser_context is None: 

-

            self.parser_context = {} 

-

        self.parser_context['request'] = self 

-

        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET 

-

 

-

    def _default_negotiator(self): 

-

        return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS() 

-

 

-

    @property 

-

    def method(self): 

-

        """ 

-

        Returns the HTTP method. 

-

 

-

        This allows the `method` to be overridden by using a hidden `form` 

-

        field on a form POST request. 

-

        """ 

-

        if not _hasattr(self, '_method'): 

-

            self._load_method_and_content_type() 

-

        return self._method 

-

 

-

    @property 

-

    def content_type(self): 

-

        """ 

-

        Returns the content type header. 

-

 

-

        This should be used instead of `request.META.get('HTTP_CONTENT_TYPE')`, 

-

        as it allows the content type to be overridden by using a hidden form 

-

        field on a form POST request. 

-

        """ 

-

        if not _hasattr(self, '_content_type'): 

-

            self._load_method_and_content_type() 

-

        return self._content_type 

-

 

-

    @property 

-

    def stream(self): 

-

        """ 

-

        Returns an object that may be used to stream the request content. 

-

        """ 

-

        if not _hasattr(self, '_stream'): 

-

            self._load_stream() 

-

        return self._stream 

-

 

-

    @property 

-

    def QUERY_PARAMS(self): 

-

        """ 

-

        More semantically correct name for request.GET. 

-

        """ 

-

        return self._request.GET 

-

 

-

    @property 

-

    def DATA(self): 

-

        """ 

-

        Parses the request body and returns the data. 

-

 

-

        Similar to usual behaviour of `request.POST`, except that it handles 

-

        arbitrary parsers, and also works on methods other than POST (eg PUT). 

-

        """ 

-

        if not _hasattr(self, '_data'): 

-

            self._load_data_and_files() 

-

        return self._data 

-

 

-

    @property 

-

    def FILES(self): 

-

        """ 

-

        Parses the request body and returns any files uploaded in the request. 

-

 

-

        Similar to usual behaviour of `request.FILES`, except that it handles 

-

        arbitrary parsers, and also works on methods other than POST (eg PUT). 

-

        """ 

-

        if not _hasattr(self, '_files'): 

-

            self._load_data_and_files() 

-

        return self._files 

-

 

-

    @property 

-

    def user(self): 

-

        """ 

-

        Returns the user associated with the current request, as authenticated 

-

        by the authentication classes provided to the request. 

-

        """ 

-

        if not hasattr(self, '_user'): 

-

            self._authenticate() 

-

        return self._user 

-

 

-

    @user.setter 

-

    def user(self, value): 

-

        """ 

-

        Sets the user on the current request. This is necessary to maintain 

-

        compatilbility with django.contrib.auth where the user proprety is 

-

        set in the login and logout functions. 

-

        """ 

-

        self._user = value 

-

 

-

    @property 

-

    def auth(self): 

-

        """ 

-

        Returns any non-user authentication information associated with the 

-

        request, such as an authentication token. 

-

        """ 

-

        if not hasattr(self, '_auth'): 

-

            self._authenticate() 

-

        return self._auth 

-

 

-

    @auth.setter 

-

    def auth(self, value): 

-

        """ 

-

        Sets any non-user authentication information associated with the 

-

        request, such as an authentication token. 

-

        """ 

-

        self._auth = value 

-

 

-

    @property 

-

    def successful_authenticator(self): 

-

        """ 

-

        Return the instance of the authentication instance class that was used 

-

        to authenticate the request, or `None`. 

-

        """ 

-

        if not hasattr(self, '_authenticator'): 

-

            self._authenticate() 

-

        return self._authenticator 

-

 

-

    def _load_data_and_files(self): 

-

        """ 

-

        Parses the request content into self.DATA and self.FILES. 

-

        """ 

-

        if not _hasattr(self, '_content_type'): 

-

            self._load_method_and_content_type() 

-

 

-

        if not _hasattr(self, '_data'): 

-

            self._data, self._files = self._parse() 

-

 

-

    def _load_method_and_content_type(self): 

-

        """ 

-

        Sets the method and content_type, and then check if they've 

-

        been overridden. 

-

        """ 

-

        self._content_type = self.META.get('HTTP_CONTENT_TYPE', 

-

                                           self.META.get('CONTENT_TYPE', '')) 

-

 

-

        self._perform_form_overloading() 

-

 

-

        if not _hasattr(self, '_method'): 

-

            self._method = self._request.method 

-

 

-

            if self._method == 'POST': 

-

                # Allow X-HTTP-METHOD-OVERRIDE header 

-

                self._method = self.META.get('HTTP_X_HTTP_METHOD_OVERRIDE', 

-

                                             self._method) 

-

 

-

    def _load_stream(self): 

-

        """ 

-

        Return the content body of the request, as a stream. 

-

        """ 

-

        try: 

-

            content_length = int(self.META.get('CONTENT_LENGTH', 

-

                                    self.META.get('HTTP_CONTENT_LENGTH'))) 

-

        except (ValueError, TypeError): 

-

            content_length = 0 

-

 

-

        if content_length == 0: 

-

            self._stream = None 

-

        elif hasattr(self._request, 'read'): 

-

            self._stream = self._request 

-

        else: 

-

            self._stream = BytesIO(self.raw_post_data) 

-

 

-

    def _perform_form_overloading(self): 

-

        """ 

-

        If this is a form POST request, then we need to check if the method and 

-

        content/content_type have been overridden by setting them in hidden 

-

        form fields or not. 

-

        """ 

-

 

-

        USE_FORM_OVERLOADING = ( 

-

            self._METHOD_PARAM or 

-

            (self._CONTENT_PARAM and self._CONTENTTYPE_PARAM) 

-

        ) 

-

 

-

        # We only need to use form overloading on form POST requests. 

-

        if (not USE_FORM_OVERLOADING 

-

            or self._request.method != 'POST' 

-

            or not is_form_media_type(self._content_type)): 

-

            return 

-

 

-

        # At this point we're committed to parsing the request as form data. 

-

        self._data = self._request.POST 

-

        self._files = self._request.FILES 

-

 

-

        # Method overloading - change the method and remove the param from the content. 

-

        if (self._METHOD_PARAM and 

-

            self._METHOD_PARAM in self._data): 

-

            self._method = self._data[self._METHOD_PARAM].upper() 

-

 

-

        # Content overloading - modify the content type, and force re-parse. 

-

        if (self._CONTENT_PARAM and 

-

            self._CONTENTTYPE_PARAM and 

-

            self._CONTENT_PARAM in self._data and 

-

            self._CONTENTTYPE_PARAM in self._data): 

-

            self._content_type = self._data[self._CONTENTTYPE_PARAM] 

-

            self._stream = BytesIO(self._data[self._CONTENT_PARAM].encode(HTTP_HEADER_ENCODING)) 

-

            self._data, self._files = (Empty, Empty) 

-

 

-

    def _parse(self): 

-

        """ 

-

        Parse the request content, returning a two-tuple of (data, files) 

-

 

-

        May raise an `UnsupportedMediaType`, or `ParseError` exception. 

-

        """ 

-

        stream = self.stream 

-

        media_type = self.content_type 

-

 

-

        if stream is None or media_type is None: 

-

            empty_data = QueryDict('', self._request._encoding) 

-

            empty_files = MultiValueDict() 

-

            return (empty_data, empty_files) 

-

 

-

        parser = self.negotiator.select_parser(self, self.parsers) 

-

 

-

        if not parser: 

-

            raise exceptions.UnsupportedMediaType(media_type) 

-

 

-

        parsed = parser.parse(stream, media_type, self.parser_context) 

-

 

-

        # Parser classes may return the raw data, or a 

-

        # DataAndFiles object.  Unpack the result as required. 

-

        try: 

-

            return (parsed.data, parsed.files) 

-

        except AttributeError: 

-

            empty_files = MultiValueDict() 

-

            return (parsed, empty_files) 

-

 

-

    def _authenticate(self): 

-

        """ 

-

        Attempt to authenticate the request using each authentication instance 

-

        in turn. 

-

        Returns a three-tuple of (authenticator, user, authtoken). 

-

        """ 

-

        for authenticator in self.authenticators: 

-

            try: 

-

                user_auth_tuple = authenticator.authenticate(self) 

-

            except exceptions.APIException: 

-

                self._not_authenticated() 

-

                raise 

-

 

-

            if not user_auth_tuple is None: 

-

                self._authenticator = authenticator 

-

                self._user, self._auth = user_auth_tuple 

-

                return 

-

 

-

        self._not_authenticated() 

-

 

-

    def _not_authenticated(self): 

-

        """ 

-

        Return a three-tuple of (authenticator, user, authtoken), representing 

-

        an unauthenticated request. 

-

 

-

        By default this will be (None, AnonymousUser, None). 

-

        """ 

-

        self._authenticator = None 

-

 

-

        if api_settings.UNAUTHENTICATED_USER: 

-

            self._user = api_settings.UNAUTHENTICATED_USER() 

-

        else: 

-

            self._user = None 

-

 

-

        if api_settings.UNAUTHENTICATED_TOKEN: 

-

            self._auth = api_settings.UNAUTHENTICATED_TOKEN() 

-

        else: 

-

            self._auth = None 

-

 

-

    def __getattr__(self, attr): 

-

        """ 

-

        Proxy other attributes to the underlying HttpRequest object. 

-

        """ 

-

        return getattr(self._request, attr) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_response.html b/htmlcov/rest_framework_response.html deleted file mode 100644 index d297ecd0b..000000000 --- a/htmlcov/rest_framework_response.html +++ /dev/null @@ -1,249 +0,0 @@ - - - - - - - - Coverage for rest_framework/response: 98% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

- -
-

""" 

-

The Response class in REST framework is similar to HTTPResponse, except that 

-

it is initialized with unrendered data, instead of a pre-rendered string. 

-

 

-

The appropriate renderer is called during Django's template response rendering. 

-

""" 

-

from __future__ import unicode_literals 

-

from django.core.handlers.wsgi import STATUS_CODE_TEXT 

-

from django.template.response import SimpleTemplateResponse 

-

from rest_framework.compat import six 

-

 

-

 

-

class Response(SimpleTemplateResponse): 

-

    """ 

-

    An HttpResponse that allows its data to be rendered into 

-

    arbitrary media types. 

-

    """ 

-

 

-

    def __init__(self, data=None, status=200, 

-

                 template_name=None, headers=None, 

-

                 exception=False, content_type=None): 

-

        """ 

-

        Alters the init arguments slightly. 

-

        For example, drop 'template_name', and instead use 'data'. 

-

 

-

        Setting 'renderer' and 'media_type' will typically be deferred, 

-

        For example being set automatically by the `APIView`. 

-

        """ 

-

        super(Response, self).__init__(None, status=status) 

-

        self.data = data 

-

        self.template_name = template_name 

-

        self.exception = exception 

-

        self.content_type = content_type 

-

 

-

        if headers: 

-

            for name, value in six.iteritems(headers): 

-

                self[name] = value 

-

 

-

    @property 

-

    def rendered_content(self): 

-

        renderer = getattr(self, 'accepted_renderer', None) 

-

        media_type = getattr(self, 'accepted_media_type', None) 

-

        context = getattr(self, 'renderer_context', None) 

-

 

-

        assert renderer, ".accepted_renderer not set on Response" 

-

        assert media_type, ".accepted_media_type not set on Response" 

-

        assert context, ".renderer_context not set on Response" 

-

        context['response'] = self 

-

 

-

        charset = renderer.charset 

-

        content_type = self.content_type 

-

 

-

        if content_type is None and charset is not None: 

-

            content_type = "{0}; charset={1}".format(media_type, charset) 

-

        elif content_type is None: 

-

            content_type = media_type 

-

        self['Content-Type'] = content_type 

-

 

-

        ret = renderer.render(self.data, media_type, context) 

-

        if isinstance(ret, six.text_type): 

-

            assert charset, 'renderer returned unicode, and did not specify ' \ 

-

            'a charset value.' 

-

            return bytes(ret.encode(charset)) 

-

        return ret 

-

 

-

    @property 

-

    def status_text(self): 

-

        """ 

-

        Returns reason text corresponding to our HTTP response status code. 

-

        Provided for convenience. 

-

        """ 

-

        # TODO: Deprecate and use a template tag instead 

-

        # TODO: Status code text for RFC 6585 status codes 

-

        return STATUS_CODE_TEXT.get(self.status_code, '') 

-

 

-

    def __getstate__(self): 

-

        """ 

-

        Remove attributes from the response that shouldn't be cached 

-

        """ 

-

        state = super(Response, self).__getstate__() 

-

        for key in ('accepted_renderer', 'renderer_context', 'data'): 

-

            if key in state: 

-

                del state[key] 

-

        return state 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_reverse.html b/htmlcov/rest_framework_reverse.html deleted file mode 100644 index 4e7a8de23..000000000 --- a/htmlcov/rest_framework_reverse.html +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - Coverage for rest_framework/reverse: 75% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

- -
-

""" 

-

Provide reverse functions that return fully qualified URLs 

-

""" 

-

from __future__ import unicode_literals 

-

from django.core.urlresolvers import reverse as django_reverse 

-

from django.utils.functional import lazy 

-

 

-

 

-

def reverse(viewname, args=None, kwargs=None, request=None, format=None, **extra): 

-

    """ 

-

    Same as `django.core.urlresolvers.reverse`, but optionally takes a request 

-

    and returns a fully qualified URL, using the request to get the base URL. 

-

    """ 

-

    if format is not None: 

-

        kwargs = kwargs or {} 

-

        kwargs['format'] = format 

-

    url = django_reverse(viewname, args=args, kwargs=kwargs, **extra) 

-

    if request: 

-

        return request.build_absolute_uri(url) 

-

    return url 

-

 

-

 

-

reverse_lazy = lazy(reverse, str) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_routers.html b/htmlcov/rest_framework_routers.html deleted file mode 100644 index f08d5007e..000000000 --- a/htmlcov/rest_framework_routers.html +++ /dev/null @@ -1,595 +0,0 @@ - - - - - - - - Coverage for rest_framework/routers: 94% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

- -
-

""" 

-

Routers provide a convenient and consistent way of automatically 

-

determining the URL conf for your API. 

-

 

-

They are used by simply instantiating a Router class, and then registering 

-

all the required ViewSets with that router. 

-

 

-

For example, you might have a `urls.py` that looks something like this: 

-

 

-

    router = routers.DefaultRouter() 

-

    router.register('users', UserViewSet, 'user') 

-

    router.register('accounts', AccountViewSet, 'account') 

-

 

-

    urlpatterns = router.urls 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

from collections import namedtuple 

-

from rest_framework import views 

-

from rest_framework.compat import patterns, url 

-

from rest_framework.response import Response 

-

from rest_framework.reverse import reverse 

-

from rest_framework.urlpatterns import format_suffix_patterns 

-

 

-

 

-

Route = namedtuple('Route', ['url', 'mapping', 'name', 'initkwargs']) 

-

 

-

 

-

def replace_methodname(format_string, methodname): 

-

    """ 

-

    Partially format a format_string, swapping out any 

-

    '{methodname}' or '{methodnamehyphen}' components. 

-

    """ 

-

    methodnamehyphen = methodname.replace('_', '-') 

-

    ret = format_string 

-

    ret = ret.replace('{methodname}', methodname) 

-

    ret = ret.replace('{methodnamehyphen}', methodnamehyphen) 

-

    return ret 

-

 

-

 

-

class BaseRouter(object): 

-

    def __init__(self): 

-

        self.registry = [] 

-

 

-

    def register(self, prefix, viewset, base_name=None): 

-

        if base_name is None: 

-

            base_name = self.get_default_base_name(viewset) 

-

        self.registry.append((prefix, viewset, base_name)) 

-

 

-

    def get_default_base_name(self, viewset): 

-

        """ 

-

        If `base_name` is not specified, attempt to automatically determine 

-

        it from the viewset. 

-

        """ 

-

        raise NotImplemented('get_default_base_name must be overridden') 

-

 

-

    def get_urls(self): 

-

        """ 

-

        Return a list of URL patterns, given the registered viewsets. 

-

        """ 

-

        raise NotImplemented('get_urls must be overridden') 

-

 

-

    @property 

-

    def urls(self): 

-

        if not hasattr(self, '_urls'): 

-

            self._urls = patterns('', *self.get_urls()) 

-

        return self._urls 

-

 

-

 

-

class SimpleRouter(BaseRouter): 

-

    routes = [ 

-

        # List route. 

-

        Route( 

-

            url=r'^{prefix}{trailing_slash}$', 

-

            mapping={ 

-

                'get': 'list', 

-

                'post': 'create' 

-

            }, 

-

            name='{basename}-list', 

-

            initkwargs={'suffix': 'List'} 

-

        ), 

-

        # Detail route. 

-

        Route( 

-

            url=r'^{prefix}/{lookup}{trailing_slash}$', 

-

            mapping={ 

-

                'get': 'retrieve', 

-

                'put': 'update', 

-

                'patch': 'partial_update', 

-

                'delete': 'destroy' 

-

            }, 

-

            name='{basename}-detail', 

-

            initkwargs={'suffix': 'Instance'} 

-

        ), 

-

        # Dynamically generated routes. 

-

        # Generated using @action or @link decorators on methods of the viewset. 

-

        Route( 

-

            url=r'^{prefix}/{lookup}/{methodname}{trailing_slash}$', 

-

            mapping={ 

-

                '{httpmethod}': '{methodname}', 

-

            }, 

-

            name='{basename}-{methodnamehyphen}', 

-

            initkwargs={} 

-

        ), 

-

    ] 

-

 

-

    def __init__(self, trailing_slash=True): 

-

        self.trailing_slash = trailing_slash and '/' or '' 

-

        super(SimpleRouter, self).__init__() 

-

 

-

    def get_default_base_name(self, viewset): 

-

        """ 

-

        If `base_name` is not specified, attempt to automatically determine 

-

        it from the viewset. 

-

        """ 

-

        model_cls = getattr(viewset, 'model', None) 

-

        queryset = getattr(viewset, 'queryset', None) 

-

        if model_cls is None and queryset is not None: 

-

            model_cls = queryset.model 

-

 

-

        assert model_cls, '`name` not argument not specified, and could ' \ 

-

            'not automatically determine the name from the viewset, as ' \ 

-

            'it does not have a `.model` or `.queryset` attribute.' 

-

 

-

        return model_cls._meta.object_name.lower() 

-

 

-

    def get_routes(self, viewset): 

-

        """ 

-

        Augment `self.routes` with any dynamically generated routes. 

-

 

-

        Returns a list of the Route namedtuple. 

-

        """ 

-

 

-

        # Determine any `@action` or `@link` decorated methods on the viewset 

-

        dynamic_routes = [] 

-

        for methodname in dir(viewset): 

-

            attr = getattr(viewset, methodname) 

-

            httpmethods = getattr(attr, 'bind_to_methods', None) 

-

            if httpmethods: 

-

                dynamic_routes.append((httpmethods, methodname)) 

-

 

-

        ret = [] 

-

        for route in self.routes: 

-

            if route.mapping == {'{httpmethod}': '{methodname}'}: 

-

                # Dynamic routes (@link or @action decorator) 

-

                for httpmethods, methodname in dynamic_routes: 

-

                    initkwargs = route.initkwargs.copy() 

-

                    initkwargs.update(getattr(viewset, methodname).kwargs) 

-

                    ret.append(Route( 

-

                        url=replace_methodname(route.url, methodname), 

-

                        mapping=dict((httpmethod, methodname) for httpmethod in httpmethods), 

-

                        name=replace_methodname(route.name, methodname), 

-

                        initkwargs=initkwargs, 

-

                    )) 

-

            else: 

-

                # Standard route 

-

                ret.append(route) 

-

 

-

        return ret 

-

 

-

    def get_method_map(self, viewset, method_map): 

-

        """ 

-

        Given a viewset, and a mapping of http methods to actions, 

-

        return a new mapping which only includes any mappings that 

-

        are actually implemented by the viewset. 

-

        """ 

-

        bound_methods = {} 

-

        for method, action in method_map.items(): 

-

            if hasattr(viewset, action): 

-

                bound_methods[method] = action 

-

        return bound_methods 

-

 

-

    def get_lookup_regex(self, viewset): 

-

        """ 

-

        Given a viewset, return the portion of URL regex that is used 

-

        to match against a single instance. 

-

        """ 

-

        base_regex = '(?P<{lookup_field}>[^/]+)' 

-

        lookup_field = getattr(viewset, 'lookup_field', 'pk') 

-

        return base_regex.format(lookup_field=lookup_field) 

-

 

-

    def get_urls(self): 

-

        """ 

-

        Use the registered viewsets to generate a list of URL patterns. 

-

        """ 

-

        ret = [] 

-

 

-

        for prefix, viewset, basename in self.registry: 

-

            lookup = self.get_lookup_regex(viewset) 

-

            routes = self.get_routes(viewset) 

-

 

-

            for route in routes: 

-

 

-

                # Only actions which actually exist on the viewset will be bound 

-

                mapping = self.get_method_map(viewset, route.mapping) 

-

                if not mapping: 

-

                    continue 

-

 

-

                # Build the url pattern 

-

                regex = route.url.format( 

-

                    prefix=prefix, 

-

                    lookup=lookup, 

-

                    trailing_slash=self.trailing_slash 

-

                ) 

-

                view = viewset.as_view(mapping, **route.initkwargs) 

-

                name = route.name.format(basename=basename) 

-

                ret.append(url(regex, view, name=name)) 

-

 

-

        return ret 

-

 

-

 

-

class DefaultRouter(SimpleRouter): 

-

    """ 

-

    The default router extends the SimpleRouter, but also adds in a default 

-

    API root view, and adds format suffix patterns to the URLs. 

-

    """ 

-

    include_root_view = True 

-

    include_format_suffixes = True 

-

    root_view_name = 'api-root' 

-

 

-

    def get_api_root_view(self): 

-

        """ 

-

        Return a view to use as the API root. 

-

        """ 

-

        api_root_dict = {} 

-

        list_name = self.routes[0].name 

-

        for prefix, viewset, basename in self.registry: 

-

            api_root_dict[prefix] = list_name.format(basename=basename) 

-

 

-

        class APIRoot(views.APIView): 

-

            _ignore_model_permissions = True 

-

 

-

            def get(self, request, format=None): 

-

                ret = {} 

-

                for key, url_name in api_root_dict.items(): 

-

                    ret[key] = reverse(url_name, request=request, format=format) 

-

                return Response(ret) 

-

 

-

        return APIRoot.as_view() 

-

 

-

    def get_urls(self): 

-

        """ 

-

        Generate the list of URL patterns, including a default root view 

-

        for the API, and appending `.json` style format suffixes. 

-

        """ 

-

        urls = [] 

-

 

-

        if self.include_root_view: 

-

            root_url = url(r'^$', self.get_api_root_view(), name=self.root_view_name) 

-

            urls.append(root_url) 

-

 

-

        default_urls = super(DefaultRouter, self).get_urls() 

-

        urls.extend(default_urls) 

-

 

-

        if self.include_format_suffixes: 

-

            urls = format_suffix_patterns(urls) 

-

 

-

        return urls 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_serializers.html b/htmlcov/rest_framework_serializers.html deleted file mode 100644 index 79dc56474..000000000 --- a/htmlcov/rest_framework_serializers.html +++ /dev/null @@ -1,2011 +0,0 @@ - - - - - - - - Coverage for rest_framework/serializers: 94% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

-

296

-

297

-

298

-

299

-

300

-

301

-

302

-

303

-

304

-

305

-

306

-

307

-

308

-

309

-

310

-

311

-

312

-

313

-

314

-

315

-

316

-

317

-

318

-

319

-

320

-

321

-

322

-

323

-

324

-

325

-

326

-

327

-

328

-

329

-

330

-

331

-

332

-

333

-

334

-

335

-

336

-

337

-

338

-

339

-

340

-

341

-

342

-

343

-

344

-

345

-

346

-

347

-

348

-

349

-

350

-

351

-

352

-

353

-

354

-

355

-

356

-

357

-

358

-

359

-

360

-

361

-

362

-

363

-

364

-

365

-

366

-

367

-

368

-

369

-

370

-

371

-

372

-

373

-

374

-

375

-

376

-

377

-

378

-

379

-

380

-

381

-

382

-

383

-

384

-

385

-

386

-

387

-

388

-

389

-

390

-

391

-

392

-

393

-

394

-

395

-

396

-

397

-

398

-

399

-

400

-

401

-

402

-

403

-

404

-

405

-

406

-

407

-

408

-

409

-

410

-

411

-

412

-

413

-

414

-

415

-

416

-

417

-

418

-

419

-

420

-

421

-

422

-

423

-

424

-

425

-

426

-

427

-

428

-

429

-

430

-

431

-

432

-

433

-

434

-

435

-

436

-

437

-

438

-

439

-

440

-

441

-

442

-

443

-

444

-

445

-

446

-

447

-

448

-

449

-

450

-

451

-

452

-

453

-

454

-

455

-

456

-

457

-

458

-

459

-

460

-

461

-

462

-

463

-

464

-

465

-

466

-

467

-

468

-

469

-

470

-

471

-

472

-

473

-

474

-

475

-

476

-

477

-

478

-

479

-

480

-

481

-

482

-

483

-

484

-

485

-

486

-

487

-

488

-

489

-

490

-

491

-

492

-

493

-

494

-

495

-

496

-

497

-

498

-

499

-

500

-

501

-

502

-

503

-

504

-

505

-

506

-

507

-

508

-

509

-

510

-

511

-

512

-

513

-

514

-

515

-

516

-

517

-

518

-

519

-

520

-

521

-

522

-

523

-

524

-

525

-

526

-

527

-

528

-

529

-

530

-

531

-

532

-

533

-

534

-

535

-

536

-

537

-

538

-

539

-

540

-

541

-

542

-

543

-

544

-

545

-

546

-

547

-

548

-

549

-

550

-

551

-

552

-

553

-

554

-

555

-

556

-

557

-

558

-

559

-

560

-

561

-

562

-

563

-

564

-

565

-

566

-

567

-

568

-

569

-

570

-

571

-

572

-

573

-

574

-

575

-

576

-

577

-

578

-

579

-

580

-

581

-

582

-

583

-

584

-

585

-

586

-

587

-

588

-

589

-

590

-

591

-

592

-

593

-

594

-

595

-

596

-

597

-

598

-

599

-

600

-

601

-

602

-

603

-

604

-

605

-

606

-

607

-

608

-

609

-

610

-

611

-

612

-

613

-

614

-

615

-

616

-

617

-

618

-

619

-

620

-

621

-

622

-

623

-

624

-

625

-

626

-

627

-

628

-

629

-

630

-

631

-

632

-

633

-

634

-

635

-

636

-

637

-

638

-

639

-

640

-

641

-

642

-

643

-

644

-

645

-

646

-

647

-

648

-

649

-

650

-

651

-

652

-

653

-

654

-

655

-

656

-

657

-

658

-

659

-

660

-

661

-

662

-

663

-

664

-

665

-

666

-

667

-

668

-

669

-

670

-

671

-

672

-

673

-

674

-

675

-

676

-

677

-

678

-

679

-

680

-

681

-

682

-

683

-

684

-

685

-

686

-

687

-

688

-

689

-

690

-

691

-

692

-

693

-

694

-

695

-

696

-

697

-

698

-

699

-

700

-

701

-

702

-

703

-

704

-

705

-

706

-

707

-

708

-

709

-

710

-

711

-

712

-

713

-

714

-

715

-

716

-

717

-

718

-

719

-

720

-

721

-

722

-

723

-

724

-

725

-

726

-

727

-

728

-

729

-

730

-

731

-

732

-

733

-

734

-

735

-

736

-

737

-

738

-

739

-

740

-

741

-

742

-

743

-

744

-

745

-

746

-

747

-

748

-

749

-

750

-

751

-

752

-

753

-

754

-

755

-

756

-

757

-

758

-

759

-

760

-

761

-

762

-

763

-

764

-

765

-

766

-

767

-

768

-

769

-

770

-

771

-

772

-

773

-

774

-

775

-

776

-

777

-

778

-

779

-

780

-

781

-

782

-

783

-

784

-

785

-

786

-

787

-

788

-

789

-

790

-

791

-

792

-

793

-

794

-

795

-

796

-

797

-

798

-

799

-

800

-

801

-

802

-

803

-

804

-

805

-

806

-

807

-

808

-

809

-

810

-

811

-

812

-

813

-

814

-

815

-

816

-

817

-

818

-

819

-

820

-

821

-

822

-

823

-

824

-

825

-

826

-

827

-

828

-

829

-

830

-

831

-

832

-

833

-

834

-

835

-

836

-

837

-

838

-

839

-

840

-

841

-

842

-

843

-

844

-

845

-

846

-

847

-

848

-

849

-

850

-

851

-

852

-

853

-

854

-

855

-

856

-

857

-

858

-

859

-

860

-

861

-

862

-

863

-

864

-

865

-

866

-

867

-

868

-

869

-

870

-

871

-

872

-

873

-

874

-

875

-

876

-

877

-

878

-

879

-

880

-

881

-

882

-

883

-

884

-

885

-

886

-

887

-

888

-

889

-

890

-

891

-

892

-

893

-

894

-

895

-

896

-

897

-

898

-

899

-

900

-

901

-

902

-

903

-

904

-

905

-

906

-

907

-

908

-

909

-

910

-

911

-

912

-

913

-

914

-

915

-

916

-

917

-

918

-

919

-

920

-

921

-

922

-

923

-

924

-

925

-

926

-

927

-

928

-

929

-

930

-

931

-

932

-

933

-

934

-

935

-

936

-

937

-

938

-

939

-

940

-

941

-

942

-

943

-

944

-

945

-

946

-

947

-

948

-

949

-

950

-

951

-

952

-

953

-

954

-

955

-

956

-

957

-

958

-

959

-

960

-

961

-

962

-

963

-

964

-

965

- -
-

""" 

-

Serializers and ModelSerializers are similar to Forms and ModelForms. 

-

Unlike forms, they are not constrained to dealing with HTML output, and 

-

form encoded input. 

-

 

-

Serialization in REST framework is a two-phase process: 

-

 

-

1. Serializers marshal between complex types like model instances, and 

-

python primatives. 

-

2. The process of marshalling between python primatives and request and 

-

response content is handled by parsers and renderers. 

-

""" 

-

from __future__ import unicode_literals 

-

import copy 

-

import datetime 

-

import types 

-

from decimal import Decimal 

-

from django.core.paginator import Page 

-

from django.db import models 

-

from django.forms import widgets 

-

from django.utils.datastructures import SortedDict 

-

from rest_framework.compat import get_concrete_model, six 

-

 

-

# Note: We do the following so that users of the framework can use this style: 

-

# 

-

#     example_field = serializers.CharField(...) 

-

# 

-

# This helps keep the separation between model fields, form fields, and 

-

# serializer fields more explicit. 

-

 

-

from rest_framework.relations import * 

-

from rest_framework.fields import * 

-

 

-

 

-

class NestedValidationError(ValidationError): 

-

    """ 

-

    The default ValidationError behavior is to stringify each item in the list 

-

    if the messages are a list of error messages. 

-

 

-

    In the case of nested serializers, where the parent has many children, 

-

    then the child's `serializer.errors` will be a list of dicts.  In the case 

-

    of a single child, the `serializer.errors` will be a dict. 

-

 

-

    We need to override the default behavior to get properly nested error dicts. 

-

    """ 

-

 

-

    def __init__(self, message): 

-

        if isinstance(message, dict): 

-

            self.messages = [message] 

-

        else: 

-

            self.messages = message 

-

 

-

 

-

class DictWithMetadata(dict): 

-

    """ 

-

    A dict-like object, that can have additional properties attached. 

-

    """ 

-

    def __getstate__(self): 

-

        """ 

-

        Used by pickle (e.g., caching). 

-

        Overridden to remove the metadata from the dict, since it shouldn't be 

-

        pickled and may in some instances be unpickleable. 

-

        """ 

-

        return dict(self) 

-

 

-

 

-

class SortedDictWithMetadata(SortedDict): 

-

    """ 

-

    A sorted dict-like object, that can have additional properties attached. 

-

    """ 

-

    def __getstate__(self): 

-

        """ 

-

        Used by pickle (e.g., caching). 

-

        Overriden to remove the metadata from the dict, since it shouldn't be 

-

        pickle and may in some instances be unpickleable. 

-

        """ 

-

        return SortedDict(self).__dict__ 

-

 

-

 

-

def _is_protected_type(obj): 

-

    """ 

-

    True if the object is a native datatype that does not need to 

-

    be serialized further. 

-

    """ 

-

    return isinstance(obj, ( 

-

        types.NoneType, 

-

        int, long, 

-

        datetime.datetime, datetime.date, datetime.time, 

-

        float, Decimal, 

-

        basestring) 

-

    ) 

-

 

-

 

-

def _get_declared_fields(bases, attrs): 

-

    """ 

-

    Create a list of serializer field instances from the passed in 'attrs', 

-

    plus any fields on the base classes (in 'bases'). 

-

 

-

    Note that all fields from the base classes are used. 

-

    """ 

-

    fields = [(field_name, attrs.pop(field_name)) 

-

              for field_name, obj in list(six.iteritems(attrs)) 

-

              if isinstance(obj, Field)] 

-

    fields.sort(key=lambda x: x[1].creation_counter) 

-

 

-

    # If this class is subclassing another Serializer, add that Serializer's 

-

    # fields.  Note that we loop over the bases in *reverse*. This is necessary 

-

    # in order to maintain the correct order of fields. 

-

    for base in bases[::-1]: 

-

        if hasattr(base, 'base_fields'): 

-

            fields = list(base.base_fields.items()) + fields 

-

 

-

    return SortedDict(fields) 

-

 

-

 

-

class SerializerMetaclass(type): 

-

    def __new__(cls, name, bases, attrs): 

-

        attrs['base_fields'] = _get_declared_fields(bases, attrs) 

-

        return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs) 

-

 

-

 

-

class SerializerOptions(object): 

-

    """ 

-

    Meta class options for Serializer 

-

    """ 

-

    def __init__(self, meta): 

-

        self.depth = getattr(meta, 'depth', 0) 

-

        self.fields = getattr(meta, 'fields', ()) 

-

        self.exclude = getattr(meta, 'exclude', ()) 

-

 

-

 

-

class BaseSerializer(WritableField): 

-

    """ 

-

    This is the Serializer implementation. 

-

    We need to implement it as `BaseSerializer` due to metaclass magicks. 

-

    """ 

-

    class Meta(object): 

-

        pass 

-

 

-

    _options_class = SerializerOptions 

-

    _dict_class = SortedDictWithMetadata 

-

 

-

    def __init__(self, instance=None, data=None, files=None, 

-

                 context=None, partial=False, many=None, 

-

                 allow_add_remove=False, **kwargs): 

-

        super(BaseSerializer, self).__init__(**kwargs) 

-

        self.opts = self._options_class(self.Meta) 

-

        self.parent = None 

-

        self.root = None 

-

        self.partial = partial 

-

        self.many = many 

-

        self.allow_add_remove = allow_add_remove 

-

 

-

        self.context = context or {} 

-

 

-

        self.init_data = data 

-

        self.init_files = files 

-

        self.object = instance 

-

        self.fields = self.get_fields() 

-

 

-

        self._data = None 

-

        self._files = None 

-

        self._errors = None 

-

        self._deleted = None 

-

 

-

        if many and instance is not None and not hasattr(instance, '__iter__'): 

-

            raise ValueError('instance should be a queryset or other iterable with many=True') 

-

 

-

        if allow_add_remove and not many: 

-

            raise ValueError('allow_add_remove should only be used for bulk updates, but you have not set many=True') 

-

 

-

    ##### 

-

    # Methods to determine which fields to use when (de)serializing objects. 

-

 

-

    def get_default_fields(self): 

-

        """ 

-

        Return the complete set of default fields for the object, as a dict. 

-

        """ 

-

        return {} 

-

 

-

    def get_fields(self): 

-

        """ 

-

        Returns the complete set of fields for the object as a dict. 

-

 

-

        This will be the set of any explicitly declared fields, 

-

        plus the set of fields returned by get_default_fields(). 

-

        """ 

-

        ret = SortedDict() 

-

 

-

        # Get the explicitly declared fields 

-

        base_fields = copy.deepcopy(self.base_fields) 

-

        for key, field in base_fields.items(): 

-

            ret[key] = field 

-

 

-

        # Add in the default fields 

-

        default_fields = self.get_default_fields() 

-

        for key, val in default_fields.items(): 

-

            if key not in ret: 

-

                ret[key] = val 

-

 

-

        # If 'fields' is specified, use those fields, in that order. 

-

        if self.opts.fields: 

-

            assert isinstance(self.opts.fields, (list, tuple)), '`fields` must be a list or tuple' 

-

            new = SortedDict() 

-

            for key in self.opts.fields: 

-

                new[key] = ret[key] 

-

            ret = new 

-

 

-

        # Remove anything in 'exclude' 

-

        if self.opts.exclude: 

-

            assert isinstance(self.opts.exclude, (list, tuple)), '`exclude` must be a list or tuple' 

-

            for key in self.opts.exclude: 

-

                ret.pop(key, None) 

-

 

-

        for key, field in ret.items(): 

-

            field.initialize(parent=self, field_name=key) 

-

 

-

        return ret 

-

 

-

    ##### 

-

    # Methods to convert or revert from objects <--> primitive representations. 

-

 

-

    def get_field_key(self, field_name): 

-

        """ 

-

        Return the key that should be used for a given field. 

-

        """ 

-

        return field_name 

-

 

-

    def restore_fields(self, data, files): 

-

        """ 

-

        Core of deserialization, together with `restore_object`. 

-

        Converts a dictionary of data into a dictionary of deserialized fields. 

-

        """ 

-

        reverted_data = {} 

-

 

-

        if data is not None and not isinstance(data, dict): 

-

            self._errors['non_field_errors'] = ['Invalid data'] 

-

            return None 

-

 

-

        for field_name, field in self.fields.items(): 

-

            field.initialize(parent=self, field_name=field_name) 

-

            try: 

-

                field.field_from_native(data, files, field_name, reverted_data) 

-

            except ValidationError as err: 

-

                self._errors[field_name] = list(err.messages) 

-

 

-

        return reverted_data 

-

 

-

    def perform_validation(self, attrs): 

-

        """ 

-

        Run `validate_<fieldname>()` and `validate()` methods on the serializer 

-

        """ 

-

        for field_name, field in self.fields.items(): 

-

            if field_name in self._errors: 

-

                continue 

-

            try: 

-

                validate_method = getattr(self, 'validate_%s' % field_name, None) 

-

                if validate_method: 

-

                    source = field.source or field_name 

-

                    attrs = validate_method(attrs, source) 

-

            except ValidationError as err: 

-

                self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages) 

-

 

-

        # If there are already errors, we don't run .validate() because 

-

        # field-validation failed and thus `attrs` may not be complete. 

-

        # which in turn can cause inconsistent validation errors. 

-

        if not self._errors: 

-

            try: 

-

                attrs = self.validate(attrs) 

-

            except ValidationError as err: 

-

                if hasattr(err, 'message_dict'): 

-

                    for field_name, error_messages in err.message_dict.items(): 

-

                        self._errors[field_name] = self._errors.get(field_name, []) + list(error_messages) 

-

                elif hasattr(err, 'messages'): 

-

                    self._errors['non_field_errors'] = err.messages 

-

 

-

        return attrs 

-

 

-

    def validate(self, attrs): 

-

        """ 

-

        Stub method, to be overridden in Serializer subclasses 

-

        """ 

-

        return attrs 

-

 

-

    def restore_object(self, attrs, instance=None): 

-

        """ 

-

        Deserialize a dictionary of attributes into an object instance. 

-

        You should override this method to control how deserialized objects 

-

        are instantiated. 

-

        """ 

-

        if instance is not None: 

-

            instance.update(attrs) 

-

            return instance 

-

        return attrs 

-

 

-

    def to_native(self, obj): 

-

        """ 

-

        Serialize objects -> primitives. 

-

        """ 

-

        ret = self._dict_class() 

-

        ret.fields = {} 

-

 

-

        for field_name, field in self.fields.items(): 

-

            field.initialize(parent=self, field_name=field_name) 

-

            key = self.get_field_key(field_name) 

-

            value = field.field_to_native(obj, field_name) 

-

            ret[key] = value 

-

            ret.fields[key] = field 

-

        return ret 

-

 

-

    def from_native(self, data, files): 

-

        """ 

-

        Deserialize primitives -> objects. 

-

        """ 

-

        self._errors = {} 

-

        if data is not None or files is not None: 

-

            attrs = self.restore_fields(data, files) 

-

            if attrs is not None: 

-

                attrs = self.perform_validation(attrs) 

-

        else: 

-

            self._errors['non_field_errors'] = ['No input provided'] 

-

 

-

        if not self._errors: 

-

            return self.restore_object(attrs, instance=getattr(self, 'object', None)) 

-

 

-

    def field_to_native(self, obj, field_name): 

-

        """ 

-

        Override default so that the serializer can be used as a nested field 

-

        across relationships. 

-

        """ 

-

        if self.source == '*': 

-

            return self.to_native(obj) 

-

 

-

        try: 

-

            source = self.source or field_name 

-

            value = obj 

-

 

-

            for component in source.split('.'): 

-

                value = get_component(value, component) 

-

                if value is None: 

-

                    break 

-

        except ObjectDoesNotExist: 

-

            return None 

-

 

-

        if is_simple_callable(getattr(value, 'all', None)): 

-

            return [self.to_native(item) for item in value.all()] 

-

 

-

        if value is None: 

-

            return None 

-

 

-

        if self.many is not None: 

-

            many = self.many 

-

        else: 

-

            many = hasattr(value, '__iter__') and not isinstance(value, (Page, dict, six.text_type)) 

-

 

-

        if many: 

-

            return [self.to_native(item) for item in value] 

-

        return self.to_native(value) 

-

 

-

    def field_from_native(self, data, files, field_name, into): 

-

        """ 

-

        Override default so that the serializer can be used as a writable 

-

        nested field across relationships. 

-

        """ 

-

        if self.read_only: 

-

            return 

-

 

-

        try: 

-

            value = data[field_name] 

-

        except KeyError: 

-

            if self.default is not None and not self.partial: 

-

                # Note: partial updates shouldn't set defaults 

-

                value = copy.deepcopy(self.default) 

-

            else: 

-

                if self.required: 

-

                    raise ValidationError(self.error_messages['required']) 

-

                return 

-

 

-

        # Set the serializer object if it exists 

-

        obj = getattr(self.parent.object, field_name) if self.parent.object else None 

-

 

-

        if self.source == '*': 

-

            if value: 

-

                into.update(value) 

-

        else: 

-

            if value in (None, ''): 

-

                into[(self.source or field_name)] = None 

-

            else: 

-

                kwargs = { 

-

                    'instance': obj, 

-

                    'data': value, 

-

                    'context': self.context, 

-

                    'partial': self.partial, 

-

                    'many': self.many 

-

                } 

-

                serializer = self.__class__(**kwargs) 

-

 

-

                if serializer.is_valid(): 

-

                    into[self.source or field_name] = serializer.object 

-

                else: 

-

                    # Propagate errors up to our parent 

-

                    raise NestedValidationError(serializer.errors) 

-

 

-

    def get_identity(self, data): 

-

        """ 

-

        This hook is required for bulk update. 

-

        It is used to determine the canonical identity of a given object. 

-

 

-

        Note that the data has not been validated at this point, so we need 

-

        to make sure that we catch any cases of incorrect datatypes being 

-

        passed to this method. 

-

        """ 

-

        try: 

-

            return data.get('id', None) 

-

        except AttributeError: 

-

            return None 

-

 

-

    @property 

-

    def errors(self): 

-

        """ 

-

        Run deserialization and return error data, 

-

        setting self.object if no errors occurred. 

-

        """ 

-

        if self._errors is None: 

-

            data, files = self.init_data, self.init_files 

-

 

-

            if self.many is not None: 

-

                many = self.many 

-

            else: 

-

                many = hasattr(data, '__iter__') and not isinstance(data, (Page, dict, six.text_type)) 

-

                if many: 

-

                    warnings.warn('Implict list/queryset serialization is deprecated. ' 

-

                                  'Use the `many=True` flag when instantiating the serializer.', 

-

                                  DeprecationWarning, stacklevel=3) 

-

 

-

            if many: 

-

                ret = [] 

-

                errors = [] 

-

                update = self.object is not None 

-

 

-

                if update: 

-

                    # If this is a bulk update we need to map all the objects 

-

                    # to a canonical identity so we can determine which 

-

                    # individual object is being updated for each item in the 

-

                    # incoming data 

-

                    objects = self.object 

-

                    identities = [self.get_identity(self.to_native(obj)) for obj in objects] 

-

                    identity_to_objects = dict(zip(identities, objects)) 

-

 

-

                if hasattr(data, '__iter__') and not isinstance(data, (dict, six.text_type)): 

-

                    for item in data: 

-

                        if update: 

-

                            # Determine which object we're updating 

-

                            identity = self.get_identity(item) 

-

                            self.object = identity_to_objects.pop(identity, None) 

-

                            if self.object is None and not self.allow_add_remove: 

-

                                ret.append(None) 

-

                                errors.append({'non_field_errors': ['Cannot create a new item, only existing items may be updated.']}) 

-

                                continue 

-

 

-

                        ret.append(self.from_native(item, None)) 

-

                        errors.append(self._errors) 

-

 

-

                    if update: 

-

                        self._deleted = identity_to_objects.values() 

-

 

-

                    self._errors = any(errors) and errors or [] 

-

                else: 

-

                    self._errors = {'non_field_errors': ['Expected a list of items.']} 

-

            else: 

-

                ret = self.from_native(data, files) 

-

 

-

            if not self._errors: 

-

                self.object = ret 

-

 

-

        return self._errors 

-

 

-

    def is_valid(self): 

-

        return not self.errors 

-

 

-

    @property 

-

    def data(self): 

-

        """ 

-

        Returns the serialized data on the serializer. 

-

        """ 

-

        if self._data is None: 

-

            obj = self.object 

-

 

-

            if self.many is not None: 

-

                many = self.many 

-

            else: 

-

                many = hasattr(obj, '__iter__') and not isinstance(obj, (Page, dict)) 

-

                if many: 

-

                    warnings.warn('Implict list/queryset serialization is deprecated. ' 

-

                                  'Use the `many=True` flag when instantiating the serializer.', 

-

                                  DeprecationWarning, stacklevel=2) 

-

 

-

            if many: 

-

                self._data = [self.to_native(item) for item in obj] 

-

            else: 

-

                self._data = self.to_native(obj) 

-

 

-

        return self._data 

-

 

-

    def save_object(self, obj, **kwargs): 

-

        obj.save(**kwargs) 

-

 

-

    def delete_object(self, obj): 

-

        obj.delete() 

-

 

-

    def save(self, **kwargs): 

-

        """ 

-

        Save the deserialized object and return it. 

-

        """ 

-

        if isinstance(self.object, list): 

-

            [self.save_object(item, **kwargs) for item in self.object] 

-

        else: 

-

            self.save_object(self.object, **kwargs) 

-

 

-

        if self.allow_add_remove and self._deleted: 

-

            [self.delete_object(item) for item in self._deleted] 

-

 

-

        return self.object 

-

 

-

    def metadata(self): 

-

        """ 

-

        Return a dictionary of metadata about the fields on the serializer. 

-

        Useful for things like responding to OPTIONS requests, or generating 

-

        API schemas for auto-documentation. 

-

        """ 

-

        return SortedDict( 

-

            [(field_name, field.metadata()) 

-

            for field_name, field in six.iteritems(self.fields)] 

-

        ) 

-

 

-

 

-

class Serializer(six.with_metaclass(SerializerMetaclass, BaseSerializer)): 

-

    pass 

-

 

-

 

-

class ModelSerializerOptions(SerializerOptions): 

-

    """ 

-

    Meta class options for ModelSerializer 

-

    """ 

-

    def __init__(self, meta): 

-

        super(ModelSerializerOptions, self).__init__(meta) 

-

        self.model = getattr(meta, 'model', None) 

-

        self.read_only_fields = getattr(meta, 'read_only_fields', ()) 

-

 

-

 

-

class ModelSerializer(Serializer): 

-

    """ 

-

    A serializer that deals with model instances and querysets. 

-

    """ 

-

    _options_class = ModelSerializerOptions 

-

 

-

    field_mapping = { 

-

        models.AutoField: IntegerField, 

-

        models.FloatField: FloatField, 

-

        models.IntegerField: IntegerField, 

-

        models.PositiveIntegerField: IntegerField, 

-

        models.SmallIntegerField: IntegerField, 

-

        models.PositiveSmallIntegerField: IntegerField, 

-

        models.DateTimeField: DateTimeField, 

-

        models.DateField: DateField, 

-

        models.TimeField: TimeField, 

-

        models.DecimalField: DecimalField, 

-

        models.EmailField: EmailField, 

-

        models.CharField: CharField, 

-

        models.URLField: URLField, 

-

        models.SlugField: SlugField, 

-

        models.TextField: CharField, 

-

        models.CommaSeparatedIntegerField: CharField, 

-

        models.BooleanField: BooleanField, 

-

        models.FileField: FileField, 

-

        models.ImageField: ImageField, 

-

    } 

-

 

-

    def get_default_fields(self): 

-

        """ 

-

        Return all the fields that should be serialized for the model. 

-

        """ 

-

 

-

        cls = self.opts.model 

-

        assert cls is not None, \ 

-

                "Serializer class '%s' is missing 'model' Meta option" % self.__class__.__name__ 

-

        opts = get_concrete_model(cls)._meta 

-

        ret = SortedDict() 

-

        nested = bool(self.opts.depth) 

-

 

-

        # Deal with adding the primary key field 

-

        pk_field = opts.pk 

-

        while pk_field.rel and pk_field.rel.parent_link: 

-

            # If model is a child via multitable inheritance, use parent's pk 

-

            pk_field = pk_field.rel.to._meta.pk 

-

 

-

        field = self.get_pk_field(pk_field) 

-

        if field: 

-

            ret[pk_field.name] = field 

-

 

-

        # Deal with forward relationships 

-

        forward_rels = [field for field in opts.fields if field.serialize] 

-

        forward_rels += [field for field in opts.many_to_many if field.serialize] 

-

 

-

        for model_field in forward_rels: 

-

            has_through_model = False 

-

 

-

            if model_field.rel: 

-

                to_many = isinstance(model_field, 

-

                                     models.fields.related.ManyToManyField) 

-

                related_model = model_field.rel.to 

-

 

-

                if to_many and not model_field.rel.through._meta.auto_created: 

-

                    has_through_model = True 

-

 

-

            if model_field.rel and nested: 

-

                if len(inspect.getargspec(self.get_nested_field).args) == 2: 

-

                    warnings.warn( 

-

                        'The `get_nested_field(model_field)` call signature ' 

-

                        'is due to be deprecated. ' 

-

                        'Use `get_nested_field(model_field, related_model, ' 

-

                        'to_many) instead', 

-

                        PendingDeprecationWarning 

-

                    ) 

-

                    field = self.get_nested_field(model_field) 

-

                else: 

-

                    field = self.get_nested_field(model_field, related_model, to_many) 

-

            elif model_field.rel: 

-

                if len(inspect.getargspec(self.get_nested_field).args) == 3: 

-

                    warnings.warn( 

-

                        'The `get_related_field(model_field, to_many)` call ' 

-

                        'signature is due to be deprecated. ' 

-

                        'Use `get_related_field(model_field, related_model, ' 

-

                        'to_many) instead', 

-

                        PendingDeprecationWarning 

-

                    ) 

-

                    field = self.get_related_field(model_field, to_many=to_many) 

-

                else: 

-

                    field = self.get_related_field(model_field, related_model, to_many) 

-

            else: 

-

                field = self.get_field(model_field) 

-

 

-

            if field: 

-

                if has_through_model: 

-

                    field.read_only = True 

-

 

-

                ret[model_field.name] = field 

-

 

-

        # Deal with reverse relationships 

-

        if not self.opts.fields: 

-

            reverse_rels = [] 

-

        else: 

-

            # Reverse relationships are only included if they are explicitly 

-

            # present in the `fields` option on the serializer 

-

            reverse_rels = opts.get_all_related_objects() 

-

            reverse_rels += opts.get_all_related_many_to_many_objects() 

-

 

-

        for relation in reverse_rels: 

-

            accessor_name = relation.get_accessor_name() 

-

            if not self.opts.fields or accessor_name not in self.opts.fields: 

-

                continue 

-

            related_model = relation.model 

-

            to_many = relation.field.rel.multiple 

-

            has_through_model = False 

-

            is_m2m = isinstance(relation.field, 

-

                                models.fields.related.ManyToManyField) 

-

 

-

            if is_m2m and not relation.field.rel.through._meta.auto_created: 

-

                has_through_model = True 

-

 

-

            if nested: 

-

                field = self.get_nested_field(None, related_model, to_many) 

-

            else: 

-

                field = self.get_related_field(None, related_model, to_many) 

-

 

-

            if field: 

-

                if has_through_model: 

-

                    field.read_only = True 

-

 

-

                ret[accessor_name] = field 

-

 

-

        # Add the `read_only` flag to any fields that have bee specified 

-

        # in the `read_only_fields` option 

-

        for field_name in self.opts.read_only_fields: 

-

            assert field_name not in self.base_fields.keys(), \ 

-

                "field '%s' on serializer '%s' specfied in " \ 

-

                "`read_only_fields`, but also added " \ 

-

                "as an explict field.  Remove it from `read_only_fields`." % \ 

-

                (field_name, self.__class__.__name__) 

-

            assert field_name in ret, \ 

-

                "Noexistant field '%s' specified in `read_only_fields` " \ 

-

                "on serializer '%s'." % \ 

-

                (self.__class__.__name__, field_name) 

-

            ret[field_name].read_only = True 

-

 

-

        return ret 

-

 

-

    def get_pk_field(self, model_field): 

-

        """ 

-

        Returns a default instance of the pk field. 

-

        """ 

-

        return self.get_field(model_field) 

-

 

-

    def get_nested_field(self, model_field, related_model, to_many): 

-

        """ 

-

        Creates a default instance of a nested relational field. 

-

 

-

        Note that model_field will be `None` for reverse relationships. 

-

        """ 

-

        class NestedModelSerializer(ModelSerializer): 

-

            class Meta: 

-

                model = related_model 

-

                depth = self.opts.depth - 1 

-

 

-

        return NestedModelSerializer(many=to_many) 

-

 

-

    def get_related_field(self, model_field, related_model, to_many): 

-

        """ 

-

        Creates a default instance of a flat relational field. 

-

 

-

        Note that model_field will be `None` for reverse relationships. 

-

        """ 

-

        # TODO: filter queryset using: 

-

        # .using(db).complex_filter(self.rel.limit_choices_to) 

-

 

-

        kwargs = { 

-

            'queryset': related_model._default_manager, 

-

            'many': to_many 

-

        } 

-

 

-

        if model_field: 

-

            kwargs['required'] = not(model_field.null or model_field.blank) 

-

 

-

        return PrimaryKeyRelatedField(**kwargs) 

-

 

-

    def get_field(self, model_field): 

-

        """ 

-

        Creates a default instance of a basic non-relational field. 

-

        """ 

-

        kwargs = {} 

-

 

-

        if model_field.null or model_field.blank: 

-

            kwargs['required'] = False 

-

 

-

        if isinstance(model_field, models.AutoField) or not model_field.editable: 

-

            kwargs['read_only'] = True 

-

 

-

        if model_field.has_default(): 

-

            kwargs['default'] = model_field.get_default() 

-

 

-

        if issubclass(model_field.__class__, models.TextField): 

-

            kwargs['widget'] = widgets.Textarea 

-

 

-

        if model_field.verbose_name is not None: 

-

            kwargs['label'] = model_field.verbose_name 

-

 

-

        if model_field.help_text is not None: 

-

            kwargs['help_text'] = model_field.help_text 

-

 

-

        # TODO: TypedChoiceField? 

-

        if model_field.flatchoices:  # This ModelField contains choices 

-

            kwargs['choices'] = model_field.flatchoices 

-

            return ChoiceField(**kwargs) 

-

 

-

        # put this below the ChoiceField because min_value isn't a valid initializer 

-

        if issubclass(model_field.__class__, models.PositiveIntegerField) or\ 

-

                issubclass(model_field.__class__, models.PositiveSmallIntegerField): 

-

            kwargs['min_value'] = 0 

-

 

-

        attribute_dict = { 

-

            models.CharField: ['max_length'], 

-

            models.CommaSeparatedIntegerField: ['max_length'], 

-

            models.DecimalField: ['max_digits', 'decimal_places'], 

-

            models.EmailField: ['max_length'], 

-

            models.FileField: ['max_length'], 

-

            models.ImageField: ['max_length'], 

-

            models.SlugField: ['max_length'], 

-

            models.URLField: ['max_length'], 

-

        } 

-

 

-

        if model_field.__class__ in attribute_dict: 

-

            attributes = attribute_dict[model_field.__class__] 

-

            for attribute in attributes: 

-

                kwargs.update({attribute: getattr(model_field, attribute)}) 

-

 

-

        try: 

-

            return self.field_mapping[model_field.__class__](**kwargs) 

-

        except KeyError: 

-

            return ModelField(model_field=model_field, **kwargs) 

-

 

-

    def get_validation_exclusions(self): 

-

        """ 

-

        Return a list of field names to exclude from model validation. 

-

        """ 

-

        cls = self.opts.model 

-

        opts = get_concrete_model(cls)._meta 

-

        exclusions = [field.name for field in opts.fields + opts.many_to_many] 

-

        for field_name, field in self.fields.items(): 

-

            field_name = field.source or field_name 

-

            if field_name in exclusions and not field.read_only: 

-

                exclusions.remove(field_name) 

-

        return exclusions 

-

 

-

    def full_clean(self, instance): 

-

        """ 

-

        Perform Django's full_clean, and populate the `errors` dictionary 

-

        if any validation errors occur. 

-

 

-

        Note that we don't perform this inside the `.restore_object()` method, 

-

        so that subclasses can override `.restore_object()`, and still get 

-

        the full_clean validation checking. 

-

        """ 

-

        try: 

-

            instance.full_clean(exclude=self.get_validation_exclusions()) 

-

        except ValidationError as err: 

-

            self._errors = err.message_dict 

-

            return None 

-

        return instance 

-

 

-

    def restore_object(self, attrs, instance=None): 

-

        """ 

-

        Restore the model instance. 

-

        """ 

-

        m2m_data = {} 

-

        related_data = {} 

-

        meta = self.opts.model._meta 

-

 

-

        # Reverse fk or one-to-one relations 

-

        for (obj, model) in meta.get_all_related_objects_with_model(): 

-

            field_name = obj.field.related_query_name() 

-

            if field_name in attrs: 

-

                related_data[field_name] = attrs.pop(field_name) 

-

 

-

        # Reverse m2m relations 

-

        for (obj, model) in meta.get_all_related_m2m_objects_with_model(): 

-

            field_name = obj.field.related_query_name() 

-

            if field_name in attrs: 

-

                m2m_data[field_name] = attrs.pop(field_name) 

-

 

-

        # Forward m2m relations 

-

        for field in meta.many_to_many: 

-

            if field.name in attrs: 

-

                m2m_data[field.name] = attrs.pop(field.name) 

-

 

-

        # Update an existing instance... 

-

        if instance is not None: 

-

            for key, val in attrs.items(): 

-

                setattr(instance, key, val) 

-

 

-

        # ...or create a new instance 

-

        else: 

-

            instance = self.opts.model(**attrs) 

-

 

-

        # Any relations that cannot be set until we've 

-

        # saved the model get hidden away on these 

-

        # private attributes, so we can deal with them 

-

        # at the point of save. 

-

        instance._related_data = related_data 

-

        instance._m2m_data = m2m_data 

-

 

-

        return instance 

-

 

-

    def from_native(self, data, files): 

-

        """ 

-

        Override the default method to also include model field validation. 

-

        """ 

-

        instance = super(ModelSerializer, self).from_native(data, files) 

-

        if not self._errors: 

-

            return self.full_clean(instance) 

-

 

-

    def save_object(self, obj, **kwargs): 

-

        """ 

-

        Save the deserialized object and return it. 

-

        """ 

-

        obj.save(**kwargs) 

-

 

-

        if getattr(obj, '_m2m_data', None): 

-

            for accessor_name, object_list in obj._m2m_data.items(): 

-

                setattr(obj, accessor_name, object_list) 

-

            del(obj._m2m_data) 

-

 

-

        if getattr(obj, '_related_data', None): 

-

            for accessor_name, related in obj._related_data.items(): 

-

                setattr(obj, accessor_name, related) 

-

            del(obj._related_data) 

-

 

-

 

-

class HyperlinkedModelSerializerOptions(ModelSerializerOptions): 

-

    """ 

-

    Options for HyperlinkedModelSerializer 

-

    """ 

-

    def __init__(self, meta): 

-

        super(HyperlinkedModelSerializerOptions, self).__init__(meta) 

-

        self.view_name = getattr(meta, 'view_name', None) 

-

        self.lookup_field = getattr(meta, 'lookup_field', None) 

-

 

-

 

-

class HyperlinkedModelSerializer(ModelSerializer): 

-

    """ 

-

    A subclass of ModelSerializer that uses hyperlinked relationships, 

-

    instead of primary key relationships. 

-

    """ 

-

    _options_class = HyperlinkedModelSerializerOptions 

-

    _default_view_name = '%(model_name)s-detail' 

-

    _hyperlink_field_class = HyperlinkedRelatedField 

-

 

-

    def get_default_fields(self): 

-

        fields = super(HyperlinkedModelSerializer, self).get_default_fields() 

-

 

-

        if self.opts.view_name is None: 

-

            self.opts.view_name = self._get_default_view_name(self.opts.model) 

-

 

-

        if 'url' not in fields: 

-

            url_field = HyperlinkedIdentityField( 

-

                view_name=self.opts.view_name, 

-

                lookup_field=self.opts.lookup_field 

-

            ) 

-

            fields.insert(0, 'url', url_field) 

-

 

-

        return fields 

-

 

-

    def get_pk_field(self, model_field): 

-

        if self.opts.fields and model_field.name in self.opts.fields: 

-

            return self.get_field(model_field) 

-

 

-

    def get_related_field(self, model_field, related_model, to_many): 

-

        """ 

-

        Creates a default instance of a flat relational field. 

-

        """ 

-

        # TODO: filter queryset using: 

-

        # .using(db).complex_filter(self.rel.limit_choices_to) 

-

        kwargs = { 

-

            'queryset': related_model._default_manager, 

-

            'view_name': self._get_default_view_name(related_model), 

-

            'many': to_many 

-

        } 

-

 

-

        if model_field: 

-

            kwargs['required'] = not(model_field.null or model_field.blank) 

-

 

-

        if self.opts.lookup_field: 

-

            kwargs['lookup_field'] = self.opts.lookup_field 

-

 

-

        return self._hyperlink_field_class(**kwargs) 

-

 

-

    def get_identity(self, data): 

-

        """ 

-

        This hook is required for bulk update. 

-

        We need to override the default, to use the url as the identity. 

-

        """ 

-

        try: 

-

            return data.get('url', None) 

-

        except AttributeError: 

-

            return None 

-

 

-

    def _get_default_view_name(self, model): 

-

        """ 

-

        Return the view name to use if 'view_name' is not specified in 'Meta' 

-

        """ 

-

        model_meta = model._meta 

-

        format_kwargs = { 

-

            'app_label': model_meta.app_label, 

-

            'model_name': model_meta.object_name.lower() 

-

        } 

-

        return self._default_view_name % format_kwargs 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_settings.html b/htmlcov/rest_framework_settings.html deleted file mode 100644 index ae47b5bc8..000000000 --- a/htmlcov/rest_framework_settings.html +++ /dev/null @@ -1,465 +0,0 @@ - - - - - - - - Coverage for rest_framework/settings: 95% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

- -
-

""" 

-

Settings for REST framework are all namespaced in the REST_FRAMEWORK setting. 

-

For example your project's `settings.py` file might look like this: 

-

 

-

REST_FRAMEWORK = { 

-

    'DEFAULT_RENDERER_CLASSES': ( 

-

        'rest_framework.renderers.JSONRenderer', 

-

        'rest_framework.renderers.YAMLRenderer', 

-

    ) 

-

    'DEFAULT_PARSER_CLASSES': ( 

-

        'rest_framework.parsers.JSONParser', 

-

        'rest_framework.parsers.YAMLParser', 

-

    ) 

-

} 

-

 

-

This module provides the `api_setting` object, that is used to access 

-

REST framework settings, checking for user settings first, then falling 

-

back to the defaults. 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

from django.conf import settings 

-

from django.utils import importlib 

-

 

-

from rest_framework import ISO_8601 

-

from rest_framework.compat import six 

-

 

-

 

-

USER_SETTINGS = getattr(settings, 'REST_FRAMEWORK', None) 

-

 

-

DEFAULTS = { 

-

    # Base API policies 

-

    'DEFAULT_RENDERER_CLASSES': ( 

-

        'rest_framework.renderers.JSONRenderer', 

-

        'rest_framework.renderers.BrowsableAPIRenderer', 

-

    ), 

-

    'DEFAULT_PARSER_CLASSES': ( 

-

        'rest_framework.parsers.JSONParser', 

-

        'rest_framework.parsers.FormParser', 

-

        'rest_framework.parsers.MultiPartParser' 

-

    ), 

-

    'DEFAULT_AUTHENTICATION_CLASSES': ( 

-

        'rest_framework.authentication.SessionAuthentication', 

-

        'rest_framework.authentication.BasicAuthentication' 

-

    ), 

-

    'DEFAULT_PERMISSION_CLASSES': ( 

-

        'rest_framework.permissions.AllowAny', 

-

    ), 

-

    'DEFAULT_THROTTLE_CLASSES': ( 

-

    ), 

-

 

-

    'DEFAULT_CONTENT_NEGOTIATION_CLASS': 

-

        'rest_framework.negotiation.DefaultContentNegotiation', 

-

 

-

    # Genric view behavior 

-

    'DEFAULT_MODEL_SERIALIZER_CLASS': 

-

        'rest_framework.serializers.ModelSerializer', 

-

    'DEFAULT_PAGINATION_SERIALIZER_CLASS': 

-

        'rest_framework.pagination.PaginationSerializer', 

-

    'DEFAULT_FILTER_BACKENDS': (), 

-

 

-

    # Throttling 

-

    'DEFAULT_THROTTLE_RATES': { 

-

        'user': None, 

-

        'anon': None, 

-

    }, 

-

 

-

    # Pagination 

-

    'PAGINATE_BY': None, 

-

    'PAGINATE_BY_PARAM': None, 

-

 

-

    # Authentication 

-

    'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser', 

-

    'UNAUTHENTICATED_TOKEN': None, 

-

 

-

    # Browser enhancements 

-

    'FORM_METHOD_OVERRIDE': '_method', 

-

    'FORM_CONTENT_OVERRIDE': '_content', 

-

    'FORM_CONTENTTYPE_OVERRIDE': '_content_type', 

-

    'URL_ACCEPT_OVERRIDE': 'accept', 

-

    'URL_FORMAT_OVERRIDE': 'format', 

-

 

-

    'FORMAT_SUFFIX_KWARG': 'format', 

-

 

-

    # Input and output formats 

-

    'DATE_INPUT_FORMATS': ( 

-

        ISO_8601, 

-

    ), 

-

    'DATE_FORMAT': None, 

-

 

-

    'DATETIME_INPUT_FORMATS': ( 

-

        ISO_8601, 

-

    ), 

-

    'DATETIME_FORMAT': None, 

-

 

-

    'TIME_INPUT_FORMATS': ( 

-

        ISO_8601, 

-

    ), 

-

    'TIME_FORMAT': None, 

-

 

-

    # Pending deprecation 

-

    'FILTER_BACKEND': None, 

-

} 

-

 

-

 

-

# List of settings that may be in string import notation. 

-

IMPORT_STRINGS = ( 

-

    'DEFAULT_RENDERER_CLASSES', 

-

    'DEFAULT_PARSER_CLASSES', 

-

    'DEFAULT_AUTHENTICATION_CLASSES', 

-

    'DEFAULT_PERMISSION_CLASSES', 

-

    'DEFAULT_THROTTLE_CLASSES', 

-

    'DEFAULT_CONTENT_NEGOTIATION_CLASS', 

-

    'DEFAULT_MODEL_SERIALIZER_CLASS', 

-

    'DEFAULT_PAGINATION_SERIALIZER_CLASS', 

-

    'DEFAULT_FILTER_BACKENDS', 

-

    'FILTER_BACKEND', 

-

    'UNAUTHENTICATED_USER', 

-

    'UNAUTHENTICATED_TOKEN', 

-

) 

-

 

-

 

-

def perform_import(val, setting_name): 

-

    """ 

-

    If the given setting is a string import notation, 

-

    then perform the necessary import or imports. 

-

    """ 

-

    if isinstance(val, six.string_types): 

-

        return import_from_string(val, setting_name) 

-

    elif isinstance(val, (list, tuple)): 

-

        return [import_from_string(item, setting_name) for item in val] 

-

    return val 

-

 

-

 

-

def import_from_string(val, setting_name): 

-

    """ 

-

    Attempt to import a class from a string representation. 

-

    """ 

-

    try: 

-

        # Nod to tastypie's use of importlib. 

-

        parts = val.split('.') 

-

        module_path, class_name = '.'.join(parts[:-1]), parts[-1] 

-

        module = importlib.import_module(module_path) 

-

        return getattr(module, class_name) 

-

    except ImportError as e: 

-

        msg = "Could not import '%s' for API setting '%s'. %s: %s." % (val, setting_name, e.__class__.__name__, e) 

-

        raise ImportError(msg) 

-

 

-

 

-

class APISettings(object): 

-

    """ 

-

    A settings object, that allows API settings to be accessed as properties. 

-

    For example: 

-

 

-

        from rest_framework.settings import api_settings 

-

        print api_settings.DEFAULT_RENDERER_CLASSES 

-

 

-

    Any setting with string import paths will be automatically resolved 

-

    and return the class, rather than the string literal. 

-

    """ 

-

    def __init__(self, user_settings=None, defaults=None, import_strings=None): 

-

        self.user_settings = user_settings or {} 

-

        self.defaults = defaults or {} 

-

        self.import_strings = import_strings or () 

-

 

-

    def __getattr__(self, attr): 

-

        if attr not in self.defaults.keys(): 

-

            raise AttributeError("Invalid API setting: '%s'" % attr) 

-

 

-

        try: 

-

            # Check if present in user settings 

-

            val = self.user_settings[attr] 

-

        except KeyError: 

-

            # Fall back to defaults 

-

            val = self.defaults[attr] 

-

 

-

        # Coerce import strings into classes 

-

        if val and attr in self.import_strings: 

-

            val = perform_import(val, attr) 

-

 

-

        self.validate_setting(attr, val) 

-

 

-

        # Cache the result 

-

        setattr(self, attr, val) 

-

        return val 

-

 

-

    def validate_setting(self, attr, val): 

-

        if attr == 'FILTER_BACKEND' and val is not None: 

-

            # Make sure we can initialize the class 

-

            val() 

-

 

-

api_settings = APISettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_status.html b/htmlcov/rest_framework_status.html deleted file mode 100644 index 85f919f6b..000000000 --- a/htmlcov/rest_framework_status.html +++ /dev/null @@ -1,187 +0,0 @@ - - - - - - - - Coverage for rest_framework/status: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

- -
-

""" 

-

Descriptive HTTP status codes, for code readability. 

-

 

-

See RFC 2616 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 

-

And RFC 6585 - http://tools.ietf.org/html/rfc6585 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

HTTP_100_CONTINUE = 100 

-

HTTP_101_SWITCHING_PROTOCOLS = 101 

-

HTTP_200_OK = 200 

-

HTTP_201_CREATED = 201 

-

HTTP_202_ACCEPTED = 202 

-

HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203 

-

HTTP_204_NO_CONTENT = 204 

-

HTTP_205_RESET_CONTENT = 205 

-

HTTP_206_PARTIAL_CONTENT = 206 

-

HTTP_300_MULTIPLE_CHOICES = 300 

-

HTTP_301_MOVED_PERMANENTLY = 301 

-

HTTP_302_FOUND = 302 

-

HTTP_303_SEE_OTHER = 303 

-

HTTP_304_NOT_MODIFIED = 304 

-

HTTP_305_USE_PROXY = 305 

-

HTTP_306_RESERVED = 306 

-

HTTP_307_TEMPORARY_REDIRECT = 307 

-

HTTP_400_BAD_REQUEST = 400 

-

HTTP_401_UNAUTHORIZED = 401 

-

HTTP_402_PAYMENT_REQUIRED = 402 

-

HTTP_403_FORBIDDEN = 403 

-

HTTP_404_NOT_FOUND = 404 

-

HTTP_405_METHOD_NOT_ALLOWED = 405 

-

HTTP_406_NOT_ACCEPTABLE = 406 

-

HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407 

-

HTTP_408_REQUEST_TIMEOUT = 408 

-

HTTP_409_CONFLICT = 409 

-

HTTP_410_GONE = 410 

-

HTTP_411_LENGTH_REQUIRED = 411 

-

HTTP_412_PRECONDITION_FAILED = 412 

-

HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413 

-

HTTP_414_REQUEST_URI_TOO_LONG = 414 

-

HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415 

-

HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416 

-

HTTP_417_EXPECTATION_FAILED = 417 

-

HTTP_428_PRECONDITION_REQUIRED = 428 

-

HTTP_429_TOO_MANY_REQUESTS = 429 

-

HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431 

-

HTTP_500_INTERNAL_SERVER_ERROR = 500 

-

HTTP_501_NOT_IMPLEMENTED = 501 

-

HTTP_502_BAD_GATEWAY = 502 

-

HTTP_503_SERVICE_UNAVAILABLE = 503 

-

HTTP_504_GATEWAY_TIMEOUT = 504 

-

HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505 

-

HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_throttling.html b/htmlcov/rest_framework_throttling.html deleted file mode 100644 index 778b0293b..000000000 --- a/htmlcov/rest_framework_throttling.html +++ /dev/null @@ -1,533 +0,0 @@ - - - - - - - - Coverage for rest_framework/throttling: 81% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

- -
-

""" 

-

Provides various throttling policies. 

-

""" 

-

from __future__ import unicode_literals 

-

from django.core.cache import cache 

-

from django.core.exceptions import ImproperlyConfigured 

-

from rest_framework.settings import api_settings 

-

import time 

-

 

-

 

-

class BaseThrottle(object): 

-

    """ 

-

    Rate throttling of requests. 

-

    """ 

-

    def allow_request(self, request, view): 

-

        """ 

-

        Return `True` if the request should be allowed, `False` otherwise. 

-

        """ 

-

        raise NotImplementedError('.allow_request() must be overridden') 

-

 

-

    def wait(self): 

-

        """ 

-

        Optionally, return a recommended number of seconds to wait before 

-

        the next request. 

-

        """ 

-

        return None 

-

 

-

 

-

class SimpleRateThrottle(BaseThrottle): 

-

    """ 

-

    A simple cache implementation, that only requires `.get_cache_key()` 

-

    to be overridden. 

-

 

-

    The rate (requests / seconds) is set by a `throttle` attribute on the View 

-

    class.  The attribute is a string of the form 'number_of_requests/period'. 

-

 

-

    Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day') 

-

 

-

    Previous request information used for throttling is stored in the cache. 

-

    """ 

-

 

-

    timer = time.time 

-

    cache_format = 'throtte_%(scope)s_%(ident)s' 

-

    scope = None 

-

    THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES 

-

 

-

    def __init__(self): 

-

        if not getattr(self, 'rate', None): 

-

            self.rate = self.get_rate() 

-

        self.num_requests, self.duration = self.parse_rate(self.rate) 

-

 

-

    def get_cache_key(self, request, view): 

-

        """ 

-

        Should return a unique cache-key which can be used for throttling. 

-

        Must be overridden. 

-

 

-

        May return `None` if the request should not be throttled. 

-

        """ 

-

        raise NotImplementedError('.get_cache_key() must be overridden') 

-

 

-

    def get_rate(self): 

-

        """ 

-

        Determine the string representation of the allowed request rate. 

-

        """ 

-

        if not getattr(self, 'scope', None): 

-

            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" % 

-

                   self.__class__.__name__) 

-

            raise ImproperlyConfigured(msg) 

-

 

-

        try: 

-

            return self.THROTTLE_RATES[self.scope] 

-

        except KeyError: 

-

            msg = "No default throttle rate set for '%s' scope" % self.scope 

-

            raise ImproperlyConfigured(msg) 

-

 

-

    def parse_rate(self, rate): 

-

        """ 

-

        Given the request rate string, return a two tuple of: 

-

        <allowed number of requests>, <period of time in seconds> 

-

        """ 

-

        if rate is None: 

-

            return (None, None) 

-

        num, period = rate.split('/') 

-

        num_requests = int(num) 

-

        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]] 

-

        return (num_requests, duration) 

-

 

-

    def allow_request(self, request, view): 

-

        """ 

-

        Implement the check to see if the request should be throttled. 

-

 

-

        On success calls `throttle_success`. 

-

        On failure calls `throttle_failure`. 

-

        """ 

-

        if self.rate is None: 

-

            return True 

-

 

-

        self.key = self.get_cache_key(request, view) 

-

        self.history = cache.get(self.key, []) 

-

        self.now = self.timer() 

-

 

-

        # Drop any requests from the history which have now passed the 

-

        # throttle duration 

-

        while self.history and self.history[-1] <= self.now - self.duration: 

-

            self.history.pop() 

-

        if len(self.history) >= self.num_requests: 

-

            return self.throttle_failure() 

-

        return self.throttle_success() 

-

 

-

    def throttle_success(self): 

-

        """ 

-

        Inserts the current request's timestamp along with the key 

-

        into the cache. 

-

        """ 

-

        self.history.insert(0, self.now) 

-

        cache.set(self.key, self.history, self.duration) 

-

        return True 

-

 

-

    def throttle_failure(self): 

-

        """ 

-

        Called when a request to the API has failed due to throttling. 

-

        """ 

-

        return False 

-

 

-

    def wait(self): 

-

        """ 

-

        Returns the recommended next request time in seconds. 

-

        """ 

-

        if self.history: 

-

            remaining_duration = self.duration - (self.now - self.history[-1]) 

-

        else: 

-

            remaining_duration = self.duration 

-

 

-

        available_requests = self.num_requests - len(self.history) + 1 

-

 

-

        return remaining_duration / float(available_requests) 

-

 

-

 

-

class AnonRateThrottle(SimpleRateThrottle): 

-

    """ 

-

    Limits the rate of API calls that may be made by a anonymous users. 

-

 

-

    The IP address of the request will be used as the unique cache key. 

-

    """ 

-

    scope = 'anon' 

-

 

-

    def get_cache_key(self, request, view): 

-

        if request.user.is_authenticated(): 

-

            return None  # Only throttle unauthenticated requests. 

-

 

-

        ident = request.META.get('REMOTE_ADDR', None) 

-

 

-

        return self.cache_format % { 

-

            'scope': self.scope, 

-

            'ident': ident 

-

        } 

-

 

-

 

-

class UserRateThrottle(SimpleRateThrottle): 

-

    """ 

-

    Limits the rate of API calls that may be made by a given user. 

-

 

-

    The user id will be used as a unique cache key if the user is 

-

    authenticated.  For anonymous requests, the IP address of the request will 

-

    be used. 

-

    """ 

-

    scope = 'user' 

-

 

-

    def get_cache_key(self, request, view): 

-

        if request.user.is_authenticated(): 

-

            ident = request.user.id 

-

        else: 

-

            ident = request.META.get('REMOTE_ADDR', None) 

-

 

-

        return self.cache_format % { 

-

            'scope': self.scope, 

-

            'ident': ident 

-

        } 

-

 

-

 

-

class ScopedRateThrottle(SimpleRateThrottle): 

-

    """ 

-

    Limits the rate of API calls by different amounts for various parts of 

-

    the API.  Any view that has the `throttle_scope` property set will be 

-

    throttled.  The unique cache key will be generated by concatenating the 

-

    user id of the request, and the scope of the view being accessed. 

-

    """ 

-

    scope_attr = 'throttle_scope' 

-

 

-

    def __init__(self): 

-

        # Override the usual SimpleRateThrottle, because we can't determine 

-

        # the rate until called by the view. 

-

        pass 

-

 

-

    def allow_request(self, request, view): 

-

        # We can only determine the scope once we're called by the view. 

-

        self.scope = getattr(view, self.scope_attr, None) 

-

 

-

        # If a view does not have a `throttle_scope` always allow the request 

-

        if not self.scope: 

-

            return True 

-

 

-

        # Determine the allowed request rate as we normally would during 

-

        # the `__init__` call. 

-

        self.rate = self.get_rate() 

-

        self.num_requests, self.duration = self.parse_rate(self.rate) 

-

 

-

        # We can now proceed as normal. 

-

        return super(ScopedRateThrottle, self).allow_request(request, view) 

-

 

-

    def get_cache_key(self, request, view): 

-

        """ 

-

        If `view.throttle_scope` is not set, don't apply this throttle. 

-

 

-

        Otherwise generate the unique cache key by concatenating the user id 

-

        with the '.throttle_scope` property of the view. 

-

        """ 

-

        if request.user.is_authenticated(): 

-

            ident = request.user.id 

-

        else: 

-

            ident = request.META.get('REMOTE_ADDR', None) 

-

 

-

        return self.cache_format % { 

-

            'scope': self.scope, 

-

            'ident': ident 

-

        } 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_urlpatterns.html b/htmlcov/rest_framework_urlpatterns.html deleted file mode 100644 index 4c824a770..000000000 --- a/htmlcov/rest_framework_urlpatterns.html +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - - - Coverage for rest_framework/urlpatterns: 87% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

- -
-

from __future__ import unicode_literals 

-

from django.core.urlresolvers import RegexURLResolver 

-

from rest_framework.compat import url, include 

-

from rest_framework.settings import api_settings 

-

 

-

 

-

def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required): 

-

    ret = [] 

-

    for urlpattern in urlpatterns: 

-

        if isinstance(urlpattern, RegexURLResolver): 

-

            # Set of included URL patterns 

-

            regex = urlpattern.regex.pattern 

-

            namespace = urlpattern.namespace 

-

            app_name = urlpattern.app_name 

-

            kwargs = urlpattern.default_kwargs 

-

            # Add in the included patterns, after applying the suffixes 

-

            patterns = apply_suffix_patterns(urlpattern.url_patterns, 

-

                                             suffix_pattern, 

-

                                             suffix_required) 

-

            ret.append(url(regex, include(patterns, namespace, app_name), kwargs)) 

-

 

-

        else: 

-

            # Regular URL pattern 

-

            regex = urlpattern.regex.pattern.rstrip('$') + suffix_pattern 

-

            view = urlpattern._callback or urlpattern._callback_str 

-

            kwargs = urlpattern.default_args 

-

            name = urlpattern.name 

-

            # Add in both the existing and the new urlpattern 

-

            if not suffix_required: 

-

                ret.append(urlpattern) 

-

            ret.append(url(regex, view, kwargs, name)) 

-

 

-

    return ret 

-

 

-

 

-

def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None): 

-

    """ 

-

    Supplement existing urlpatterns with corresponding patterns that also 

-

    include a '.format' suffix.  Retains urlpattern ordering. 

-

 

-

    urlpatterns: 

-

        A list of URL patterns. 

-

 

-

    suffix_required: 

-

        If `True`, only suffixed URLs will be generated, and non-suffixed 

-

        URLs will not be used.  Defaults to `False`. 

-

 

-

    allowed: 

-

        An optional tuple/list of allowed suffixes.  eg ['json', 'api'] 

-

        Defaults to `None`, which allows any suffix. 

-

    """ 

-

    suffix_kwarg = api_settings.FORMAT_SUFFIX_KWARG 

-

    if allowed: 

-

        if len(allowed) == 1: 

-

            allowed_pattern = allowed[0] 

-

        else: 

-

            allowed_pattern = '(%s)' % '|'.join(allowed) 

-

        suffix_pattern = r'\.(?P<%s>%s)$' % (suffix_kwarg, allowed_pattern) 

-

    else: 

-

        suffix_pattern = r'\.(?P<%s>[a-z]+)$' % suffix_kwarg 

-

 

-

    return apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_urls.html b/htmlcov/rest_framework_urls.html deleted file mode 100644 index 7720a6d40..000000000 --- a/htmlcov/rest_framework_urls.html +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - Coverage for rest_framework/urls: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

- -
-

""" 

-

Login and logout views for the browsable API. 

-

 

-

Add these to your root URLconf if you're using the browsable API and 

-

your API requires authentication. 

-

 

-

The urls must be namespaced as 'rest_framework', and you should make sure 

-

your authentication settings include `SessionAuthentication`. 

-

 

-

    urlpatterns = patterns('', 

-

        ... 

-

        url(r'^auth', include('rest_framework.urls', namespace='rest_framework')) 

-

    ) 

-

""" 

-

from __future__ import unicode_literals 

-

from rest_framework.compat import patterns, url 

-

 

-

 

-

template_name = {'template_name': 'rest_framework/login.html'} 

-

 

-

urlpatterns = patterns('django.contrib.auth.views', 

-

    url(r'^login/$', 'login', template_name, name='login'), 

-

    url(r'^logout/$', 'logout', template_name, name='logout'), 

-

) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_utils___init__.html b/htmlcov/rest_framework_utils___init__.html deleted file mode 100644 index 99eb18c4f..000000000 --- a/htmlcov/rest_framework_utils___init__.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - Coverage for rest_framework/utils/__init__: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
- - - -
-
- - - - - diff --git a/htmlcov/rest_framework_utils_breadcrumbs.html b/htmlcov/rest_framework_utils_breadcrumbs.html deleted file mode 100644 index 14fb8955d..000000000 --- a/htmlcov/rest_framework_utils_breadcrumbs.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - Coverage for rest_framework/utils/breadcrumbs: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

- -
-

from __future__ import unicode_literals 

-

from django.core.urlresolvers import resolve, get_script_prefix 

-

from rest_framework.utils.formatting import get_view_name 

-

 

-

 

-

def get_breadcrumbs(url): 

-

    """ 

-

    Given a url returns a list of breadcrumbs, which are each a 

-

    tuple of (name, url). 

-

    """ 

-

 

-

    from rest_framework.views import APIView 

-

 

-

    def breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen): 

-

        """ 

-

        Add tuples of (name, url) to the breadcrumbs list, 

-

        progressively chomping off parts of the url. 

-

        """ 

-

 

-

        try: 

-

            (view, unused_args, unused_kwargs) = resolve(url) 

-

        except Exception: 

-

            pass 

-

        else: 

-

            # Check if this is a REST framework view, 

-

            # and if so add it to the breadcrumbs 

-

            cls = getattr(view, 'cls', None) 

-

            if cls is not None and issubclass(cls, APIView): 

-

                # Don't list the same view twice in a row. 

-

                # Probably an optional trailing slash. 

-

                if not seen or seen[-1] != view: 

-

                    suffix = getattr(view, 'suffix', None) 

-

                    name = get_view_name(view.cls, suffix) 

-

                    breadcrumbs_list.insert(0, (name, prefix + url)) 

-

                    seen.append(view) 

-

 

-

        if url == '': 

-

            # All done 

-

            return breadcrumbs_list 

-

 

-

        elif url.endswith('/'): 

-

            # Drop trailing slash off the end and continue to try to 

-

            # resolve more breadcrumbs 

-

            url = url.rstrip('/') 

-

            return breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen) 

-

 

-

        # Drop trailing non-slash off the end and continue to try to 

-

        # resolve more breadcrumbs 

-

        url = url[:url.rfind('/') + 1] 

-

        return breadcrumbs_recursive(url, breadcrumbs_list, prefix, seen) 

-

 

-

    prefix = get_script_prefix().rstrip('/') 

-

    url = url[len(prefix):] 

-

    return breadcrumbs_recursive(url, [], prefix, []) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_utils_encoders.html b/htmlcov/rest_framework_utils_encoders.html deleted file mode 100644 index 9f0ca343a..000000000 --- a/htmlcov/rest_framework_utils_encoders.html +++ /dev/null @@ -1,275 +0,0 @@ - - - - - - - - Coverage for rest_framework/utils/encoders: 73% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

- -
-

""" 

-

Helper classes for parsers. 

-

""" 

-

from __future__ import unicode_literals 

-

from django.utils.datastructures import SortedDict 

-

from django.utils.functional import Promise 

-

from rest_framework.compat import timezone, force_text 

-

from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata 

-

import datetime 

-

import decimal 

-

import types 

-

import json 

-

 

-

 

-

class JSONEncoder(json.JSONEncoder): 

-

    """ 

-

    JSONEncoder subclass that knows how to encode date/time/timedelta, 

-

    decimal types, and generators. 

-

    """ 

-

    def default(self, o): 

-

        # For Date Time string spec, see ECMA 262 

-

        # http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15 

-

        if isinstance(o, Promise): 

-

            return force_text(o) 

-

        elif isinstance(o, datetime.datetime): 

-

            r = o.isoformat() 

-

            if o.microsecond: 

-

                r = r[:23] + r[26:] 

-

            if r.endswith('+00:00'): 

-

                r = r[:-6] + 'Z' 

-

            return r 

-

        elif isinstance(o, datetime.date): 

-

            return o.isoformat() 

-

        elif isinstance(o, datetime.time): 

-

            if timezone and timezone.is_aware(o): 

-

                raise ValueError("JSON can't represent timezone-aware times.") 

-

            r = o.isoformat() 

-

            if o.microsecond: 

-

                r = r[:12] 

-

            return r 

-

        elif isinstance(o, datetime.timedelta): 

-

            return str(o.total_seconds()) 

-

        elif isinstance(o, decimal.Decimal): 

-

            return str(o) 

-

        elif hasattr(o, '__iter__'): 

-

            return [i for i in o] 

-

        return super(JSONEncoder, self).default(o) 

-

 

-

 

-

try: 

-

    import yaml 

-

except ImportError: 

-

    SafeDumper = None 

-

else: 

-

    # Adapted from http://pyyaml.org/attachment/ticket/161/use_ordered_dict.py 

-

    class SafeDumper(yaml.SafeDumper): 

-

        """ 

-

        Handles decimals as strings. 

-

        Handles SortedDicts as usual dicts, but preserves field order, rather 

-

        than the usual behaviour of sorting the keys. 

-

        """ 

-

        def represent_decimal(self, data): 

-

            return self.represent_scalar('tag:yaml.org,2002:str', str(data)) 

-

 

-

        def represent_mapping(self, tag, mapping, flow_style=None): 

-

            value = [] 

-

            node = yaml.MappingNode(tag, value, flow_style=flow_style) 

-

            if self.alias_key is not None: 

-

                self.represented_objects[self.alias_key] = node 

-

            best_style = True 

-

            if hasattr(mapping, 'items'): 

-

                mapping = list(mapping.items()) 

-

                if not isinstance(mapping, SortedDict): 

-

                    mapping.sort() 

-

            for item_key, item_value in mapping: 

-

                node_key = self.represent_data(item_key) 

-

                node_value = self.represent_data(item_value) 

-

                if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style): 

-

                    best_style = False 

-

                if not (isinstance(node_value, yaml.ScalarNode) and not node_value.style): 

-

                    best_style = False 

-

                value.append((node_key, node_value)) 

-

            if flow_style is None: 

-

                if self.default_flow_style is not None: 

-

                    node.flow_style = self.default_flow_style 

-

                else: 

-

                    node.flow_style = best_style 

-

            return node 

-

 

-

    SafeDumper.add_representer(SortedDict, 

-

            yaml.representer.SafeRepresenter.represent_dict) 

-

    SafeDumper.add_representer(DictWithMetadata, 

-

            yaml.representer.SafeRepresenter.represent_dict) 

-

    SafeDumper.add_representer(SortedDictWithMetadata, 

-

            yaml.representer.SafeRepresenter.represent_dict) 

-

    SafeDumper.add_representer(types.GeneratorType, 

-

            yaml.representer.SafeRepresenter.represent_list) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_utils_formatting.html b/htmlcov/rest_framework_utils_formatting.html deleted file mode 100644 index 54e1570f7..000000000 --- a/htmlcov/rest_framework_utils_formatting.html +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - - - Coverage for rest_framework/utils/formatting: 97% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

- -
-

""" 

-

Utility functions to return a formatted name and description for a given view. 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

from django.utils.html import escape 

-

from django.utils.safestring import mark_safe 

-

from rest_framework.compat import apply_markdown 

-

import re 

-

 

-

 

-

def _remove_trailing_string(content, trailing): 

-

    """ 

-

    Strip trailing component `trailing` from `content` if it exists. 

-

    Used when generating names from view classes. 

-

    """ 

-

    if content.endswith(trailing) and content != trailing: 

-

        return content[:-len(trailing)] 

-

    return content 

-

 

-

 

-

def _remove_leading_indent(content): 

-

    """ 

-

    Remove leading indent from a block of text. 

-

    Used when generating descriptions from docstrings. 

-

    """ 

-

    whitespace_counts = [len(line) - len(line.lstrip(' ')) 

-

                         for line in content.splitlines()[1:] if line.lstrip()] 

-

 

-

    # unindent the content if needed 

-

    if whitespace_counts: 

-

        whitespace_pattern = '^' + (' ' * min(whitespace_counts)) 

-

        content = re.sub(re.compile(whitespace_pattern, re.MULTILINE), '', content) 

-

    content = content.strip('\n') 

-

    return content 

-

 

-

 

-

def _camelcase_to_spaces(content): 

-

    """ 

-

    Translate 'CamelCaseNames' to 'Camel Case Names'. 

-

    Used when generating names from view classes. 

-

    """ 

-

    camelcase_boundry = '(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))' 

-

    content = re.sub(camelcase_boundry, ' \\1', content).strip() 

-

    return ' '.join(content.split('_')).title() 

-

 

-

 

-

def get_view_name(cls, suffix=None): 

-

    """ 

-

    Return a formatted name for an `APIView` class or `@api_view` function. 

-

    """ 

-

    name = cls.__name__ 

-

    name = _remove_trailing_string(name, 'View') 

-

    name = _remove_trailing_string(name, 'ViewSet') 

-

    name = _camelcase_to_spaces(name) 

-

    if suffix: 

-

        name += ' ' + suffix 

-

    return name 

-

 

-

 

-

def get_view_description(cls, html=False): 

-

    """ 

-

    Return a description for an `APIView` class or `@api_view` function. 

-

    """ 

-

    description = cls.__doc__ or '' 

-

    description = _remove_leading_indent(description) 

-

    if html: 

-

        return markup_description(description) 

-

    return description 

-

 

-

 

-

def markup_description(description): 

-

    """ 

-

    Apply HTML markup to the given description. 

-

    """ 

-

    if apply_markdown: 

-

        description = apply_markdown(description) 

-

    else: 

-

        description = escape(description).replace('\n', '<br />') 

-

    return mark_safe(description) 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_utils_mediatypes.html b/htmlcov/rest_framework_utils_mediatypes.html deleted file mode 100644 index 2ce44ab59..000000000 --- a/htmlcov/rest_framework_utils_mediatypes.html +++ /dev/null @@ -1,257 +0,0 @@ - - - - - - - - Coverage for rest_framework/utils/mediatypes: 77% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

- -
-

""" 

-

Handling of media types, as found in HTTP Content-Type and Accept headers. 

-

 

-

See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 

-

""" 

-

from __future__ import unicode_literals 

-

from django.http.multipartparser import parse_header 

-

from rest_framework import HTTP_HEADER_ENCODING 

-

 

-

 

-

def media_type_matches(lhs, rhs): 

-

    """ 

-

    Returns ``True`` if the media type in the first argument <= the 

-

    media type in the second argument.  The media types are strings 

-

    as described by the HTTP spec. 

-

 

-

    Valid media type strings include: 

-

 

-

    'application/json; indent=4' 

-

    'application/json' 

-

    'text/*' 

-

    '*/*' 

-

    """ 

-

    lhs = _MediaType(lhs) 

-

    rhs = _MediaType(rhs) 

-

    return lhs.match(rhs) 

-

 

-

 

-

def order_by_precedence(media_type_lst): 

-

    """ 

-

    Returns a list of sets of media type strings, ordered by precedence. 

-

    Precedence is determined by how specific a media type is: 

-

 

-

    3. 'type/subtype; param=val' 

-

    2. 'type/subtype' 

-

    1. 'type/*' 

-

    0. '*/*' 

-

    """ 

-

    ret = [set(), set(), set(), set()] 

-

    for media_type in media_type_lst: 

-

        precedence = _MediaType(media_type).precedence 

-

        ret[3 - precedence].add(media_type) 

-

    return [media_types for media_types in ret if media_types] 

-

 

-

 

-

class _MediaType(object): 

-

    def __init__(self, media_type_str): 

-

        if media_type_str is None: 

-

            media_type_str = '' 

-

        self.orig = media_type_str 

-

        self.full_type, self.params = parse_header(media_type_str.encode(HTTP_HEADER_ENCODING)) 

-

        self.main_type, sep, self.sub_type = self.full_type.partition('/') 

-

 

-

    def match(self, other): 

-

        """Return true if this MediaType satisfies the given MediaType.""" 

-

        for key in self.params.keys(): 

-

            if key != 'q' and other.params.get(key, None) != self.params.get(key, None): 

-

                return False 

-

 

-

        if self.sub_type != '*' and other.sub_type != '*'  and other.sub_type != self.sub_type: 

-

            return False 

-

 

-

        if self.main_type != '*' and other.main_type != '*' and other.main_type != self.main_type: 

-

            return False 

-

 

-

        return True 

-

 

-

    @property 

-

    def precedence(self): 

-

        """ 

-

        Return a precedence level from 0-3 for the media type given how specific it is. 

-

        """ 

-

        if self.main_type == '*': 

-

            return 0 

-

        elif self.sub_type == '*': 

-

            return 1 

-

        elif not self.params or self.params.keys() == ['q']: 

-

            return 2 

-

        return 3 

-

 

-

    def __str__(self): 

-

        return unicode(self).encode('utf-8') 

-

 

-

    def __unicode__(self): 

-

        ret = "%s/%s" % (self.main_type, self.sub_type) 

-

        for key, val in self.params.items(): 

-

            ret += "; %s=%s" % (key, val) 

-

        return ret 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_views.html b/htmlcov/rest_framework_views.html deleted file mode 100644 index f836e71fb..000000000 --- a/htmlcov/rest_framework_views.html +++ /dev/null @@ -1,793 +0,0 @@ - - - - - - - - Coverage for rest_framework/views: 100% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

-

140

-

141

-

142

-

143

-

144

-

145

-

146

-

147

-

148

-

149

-

150

-

151

-

152

-

153

-

154

-

155

-

156

-

157

-

158

-

159

-

160

-

161

-

162

-

163

-

164

-

165

-

166

-

167

-

168

-

169

-

170

-

171

-

172

-

173

-

174

-

175

-

176

-

177

-

178

-

179

-

180

-

181

-

182

-

183

-

184

-

185

-

186

-

187

-

188

-

189

-

190

-

191

-

192

-

193

-

194

-

195

-

196

-

197

-

198

-

199

-

200

-

201

-

202

-

203

-

204

-

205

-

206

-

207

-

208

-

209

-

210

-

211

-

212

-

213

-

214

-

215

-

216

-

217

-

218

-

219

-

220

-

221

-

222

-

223

-

224

-

225

-

226

-

227

-

228

-

229

-

230

-

231

-

232

-

233

-

234

-

235

-

236

-

237

-

238

-

239

-

240

-

241

-

242

-

243

-

244

-

245

-

246

-

247

-

248

-

249

-

250

-

251

-

252

-

253

-

254

-

255

-

256

-

257

-

258

-

259

-

260

-

261

-

262

-

263

-

264

-

265

-

266

-

267

-

268

-

269

-

270

-

271

-

272

-

273

-

274

-

275

-

276

-

277

-

278

-

279

-

280

-

281

-

282

-

283

-

284

-

285

-

286

-

287

-

288

-

289

-

290

-

291

-

292

-

293

-

294

-

295

-

296

-

297

-

298

-

299

-

300

-

301

-

302

-

303

-

304

-

305

-

306

-

307

-

308

-

309

-

310

-

311

-

312

-

313

-

314

-

315

-

316

-

317

-

318

-

319

-

320

-

321

-

322

-

323

-

324

-

325

-

326

-

327

-

328

-

329

-

330

-

331

-

332

-

333

-

334

-

335

-

336

-

337

-

338

-

339

-

340

-

341

-

342

-

343

-

344

-

345

-

346

-

347

-

348

-

349

-

350

-

351

-

352

-

353

-

354

-

355

-

356

- -
-

""" 

-

Provides an APIView class that is the base of all views in REST framework. 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

from django.core.exceptions import PermissionDenied 

-

from django.http import Http404, HttpResponse 

-

from django.utils.datastructures import SortedDict 

-

from django.views.decorators.csrf import csrf_exempt 

-

from rest_framework import status, exceptions 

-

from rest_framework.compat import View 

-

from rest_framework.request import Request 

-

from rest_framework.response import Response 

-

from rest_framework.settings import api_settings 

-

from rest_framework.utils.formatting import get_view_name, get_view_description 

-

 

-

 

-

class APIView(View): 

-

    settings = api_settings 

-

 

-

    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES 

-

    parser_classes = api_settings.DEFAULT_PARSER_CLASSES 

-

    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES 

-

    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES 

-

    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES 

-

    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS 

-

 

-

    @classmethod 

-

    def as_view(cls, **initkwargs): 

-

        """ 

-

        Store the original class on the view function. 

-

 

-

        This allows us to discover information about the view when we do URL 

-

        reverse lookups.  Used for breadcrumb generation. 

-

        """ 

-

        view = super(APIView, cls).as_view(**initkwargs) 

-

        view.cls = cls 

-

        return view 

-

 

-

    @property 

-

    def allowed_methods(self): 

-

        """ 

-

        Wrap Django's private `_allowed_methods` interface in a public property. 

-

        """ 

-

        return self._allowed_methods() 

-

 

-

    @property 

-

    def default_response_headers(self): 

-

        # TODO: deprecate? 

-

        # TODO: Only vary by accept if multiple renderers 

-

        return { 

-

            'Allow': ', '.join(self.allowed_methods), 

-

            'Vary': 'Accept' 

-

        } 

-

 

-

    def http_method_not_allowed(self, request, *args, **kwargs): 

-

        """ 

-

        If `request.method` does not correspond to a handler method, 

-

        determine what kind of exception to raise. 

-

        """ 

-

        raise exceptions.MethodNotAllowed(request.method) 

-

 

-

    def permission_denied(self, request): 

-

        """ 

-

        If request is not permitted, determine what kind of exception to raise. 

-

        """ 

-

        if not self.request.successful_authenticator: 

-

            raise exceptions.NotAuthenticated() 

-

        raise exceptions.PermissionDenied() 

-

 

-

    def throttled(self, request, wait): 

-

        """ 

-

        If request is throttled, determine what kind of exception to raise. 

-

        """ 

-

        raise exceptions.Throttled(wait) 

-

 

-

    def get_authenticate_header(self, request): 

-

        """ 

-

        If a request is unauthenticated, determine the WWW-Authenticate 

-

        header to use for 401 responses, if any. 

-

        """ 

-

        authenticators = self.get_authenticators() 

-

        if authenticators: 

-

            return authenticators[0].authenticate_header(request) 

-

 

-

    def get_parser_context(self, http_request): 

-

        """ 

-

        Returns a dict that is passed through to Parser.parse(), 

-

        as the `parser_context` keyword argument. 

-

        """ 

-

        # Note: Additionally `request` will also be added to the context 

-

        #       by the Request object. 

-

        return { 

-

            'view': self, 

-

            'args': getattr(self, 'args', ()), 

-

            'kwargs': getattr(self, 'kwargs', {}) 

-

        } 

-

 

-

    def get_renderer_context(self): 

-

        """ 

-

        Returns a dict that is passed through to Renderer.render(), 

-

        as the `renderer_context` keyword argument. 

-

        """ 

-

        # Note: Additionally 'response' will also be added to the context, 

-

        #       by the Response object. 

-

        return { 

-

            'view': self, 

-

            'args': getattr(self, 'args', ()), 

-

            'kwargs': getattr(self, 'kwargs', {}), 

-

            'request': getattr(self, 'request', None) 

-

        } 

-

 

-

    # API policy instantiation methods 

-

 

-

    def get_format_suffix(self, **kwargs): 

-

        """ 

-

        Determine if the request includes a '.json' style format suffix 

-

        """ 

-

        if self.settings.FORMAT_SUFFIX_KWARG: 

-

            return kwargs.get(self.settings.FORMAT_SUFFIX_KWARG) 

-

 

-

    def get_renderers(self): 

-

        """ 

-

        Instantiates and returns the list of renderers that this view can use. 

-

        """ 

-

        return [renderer() for renderer in self.renderer_classes] 

-

 

-

    def get_parsers(self): 

-

        """ 

-

        Instantiates and returns the list of parsers that this view can use. 

-

        """ 

-

        return [parser() for parser in self.parser_classes] 

-

 

-

    def get_authenticators(self): 

-

        """ 

-

        Instantiates and returns the list of authenticators that this view can use. 

-

        """ 

-

        return [auth() for auth in self.authentication_classes] 

-

 

-

    def get_permissions(self): 

-

        """ 

-

        Instantiates and returns the list of permissions that this view requires. 

-

        """ 

-

        return [permission() for permission in self.permission_classes] 

-

 

-

    def get_throttles(self): 

-

        """ 

-

        Instantiates and returns the list of throttles that this view uses. 

-

        """ 

-

        return [throttle() for throttle in self.throttle_classes] 

-

 

-

    def get_content_negotiator(self): 

-

        """ 

-

        Instantiate and return the content negotiation class to use. 

-

        """ 

-

        if not getattr(self, '_negotiator', None): 

-

            self._negotiator = self.content_negotiation_class() 

-

        return self._negotiator 

-

 

-

    # API policy implementation methods 

-

 

-

    def perform_content_negotiation(self, request, force=False): 

-

        """ 

-

        Determine which renderer and media type to use render the response. 

-

        """ 

-

        renderers = self.get_renderers() 

-

        conneg = self.get_content_negotiator() 

-

 

-

        try: 

-

            return conneg.select_renderer(request, renderers, self.format_kwarg) 

-

        except Exception: 

-

            if force: 

-

                return (renderers[0], renderers[0].media_type) 

-

            raise 

-

 

-

    def perform_authentication(self, request): 

-

        """ 

-

        Perform authentication on the incoming request. 

-

 

-

        Note that if you override this and simply 'pass', then authentication 

-

        will instead be performed lazily, the first time either 

-

        `request.user` or `request.auth` is accessed. 

-

        """ 

-

        request.user 

-

 

-

    def check_permissions(self, request): 

-

        """ 

-

        Check if the request should be permitted. 

-

        Raises an appropriate exception if the request is not permitted. 

-

        """ 

-

        for permission in self.get_permissions(): 

-

            if not permission.has_permission(request, self): 

-

                self.permission_denied(request) 

-

 

-

    def check_object_permissions(self, request, obj): 

-

        """ 

-

        Check if the request should be permitted for a given object. 

-

        Raises an appropriate exception if the request is not permitted. 

-

        """ 

-

        for permission in self.get_permissions(): 

-

            if not permission.has_object_permission(request, self, obj): 

-

                self.permission_denied(request) 

-

 

-

    def check_throttles(self, request): 

-

        """ 

-

        Check if request should be throttled. 

-

        Raises an appropriate exception if the request is throttled. 

-

        """ 

-

        for throttle in self.get_throttles(): 

-

            if not throttle.allow_request(request, self): 

-

                self.throttled(request, throttle.wait()) 

-

 

-

    # Dispatch methods 

-

 

-

    def initialize_request(self, request, *args, **kargs): 

-

        """ 

-

        Returns the initial request object. 

-

        """ 

-

        parser_context = self.get_parser_context(request) 

-

 

-

        return Request(request, 

-

                       parsers=self.get_parsers(), 

-

                       authenticators=self.get_authenticators(), 

-

                       negotiator=self.get_content_negotiator(), 

-

                       parser_context=parser_context) 

-

 

-

    def initial(self, request, *args, **kwargs): 

-

        """ 

-

        Runs anything that needs to occur prior to calling the method handler. 

-

        """ 

-

        self.format_kwarg = self.get_format_suffix(**kwargs) 

-

 

-

        # Ensure that the incoming request is permitted 

-

        self.perform_authentication(request) 

-

        self.check_permissions(request) 

-

        self.check_throttles(request) 

-

 

-

        # Perform content negotiation and store the accepted info on the request 

-

        neg = self.perform_content_negotiation(request) 

-

        request.accepted_renderer, request.accepted_media_type = neg 

-

 

-

    def finalize_response(self, request, response, *args, **kwargs): 

-

        """ 

-

        Returns the final response object. 

-

        """ 

-

        # Make the error obvious if a proper response is not returned 

-

        assert isinstance(response, HttpResponse), ( 

-

            'Expected a `Response` to be returned from the view, ' 

-

            'but received a `%s`' % type(response) 

-

        ) 

-

 

-

        if isinstance(response, Response): 

-

            if not getattr(request, 'accepted_renderer', None): 

-

                neg = self.perform_content_negotiation(request, force=True) 

-

                request.accepted_renderer, request.accepted_media_type = neg 

-

 

-

            response.accepted_renderer = request.accepted_renderer 

-

            response.accepted_media_type = request.accepted_media_type 

-

            response.renderer_context = self.get_renderer_context() 

-

 

-

        for key, value in self.headers.items(): 

-

            response[key] = value 

-

 

-

        return response 

-

 

-

    def handle_exception(self, exc): 

-

        """ 

-

        Handle any exception that occurs, by returning an appropriate response, 

-

        or re-raising the error. 

-

        """ 

-

        if isinstance(exc, exceptions.Throttled): 

-

            # Throttle wait header 

-

            self.headers['X-Throttle-Wait-Seconds'] = '%d' % exc.wait 

-

 

-

        if isinstance(exc, (exceptions.NotAuthenticated, 

-

                            exceptions.AuthenticationFailed)): 

-

            # WWW-Authenticate header for 401 responses, else coerce to 403 

-

            auth_header = self.get_authenticate_header(self.request) 

-

 

-

            if auth_header: 

-

                self.headers['WWW-Authenticate'] = auth_header 

-

            else: 

-

                exc.status_code = status.HTTP_403_FORBIDDEN 

-

 

-

        if isinstance(exc, exceptions.APIException): 

-

            return Response({'detail': exc.detail}, 

-

                            status=exc.status_code, 

-

                            exception=True) 

-

        elif isinstance(exc, Http404): 

-

            return Response({'detail': 'Not found'}, 

-

                            status=status.HTTP_404_NOT_FOUND, 

-

                            exception=True) 

-

        elif isinstance(exc, PermissionDenied): 

-

            return Response({'detail': 'Permission denied'}, 

-

                            status=status.HTTP_403_FORBIDDEN, 

-

                            exception=True) 

-

        raise 

-

 

-

    # Note: session based authentication is explicitly CSRF validated, 

-

    # all other authentication is CSRF exempt. 

-

    @csrf_exempt 

-

    def dispatch(self, request, *args, **kwargs): 

-

        """ 

-

        `.dispatch()` is pretty much the same as Django's regular dispatch, 

-

        but with extra hooks for startup, finalize, and exception handling. 

-

        """ 

-

        self.args = args 

-

        self.kwargs = kwargs 

-

        request = self.initialize_request(request, *args, **kwargs) 

-

        self.request = request 

-

        self.headers = self.default_response_headers  # deprecate? 

-

 

-

        try: 

-

            self.initial(request, *args, **kwargs) 

-

 

-

            # Get the appropriate handler method 

-

            if request.method.lower() in self.http_method_names: 

-

                handler = getattr(self, request.method.lower(), 

-

                                  self.http_method_not_allowed) 

-

            else: 

-

                handler = self.http_method_not_allowed 

-

 

-

            response = handler(request, *args, **kwargs) 

-

 

-

        except Exception as exc: 

-

            response = self.handle_exception(exc) 

-

 

-

        self.response = self.finalize_response(request, response, *args, **kwargs) 

-

        return self.response 

-

 

-

    def options(self, request, *args, **kwargs): 

-

        """ 

-

        Handler method for HTTP 'OPTIONS' request. 

-

        We may as well implement this as Django will otherwise provide 

-

        a less useful default implementation. 

-

        """ 

-

        return Response(self.metadata(request), status=status.HTTP_200_OK) 

-

 

-

    def metadata(self, request): 

-

        """ 

-

        Return a dictionary of metadata about the view. 

-

        Used to return responses for OPTIONS requests. 

-

        """ 

-

 

-

        # This is used by ViewSets to disambiguate instance vs list views 

-

        view_name_suffix = getattr(self, 'suffix', None) 

-

 

-

        # By default we can't provide any form-like information, however the 

-

        # generic views override this implementation and add additional 

-

        # information for POST and PUT methods, based on the serializer. 

-

        ret = SortedDict() 

-

        ret['name'] = get_view_name(self.__class__, view_name_suffix) 

-

        ret['description'] = get_view_description(self.__class__) 

-

        ret['renders'] = [renderer.media_type for renderer in self.renderer_classes] 

-

        ret['parses'] = [parser.media_type for parser in self.parser_classes] 

-

        return ret 

- -
-
- - - - - diff --git a/htmlcov/rest_framework_viewsets.html b/htmlcov/rest_framework_viewsets.html deleted file mode 100644 index 8264ddc0c..000000000 --- a/htmlcov/rest_framework_viewsets.html +++ /dev/null @@ -1,359 +0,0 @@ - - - - - - - - Coverage for rest_framework/viewsets: 95% - - - - - - - - - - - -
- -

Hot-keys on this page

-
-

- r - m - x - p   toggle line displays -

-

- j - k   next/prev highlighted chunk -

-

- 0   (zero) top of page -

-

- 1   (one) first highlighted chunk -

-
-
- -
- - - - - -
-

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-

32

-

33

-

34

-

35

-

36

-

37

-

38

-

39

-

40

-

41

-

42

-

43

-

44

-

45

-

46

-

47

-

48

-

49

-

50

-

51

-

52

-

53

-

54

-

55

-

56

-

57

-

58

-

59

-

60

-

61

-

62

-

63

-

64

-

65

-

66

-

67

-

68

-

69

-

70

-

71

-

72

-

73

-

74

-

75

-

76

-

77

-

78

-

79

-

80

-

81

-

82

-

83

-

84

-

85

-

86

-

87

-

88

-

89

-

90

-

91

-

92

-

93

-

94

-

95

-

96

-

97

-

98

-

99

-

100

-

101

-

102

-

103

-

104

-

105

-

106

-

107

-

108

-

109

-

110

-

111

-

112

-

113

-

114

-

115

-

116

-

117

-

118

-

119

-

120

-

121

-

122

-

123

-

124

-

125

-

126

-

127

-

128

-

129

-

130

-

131

-

132

-

133

-

134

-

135

-

136

-

137

-

138

-

139

- -
-

""" 

-

ViewSets are essentially just a type of class based view, that doesn't provide 

-

any method handlers, such as `get()`, `post()`, etc... but instead has actions, 

-

such as `list()`, `retrieve()`, `create()`, etc... 

-

 

-

Actions are only bound to methods at the point of instantiating the views. 

-

 

-

    user_list = UserViewSet.as_view({'get': 'list'}) 

-

    user_detail = UserViewSet.as_view({'get': 'retrieve'}) 

-

 

-

Typically, rather than instantiate views from viewsets directly, you'll 

-

regsiter the viewset with a router and let the URL conf be determined 

-

automatically. 

-

 

-

    router = DefaultRouter() 

-

    router.register(r'users', UserViewSet, 'user') 

-

    urlpatterns = router.urls 

-

""" 

-

from __future__ import unicode_literals 

-

 

-

from functools import update_wrapper 

-

from django.utils.decorators import classonlymethod 

-

from rest_framework import views, generics, mixins 

-

 

-

 

-

class ViewSetMixin(object): 

-

    """ 

-

    This is the magic. 

-

 

-

    Overrides `.as_view()` so that it takes an `actions` keyword that performs 

-

    the binding of HTTP methods to actions on the Resource. 

-

 

-

    For example, to create a concrete view binding the 'GET' and 'POST' methods 

-

    to the 'list' and 'create' actions... 

-

 

-

    view = MyViewSet.as_view({'get': 'list', 'post': 'create'}) 

-

    """ 

-

 

-

    @classonlymethod 

-

    def as_view(cls, actions=None, **initkwargs): 

-

        """ 

-

        Because of the way class based views create a closure around the 

-

        instantiated view, we need to totally reimplement `.as_view`, 

-

        and slightly modify the view function that is created and returned. 

-

        """ 

-

        # The suffix initkwarg is reserved for identifing the viewset type 

-

        # eg. 'List' or 'Instance'. 

-

        cls.suffix = None 

-

 

-

        # sanitize keyword arguments 

-

        for key in initkwargs: 

-

            if key in cls.http_method_names: 

-

                raise TypeError("You tried to pass in the %s method name as a " 

-

                                "keyword argument to %s(). Don't do that." 

-

                                % (key, cls.__name__)) 

-

            if not hasattr(cls, key): 

-

                raise TypeError("%s() received an invalid keyword %r" % ( 

-

                    cls.__name__, key)) 

-

 

-

        def view(request, *args, **kwargs): 

-

            self = cls(**initkwargs) 

-

            # We also store the mapping of request methods to actions, 

-

            # so that we can later set the action attribute. 

-

            # eg. `self.action = 'list'` on an incoming GET request. 

-

            self.action_map = actions 

-

 

-

            # Bind methods to actions 

-

            # This is the bit that's different to a standard view 

-

            for method, action in actions.items(): 

-

                handler = getattr(self, action) 

-

                setattr(self, method, handler) 

-

 

-

            # Patch this in as it's otherwise only present from 1.5 onwards 

-

            if hasattr(self, 'get') and not hasattr(self, 'head'): 

-

                self.head = self.get 

-

 

-

            # And continue as usual 

-

            return self.dispatch(request, *args, **kwargs) 

-

 

-

        # take name and docstring from class 

-

        update_wrapper(view, cls, updated=()) 

-

 

-

        # and possible attributes set by decorators 

-

        # like csrf_exempt from dispatch 

-

        update_wrapper(view, cls.dispatch, assigned=()) 

-

 

-

        # We need to set these on the view function, so that breadcrumb 

-

        # generation can pick out these bits of information from a 

-

        # resolved URL. 

-

        view.cls = cls 

-

        view.suffix = initkwargs.get('suffix', None) 

-

        return view 

-

 

-

    def initialize_request(self, request, *args, **kargs): 

-

        """ 

-

        Set the `.action` attribute on the view, 

-

        depending on the request method. 

-

        """ 

-

        request = super(ViewSetMixin, self).initialize_request(request, *args, **kargs) 

-

        self.action = self.action_map.get(request.method.lower()) 

-

        return request 

-

 

-

 

-

class ViewSet(ViewSetMixin, views.APIView): 

-

    """ 

-

    The base ViewSet class does not provide any actions by default. 

-

    """ 

-

    pass 

-

 

-

 

-

class GenericViewSet(ViewSetMixin, generics.GenericAPIView): 

-

    """ 

-

    The GenericViewSet class does not provide any actions by default, 

-

    but does include the base set of generic view behavior, such as 

-

    the `get_object` and `get_queryset` methods. 

-

    """ 

-

    pass 

-

 

-

 

-

class ReadOnlyModelViewSet(mixins.RetrieveModelMixin, 

-

                           mixins.ListModelMixin, 

-

                           GenericViewSet): 

-

    """ 

-

    A viewset that provides default `list()` and `retrieve()` actions. 

-

    """ 

-

    pass 

-

 

-

 

-

class ModelViewSet(mixins.CreateModelMixin, 

-

                    mixins.RetrieveModelMixin, 

-

                    mixins.UpdateModelMixin, 

-

                    mixins.DestroyModelMixin, 

-

                    mixins.ListModelMixin, 

-

                    GenericViewSet): 

-

    """ 

-

    A viewset that provides default `create()`, `retrieve()`, `update()`, 

-

    `partial_update()`, `destroy()` and `list()` actions. 

-

    """ 

-

    pass 

- -
-
- - - - - diff --git a/htmlcov/status.dat b/htmlcov/status.dat deleted file mode 100644 index 9e448e377..000000000 --- a/htmlcov/status.dat +++ /dev/null @@ -1,1258 +0,0 @@ -(dp1 -S'files' -p2 -(dp3 -S'rest_framework_utils_encoders' -p4 -(dp5 -S'index' -p6 -(dp7 -S'par' -p8 -I0 -sS'html_filename' -p9 -S'rest_framework_utils_encoders.html' -p10 -sS'name' -p11 -S'rest_framework/utils/encoders' -p12 -sS'nums' -p13 -ccopy_reg -_reconstructor -p14 -(ccoverage.results -Numbers -p15 -c__builtin__ -object -p16 -NtRp17 -(dp18 -S'n_files' -p19 -I1 -sS'n_branches' -p20 -I0 -sS'n_statements' -p21 -I70 -sS'n_excluded' -p22 -I0 -sS'n_missing' -p23 -I19 -sS'n_missing_branches' -p24 -I0 -sbssS'hash' -p25 -S'\x9d|\xea|-p\x0c#\xef\x82\x8c\x91\xf1\xcd}f' -p26 -ssS'rest_framework___init__' -p27 -(dp28 -g6 -(dp29 -g8 -I0 -sg9 -S'rest_framework___init__.html' -p30 -sg11 -S'rest_framework/__init__' -p31 -sg13 -g14 -(g15 -g16 -NtRp32 -(dp33 -g19 -I1 -sg20 -I0 -sg21 -I4 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'\xbc\xf1f\xd9[\xd4\xcf\x9cQ\x94H\xfd3)\xea[' -p34 -ssS'rest_framework_urlpatterns' -p35 -(dp36 -g6 -(dp37 -g8 -I0 -sg9 -S'rest_framework_urlpatterns.html' -p38 -sg11 -S'rest_framework/urlpatterns' -p39 -sg13 -g14 -(g15 -g16 -NtRp40 -(dp41 -g19 -I1 -sg20 -I0 -sg21 -I31 -sg22 -I0 -sg23 -I4 -sg24 -I0 -sbssg25 -S"\x84\xb0-\xc6Y\x7f\xebA'\x8c5+\xcf\xf6\xcf\xda" -p42 -ssS'rest_framework_permissions' -p43 -(dp44 -g6 -(dp45 -g8 -I0 -sg9 -S'rest_framework_permissions.html' -p46 -sg11 -S'rest_framework/permissions' -p47 -sg13 -g14 -(g15 -g16 -NtRp48 -(dp49 -g19 -I1 -sg20 -I0 -sg21 -I63 -sg22 -I0 -sg23 -I12 -sg24 -I0 -sbssg25 -S",\xc4,\xda\x05\x86\x17\xe8u2~ls*'\xc1" -p50 -ssS'rest_framework_fields' -p51 -(dp52 -g6 -(dp53 -g8 -I0 -sg9 -S'rest_framework_fields.html' -p54 -sg11 -S'rest_framework/fields' -p55 -sg13 -g14 -(g15 -g16 -NtRp56 -(dp57 -g19 -I1 -sg20 -I0 -sg21 -I594 -sg22 -I0 -sg23 -I80 -sg24 -I0 -sbssg25 -S'\x08\x1b\xd2m\x91l\x14e\x97CDA\x1c&k\xf9' -p58 -ssS'rest_framework_models' -p59 -(dp60 -g6 -(dp61 -g8 -I0 -sg9 -S'rest_framework_models.html' -p62 -sg11 -S'rest_framework/models' -p63 -sg13 -g14 -(g15 -g16 -NtRp64 -(dp65 -g19 -I1 -sg20 -I0 -sg21 -I0 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S' E\xaf\xdd\xe7\xbb\xc4\x11z\xf8\x80\x18v.\xec\xf6' -p66 -ssS'rest_framework_utils_breadcrumbs' -p67 -(dp68 -g6 -(dp69 -g8 -I0 -sg9 -S'rest_framework_utils_breadcrumbs.html' -p70 -sg11 -S'rest_framework/utils/breadcrumbs' -p71 -sg13 -g14 -(g15 -g16 -NtRp72 -(dp73 -g19 -I1 -sg20 -I0 -sg21 -I27 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'V"\xf6\xbc\\m)\x12R4>c\xff\xea\xde\x8b' -p74 -ssS'rest_framework_urls' -p75 -(dp76 -g6 -(dp77 -g8 -I0 -sg9 -S'rest_framework_urls.html' -p78 -sg11 -S'rest_framework/urls' -p79 -sg13 -g14 -(g15 -g16 -NtRp80 -(dp81 -g19 -I1 -sg20 -I0 -sg21 -I4 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'\xba\x9b\xdaeu\x17\x8b\xe0e\xc6-\xc5R\xba\xa2\xd5' -p82 -ssS'rest_framework_serializers' -p83 -(dp84 -g6 -(dp85 -g8 -I0 -sg9 -S'rest_framework_serializers.html' -p86 -sg11 -S'rest_framework/serializers' -p87 -sg13 -g14 -(g15 -g16 -NtRp88 -(dp89 -g19 -I1 -sg20 -I0 -sg21 -I464 -sg22 -I0 -sg23 -I27 -sg24 -I0 -sbssg25 -S'O\\\xf6\x81y\x95\xae\x9a)\xe9~\xb8\xab\t\x88#' -p90 -ssS'rest_framework_exceptions' -p91 -(dp92 -g6 -(dp93 -g8 -I0 -sg9 -S'rest_framework_exceptions.html' -p94 -sg11 -S'rest_framework/exceptions' -p95 -sg13 -g14 -(g15 -g16 -NtRp96 -(dp97 -g19 -I1 -sg20 -I0 -sg21 -I51 -sg22 -I0 -sg23 -I2 -sg24 -I0 -sbssg25 -S'\xdd\xcaE\x12\x1f4V\xe6\x91\x11\xef:T\xe1r\xca' -p98 -ssS'rest_framework_status' -p99 -(dp100 -g6 -(dp101 -g8 -I0 -sg9 -S'rest_framework_status.html' -p102 -sg11 -S'rest_framework/status' -p103 -sg13 -g14 -(g15 -g16 -NtRp104 -(dp105 -g19 -I1 -sg20 -I0 -sg21 -I46 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'\x97z\xcd\xfd\xdc\x0c\xe3\xa9j\x04\xab\x13]\x98\xbf\x80' -p106 -ssS'rest_framework_relations' -p107 -(dp108 -g6 -(dp109 -g8 -I0 -sg9 -S'rest_framework_relations.html' -p110 -sg11 -S'rest_framework/relations' -p111 -sg13 -g14 -(g15 -g16 -NtRp112 -(dp113 -g19 -I1 -sg20 -I0 -sg21 -I365 -sg22 -I0 -sg23 -I88 -sg24 -I0 -sbssg25 -S'\xdb"\xfe\xc2\xb3\x8a\xe2(\xbeoNk\x1b\xd3H9' -p114 -ssS'rest_framework_authtoken_views' -p115 -(dp116 -g6 -(dp117 -g8 -I0 -sg9 -S'rest_framework_authtoken_views.html' -p118 -sg11 -S'rest_framework/authtoken/views' -p119 -sg13 -g14 -(g15 -g16 -NtRp120 -(dp121 -g19 -I1 -sg20 -I0 -sg21 -I21 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'\xb8A\x13\xee\xfc9\x8b\x1eY-\xad\x00\xa7\x9dH]' -p122 -ssS'rest_framework_mixins' -p123 -(dp124 -g6 -(dp125 -g8 -I0 -sg9 -S'rest_framework_mixins.html' -p126 -sg11 -S'rest_framework/mixins' -p127 -sg13 -g14 -(g15 -g16 -NtRp128 -(dp129 -g19 -I1 -sg20 -I0 -sg21 -I97 -sg22 -I0 -sg23 -I7 -sg24 -I0 -sbssg25 -S'\xcd\xe5\x9f\xc2\xbb\xd9\xcb\x14*\x88\x99\xe8\xdf\xd2\xa8\xd6' -p130 -ssS'rest_framework_views' -p131 -(dp132 -g6 -(dp133 -g8 -I0 -sg9 -S'rest_framework_views.html' -p134 -sg11 -S'rest_framework/views' -p135 -sg13 -g14 -(g15 -g16 -NtRp136 -(dp137 -g19 -I1 -sg20 -I0 -sg21 -I146 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'ZBo\x84oh^\x1f\x8c\x94Mp$\xf3\xd2\xa1' -p138 -ssS'rest_framework_generics' -p139 -(dp140 -g6 -(dp141 -g8 -I0 -sg9 -S'rest_framework_generics.html' -p142 -sg11 -S'rest_framework/generics' -p143 -sg13 -g14 -(g15 -g16 -NtRp144 -(dp145 -g19 -I1 -sg20 -I0 -sg21 -I196 -sg22 -I0 -sg23 -I34 -sg24 -I0 -sbssg25 -S'@\x1c\x97\x176\x18\x9c\xfc"| |\xb8^\xbb\x83' -p146 -ssS'rest_framework_utils___init__' -p147 -(dp148 -g6 -(dp149 -g8 -I0 -sg9 -S'rest_framework_utils___init__.html' -p150 -sg11 -S'rest_framework/utils/__init__' -p151 -sg13 -g14 -(g15 -g16 -NtRp152 -(dp153 -g19 -I1 -sg20 -I0 -sg21 -I0 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'\xb0\xc8pN\xaf>\xa0\xbaz\x144\xe0A9\xb8?' -p154 -ssS'rest_framework_renderers' -p155 -(dp156 -g6 -(dp157 -g8 -I0 -sg9 -S'rest_framework_renderers.html' -p158 -sg11 -S'rest_framework/renderers' -p159 -sg13 -g14 -(g15 -g16 -NtRp160 -(dp161 -g19 -I1 -sg20 -I0 -sg21 -I282 -sg22 -I0 -sg23 -I23 -sg24 -I0 -sbssg25 -S'\t\x11\xd4\xafO\xae\\*\x8d\xaf\xa4f\xde\x86\xe8N' -p162 -ssS'rest_framework_negotiation' -p163 -(dp164 -g6 -(dp165 -g8 -I0 -sg9 -S'rest_framework_negotiation.html' -p166 -sg11 -S'rest_framework/negotiation' -p167 -sg13 -g14 -(g15 -g16 -NtRp168 -(dp169 -g19 -I1 -sg20 -I0 -sg21 -I41 -sg22 -I0 -sg23 -I4 -sg24 -I0 -sbssg25 -S'\xd2\xa2\x94\xc8}y\xba\x9eZE\xe5M\xa5>\x9f\x8d' -p170 -ssS'rest_framework_throttling' -p171 -(dp172 -g6 -(dp173 -g8 -I0 -sg9 -S'rest_framework_throttling.html' -p174 -sg11 -S'rest_framework/throttling' -p175 -sg13 -g14 -(g15 -g16 -NtRp176 -(dp177 -g19 -I1 -sg20 -I0 -sg21 -I90 -sg22 -I0 -sg23 -I17 -sg24 -I0 -sbssg25 -S'a\xbcT\xe7\xff\x1an\xb5\x886\xa3\xa2e\x90PZ' -p178 -ssS'rest_framework_reverse' -p179 -(dp180 -g6 -(dp181 -g8 -I0 -sg9 -S'rest_framework_reverse.html' -p182 -sg11 -S'rest_framework/reverse' -p183 -sg13 -g14 -(g15 -g16 -NtRp184 -(dp185 -g19 -I1 -sg20 -I0 -sg21 -I12 -sg22 -I0 -sg23 -I3 -sg24 -I0 -sbssg25 -S"#\xe7D\x01\x10\xe8'1\x9c\xc9yX4\xb4\xef\x19" -p186 -ssS'rest_framework_request' -p187 -(dp188 -g6 -(dp189 -g8 -I0 -sg9 -S'rest_framework_request.html' -p190 -sg11 -S'rest_framework/request' -p191 -sg13 -g14 -(g15 -g16 -NtRp192 -(dp193 -g19 -I1 -sg20 -I0 -sg21 -I161 -sg22 -I0 -sg23 -I8 -sg24 -I0 -sbssg25 -S'C\xd4v\x9b\xf2Z\xe47\xe8\xc8\x03\xf4\xf8\xac\xefs' -p194 -ssS'rest_framework_parsers' -p195 -(dp196 -g6 -(dp197 -g8 -I0 -sg9 -S'rest_framework_parsers.html' -p198 -sg11 -S'rest_framework/parsers' -p199 -sg13 -g14 -(g15 -g16 -NtRp200 -(dp201 -g19 -I1 -sg20 -I0 -sg21 -I153 -sg22 -I0 -sg23 -I13 -sg24 -I0 -sbssg25 -S'\x11o\x05[\x99{\x9c\x8bj\xa8\xd0t\xe8\x16\\\xae' -p202 -ssS'rest_framework_settings' -p203 -(dp204 -g6 -(dp205 -g8 -I0 -sg9 -S'rest_framework_settings.html' -p206 -sg11 -S'rest_framework/settings' -p207 -sg13 -g14 -(g15 -g16 -NtRp208 -(dp209 -g19 -I1 -sg20 -I0 -sg21 -I44 -sg22 -I0 -sg23 -I2 -sg24 -I0 -sbssg25 -S'\n\xb8|\x03\xa7d|\xfc9\xda\xb5\xb9\x1a\x00@\xc3' -p210 -ssS'rest_framework_authtoken_models' -p211 -(dp212 -g6 -(dp213 -g8 -I0 -sg9 -S'rest_framework_authtoken_models.html' -p214 -sg11 -S'rest_framework/authtoken/models' -p215 -sg13 -g14 -(g15 -g16 -NtRp216 -(dp217 -g19 -I1 -sg20 -I0 -sg21 -I21 -sg22 -I0 -sg23 -I1 -sg24 -I0 -sbssg25 -S'U;\xc7\xf5{\xf6r\xc7]\x95\xffF\xde\x8caE' -p218 -ssS'rest_framework_decorators' -p219 -(dp220 -g6 -(dp221 -g8 -I0 -sg9 -S'rest_framework_decorators.html' -p222 -sg11 -S'rest_framework/decorators' -p223 -sg13 -g14 -(g15 -g16 -NtRp224 -(dp225 -g19 -I1 -sg20 -I0 -sg21 -I60 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S"\xd4\x88\xa2\x16\xf4#X\xb4X\xe97Lj\xeb\x16'" -p226 -ssS'rest_framework_authentication' -p227 -(dp228 -g6 -(dp229 -g8 -I0 -sg9 -S'rest_framework_authentication.html' -p230 -sg11 -S'rest_framework/authentication' -p231 -sg13 -g14 -(g15 -g16 -NtRp232 -(dp233 -g19 -I1 -sg20 -I0 -sg21 -I169 -sg22 -I0 -sg23 -I33 -sg24 -I0 -sbssg25 -S'^\x80:,\x1cL\xde\t\xc1\x93\xe0\x8b\x11\xf4\xb8\x06' -p234 -ssS'rest_framework_utils_formatting' -p235 -(dp236 -g6 -(dp237 -g8 -I0 -sg9 -S'rest_framework_utils_formatting.html' -p238 -sg11 -S'rest_framework/utils/formatting' -p239 -sg13 -g14 -(g15 -g16 -NtRp240 -(dp241 -g19 -I1 -sg20 -I0 -sg21 -I39 -sg22 -I0 -sg23 -I1 -sg24 -I0 -sbssg25 -S'\xdd\x05M\xeb\xfe\tl\xe6\xdd\xc5k\xae\xa8\xf9um' -p242 -ssS'rest_framework_pagination' -p243 -(dp244 -g6 -(dp245 -g8 -I0 -sg9 -S'rest_framework_pagination.html' -p246 -sg11 -S'rest_framework/pagination' -p247 -sg13 -g14 -(g15 -g16 -NtRp248 -(dp249 -g19 -I1 -sg20 -I0 -sg21 -I43 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'y\xa8f\rv\x8c\x9b\x9a:9\xdc\x89\t>\x0c' -p282 -ssS'rest_framework_viewsets' -p283 -(dp284 -g6 -(dp285 -g8 -I0 -sg9 -S'rest_framework_viewsets.html' -p286 -sg11 -S'rest_framework/viewsets' -p287 -sg13 -g14 -(g15 -g16 -NtRp288 -(dp289 -g19 -I1 -sg20 -I0 -sg21 -I39 -sg22 -I0 -sg23 -I2 -sg24 -I0 -sbssg25 -S'ic\x82\xc6e\x93$\x1b\x0c\x8bK\x10\x0f9\xe8\n' -p290 -ssS'rest_framework_authtoken___init__' -p291 -(dp292 -g6 -(dp293 -g8 -I0 -sg9 -S'rest_framework_authtoken___init__.html' -p294 -sg11 -S'rest_framework/authtoken/__init__' -p295 -sg13 -g14 -(g15 -g16 -NtRp296 -(dp297 -g19 -I1 -sg20 -I0 -sg21 -I0 -sg22 -I0 -sg23 -I0 -sg24 -I0 -sbssg25 -S'\xb0\xc8pN\xaf>\xa0\xbaz\x144\xe0A9\xb8?' -p298 -ssS'rest_framework_routers' -p299 -(dp300 -g6 -(dp301 -g8 -I0 -sg9 -S'rest_framework_routers.html' -p302 -sg11 -S'rest_framework/routers' -p303 -sg13 -g14 -(g15 -g16 -NtRp304 -(dp305 -g19 -I1 -sg20 -I0 -sg21 -I108 -sg22 -I0 -sg23 -I7 -sg24 -I0 -sbssg25 -S'i\xa8[\x1f\x0f|\xd6\xa0R\x98\xa9\xecs\xe53\xb3' -p306 -sssS'version' -p307 -S'3.5.1' -p308 -sS'settings' -p309 -S'\xfe\xa4\x01e\x06\x8a\x97H\x97\xaf\xbf\xcd\xfez\xe4\xbf' -p310 -sS'format' -p311 -I1 -s. \ No newline at end of file diff --git a/htmlcov/style.css b/htmlcov/style.css deleted file mode 100644 index c40357b8b..000000000 --- a/htmlcov/style.css +++ /dev/null @@ -1,275 +0,0 @@ -/* CSS styles for Coverage. */ -/* Page-wide styles */ -html, body, h1, h2, h3, p, td, th { - margin: 0; - padding: 0; - border: 0; - outline: 0; - font-weight: inherit; - font-style: inherit; - font-size: 100%; - font-family: inherit; - vertical-align: baseline; - } - -/* Set baseline grid to 16 pt. */ -body { - font-family: georgia, serif; - font-size: 1em; - } - -html>body { - font-size: 16px; - } - -/* Set base font size to 12/16 */ -p { - font-size: .75em; /* 12/16 */ - line-height: 1.3333em; /* 16/12 */ - } - -table { - border-collapse: collapse; - } - -a.nav { - text-decoration: none; - color: inherit; - } -a.nav:hover { - text-decoration: underline; - color: inherit; - } - -/* Page structure */ -#header { - background: #f8f8f8; - width: 100%; - border-bottom: 1px solid #eee; - } - -#source { - padding: 1em; - font-family: "courier new", monospace; - } - -#indexfile #footer { - margin: 1em 3em; - } - -#pyfile #footer { - margin: 1em 1em; - } - -#footer .content { - padding: 0; - font-size: 85%; - font-family: verdana, sans-serif; - color: #666666; - font-style: italic; - } - -#index { - margin: 1em 0 0 3em; - } - -/* Header styles */ -#header .content { - padding: 1em 3em; - } - -h1 { - font-size: 1.25em; -} - -h2.stats { - margin-top: .5em; - font-size: 1em; -} -.stats span { - border: 1px solid; - padding: .1em .25em; - margin: 0 .1em; - cursor: pointer; - border-color: #999 #ccc #ccc #999; -} -.stats span.hide_run, .stats span.hide_exc, -.stats span.hide_mis, .stats span.hide_par, -.stats span.par.hide_run.hide_par { - border-color: #ccc #999 #999 #ccc; -} -.stats span.par.hide_run { - border-color: #999 #ccc #ccc #999; -} - -/* Help panel */ -#keyboard_icon { - float: right; - cursor: pointer; -} - -.help_panel { - position: absolute; - background: #ffc; - padding: .5em; - border: 1px solid #883; - display: none; -} - -#indexfile .help_panel { - width: 20em; height: 4em; -} - -#pyfile .help_panel { - width: 16em; height: 8em; -} - -.help_panel .legend { - font-style: italic; - margin-bottom: 1em; -} - -#panel_icon { - float: right; - cursor: pointer; -} - -.keyhelp { - margin: .75em; -} - -.keyhelp .key { - border: 1px solid black; - border-color: #888 #333 #333 #888; - padding: .1em .35em; - font-family: monospace; - font-weight: bold; - background: #eee; -} - -/* Source file styles */ -.linenos p { - text-align: right; - margin: 0; - padding: 0 .5em; - color: #999999; - font-family: verdana, sans-serif; - font-size: .625em; /* 10/16 */ - line-height: 1.6em; /* 16/10 */ - } -.linenos p.highlight { - background: #ffdd00; - } -.linenos p a { - text-decoration: none; - color: #999999; - } -.linenos p a:hover { - text-decoration: underline; - color: #999999; - } - -td.text { - width: 100%; - } -.text p { - margin: 0; - padding: 0 0 0 .5em; - border-left: 2px solid #ffffff; - white-space: nowrap; - } - -.text p.mis { - background: #ffdddd; - border-left: 2px solid #ff0000; - } -.text p.run, .text p.run.hide_par { - background: #ddffdd; - border-left: 2px solid #00ff00; - } -.text p.exc { - background: #eeeeee; - border-left: 2px solid #808080; - } -.text p.par, .text p.par.hide_run { - background: #ffffaa; - border-left: 2px solid #eeee99; - } -.text p.hide_run, .text p.hide_exc, .text p.hide_mis, .text p.hide_par, -.text p.hide_run.hide_par { - background: inherit; - } - -.text span.annotate { - font-family: georgia; - font-style: italic; - color: #666; - float: right; - padding-right: .5em; - } -.text p.hide_par span.annotate { - display: none; - } - -/* Syntax coloring */ -.text .com { - color: green; - font-style: italic; - line-height: 1px; - } -.text .key { - font-weight: bold; - line-height: 1px; - } -.text .str { - color: #000080; - } - -/* index styles */ -#index td, #index th { - text-align: right; - width: 5em; - padding: .25em .5em; - border-bottom: 1px solid #eee; - } -#index th { - font-style: italic; - color: #333; - border-bottom: 1px solid #ccc; - cursor: pointer; - } -#index th:hover { - background: #eee; - border-bottom: 1px solid #999; - } -#index td.left, #index th.left { - padding-left: 0; - } -#index td.right, #index th.right { - padding-right: 0; - } -#index th.headerSortDown, #index th.headerSortUp { - border-bottom: 1px solid #000; - } -#index td.name, #index th.name { - text-align: left; - width: auto; - } -#index td.name a { - text-decoration: none; - color: #000; - } -#index td.name a:hover { - text-decoration: underline; - color: #000; - } -#index tr.total { - } -#index tr.total td { - font-weight: bold; - border-top: 1px solid #ccc; - border-bottom: none; - } -#index tr.file:hover { - background: #eeeeee; - } From 209b65f426e1935c970c95fad389ba5c03388592 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 22:12:37 +0100 Subject: [PATCH 03/38] Update assertion error to reference 'base_name' argument, not incorrect 'name' argument. Closes #933 --- rest_framework/routers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/routers.py b/rest_framework/routers.py index f70c2cdb1..2a26f6a72 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -117,7 +117,7 @@ class SimpleRouter(BaseRouter): if model_cls is None and queryset is not None: model_cls = queryset.model - assert model_cls, '`name` not argument not specified, and could ' \ + assert model_cls, '`base_name` argument not specified, and could ' \ 'not automatically determine the name from the viewset, as ' \ 'it does not have a `.model` or `.queryset` attribute.' From 2d5f7f201ffcc8c371e9f36821c2ae0e13dcecca Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 22:19:14 +0100 Subject: [PATCH 04/38] Update router docs on base_name. Refs #933. --- docs/api-guide/routers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index f16fa9468..b74b6e13b 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -26,7 +26,7 @@ There are two mandatory arguments to the `register()` method: Optionally, you may also specify an additional argument: -* `base_name` - The base to use for the URL names that are created. If unset the basename will be automatically generated based on the `model` or `queryset` attribute on the viewset, if it has one. +* `base_name` - The base to use for the URL names that are created. If unset the basename will be automatically generated based on the `model` or `queryset` attribute on the viewset, if it has one. Note that if the viewset does not include a `model` or `queryset` attribute then you must set `base_name` when registering the viewset. The example above would generate the following URL patterns: From a68f473dd8438c7d7b0f8ec4b4dc74aa0544143d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 23:25:14 +0200 Subject: [PATCH 05/38] Brackets not required on decorator without arguments --- docs/api-guide/viewsets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 25d11bfb5..ad961636d 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -108,7 +108,7 @@ For example: queryset = User.objects.all() serializer_class = UserSerializer - @action() + @action def set_password(self, request, pk=None): user = self.get_object() serializer = PasswordSerializer(data=request.DATA) From 4f7f93e20ef53fbc0b66766158bca75ebddce2ed Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 22:28:36 +0100 Subject: [PATCH 06/38] Added @freakydug, for changes in #941. Thanks :) --- docs/topics/credits.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index 3f0ee429c..a7c09b5bc 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -142,6 +142,7 @@ The following people have helped make REST framework great. * Areski Belaid - [areski] * Ethan Freman - [mindlace] * David Sanders - [davesque] +* Philip Douglas - [freakydug] Many thanks to everyone who's contributed to the project. @@ -320,4 +321,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [areski]: https://github.com/areski [mindlace]: https://github.com/mindlace [davesque]: https://github.com/davesque - +[freakydug]: https://github.com/freakydug From 8cc63b09f6065e0197e060cc4d62b560196c8877 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 22:42:04 +0100 Subject: [PATCH 07/38] Add support for StreamingHttpResponse. Closes #939 --- docs/api-guide/responses.md | 2 +- rest_framework/compat.py | 6 ++++++ rest_framework/views.py | 11 ++++++----- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/api-guide/responses.md b/docs/api-guide/responses.md index f83b8194a..399b7c23f 100644 --- a/docs/api-guide/responses.md +++ b/docs/api-guide/responses.md @@ -10,7 +10,7 @@ REST framework supports HTTP content negotiation by providing a `Response` class The `Response` class subclasses Django's `SimpleTemplateResponse`. `Response` objects are initialised with data, which should consist of native Python primitives. REST framework then uses standard HTTP content negotiation to determine how it should render the final response content. -There's no requirement for you to use the `Response` class, you can also return regular `HttpResponse` objects from your views if you want, but it provides a nicer interface for returning Web API responses. +There's no requirement for you to use the `Response` class, you can also return regular `HttpResponse` or `StreamingHttpResponse` objects from your views if required. Using the `Response` class simply provides a nicer interface for returning content-negotiated Web API responses, that can be rendered to multiple formats. Unless you want to heavily customize REST framework for some reason, you should always use an `APIView` class or `@api_view` function for views that return `Response` objects. Doing so ensures that the view can perform content negotiation and select the appropriate renderer for the response, before it is returned from the view. diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 76dc00526..a19bd778b 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -33,6 +33,12 @@ except ImportError: from django.utils.encoding import force_unicode as force_text +# HttpResponseBase only exists from 1.5 onwards +try: + from django.http.response import HttpResponseBase +except ImportError: + from django.http import HttpResponse as HttpResponseBase + # django-filter is optional try: import django_filters diff --git a/rest_framework/views.py b/rest_framework/views.py index c28d2835f..37bba7f02 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -4,11 +4,11 @@ Provides an APIView class that is the base of all views in REST framework. from __future__ import unicode_literals from django.core.exceptions import PermissionDenied -from django.http import Http404, HttpResponse +from django.http import Http404 from django.utils.datastructures import SortedDict from django.views.decorators.csrf import csrf_exempt from rest_framework import status, exceptions -from rest_framework.compat import View +from rest_framework.compat import View, HttpResponseBase from rest_framework.request import Request from rest_framework.response import Response from rest_framework.settings import api_settings @@ -244,9 +244,10 @@ class APIView(View): Returns the final response object. """ # Make the error obvious if a proper response is not returned - assert isinstance(response, HttpResponse), ( - 'Expected a `Response` to be returned from the view, ' - 'but received a `%s`' % type(response) + assert isinstance(response, HttpResponseBase), ( + 'Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` ' + 'to be returned from the view, but received a `%s`' + % type(response) ) if isinstance(response, Response): From fb6bcd9f06daaca51441c6b851d6411621d32c26 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 22:43:01 +0100 Subject: [PATCH 08/38] Update release notes, noting support for StreamingHttpResponse. Refs #939 --- docs/topics/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index f49dd5c84..b08ac0583 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -43,6 +43,7 @@ You can determine your currently installed version using `pip freeze`: ### Master * Added `trailing_slash` option to routers. +* Include support for `HttpStreamingResponse`. * Support wider range of default serializer validation when used with custom model fields. * Bugfix: Return error correctly when OAuth non-existent consumer occurs. * Bugfix: Allow `FileUploadParser` to correctly filename if provided as URL kwarg. From 8d83ff8e6c8513d0a88d6b1fecb34ed86f1e2085 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 21 Jun 2013 23:12:16 +0100 Subject: [PATCH 09/38] Add decorator brackets back. Refs #941 --- docs/api-guide/viewsets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index ad961636d..25d11bfb5 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -108,7 +108,7 @@ For example: queryset = User.objects.all() serializer_class = UserSerializer - @action + @action() def set_password(self, request, pk=None): user = self.get_object() serializer = PasswordSerializer(data=request.DATA) From 2bf5f6305030d5ebbd5a8a0fd5c31586c08a558d Mon Sep 17 00:00:00 2001 From: Igor Kalat Date: Sat, 22 Jun 2013 13:43:45 +0200 Subject: [PATCH 10/38] Make browsable API views play nice with utf-8 --- rest_framework/tests/test_utils.py | 36 ++++++++++++++++++++++++++++++ rest_framework/utils/formatting.py | 5 +++++ 2 files changed, 41 insertions(+) create mode 100644 rest_framework/tests/test_utils.py diff --git a/rest_framework/tests/test_utils.py b/rest_framework/tests/test_utils.py new file mode 100644 index 000000000..da508dbbe --- /dev/null +++ b/rest_framework/tests/test_utils.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +from django.test import TestCase +from rest_framework.utils import formatting +import sys + + +class FormattingUnitTests(TestCase): + def setUp(self): + # test strings snatched from http://www.columbia.edu/~fdc/utf8/, + # http://winrus.com/utf8-jap.htm and memory + self.utf8_test_string = ( + 'zażółć gęślą jaźń' + 'Sîne klâwen durh die wolken sint geslagen' + 'Τη γλώσσα μου έδωσαν ελληνική' + 'யாமறிந்த மொழிகளிலே தமிழ்மொழி' + 'На берегу пустынных волн' + ' てすと' + 'アイウエオカキクケコサシスセソタチツテ' + ) + self.non_utf8_test_string = ('The quick brown fox jumps over the lazy ' + 'dog') + + def test_for_ascii_support_in_remove_leading_indent(self): + if sys.version_info < (3, 0): + # only Python 2.x is affected, so we skip the test entirely + # if on Python 3.x + self.assertEqual(formatting._remove_leading_indent( + self.non_utf8_test_string), self.non_utf8_test_string) + + def test_for_utf8_support_in_remove_leading_indent(self): + if sys.version_info < (3, 0): + # only Python 2.x is affected, so we skip the test entirely + # if on Python 3.x + self.assertEqual(formatting._remove_leading_indent( + self.utf8_test_string), self.utf8_test_string.decode('utf-8')) diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py index ebadb3a67..a2a5609c0 100644 --- a/rest_framework/utils/formatting.py +++ b/rest_framework/utils/formatting.py @@ -24,6 +24,11 @@ def _remove_leading_indent(content): Remove leading indent from a block of text. Used when generating descriptions from docstrings. """ + try: + content = content.decode('utf-8') + except (AttributeError, UnicodeEncodeError): + pass # the string should keep the default 'ascii' encoding in + # Python 2.x or stay a unicode string in Python 3.x whitespace_counts = [len(line) - len(line.lstrip(' ')) for line in content.splitlines()[1:] if line.lstrip()] From 13a3c993ab20e7af510d615a5eafaa87667b8efb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Jun 2013 11:30:27 +0100 Subject: [PATCH 11/38] Fix incorrect example --- docs/api-guide/generic-views.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index cd1bc7a1c..67853ed01 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -92,7 +92,8 @@ May be overridden to provide dynamic behavior such as returning a queryset that For example: def get_queryset(self): - return self.user.accounts.all() + user = self.request.user + return user.accounts.all() #### `get_object(self)` From 715bd47dfababd39be9b3295ada99f2107d7c00c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Jun 2013 17:56:42 +0100 Subject: [PATCH 12/38] Use AUTH_USER_MODEL consistently between various Django versions. Closes #946 --- rest_framework/authtoken/models.py | 4 ++-- rest_framework/compat.py | 10 ++-------- rest_framework/runtests/settings.py | 2 ++ 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py index 52c45ad11..7601f5b79 100644 --- a/rest_framework/authtoken/models.py +++ b/rest_framework/authtoken/models.py @@ -1,7 +1,7 @@ import uuid import hmac from hashlib import sha1 -from rest_framework.compat import User +from rest_framework.compat import AUTH_USER_MODEL from django.conf import settings from django.db import models @@ -11,7 +11,7 @@ class Token(models.Model): The default authorization token model. """ key = models.CharField(max_length=40, primary_key=True) - user = models.OneToOneField(User, related_name='auth_token') + user = models.OneToOneField(AUTH_USER_MODEL, related_name='auth_token') created = models.DateTimeField(auto_now_add=True) class Meta: diff --git a/rest_framework/compat.py b/rest_framework/compat.py index a19bd778b..69853730a 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -83,15 +83,9 @@ def get_concrete_model(model_cls): # Django 1.5 add support for custom auth user model if django.VERSION >= (1, 5): from django.conf import settings - if hasattr(settings, 'AUTH_USER_MODEL'): - User = settings.AUTH_USER_MODEL - else: - from django.contrib.auth.models import User + AUTH_USER_MODEL = settings.AUTH_USER_MODEL else: - try: - from django.contrib.auth.models import User - except ImportError: - raise ImportError("User model is not to be found.") + AUTH_USER_MODEL = 'auth.User' if django.VERSION >= (1, 5): diff --git a/rest_framework/runtests/settings.py b/rest_framework/runtests/settings.py index 9dd7b545e..b3702d0bf 100644 --- a/rest_framework/runtests/settings.py +++ b/rest_framework/runtests/settings.py @@ -134,6 +134,8 @@ PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.CryptPasswordHasher', ) +AUTH_USER_MODEL = 'auth.User' + import django if django.VERSION < (1, 3): From c8b0e6c40f6bcf447aa539ff98b9985aa53032ce Mon Sep 17 00:00:00 2001 From: Igor Kalat Date: Wed, 26 Jun 2013 22:12:02 +0200 Subject: [PATCH 13/38] Refactored get_view_description, moved appropriate tests to test_description.py --- rest_framework/tests/test_description.py | 13 +++++---- rest_framework/tests/test_utils.py | 36 ------------------------ rest_framework/tests/views.py | 25 ++++++++++++++++ rest_framework/utils/formatting.py | 9 ++---- 4 files changed, 34 insertions(+), 49 deletions(-) delete mode 100644 rest_framework/tests/test_utils.py create mode 100644 rest_framework/tests/views.py diff --git a/rest_framework/tests/test_description.py b/rest_framework/tests/test_description.py index 52c1a34c1..bc86e1064 100644 --- a/rest_framework/tests/test_description.py +++ b/rest_framework/tests/test_description.py @@ -3,8 +3,10 @@ from __future__ import unicode_literals from django.test import TestCase from rest_framework.views import APIView -from rest_framework.compat import apply_markdown +from rest_framework.compat import apply_markdown, smart_text from rest_framework.utils.formatting import get_view_name, get_view_description +from rest_framework.tests.views import ( + ViewWithNonASCIICharactersInDocstring, UTF8_TEST_DOCSTRING) # We check that docstrings get nicely un-indented. DESCRIPTION = """an example docstring @@ -83,11 +85,10 @@ class TestViewNamesAndDescriptions(TestCase): Unicode in docstrings should be respected. """ - class MockView(APIView): - """Проверка""" - pass - - self.assertEqual(get_view_description(MockView), "Проверка") + self.assertEqual( + get_view_description(ViewWithNonASCIICharactersInDocstring), + smart_text(UTF8_TEST_DOCSTRING) + ) def test_view_description_can_be_empty(self): """ diff --git a/rest_framework/tests/test_utils.py b/rest_framework/tests/test_utils.py deleted file mode 100644 index da508dbbe..000000000 --- a/rest_framework/tests/test_utils.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- - -from django.test import TestCase -from rest_framework.utils import formatting -import sys - - -class FormattingUnitTests(TestCase): - def setUp(self): - # test strings snatched from http://www.columbia.edu/~fdc/utf8/, - # http://winrus.com/utf8-jap.htm and memory - self.utf8_test_string = ( - 'zażółć gęślą jaźń' - 'Sîne klâwen durh die wolken sint geslagen' - 'Τη γλώσσα μου έδωσαν ελληνική' - 'யாமறிந்த மொழிகளிலே தமிழ்மொழி' - 'На берегу пустынных волн' - ' てすと' - 'アイウエオカキクケコサシスセソタチツテ' - ) - self.non_utf8_test_string = ('The quick brown fox jumps over the lazy ' - 'dog') - - def test_for_ascii_support_in_remove_leading_indent(self): - if sys.version_info < (3, 0): - # only Python 2.x is affected, so we skip the test entirely - # if on Python 3.x - self.assertEqual(formatting._remove_leading_indent( - self.non_utf8_test_string), self.non_utf8_test_string) - - def test_for_utf8_support_in_remove_leading_indent(self): - if sys.version_info < (3, 0): - # only Python 2.x is affected, so we skip the test entirely - # if on Python 3.x - self.assertEqual(formatting._remove_leading_indent( - self.utf8_test_string), self.utf8_test_string.decode('utf-8')) diff --git a/rest_framework/tests/views.py b/rest_framework/tests/views.py new file mode 100644 index 000000000..fc00cc0b5 --- /dev/null +++ b/rest_framework/tests/views.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +from rest_framework.views import APIView + + +# test strings snatched from http://www.columbia.edu/~fdc/utf8/, +# http://winrus.com/utf8-jap.htm and memory +UTF8_TEST_DOCSTRING = ( + 'zażółć gęślą jaźń' + 'Sîne klâwen durh die wolken sint geslagen' + 'Τη γλώσσα μου έδωσαν ελληνική' + 'யாமறிந்த மொழிகளிலே தமிழ்மொழி' + 'На берегу пустынных волн' + 'てすと' + 'アイウエオカキクケコサシスセソタチツテ' +) + + +# Apparently there is an issue where docstrings of imported view classes +# do not retain their encoding information even if a module has a proper +# encoding declaration at the top of its source file. Therefore for tests +# to catch unicode related errors, a mock view has to be declared in a separate +# module. +class ViewWithNonASCIICharactersInDocstring(APIView): + __doc__ = UTF8_TEST_DOCSTRING diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py index a2a5609c0..4bec83877 100644 --- a/rest_framework/utils/formatting.py +++ b/rest_framework/utils/formatting.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals from django.utils.html import escape from django.utils.safestring import mark_safe -from rest_framework.compat import apply_markdown +from rest_framework.compat import apply_markdown, smart_text import re @@ -24,11 +24,6 @@ def _remove_leading_indent(content): Remove leading indent from a block of text. Used when generating descriptions from docstrings. """ - try: - content = content.decode('utf-8') - except (AttributeError, UnicodeEncodeError): - pass # the string should keep the default 'ascii' encoding in - # Python 2.x or stay a unicode string in Python 3.x whitespace_counts = [len(line) - len(line.lstrip(' ')) for line in content.splitlines()[1:] if line.lstrip()] @@ -68,7 +63,7 @@ def get_view_description(cls, html=False): Return a description for an `APIView` class or `@api_view` function. """ description = cls.__doc__ or '' - description = _remove_leading_indent(description) + description = _remove_leading_indent(smart_text(description)) if html: return markup_description(description) return description From 69e5e3cc0db481e4fad7ac34bf28b73f4786e790 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Jun 2013 21:18:13 +0100 Subject: [PATCH 14/38] Use timezone aware datetimes with oauth2 provider, when supported. Closes #947. --- rest_framework/authentication.py | 9 ++++----- rest_framework/compat.py | 10 ++++++++++ rest_framework/serializers.py | 5 ++++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index f659a172e..102980271 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -3,14 +3,13 @@ Provides various authentication policies. """ from __future__ import unicode_literals import base64 -from datetime import datetime from django.contrib.auth import authenticate from django.core.exceptions import ImproperlyConfigured from rest_framework import exceptions, HTTP_HEADER_ENCODING from rest_framework.compat import CsrfViewMiddleware from rest_framework.compat import oauth, oauth_provider, oauth_provider_store -from rest_framework.compat import oauth2_provider +from rest_framework.compat import oauth2_provider, provider_now from rest_framework.authtoken.models import Token @@ -320,9 +319,9 @@ class OAuth2Authentication(BaseAuthentication): try: token = oauth2_provider.models.AccessToken.objects.select_related('user') - # TODO: Change to timezone aware datetime when oauth2_provider add - # support to it. - token = token.get(token=access_token, expires__gt=datetime.now()) + # provider_now switches to timezone aware datetime when + # the oauth2_provider version supports to it. + token = token.get(token=access_token, expires__gt=provider_now()) except oauth2_provider.models.AccessToken.DoesNotExist: raise exceptions.AuthenticationFailed('Invalid token') diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 69853730a..b748dcc51 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -2,6 +2,7 @@ The `compat` module provides support for backwards compatibility with older versions of django/python, and compatibility wrappers around optional packages. """ + # flake8: noqa from __future__ import unicode_literals @@ -489,12 +490,21 @@ try: from provider.oauth2 import forms as oauth2_provider_forms from provider import scope as oauth2_provider_scope from provider import constants as oauth2_constants + from provider import __version__ as provider_version + if provider_version in ('0.2.3', '0.2.4'): + # 0.2.3 and 0.2.4 are supported version that do not support + # timezone aware datetimes + from datetime.datetime import now as provider_now + else: + # Any other supported version does use timezone aware datetimes + from django.utils.timezone import now as provider_now except ImportError: oauth2_provider = None oauth2_provider_models = None oauth2_provider_forms = None oauth2_provider_scope = None oauth2_constants = None + provider_now = None # Handle lazy strings from django.utils.functional import Promise diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 5a8fd89f0..023f7ccfb 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -915,7 +915,10 @@ class HyperlinkedModelSerializer(ModelSerializer): view_name=self.opts.view_name, lookup_field=self.opts.lookup_field ) - fields.insert(0, 'url', url_field) + ret = self._dict_class() + ret['url'] = url_field + ret.update(fields) + fields = ret return fields From 494703fc8e916a9b7a318ec8bc7d774ef31de14e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Jun 2013 22:40:14 +0100 Subject: [PATCH 15/38] Update release notes --- docs/topics/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index b08ac0583..ce4df83c1 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -45,6 +45,7 @@ You can determine your currently installed version using `pip freeze`: * Added `trailing_slash` option to routers. * Include support for `HttpStreamingResponse`. * Support wider range of default serializer validation when used with custom model fields. +* OAuth2 provider usez timezone aware datetimes when supported. * Bugfix: Return error correctly when OAuth non-existent consumer occurs. * Bugfix: Allow `FileUploadParser` to correctly filename if provided as URL kwarg. * Bugfix: Fix `ScopedRateThrottle`. From 91b9fcb0ba2541b2752e2ab0706becad14bdda20 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Jun 2013 22:43:17 +0100 Subject: [PATCH 16/38] Minor test cleanup --- rest_framework/tests/test_description.py | 24 +++++++++++++++++++++-- rest_framework/tests/views.py | 25 ------------------------ 2 files changed, 22 insertions(+), 27 deletions(-) delete mode 100644 rest_framework/tests/views.py diff --git a/rest_framework/tests/test_description.py b/rest_framework/tests/test_description.py index bc86e1064..ea4b2c3ab 100644 --- a/rest_framework/tests/test_description.py +++ b/rest_framework/tests/test_description.py @@ -5,8 +5,6 @@ from django.test import TestCase from rest_framework.views import APIView from rest_framework.compat import apply_markdown, smart_text from rest_framework.utils.formatting import get_view_name, get_view_description -from rest_framework.tests.views import ( - ViewWithNonASCIICharactersInDocstring, UTF8_TEST_DOCSTRING) # We check that docstrings get nicely un-indented. DESCRIPTION = """an example docstring @@ -51,6 +49,28 @@ MARKED_DOWN_gte_21 = """

an example docstring

hash style header

""" +# test strings snatched from http://www.columbia.edu/~fdc/utf8/, +# http://winrus.com/utf8-jap.htm and memory +UTF8_TEST_DOCSTRING = ( + 'zażółć gęślą jaźń' + 'Sîne klâwen durh die wolken sint geslagen' + 'Τη γλώσσα μου έδωσαν ελληνική' + 'யாமறிந்த மொழிகளிலே தமிழ்மொழி' + 'На берегу пустынных волн' + 'てすと' + 'アイウエオカキクケコサシスセソタチツテ' +) + + +# Apparently there is an issue where docstrings of imported view classes +# do not retain their encoding information even if a module has a proper +# encoding declaration at the top of its source file. Therefore for tests +# to catch unicode related errors, a mock view has to be declared in a separate +# module. +class ViewWithNonASCIICharactersInDocstring(APIView): + __doc__ = UTF8_TEST_DOCSTRING + + class TestViewNamesAndDescriptions(TestCase): def test_view_name_uses_class_name(self): """ diff --git a/rest_framework/tests/views.py b/rest_framework/tests/views.py deleted file mode 100644 index fc00cc0b5..000000000 --- a/rest_framework/tests/views.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- - -from rest_framework.views import APIView - - -# test strings snatched from http://www.columbia.edu/~fdc/utf8/, -# http://winrus.com/utf8-jap.htm and memory -UTF8_TEST_DOCSTRING = ( - 'zażółć gęślą jaźń' - 'Sîne klâwen durh die wolken sint geslagen' - 'Τη γλώσσα μου έδωσαν ελληνική' - 'யாமறிந்த மொழிகளிலே தமிழ்மொழி' - 'На берегу пустынных волн' - 'てすと' - 'アイウエオカキクケコサシスセソタチツテ' -) - - -# Apparently there is an issue where docstrings of imported view classes -# do not retain their encoding information even if a module has a proper -# encoding declaration at the top of its source file. Therefore for tests -# to catch unicode related errors, a mock view has to be declared in a separate -# module. -class ViewWithNonASCIICharactersInDocstring(APIView): - __doc__ = UTF8_TEST_DOCSTRING From cb83bc373f8044ec21f5affabda0540ed0876357 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Jun 2013 22:44:44 +0100 Subject: [PATCH 17/38] Added @trwired for fix #943. Thanks :) --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index a7c09b5bc..94760c74b 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -143,6 +143,7 @@ The following people have helped make REST framework great. * Ethan Freman - [mindlace] * David Sanders - [davesque] * Philip Douglas - [freakydug] +* Igor Kalat - [trwired] Many thanks to everyone who's contributed to the project. @@ -322,3 +323,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [mindlace]: https://github.com/mindlace [davesque]: https://github.com/davesque [freakydug]: https://github.com/freakydug +[trwired]: https://github.com/trwired From af2fdc03a6f4cafe6e2f19b2adcf59c8918088f2 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Jun 2013 22:45:39 +0100 Subject: [PATCH 18/38] Update release notes --- docs/topics/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index ce4df83c1..4fecbf1f2 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -45,6 +45,7 @@ You can determine your currently installed version using `pip freeze`: * Added `trailing_slash` option to routers. * Include support for `HttpStreamingResponse`. * Support wider range of default serializer validation when used with custom model fields. +* UTF-8 Support for browsable API descriptions. * OAuth2 provider usez timezone aware datetimes when supported. * Bugfix: Return error correctly when OAuth non-existent consumer occurs. * Bugfix: Allow `FileUploadParser` to correctly filename if provided as URL kwarg. From c127e63c32b2fb93d1a9422943005c1f6cc5328b Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Wed, 26 Jun 2013 23:00:42 +0100 Subject: [PATCH 19/38] Raise exception when attempting to dynamically route to a method that is already routed to. Fixes #940 --- rest_framework/routers.py | 14 ++++++++++++++ rest_framework/tests/test_routers.py | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/rest_framework/routers.py b/rest_framework/routers.py index c222f5046..930011d39 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -15,7 +15,9 @@ For example, you might have a `urls.py` that looks something like this: """ from __future__ import unicode_literals +import itertools from collections import namedtuple +from django.core.exceptions import ImproperlyConfigured from rest_framework import views from rest_framework.compat import patterns, url from rest_framework.response import Response @@ -38,6 +40,13 @@ def replace_methodname(format_string, methodname): return ret +def flatten(list_of_lists): + """ + Takes an iterable of iterables, returns a single iterable containing all items + """ + return itertools.chain(*list_of_lists) + + class BaseRouter(object): def __init__(self): self.registry = [] @@ -130,12 +139,17 @@ class SimpleRouter(BaseRouter): Returns a list of the Route namedtuple. """ + known_actions = flatten([route.mapping.values() for route in self.routes]) + # Determine any `@action` or `@link` decorated methods on the viewset dynamic_routes = [] for methodname in dir(viewset): attr = getattr(viewset, methodname) httpmethods = getattr(attr, 'bind_to_methods', None) if httpmethods: + if methodname in known_actions: + raise ImproperlyConfigured('Cannot use @action or @link decorator on ' + 'method "%s" as it is an existing route' % methodname) httpmethods = [method.lower() for method in httpmethods] dynamic_routes.append((httpmethods, methodname)) diff --git a/rest_framework/tests/test_routers.py b/rest_framework/tests/test_routers.py index fe0711fa2..d375f4a8c 100644 --- a/rest_framework/tests/test_routers.py +++ b/rest_framework/tests/test_routers.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from django.db import models from django.test import TestCase from django.test.client import RequestFactory +from django.core.exceptions import ImproperlyConfigured from rest_framework import serializers, viewsets, permissions from rest_framework.compat import include, patterns, url from rest_framework.decorators import link, action @@ -191,3 +192,24 @@ class TestActionKeywordArgs(TestCase): response.data, {'permission_classes': [permissions.AllowAny]} ) + +class TestActionAppliedToExistingRoute(TestCase): + """ + Ensure `@action` decorator raises an except when applied + to an existing route + """ + + def test_exception_raised_when_action_applied_to_existing_route(self): + class TestViewSet(viewsets.ModelViewSet): + + @action() + def retrieve(self, request, *args, **kwargs): + return Response({ + 'hello': 'world' + }) + + self.router = SimpleRouter() + self.router.register(r'test', TestViewSet, base_name='test') + + with self.assertRaises(ImproperlyConfigured): + self.router.urls From 4d22a65e78432a2aa70ddc80395a014a7c9e299e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 26 Jun 2013 23:26:35 +0100 Subject: [PATCH 20/38] Fix sidebar styling when browser window is too small --- docs/css/default.css | 4 ++++ docs/template.html | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/docs/css/default.css b/docs/css/default.css index a4f05daa8..af6a9cc03 100644 --- a/docs/css/default.css +++ b/docs/css/default.css @@ -303,3 +303,7 @@ table { border-color: white; margin-bottom: 0.6em; } + +.side-nav { + overflow-y: scroll; +} diff --git a/docs/template.html b/docs/template.html index 53656e7d4..14ecc9c7a 100644 --- a/docs/template.html +++ b/docs/template.html @@ -198,5 +198,14 @@ $('.dropdown-menu').on('click touchstart', function(event) { event.stopPropagation(); }); + + // Dynamically force sidenav to no higher than browser window + $('.side-nav').css('max-height', window.innerHeight - 125); + + $(function(){ + $(window).resize(function(){ + $('.side-nav').css('max-height', window.innerHeight - 125); + }); + }); From 96f41fd12d376833a5822918cedcec5e74d59d02 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 27 Jun 2013 11:58:34 +0100 Subject: [PATCH 21/38] Use imported views to expose python 2.6 bug. Refs #943 --- rest_framework/tests/description.py | 26 ++++++++++++++++++++++++ rest_framework/tests/test_description.py | 26 +++--------------------- 2 files changed, 29 insertions(+), 23 deletions(-) create mode 100644 rest_framework/tests/description.py diff --git a/rest_framework/tests/description.py b/rest_framework/tests/description.py new file mode 100644 index 000000000..b46d7f54d --- /dev/null +++ b/rest_framework/tests/description.py @@ -0,0 +1,26 @@ +# -- coding: utf-8 -- + +# Apparently there is a python 2.6 issue where docstrings of imported view classes +# do not retain their encoding information even if a module has a proper +# encoding declaration at the top of its source file. Therefore for tests +# to catch unicode related errors, a mock view has to be declared in a separate +# module. + +from rest_framework.views import APIView + + +# test strings snatched from http://www.columbia.edu/~fdc/utf8/, +# http://winrus.com/utf8-jap.htm and memory +UTF8_TEST_DOCSTRING = ( + 'zażółć gęślą jaźń' + 'Sîne klâwen durh die wolken sint geslagen' + 'Τη γλώσσα μου έδωσαν ελληνική' + 'யாமறிந்த மொழிகளிலே தமிழ்மொழி' + 'На берегу пустынных волн' + 'てすと' + 'アイウエオカキクケコサシスセソタチツテ' +) + + +class ViewWithNonASCIICharactersInDocstring(APIView): + __doc__ = UTF8_TEST_DOCSTRING diff --git a/rest_framework/tests/test_description.py b/rest_framework/tests/test_description.py index ea4b2c3ab..8019f5eca 100644 --- a/rest_framework/tests/test_description.py +++ b/rest_framework/tests/test_description.py @@ -2,8 +2,10 @@ from __future__ import unicode_literals from django.test import TestCase -from rest_framework.views import APIView from rest_framework.compat import apply_markdown, smart_text +from rest_framework.views import APIView +from rest_framework.tests.description import ViewWithNonASCIICharactersInDocstring +from rest_framework.tests.description import UTF8_TEST_DOCSTRING from rest_framework.utils.formatting import get_view_name, get_view_description # We check that docstrings get nicely un-indented. @@ -49,28 +51,6 @@ MARKED_DOWN_gte_21 = """

an example docstring

hash style header

""" -# test strings snatched from http://www.columbia.edu/~fdc/utf8/, -# http://winrus.com/utf8-jap.htm and memory -UTF8_TEST_DOCSTRING = ( - 'zażółć gęślą jaźń' - 'Sîne klâwen durh die wolken sint geslagen' - 'Τη γλώσσα μου έδωσαν ελληνική' - 'யாமறிந்த மொழிகளிலே தமிழ்மொழி' - 'На берегу пустынных волн' - 'てすと' - 'アイウエオカキクケコサシスセソタチツテ' -) - - -# Apparently there is an issue where docstrings of imported view classes -# do not retain their encoding information even if a module has a proper -# encoding declaration at the top of its source file. Therefore for tests -# to catch unicode related errors, a mock view has to be declared in a separate -# module. -class ViewWithNonASCIICharactersInDocstring(APIView): - __doc__ = UTF8_TEST_DOCSTRING - - class TestViewNamesAndDescriptions(TestCase): def test_view_name_uses_class_name(self): """ From 124ae8c2c88d48b67fbaee77e337e8a6f37d1b70 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 27 Jun 2013 12:58:38 +0100 Subject: [PATCH 22/38] Tweak styling for max-height of sidenav --- docs/template.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/template.html b/docs/template.html index 14ecc9c7a..217710250 100644 --- a/docs/template.html +++ b/docs/template.html @@ -200,11 +200,11 @@ }); // Dynamically force sidenav to no higher than browser window - $('.side-nav').css('max-height', window.innerHeight - 125); + $('.side-nav').css('max-height', window.innerHeight - 130); $(function(){ $(window).resize(function(){ - $('.side-nav').css('max-height', window.innerHeight - 125); + $('.side-nav').css('max-height', window.innerHeight - 130); }); }); From 7ba2f44a0f0e5ed7bac0fbdbb0112bbfe43f6d24 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 27 Jun 2013 13:00:05 +0100 Subject: [PATCH 23/38] Version 2.3.6 --- docs/topics/release-notes.md | 6 ++++-- rest_framework/__init__.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 4fecbf1f2..d379ab74f 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -40,13 +40,15 @@ You can determine your currently installed version using `pip freeze`: ## 2.3.x series -### Master +### 2.3.6 + +**Date**: 27th June 2013 * Added `trailing_slash` option to routers. * Include support for `HttpStreamingResponse`. * Support wider range of default serializer validation when used with custom model fields. * UTF-8 Support for browsable API descriptions. -* OAuth2 provider usez timezone aware datetimes when supported. +* OAuth2 provider uses timezone aware datetimes when supported. * Bugfix: Return error correctly when OAuth non-existent consumer occurs. * Bugfix: Allow `FileUploadParser` to correctly filename if provided as URL kwarg. * Bugfix: Fix `ScopedRateThrottle`. diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 0a2101863..776618ac3 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -1,4 +1,4 @@ -__version__ = '2.3.5' +__version__ = '2.3.6' VERSION = __version__ # synonym From 1f6a59d76da286e7a89e8e41317beb16a4aab7c7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 27 Jun 2013 13:41:42 +0100 Subject: [PATCH 24/38] Moar hyperlinks --- README.md | 23 ++++++++++++++++++----- docs/index.md | 16 +++++++++++----- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 12ed09f9f..62883e32e 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,11 @@ Django REST framework is a powerful and flexible toolkit that makes it easy to b Some reasons you might want to use REST framework: -* The Web browseable API is a huge useability win for your developers. -* Authentication policies including OAuth1a and OAuth2 out of the box. -* Serialization that supports both ORM and non-ORM data sources. -* Customizable all the way down - just use regular function-based views if you don't need the more powerful features. -* Extensive documentation, and great community support. +* The [Web browseable API][sandbox] is a huge useability win for your developers. +* [Authentication policies][authentication] including [OAuth1a][oauth1-section] and [OAuth2][oauth2-section] out of the box. +* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources. +* Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers]. +* [Extensive documentation][index], and [great community support][group]. There is a live example API for testing purposes, [available here][sandbox]. @@ -139,6 +139,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework [0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X [sandbox]: http://restframework.herokuapp.com/ + +[index]: http://django-rest-framework.org/ +[oauth1-section]: http://django-rest-framework.org/api-guide/authentication.html#oauthauthentication +[oauth2-section]: http://django-rest-framework.org/api-guide/authentication.html#oauth2authentication +[serializer-section]: http://django-rest-framework.org/api-guide/serializers.html#serializers +[modelserializer-section]: http://django-rest-framework.org/api-guide/serializers.html#modelserializer +[functionview-section]: http://django-rest-framework.org/api-guide/views.html#function-based-views +[generic-views]: http://django-rest-framework.org/api-guide/generic-views.html +[viewsets]: http://django-rest-framework.org/api-guide/viewsets.html +[routers]: http://django-rest-framework.org/api-guide/routers.html +[serializers]: http://django-rest-framework.org/api-guide/serializers.html +[authentication]: http://django-rest-framework.org/api-guide/authentication.html + [rest-framework-2-announcement]: http://django-rest-framework.org/topics/rest-framework-2-announcement.html [2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion [image]: http://django-rest-framework.org/img/quickstart.png diff --git a/docs/index.md b/docs/index.md index b04e23465..de4b01c61 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,11 +15,11 @@ Django REST framework is a powerful and flexible toolkit that makes it easy to b Some reasons you might want to use REST framework: -* The Web browseable API is a huge usability win for your developers. -* Authentication policies including OAuth1a and OAuth2 out of the box. -* Serialization that supports both ORM and non-ORM data sources. -* Customizable all the way down - just use regular function-based views if you don't need the more powerful features. -* Extensive documentation, and great community support. +* The [Web browseable API][sandbox] is a huge usability win for your developers. +* [Authentication policies][authentication] including [OAuth1a][oauth1-section] and [OAuth2][oauth2-section] out of the box. +* [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources. +* Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers]. +* [Extensive documentation][index], and [great community support][group]. There is a live example API for testing purposes, [available here][sandbox]. @@ -250,6 +250,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [django-oauth2-provider]: https://github.com/caffeinehit/django-oauth2-provider [0.4]: https://github.com/tomchristie/django-rest-framework/tree/0.4.X [image]: img/quickstart.png +[index]: . +[oauth1-section]: api-guide/authentication.html#oauthauthentication +[oauth2-section]: api-guide/authentication.html#oauth2authentication +[serializer-section]: api-guide/serializers.html#serializers +[modelserializer-section]: api-guide/serializers.html#modelserializer +[functionview-section]: api-guide/views.html#function-based-views [sandbox]: http://restframework.herokuapp.com/ [quickstart]: tutorial/quickstart.md From f5f23793e34324552f323725fa25f09b34380acc Mon Sep 17 00:00:00 2001 From: Rudolf Olah Date: Thu, 27 Jun 2013 16:30:24 -0400 Subject: [PATCH 25/38] #955 updated documentation for overriding `routes` attribute in Router sub-classes --- docs/api-guide/routers.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index b74b6e13b..feff0fbfe 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -98,7 +98,7 @@ As with `SimpleRouter` the trailing slashs on the URL routes can be removed by s Implementing a custom router isn't something you'd need to do very often, but it can be useful if you have specific requirements about how the your URLs for your API are strutured. Doing so allows you to encapsulate the URL structure in a reusable way that ensures you don't have to write your URL patterns explicitly for each new view. -The simplest way to implement a custom router is to subclass one of the existing router classes. The `.routes` attribute is used to template the URL patterns that will be mapped to each viewset. +The simplest way to implement a custom router is to subclass one of the existing router classes. The `.routes` attribute is used to template the URL patterns that will be mapped to each viewset. The `.routes` attribute is a list of `Route` named tuples. ## Example @@ -109,10 +109,18 @@ The following example will only route to the `list` and `retrieve` actions, and A router for read-only APIs, which doesn't use trailing suffixes. """ routes = [ - (r'^{prefix}$', {'get': 'list'}, '{basename}-list'), - (r'^{prefix}/{lookup}$', {'get': 'retrieve'}, '{basename}-detail') + Route(url=r'^{prefix}$', + mapping={'get': 'list'}, + name='{basename}-list', + initkwargs={}), + Route(url=r'^{prefix}/{lookup}$', + mapping={'get': 'retrieve'}, + name='{basename}-detail', + initkwargs={}) ] +The `SimpleRouter` class provides another example of setting the `.routes` attribute. + ## Advanced custom routers If you want to provide totally custom behavior, you can override `BaseRouter` and override the `get_urls(self)` method. The method should insect the registered viewsets and return a list of URL patterns. The registered prefix, viewset and basename tuples may be inspected by accessing the `self.registry` attribute. From 4ee9cdc7aff30fc3f45e78292da77b5989bb0e23 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 28 Jun 2013 09:35:52 +0100 Subject: [PATCH 26/38] Fix compat datetime import when oauth2 provide does not support timezone aware datetimes --- .gitignore | 1 + rest_framework/compat.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2255cd9aa..ae73f8379 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ .* html/ +htmlcov/ coverage/ build/ dist/ diff --git a/rest_framework/compat.py b/rest_framework/compat.py index b748dcc51..cb1228465 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -494,7 +494,8 @@ try: if provider_version in ('0.2.3', '0.2.4'): # 0.2.3 and 0.2.4 are supported version that do not support # timezone aware datetimes - from datetime.datetime import now as provider_now + import datetime + provider_now = datetime.datetime.now else: # Any other supported version does use timezone aware datetimes from django.utils.timezone import now as provider_now From 53dc98eefb5644e60495ca86c7424e669d0a86f1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 1 Jul 2013 17:22:42 +0100 Subject: [PATCH 27/38] Added Django OAuth2 Consumer package --- docs/api-guide/authentication.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 8cf995b38..22c3297ce 100755 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -355,6 +355,10 @@ HTTP digest authentication is a widely implemented scheme that was intended to r The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support, and works with Python 2.7 and Python 3.3+. The package is maintained by [Evonove][evonove] and uses the excelllent [OAuthLib][oauthlib]. The package is well documented, and comes as a recommended alternative for OAuth 2.0 support. +## Django OAuth2 Consumer + +The [Django Oauth2 Consumer][doac] library from [Rediker Software][rediker] is another package that provides [OAuth2 support for REST framework][doac-rest-framework]. The package includes token scoping permissions on tokens, which allows finer-grained access to your API. + [cite]: http://jacobian.org/writing/rest-worst-practices/ [http401]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2 [http403]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4 @@ -376,3 +380,6 @@ The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 supp [django-oauth-toolkit]: https://github.com/evonove/django-oauth-toolkit [evonove]: https://github.com/evonove/ [oauthlib]: https://github.com/idan/oauthlib +[doac]: https://github.com/Rediker-Software/doac +[rediker]: https://github.com/Rediker-Software +[doac-rest-framework]: https://github.com/Rediker-Software/doac/blob/master/docs/markdown/integrations.md From 8274ff7d9c692d27f926af7610a5a547ced09a2e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 1 Jul 2013 17:27:23 +0100 Subject: [PATCH 28/38] Capitalization on OAuth --- docs/api-guide/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 22c3297ce..768f156b3 100755 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -357,7 +357,7 @@ The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 supp ## Django OAuth2 Consumer -The [Django Oauth2 Consumer][doac] library from [Rediker Software][rediker] is another package that provides [OAuth2 support for REST framework][doac-rest-framework]. The package includes token scoping permissions on tokens, which allows finer-grained access to your API. +The [Django OAuth2 Consumer][doac] library from [Rediker Software][rediker] is another package that provides [OAuth2 support for REST framework][doac-rest-framework]. The package includes token scoping permissions on tokens, which allows finer-grained access to your API. [cite]: http://jacobian.org/writing/rest-worst-practices/ [http401]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2 From 8d410c4671fd4596089883e360f5d3e8f9e0f62b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 1 Jul 2013 17:32:06 +0100 Subject: [PATCH 29/38] Tweak text --- docs/api-guide/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 768f156b3..390fba8cb 100755 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -357,7 +357,7 @@ The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 supp ## Django OAuth2 Consumer -The [Django OAuth2 Consumer][doac] library from [Rediker Software][rediker] is another package that provides [OAuth2 support for REST framework][doac-rest-framework]. The package includes token scoping permissions on tokens, which allows finer-grained access to your API. +The [Django OAuth2 Consumer][doac] library from [Rediker Software][rediker] is another package that provides [OAuth 2.0 support for REST framework][doac-rest-framework]. The package includes token scoping permissions on tokens, which allows finer-grained access to your API. [cite]: http://jacobian.org/writing/rest-worst-practices/ [http401]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2 From e7529b4072274797daf5b886dbac4c0e65a65674 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 2 Jul 2013 16:22:22 +0100 Subject: [PATCH 30/38] Fix broken link by hacking around md->html translating --- docs/api-guide/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 390fba8cb..5d6e0d91d 100755 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -382,4 +382,4 @@ The [Django OAuth2 Consumer][doac] library from [Rediker Software][rediker] is a [oauthlib]: https://github.com/idan/oauthlib [doac]: https://github.com/Rediker-Software/doac [rediker]: https://github.com/Rediker-Software -[doac-rest-framework]: https://github.com/Rediker-Software/doac/blob/master/docs/markdown/integrations.md +[doac-rest-framework]: https://github.com/Rediker-Software/doac/blob/master/docs/markdown/integrations.md# From e460180a4dd86ff74cc786aabfeeba1c31d17413 Mon Sep 17 00:00:00 2001 From: Rudolf Olah Date: Tue, 2 Jul 2013 13:20:25 -0400 Subject: [PATCH 31/38] #955 updated router docs with more information on the `Route` named tuple and its parameters. --- docs/api-guide/routers.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index feff0fbfe..1fb15fae3 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -100,6 +100,18 @@ Implementing a custom router isn't something you'd need to do very often, but it The simplest way to implement a custom router is to subclass one of the existing router classes. The `.routes` attribute is used to template the URL patterns that will be mapped to each viewset. The `.routes` attribute is a list of `Route` named tuples. +The arguments to the `Route` named tuple are: + +* `url`: The URL to be routed. There are format arguments available, defined in `SimpleRouter.get_urls`: + * `prefix` - The URL prefix to use for this set of routes. + * `lookup` - The lookup field used to match against a single instance. + * `trailing_slash` - the value of `.trailing_slash`. +* `mapping`: Mapping of HTTP method names to the object's methods +* `name`: The name of the URL as used in `reverse` calls. There are format arguments available, defined in `SimpleRouter.get_urls`: + * `basename` - The base to use for the URL names that are created. +* `initkwargs`: Any additional arguments to the view. + * `suffix` - reserved for identifying the viewset type, used when generating the breadcrumb links, e.g. `AccountViewSet` becomes `Account List` when `suffix='List'`. + ## Example The following example will only route to the `list` and `retrieve` actions, and does not use the trailing slash convention. From e969c96aa020047becd7a759a80e2fdc46b21170 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 2 Jul 2013 22:08:40 +0100 Subject: [PATCH 32/38] Added @omouse for work on #955 - thanks! :) --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index 94760c74b..e6fb9134e 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -144,6 +144,7 @@ The following people have helped make REST framework great. * David Sanders - [davesque] * Philip Douglas - [freakydug] * Igor Kalat - [trwired] +* Rudolf Olah - [omouse] Many thanks to everyone who's contributed to the project. @@ -324,3 +325,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [davesque]: https://github.com/davesque [freakydug]: https://github.com/freakydug [trwired]: https://github.com/trwired +[omouse]: https://github.com/omouse From 03e0ce35fe497d52d1a332e98c44e42acbc8af75 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 2 Jul 2013 22:15:46 +0100 Subject: [PATCH 33/38] Added 'Documenting your API' section. Closes #616 --- docs/img/apiary.png | Bin 0 -> 55554 bytes docs/img/django-rest-swagger.png | Bin 0 -> 76945 bytes docs/img/rest-framework-docs.png | Bin 0 -> 76612 bytes docs/img/self-describing.png | Bin 0 -> 53953 bytes docs/index.md | 2 + docs/template.html | 1 + docs/topics/documenting-your-api.md | 108 +++++++++++++++++++++++++ docs/topics/rest-hypermedia-hateoas.md | 3 +- mkdocs.py | 1 + 9 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 docs/img/apiary.png create mode 100644 docs/img/django-rest-swagger.png create mode 100644 docs/img/rest-framework-docs.png create mode 100644 docs/img/self-describing.png create mode 100644 docs/topics/documenting-your-api.md diff --git a/docs/img/apiary.png b/docs/img/apiary.png new file mode 100644 index 0000000000000000000000000000000000000000..923d384ebb66d833b13b9971cda69eb718651c1d GIT binary patch literal 55554 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 literal 0 HcmV?d00001 diff --git a/docs/img/django-rest-swagger.png b/docs/img/django-rest-swagger.png new file mode 100644 index 0000000000000000000000000000000000000000..96a6b23800d2f7dec53631e0b017e2f383bda659 GIT binary patch literal 76945 zcmZU)1ymi)vM5Y|ph1EL_l@t3ySuw&{_os#-ka64 zrl(d-Sx@&=SJi|m$cZB%;37akKp;sqK0`nt2wDgWD@Y0p6Dv5{nOazzKtM&-^`+o0y=@og)lN<<-+HclkW6YS>G4!)3yz#rT}(xaWyA!+F7zA+Y~m^DYo(>p>uUo zDgKz{d&8P^s7R(m>^)G;&OT5m-%a;?LdL@{vA!>%DA;1c^d+Z~Z3o;c03 zA>0eicEh2=Bn=C{E~Z-S9S)g3U)3o;88Xr)_R4PkbeEaDt^X3ufc>M&E?M*wg{DoK zdE8kA{@I!4!oK-a*YYNzuksu3kmrl)72tF-TQD+PbJO~?9MpjYaZj$5K~UG3qzX}Q z10%4vRW5%)3~~J#qJAUuhA_lhFTQX43ngO2>8Z8TryMIF?IHV1PJrD=R|w}r+UWU< z8h0T!zdxBICbNm?USUz8w8+@l7vz*lN7&}tr(c$E823dDQqQ$~G|3YB#2Qom? zeMiFo!E6t;K@1V}Ljn~l844%?X(0ezE6BI>_Z4JZ`s5mDX%8{y?>Pl67rRhO92ijj!eOzf@d8g% zfZ}g7fu*^^3cR;u&bX~`x`EHXV5f*#VBS9Y1bt0~oHamNf@u7)T+NLSC*CvqN5ma7 z6BhgT>XuY979Vm`5B3(`B?Miteoq3Nzz`hNw3r4Q-j#?@tOog~uOwD+qy>;AqUo{7 z1$Zq{5^)Z@P%xpn2C3h$25@#lH6ynS)eII4r5NT@jZ)QQu=m+aQJMXp0>FmzHH8|K z<@~PjY+o~?==;>Z$9&hWPN{)fGC1YNM;`o*vyE@>+x$(3$(FSex(Q`5vhBx7FVYs^ z%DtUt1LGn9rQ7M!(3_+a!4Kb$a5IKzpo=mVUJKeP0EHx(nB1CN1r-uoQtySY&5(-S0^Q2~`PFK3Ko6zJ-1dgg_D8MARLbG6xndW-gdaf0d6$S!v_JWt$ zgrojjqQj0`coPZ}?LDeJ{5{;gx)CLq9)|Ca%t|=m$b`tY$e0_J{g%m{$yK&03%!qa z>ZB(cTN>{q(j?QQPf0PQGOBj-81q@BHl^}&43+>(1IuO0mw8}W)MU}5TlQp@KFi1M zYv^%xYnW&Av*lgyG4F8#ZaQu!E)K3LZVJl)D^9v?`f&PQ`T@&ey}1@*1GLtj)}dKqtpQE{DyS-xD|1iEfKP@7$_NvBEKO-NoEqyf^e zQoP9JQ_QKzDd-aK()JtqJw?3oJwh;}yEr#Ix62^gAb-npL>AAsL>(( zif>07P#u;f87~<%EUUaUx1sXAb+Uf)i`Bk4fTfJJZ7{ETO;fX6Y~dGYFVB#7zDLFr zEh2A(Mno3YI+i}&8J#ly2_2CpwdQ9HoBHGG=iZgRbSqsWpTQ8z9izI5!u9=ziW2%w z*RC@k?sVPib*whIw#j?sd(s!c3nMIVsC#G!^23s|D^**N&-~Yu!>#6ttH>Q#N-xTq zS;?Z!EX@h(38aa9#4p&F*mLyX9H$+&n(-aufX2W`eRuoZIn~vq9j%k+G1Rl1p*yGB2s`IMW}W1^(-p^t zy^_(%%2@qwO$Qo6JR*Wk)Q>Rqo{~M8%3J*ww>{{^gtpL^_=5r3gQzRrN0Zk$=#-$^ zplOkmKHV@Q$x2B~$sI^}!G=FBwffioyCsRSiTTvbCDSG27D4ro^|L}02I1ATy3D+k z9iU#8dASY01j9GOFTSj%tZpu5ov!a8uidQYR>&2>3za&nEpl$U-K|@9ohLCi#5NMkm!o2% zQ6_ksw&5FdioX=AJHMOPUF;7CwFoJ0b?|GY-;+bKdq152c30*@4+u_e_Y|**4B?&Z#2>jHT9OKD*xzK?2TRLT{pFM=Od;4=I?1f%-q`;it@(9gA!g;kl_(*h-{e@@QBiyI-i1IDxzHv$`Tq}2BZ6U0& zfX%{7#%uHF$7~kC1OYvN#)tQ9Tt7=M_StX|~w@;Q&$63f*)B<5!cC!Adufb1V)bIj;uYeP zkx!B9o;A1qm!pv>vpd0E)bEWCNz-LHzHToaW`@IkmsZm!Y5Yk$#)rGJ zZWb4XMd2G|o&5LyZ@Di(*X{rt)BVkdpqm#aE=}LR&LzCP zgr*Y&1S0xB=O>8t%x@47pKL8uG@LbLWw?y&Y=8#Fc7`TEcN_b^*bopr?p%MnHYUyn z#O^lMwoY8`yrloa;QHJD2Mi)5{uhd~6)&lVtOBvHoudgc3y=lKK+11||>_6a8NddM6KCX9IV7TPL#r zA@YCdh?qDTIa=5|TiDqW|3lZn(9Xq~mz4A$M*lwkM^0m-fAg_-akTyy7h@xkiM5H1 ziLJ8}h!MyL`Zw{v&+-@>aXGmdTACO+|4Z(FllzwfLI!4_{{ZuV{$Fe!&_5FWhlKy5 z{=cAq_2)z20sY&SdDykbF7h?=#m!y$Y3_nI51LU-r zY)T3m<>WOBY-Vf$Ci2^LngwSela=%f=L4O_i7y!Eh4GE@&22b^Z~bgY4TLqd3SKy) z*wFd|*!IxDpFRVbd&fT9&a-e}zI}(m5q{bD+49i-I>DLaMKJM@y~!zleO*b63=8)I z;=kH06y%Jr0<6j>XaR`-YTrH`qVvEKLqPs#dkcoRmGo;tFaE#j+`?ynLWTIx=Kn4P z@m$cg20sV&Uvvc^o}nlITZSJ3ek2e+Gy3Py$DjU-9WjJYfZ2bGCGe{Y^#|V($PM!D zzu0~H3+-n9Z)u6A^x>fIY((*qVQ~sRz-VDbCT+bWdeSSQUWMJ9|7C_Bzfm#;HV@I` zDe?ec_h2QzB3@K$W?2Vf=-5{)S8_68Fnda4p2J(JAH3*BeB4m6z{li!!s?584 zOzp`4B3Q*>`k9g;q=g)Oh17l8-5xKzdb#m*b5PX>c^&X~gnpwW|LzMzZ(3Ym@wvHT zUF9X}yJv^&s)qtPVIPNI-%E>7q1RLgirZ`N1+<>IJJCJvDdLkNiHXmra*mA zcIq;JFO8%Vn^~7|ksMg(`e#A}?j?5#i+0g{!S^ZN*uLDjT1#pLJbxYw@9JH~pmgU3 zNiH{m6JOd%h{bNd8%1WQb6jvBuBJd)pm>UHRTkwXR59M|&}#JFpq9BW60#D7!b;P6hkce$LPPJAY67rknjfP z8-m?=dl$>q>EKRtpuN3IB{T7#Yv<)UMHklv9Y<%jIgD)y+{((U$fZDcVZx}CDj7Fs zXRLtG#IYsLbbC?*A6|eW7rwP8g5+1cmt2i(Hx$v9SdHEPQLir zvoFRj!{dVYp_r6R`^>duZqa1>)U{29hIMh#ui0miQvX$(aD~U+861O3-eZpFbt~Z% zJ4xch)vQ)RUL(n1VnfoR9FfPOEefg)v2Z`?(J&YlTdWKZi60Sr|ODHyjl?Mzeyt|I6jl$Ibx zM|kQ?HCW07Cu|wl79Ct}k?^DI#r^z4@PbRy7Q0+jM3gEw{{6y&8a#c>pEKf;k>P;U zaQgz#%(^Lfn*ekY4E&z%btuzM4l>FA&K)3 zS<^bM0DLs7I^Ot%O+5k!mb_uoI!ZmINb4_Qj?kjlg8Imo<%(LXC6?+wm+=3)x~WV2 z;mUB@P7)((tigT`lKok}zDKraBnu;6e>%_Wd8;`-wF*x3#VdQF|8j!&Yq~fSlWNhC+ zo1uyNf>7JYMOR@h6rQAJ8!K7wEwN_8391_$36P{<*Cb$<>;Wz|ERZh+O?LpL1)=|A zIsL6l{3nNxWUpK8#L{stvraiDp~VG$B@lY}N9-k|u$^*nS&Zs=S*v?uVNZl|Vo9B( zFohtKrs3&hQ1{A;bQ0yH&+L+Sm>Paz?|>7F+uqpl0v@UHZT5N{nHMH*3@N1W52R%H zQV|tWo9&=*^bjtM8a8R8*~+Batl;?K?(kdw&rz8&vx2E222i%AsB9w!)LuM&4c!us z^;1TaTR_%T3l>m#!fc>h%RG?6P6Xh)WVlGKsHJt*jjSnARvssI#nH$noY2s~D3Qw1 z-=`b{Ci(Oq`Uyb5KbFa-)~Q7eZ#XY=QV9_VZft);#pU|gz;t^*>?$s_dVia1iNM@0 zl`!ltnf*qN(rR;h3R}oW8#l7?^yv2SzC91i#l2#+w1N1_A`v30Hf!FZn5 ztJIYRjzBAgKAD4N|3Nu~hlNKc|8@oWJt@FvQXSFMM~-@q zSiWrZD&DX}RRbf40+#pj`o;}f?YLOzXultrrY-7|2``{a@$0-E^!@Xp~Yj9!Q$6JLlY+q*gnjNU5^N!U|S~%&3h~^{NoT` zEtWpN`v(qX80~WlLHyL$AlM|j1bfG@Am@lEF`Xq#T+3SHp|7B2)05IUzYLksihT7L9LK?;{n+a4!in}Xr|iOy=>*G+ zXj;>^p_JoceD!;I6sL^wvcHIv-7^~v*i+(Oq_Hr$*|8uEuX-jw9CZ6QG|JL8WuTD) zE3pg7^2hZ1@mh-a`ja8OA}LB?kr6PHsyLJjYFuih^XR}CZ8=k2gdsd@S4@qj1aF}4 zpkaFVCTl5*et9~*QsiQ+IB8q1b3kdmYWEdLrZtmK?kW`)_zo{G_ zx1^ZQ|7^ki*Zh)_!T^f?wXSi7hwsXv!I{(%Y}BM_L~mETG?Rs{$~k4&{eAGth?Ucr zp(+3*8zhtju@FumVbhr=Ma!0;#j7j}-JrwyXLKIId|OMJB^!k$d89w$^VFY@WN0}U z?yN{k`p6k(xmB3Xa4qvc$9z5#QVO%2buPIZBkrV5&8JpiIh~Ou`3d=NFV7zW_2(QI zFK4lIMVqbsb#$B}Wi|B47n21*UuS*(J(@d%?_wP3l>U^}rP*_jPrbEsL9mx#o2ff7 z>^c54xH)LexWKFw<^AR6gHziloGY-Ln`)_G)3EBodVG5R0#s^OPWUYjl^oF&`TJaO z0ZWgRJ_|U6?nMDnG;;sqtUpW~@VCL3hj4m{gHdB00$5%TQ!CbMU#uWK9hjZRqrsIqt6dtA z(_dOOKk$|xhA|Flck5cEORKxFVGA@HZ@zkP$R2m-?~hqoLFo@GbDfZ9lgLZ7-?B!F z7hxPy^DXrluTA#j+u?X1aC*FXKvhF8lwn)(3KbJO3?sELbo$n-)fv{}%oJd^rIo}K z?K{gEtiaJfa|l11d)vjpsqc>A;{nO78V!3B@t1MknNFC_w~)7kaas%%&0foQx2QDARy#opND10UEp>x zJS=A~o_#;Ox5>6sVRIs7tkJ~iD3EelAj+_~ikcI6m~D|RS-EP&rR2KEHaorWE39d) z`A#nBSGBMor^%&0yK0-|NkGr%mfNOr6}ugq_T|~HV_C;EIa51=@}FPE5dbNjh_9<% zjf;{L265JCJp8|H!xA%DI3~^$`Y2y?*&UbT?Mng6#fT&ZOlcMK(}(77hD+e^RMWD# zuf{2B#s~!31fCk}w^^;j0GFY&_=4luRXG&6gdquym~tdeY=a6!8*SmS3k2u1Zk_lx z!!{*QpI5e9TDNpiO>;r7$xA0E)rvxh>;IA~P1!>Ghz#cpF{o_6%QE_tKu zpp)_E87o%75jNg!mj|W#WYVq)P)=4kImy^kL)r_QV)ypXaOE493U$dtF>6o9C9VD@ z;NF}3d4Z|i|M#63u?8kOJ<82J(}kxM^@@`&>K{bCq0w^t`&F&Oq!W~TdzT+RPy)I# zD?_Pyh`_Afe83n2t*r(+%JV$ z&&J(?Bx9c<6J{Ub=>{%O8Bkg3X?laWSC`%Eb=!6}$S)td;E{EZ{8Q)$L|I#4=l3RHnZA?;%7mt?En z@sa}49jEl_vJbi%Oe3RF8ZR_nl-2TYq+VOn(F$|Q@JcGabuz59xyP-mxSzqh}td zlz6R(2BcxwM_92j9a1o%G6IgyxXITLJ5 zzYDsmV)#w3GsRD+&FRp_fde0p(Pf&%Bar^I6yI+WDZ&d>dTTN0TZm7eQ(Wg6^_b_Y zUxBSG7qoLg{z5d~<`Mp>6!Yu-jbg;QG~^`HU7z~((+;#a$)T(=D49{oBu<1hV3qPnrLW`|ltFEut0$>36%~hVU$8-^HeOhC zUXv{tDhkC6P#da^CkY`*&!HS7CBI>0PkWl-wX>r7!rqi9J|^`w?i1~2%q?am&&8E7 zopMmp+Eh3{7QMCSRM+Z4gdbl}{?M7|*aj-~3cWe)&{43O;{7388`n8K0mohTws4i?MNG&h!q z_IUi_?G73|^ZowIcx|VtdD7*xm?OOM@y#`}(wvAy9$*oyv}iP*Vyf_~bI8L6NKZFg z04~-{%RV4<`4c6&T$1O9ftjtUvG0q|iIr4{j-VKSq7pZqHm(z&lDi1!tBj_$1sTIb zn0%xkl;R2x-wK>lIlW#`Ds{1F31~~EM9}pg&X20!hI&I+q1OG-Qn92M%m~K%77-y& zeTzd)QzJH#=gInvv1ry$3v}Gb%2us=0oGaeX{LP0U zJh87q;^=)4uQ2{ZaQ^$pWI1QLv+vnmni;kzYrktN4>^{{eqa$LuV++S1wyw4#=7$`kXsgHj!23`9 zu^9oTFv$Ya{o**>8h!#k;+BPCN%ueXq_Lw+(yT^THUwijt9IclR)qYV1Lk%LvFO_s z-NV*v48tE2w=-XupdEx9N`n+NBZxq$Q3=nwed1Tmb3_F z1QEaJ=>3=H6%-#K(bb@)*=iOQ&)ygxNmsEdsl$je zYSY>La*0Rg86q$h;d_?h!o@Cgw)*SsI}H$d@CxT+2}gu360AHp4Y600#9{|LrR!>`7paz zadK4)Kl;!#xDe3SWXN5}^?dyb3FNiA;VxsI2ix&vs(ml8OStU;>5@Ie4)ZR^A zv`j|K%q4c1JZxJldrr6Pw~~2q^&0wU@tNcb59|Ci1uL*;5wzz{{gRWNcjhr$+%}*Y znc}^yRZO{=2qm(en9oq>DMyzo(r|Jx9f1Pj)h}nbovmb+(Rw}k&sjsQ!x{M=6GTKL zr9BayuiR~EYvT6e-OOH(=)kP{cv@jo9JVSb3wo~1)F$fCII0UKw;CUH9I+@4pnqoF z%#V_?5nD-p?m5jg%wM*q8=jot$IM6`?tF7k@Mp4QmGclXW+9)AgqiXEJGYRmc9waA zD$nJ(InF@Ed(`q3J9jM?)`cP1llsT+YKJiD>JLNQV!6K?0remvbz=K0baSeY`rS6% zrz!2iHQ$#+ZI)alv(6Q#;_h|x8@EvR@x+{0I-6EG)fZh!Ne_7^xi!w!ES8-2Z6jfu zp!;Z|7($5z8Ty0!v)B65JO#^Pc7+>J74GA(c$pUr{qffQReSxs zaYdWmfLU6*>~t*p5U+P+DG!Mpp~<2>>-%_q>3z&yRV8b8Pcf<5KbqSzxcHT&z){^g zYMVW?5P*7eE9egK##ZCj^O^m&m6yk-aiQ$}imn`GBG@)_(sf8Tq5%42r^W$=8cDDp zIZ`?!z2XaUzK#6IWb(1t`b)xaS84HD)5DM#y4~ZE-x`lTVZr11?jS}nF6zT_w;OWy zYu8Z|pNH`FSzK;lRH2=0!$ClxMLG{&9ja;mtxnRy=qS5S*>l+avVo00ROwQxNu3el z%lRK4B0Ua@^zUROYXyR*LOA&|K@Jaa2l$)LzouPpzf8h!=!f+R8=a^c>0pe|h+NP; zpTqkxzKKshyTjs^VkaR#F^Nw7+G6zc}=SSOBdWUo~s^xjZ?5~FJfeYoB5;}Q9aO}xh z(J%1X#4Df)E}sW%X%ncyW8;z&cq(!hi21Tb9QAhQVfZy;f z0u!r=aW@j?R;;mMG~TG!$j%Cyq!5@>&n2X{q(eYJmeeEJP_a?t5T$&R4r?~1KRgFf zi3*HmzZfErkc($;5T(NHM#q_rcNGV9GBg!3kLqQ7rj@{a(!D_5$*k#HMYY%t|D1gq z9#+*VMk~Uh32Ir81S95tId{(M%CHbg7oVsNoivD^(GfS_KjYTez@Sm@@cE>{`|7et zg3#Dahbt&)yFphMyOfretv+&iVnSwC$+Samd@r$70_|&H7?m9U9s=Y)n2oN+2q8M+tG=KtjL~NTI4~)<1Ebu~KxDBeJgK|tfT2@=U zHmHFT*x8W#QTLawjJk`>t*zqi6Wn68iwn@8Bmq=7oi6BfknXOEuyWxVU8hx~a>If( ztBQWD{Z~VM0bE|jy^wF7y%B1X}Eo@lSng(>@~Kb4%_HS6H*em zTajPGsT~Wk_w907zu%*;H@#z@JVHe~fEWa(Bq48R<8HJ1j!Ar6hf(SGrh9?P2~_MI zxpedlN=0+2O*=vhuiZLh#fjB1$n_-zP8-*<(wdOb31xMCblt+Wb%f)E{5_0xsS$*E zad;CCjZNgKms@`my63`5QbKJ9isCUIU3)iOU!q#B)`E@P)~|0KyI0Uv_3bvh3Jc#=7?LJ8 z^brXpn2uMcHD0_8j?%Y;^)i`^qgPoYk{3EGPFGc53JHI0ppG;CahsJvyTSv|dCLTi zQwtrBi)(Y-^uv&0=jLV`L@M$AG#!qB?doM!!^>dVPO;hUtMzevx`DMqVVRkrp{~qQ z&GZODdGVM3qO4{6>Yna3)`w$N$@OwGPPsB*JajcSm`G2E8)&yeTN!w&3CLP?cPObr z?tVK`0yt%^Sgqe`4`Ra>K$%x6RXa~Hv^5O#8P_{*oOJgB;=70w0M1k2LNAYq-30uQ zd#45-xvX?L41Z8abhDZ6x|flSk+gNlsFoL3M4%4z_EE}E`CFIaYFKMTL{i4lpAH%B zYmGae;IQh09Xr0<6d@&J=a0;uyV;KEd0Ac3=LnwBDIKEfz+(;r%pyCjC93%@y z-%t???N-o{=;lJn7b3k%S#)(#HV(~RwhB>PPEMN zrlr*FO~KqZ1h#j)U7eBie?IU zf6*AQ3tdR%E|?v1DCRf^mW_HDAEOy*qjSXxLcr-vLJOhUwsHI8np;>m&wXdhkS((K zA@Ul6olcM&25=z7XHpFaXVxp3sb z#S=)~Re#FNy*9i-`bq*NZYe}N60@j*hGx9KW9Gg$6-T4qik$2K57~+K>*>lb&2Q;W zX*H?rUBN=I0hloi)oHGsb|czOo7@^`ntRcsojSkje-jWKS<3NoxqaJwyU2$U;&5zy?yA~cS9=DI^As85yG&8|9lnk+0}xKZ9j*BzVPBJ*^Ah=7wiuvUy(0a)s3+q@ zpjhL4nGT21V^8(roaTx8*0yBFpsLRnXC+(lRBZDz1ed5Von!9q&1JcLo%2bA%e0w8 zGg!4(N0H6V&W4x!F4d_AxQZ+~_h)mH^e6g8du60;M@Q%T_4zDzV@`f>!YTn@Aq)Gp zDNxA0W89&k$nPFB`{Zg`8HebpwL2fG1f8e2wyJ5YLq2(aT&ZttNRL0@CH>j+F7{$*)l-Ee7 z(o)+~9y!K-UmMEhI7Yq*jWS7I=~Q)gT&RHig@$IY&gmLKjc%jXkg@0+Gi6n^z~!Q2 zn)SnQ%D%D9mZ=SJLf!~-{zTa&bHB0tH2$^+156Xo>^ZTGIx^2-{rAhlgX4;lJJ{Bn z5#gloyL=~3s>#b5Xxp-~BUU_T8T|O|Uz1{vLTnOHPZoxV+Pnc@&xXIL{(OeR>}5lX zq*}t|Z7>?R+UGArlj4|S4r7gN$Jw4=%dV+H)ABT?zPEGUK@tG2(JEo++}?ShS=oc@ z5a}kljd$ApQ7gO$h~SDGMn~F}GNb9dC> z$QsFr!ov&V%ICcw%$Zoku5t$)o=Quz)!<=HQ9d5;^b=8dzw-dAUxCgN+SG+K8y@P$ z-NP*U{i9!5Y*r4vpdWzwTT6KaCNUROAM+cf58Pqso{-)$l5JJ^_WZh6S11juE&b9+ z&ladRMYv3H((P#pZ1%O%ENt864brOj)duh^tieVZRN-aFv&vJefu~bn9>2eIZ^@b* z4-m(TsD-VMH2#cfD{jA8^)K~w1zCuDaH$|EA>`V}1L&euN!{$lw{cOB>>5Wi6OO#J zQT%QiQe{ou2sg&O(}v*7*Si-iW(p&&yKmOW;{IG4lg4fH4JD?@#8oSlVPu+-WR}!7 z_kG_-;7#k8$GL{5*8b_)a4V&Y2lyJNo1YovB ziI}Dv2IDBh-t+AG&qpMGPXF0m6iY`E%A#8DV8gvCz|}AKC|}RxL_A~pSguSGdR}(6 z*^zbGO!khnnZ-EXVl7V$_wrywn2l6UB$1srE9Wq&_#Ui7BHJ3)!(x>8wHxBE_l$b5m+848{zis}_IJ9o@t`sb;B?4Gvgt!Nlb7GMZel2dW zdC}V0U_7VY^0m(|SeitT#%(6|Zb z#IvU?fFE;Em`FeubN&kwURGf;v@{%xTHD(T-7wL%%1yzr`h{wHb6fno888!EsW+djr`hqYb!9gobx%iJ5CLF9&mjQ zf-M2TBpj%petyc*M-J~QHjbfuTUu@}f{EzR+`6W^VVGxk$@g0zd>FdPJjFwhl9@1+ z>3;(2kXHO!t<*sSk10Xd+bKYy0_yCZ>lPy>HvpqcIBKs$48>C63Z=AlyGC{=L z>1DKOA*hQd2D0oq#5=xaE}5%_6N;wPW))!z4MFM zH%mDY&!xfBLZdb1szk;J%zs>*W@}d>px2>dG8WA+tCw%;Z|zH*)DK$i&H&2 zyM?clKNf0$WVHTyPkBw!t0?{M`!z{eJ}Nz2qr#3UAC0!-Rg;*zsVV&F%oCG-n%Mv! z{s&>`=?oz>5(6mm`f2Wr03oXuguS1ofoHaY@gfRj8xbk?Bj3}5Wt2~srx$p^SLj+^ z$+q3U8W}FI`V>k$73SJ4Zg(i+wthXSXft#@6wJP<-~bO{I}PkQmarTChI`mV^-5hy z%~$@Y&?Ro#mYXwdmSSuk(TJPJyeSD+IV~e9U4-r>1g5lTIQX_7hgpr1ygqi&+d^YpZDDaZ-IwKIK~`qTv@ex!E?q5XIf!Zm*DLv(wLw?$#pW+9 z%=hW?oF;JCrN8DeslM{cxu)BYBIsW<*fH<%ZeSkA#wob=>A^+s2H>8ulm(G4?TGY~ z>mZ~(X6<;ZNLyDJn)^sdmobD%X)LB=NEuhNbOIiOT9->N&#s)34p%8VxYVa60yv*^ z#5^rmTmFpB)xH?`v{F+F%ib(u+o?}@QcBZ)qaT%@q*>U-*uVS?qiR_h^7)!vRp&s6 z^Z{g}cAohPyMHn}&WWQ1u8Ca(KU$PI-QHGP&NvVC9)`P0r=jVR%K2gGHN$&;(lqS;_fqKN>Q6T2Mw%Pz@F3g^ICOXY|{w zg_W;odaZ)Bk!?7T)=cYN8K(qCB%o9|Gvf}XjG~O6vTs~oR&~#;$PtX;bG_Rouq?u>K+&Ur2#2Z7{Yz^(7OB7= zE)rCp@3EQ8YTjNttXdRSO%uB+*B1vd-C=#Q_~ zyqo(>kibAa!t5e0AN{>cj!xZ>= zZULbUzgF3)LNk+e5_s6GL+id1fvw|{bk#B&sE*_m9X_RP@0lfiY}o6X|16hcYF`LO zd6^E}t6KYY)B4OHNxUv=#BXlhdWXJ}H~o-UpYAz7xdHEKcmLC(ylj8(awoVcWz8SS z5G^r@_|Ya#JB6GaXg&fNe8e)&58wL6XmR@O${}x4&%{4gxB8DTF9l1eBw#udQL-EX zGx>L6t%Wwh70xc5nz(lZ(|#Ulv8tg_TMffBsX4Z%0D(6%`>FHckpDt$oGc4=IC`V= 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 literal 0 HcmV?d00001 diff --git a/docs/img/rest-framework-docs.png b/docs/img/rest-framework-docs.png new file mode 100644 index 0000000000000000000000000000000000000000..736a0095500cce86611e6fa80ddabbdedb878f4e GIT binary patch literal 76612 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

yMZfoSE<{aD_|fOb~6MCO4H4^wZO- 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�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 z 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 Q8{nT1pEz&HHw~}<2TrG-+W-In literal 0 HcmV?d00001 diff --git a/docs/index.md b/docs/index.md index de4b01c61..99cd6b882 100644 --- a/docs/index.md +++ b/docs/index.md @@ -170,6 +170,7 @@ The API guide is your complete reference manual to all the functionality provide General guides to using REST framework. +* [Documenting your API][documenting-your-api] * [AJAX, CSRF & CORS][ajax-csrf-cors] * [Browser enhancements][browser-enhancements] * [The Browsable API][browsableapi] @@ -289,6 +290,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [status]: api-guide/status-codes.md [settings]: api-guide/settings.md +[documenting-your-api]: topics/documenting-your-api.md [ajax-csrf-cors]: topics/ajax-csrf-cors.md [browser-enhancements]: topics/browser-enhancements.md [browsableapi]: topics/browsable-api.md diff --git a/docs/template.html b/docs/template.html index 217710250..27bc10622 100644 --- a/docs/template.html +++ b/docs/template.html @@ -95,6 +95,7 @@