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;i + + + + + + + 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]} + )