This commit is contained in:
martin.bohacek 2012-06-05 13:29:33 +02:00
commit eee791e9b2
190 changed files with 3268 additions and 2317 deletions

View File

@ -93,7 +93,7 @@ answer newbie questions, and generally made Django that much better:
Simon Blanchard Simon Blanchard
David Blewett <david@dawninglight.net> David Blewett <david@dawninglight.net>
Matt Boersma <matt@sprout.org> Matt Boersma <matt@sprout.org>
boobsd@gmail.com Artem Gnilov <boobsd@gmail.com>
Matías Bordese Matías Bordese
Nate Bragg <jonathan.bragg@alum.rpi.edu> Nate Bragg <jonathan.bragg@alum.rpi.edu>
Sean Brant Sean Brant
@ -211,7 +211,6 @@ answer newbie questions, and generally made Django that much better:
Dimitris Glezos <dimitris@glezos.com> Dimitris Glezos <dimitris@glezos.com>
glin@seznam.cz glin@seznam.cz
martin.glueck@gmail.com martin.glueck@gmail.com
Artyom Gnilov <boobsd@gmail.com>
Ben Godfrey <http://aftnn.org> Ben Godfrey <http://aftnn.org>
GomoX <gomo@datafull.com> GomoX <gomo@datafull.com>
Guilherme Mesquita Gondim <semente@taurinus.org> Guilherme Mesquita Gondim <semente@taurinus.org>
@ -347,6 +346,7 @@ answer newbie questions, and generally made Django that much better:
Frantisek Malina <vizualbod@vizualbod.com> Frantisek Malina <vizualbod@vizualbod.com>
Mike Malone <mjmalone@gmail.com> Mike Malone <mjmalone@gmail.com>
Martin Maney <http://www.chipy.org/Martin_Maney> Martin Maney <http://www.chipy.org/Martin_Maney>
Michael Manfre <mmanfre@gmail.com>
masonsimon+django@gmail.com masonsimon+django@gmail.com
Manuzhai Manuzhai
Petr Marhoun <petr.marhoun@gmail.com> Petr Marhoun <petr.marhoun@gmail.com>
@ -401,6 +401,7 @@ answer newbie questions, and generally made Django that much better:
Christian Oudard <christian.oudard@gmail.com> Christian Oudard <christian.oudard@gmail.com>
oggie rob <oz.robharvey@gmail.com> oggie rob <oz.robharvey@gmail.com>
oggy <ognjen.maric@gmail.com> oggy <ognjen.maric@gmail.com>
Jens Page
Jay Parlar <parlar@gmail.com> Jay Parlar <parlar@gmail.com>
Carlos Eduardo de Paula <carlosedp@gmail.com> Carlos Eduardo de Paula <carlosedp@gmail.com>
John Paulett <john@paulett.org> John Paulett <john@paulett.org>

View File

@ -3,7 +3,7 @@
var options = $.extend({}, $.fn.actions.defaults, opts); var options = $.extend({}, $.fn.actions.defaults, opts);
var actionCheckboxes = $(this); var actionCheckboxes = $(this);
var list_editable_changed = false; var list_editable_changed = false;
checker = function(checked) { var checker = function(checked) {
if (checked) { if (checked) {
showQuestion(); showQuestion();
} else { } else {
@ -11,7 +11,7 @@
} }
$(actionCheckboxes).attr("checked", checked) $(actionCheckboxes).attr("checked", checked)
.parent().parent().toggleClass(options.selectedClass, checked); .parent().parent().toggleClass(options.selectedClass, checked);
} },
updateCounter = function() { updateCounter = function() {
var sel = $(actionCheckboxes).filter(":checked").length; var sel = $(actionCheckboxes).filter(":checked").length;
$(options.counterContainer).html(interpolate( $(options.counterContainer).html(interpolate(
@ -29,30 +29,30 @@
} }
return value; return value;
}); });
} },
showQuestion = function() { showQuestion = function() {
$(options.acrossClears).hide(); $(options.acrossClears).hide();
$(options.acrossQuestions).show(); $(options.acrossQuestions).show();
$(options.allContainer).hide(); $(options.allContainer).hide();
} },
showClear = function() { showClear = function() {
$(options.acrossClears).show(); $(options.acrossClears).show();
$(options.acrossQuestions).hide(); $(options.acrossQuestions).hide();
$(options.actionContainer).toggleClass(options.selectedClass); $(options.actionContainer).toggleClass(options.selectedClass);
$(options.allContainer).show(); $(options.allContainer).show();
$(options.counterContainer).hide(); $(options.counterContainer).hide();
} },
reset = function() { reset = function() {
$(options.acrossClears).hide(); $(options.acrossClears).hide();
$(options.acrossQuestions).hide(); $(options.acrossQuestions).hide();
$(options.allContainer).hide(); $(options.allContainer).hide();
$(options.counterContainer).show(); $(options.counterContainer).show();
} },
clearAcross = function() { clearAcross = function() {
reset(); reset();
$(options.acrossInput).val(0); $(options.acrossInput).val(0);
$(options.actionContainer).removeClass(options.selectedClass); $(options.actionContainer).removeClass(options.selectedClass);
} };
// Show counter by default // Show counter by default
$(options.counterContainer).show(); $(options.counterContainer).show();
// Check state of checkboxes and reinit state if needed // Check state of checkboxes and reinit state if needed
@ -81,9 +81,9 @@
}); });
lastChecked = null; lastChecked = null;
$(actionCheckboxes).click(function(event) { $(actionCheckboxes).click(function(event) {
if (!event) { var event = window.event; } if (!event) { event = window.event; }
var target = event.target ? event.target : event.srcElement; var target = event.target ? event.target : event.srcElement;
if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey == true) { if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey === true) {
var inrange = false; var inrange = false;
$(lastChecked).attr("checked", target.checked) $(lastChecked).attr("checked", target.checked)
.parent().parent().toggleClass(options.selectedClass, target.checked); .parent().parent().toggleClass(options.selectedClass, target.checked);
@ -124,7 +124,7 @@
} }
} }
}); });
} };
/* Setup plugin defaults */ /* Setup plugin defaults */
$.fn.actions.defaults = { $.fn.actions.defaults = {
actionContainer: "div.actions", actionContainer: "div.actions",
@ -135,5 +135,5 @@
acrossClears: "div.actions span.clear", acrossClears: "div.actions span.clear",
allToggle: "#action-toggle", allToggle: "#action-toggle",
selectedClass: "selected" selectedClass: "selected"
} };
})(django.jQuery); })(django.jQuery);

View File

@ -1,7 +1,6 @@
(function(a){a.fn.actions=function(g){var b=a.extend({},a.fn.actions.defaults,g),f=a(this),e=!1;checker=function(c){c?showQuestion():reset();a(f).attr("checked",c).parent().parent().toggleClass(b.selectedClass,c)};updateCounter=function(){var c=a(f).filter(":checked").length;a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:_actions_icnt},!0));a(b.allToggle).attr("checked",function(){c==f.length?(value=!0,showQuestion()):(value= (function(a){a.fn.actions=function(m){var b=a.extend({},a.fn.actions.defaults,m),f=a(this),e=!1,j=function(c){c?h():i();a(f).attr("checked",c).parent().parent().toggleClass(b.selectedClass,c)},g=function(){var c=a(f).filter(":checked").length;a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:_actions_icnt},!0));a(b.allToggle).attr("checked",function(){c==f.length?(value=!0,h()):(value=!1,k());return value})},h=function(){a(b.acrossClears).hide();
!1,clearAcross());return value})};showQuestion=function(){a(b.acrossClears).hide();a(b.acrossQuestions).show();a(b.allContainer).hide()};showClear=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()};reset=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()};clearAcross=function(){reset();a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)}; a(b.acrossQuestions).show();a(b.allContainer).hide()},l=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},i=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},k=function(){i();a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)};a(b.counterContainer).show();a(this).filter(":checked").each(function(){a(this).parent().parent().toggleClass(b.selectedClass);
a(b.counterContainer).show();a(this).filter(":checked").each(function(){a(this).parent().parent().toggleClass(b.selectedClass);updateCounter();1==a(b.acrossInput).val()&&showClear()});a(b.allToggle).show().click(function(){checker(a(this).attr("checked"));updateCounter()});a("div.actions span.question a").click(function(c){c.preventDefault();a(b.acrossInput).val(1);showClear()});a("div.actions span.clear a").click(function(c){c.preventDefault();a(b.allToggle).attr("checked",!1);clearAcross();checker(0); g();1==a(b.acrossInput).val()&&l()});a(b.allToggle).show().click(function(){j(a(this).attr("checked"));g()});a("div.actions span.question a").click(function(c){c.preventDefault();a(b.acrossInput).val(1);l()});a("div.actions span.clear a").click(function(c){c.preventDefault();a(b.allToggle).attr("checked",!1);k();j(0);g()});lastChecked=null;a(f).click(function(c){c||(c=window.event);var d=c.target?c.target:c.srcElement;if(lastChecked&&a.data(lastChecked)!=a.data(d)&&!0===c.shiftKey){var e=!1;a(lastChecked).attr("checked",
updateCounter()});lastChecked=null;a(f).click(function(c){if(!c)c=window.event;var d=c.target?c.target:c.srcElement;if(lastChecked&&a.data(lastChecked)!=a.data(d)&&!0==c.shiftKey){var e=!1;a(lastChecked).attr("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(f).each(function(){if(a.data(this)==a.data(lastChecked)||a.data(this)==a.data(d))e=e?!1:!0;e&&a(this).attr("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass, d.checked).parent().parent().toggleClass(b.selectedClass,d.checked);a(f).each(function(){if(a.data(this)==a.data(lastChecked)||a.data(this)==a.data(d))e=e?false:true;e&&a(this).attr("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);lastChecked=d;g()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){e=!0});a('form#changelist-form button[name="index"]').click(function(){if(e)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))});
d.checked);lastChecked=d;updateCounter()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){e=!0});a('form#changelist-form button[name="index"]').click(function(){if(e)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))});a('form#changelist-form input[name="_save"]').click(function(){var b=!1;a("div.actions select option:selected").each(function(){a(this).val()&&(b=!0)}); a('form#changelist-form input[name="_save"]').click(function(){var b=!1;a("div.actions select option:selected").each(function(){a(this).val()&&(b=!0)});if(b)return e?confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")):confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."))})};
if(b)return e?confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")):confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."))})};a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across", a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across",acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"}})(django.jQuery);
acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"}})(django.jQuery);

View File

@ -31,11 +31,11 @@
} }
}; };
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off"); var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off");
var nextIndex = parseInt(totalForms.val()); var nextIndex = parseInt(totalForms.val(), 10);
var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").attr("autocomplete", "off"); var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").attr("autocomplete", "off");
// only show the add button if we are allowed to add more items, // only show the add button if we are allowed to add more items,
// note that max_num = None translates to a blank string. // note that max_num = None translates to a blank string.
var showAddButton = maxForms.val() == '' || (maxForms.val()-totalForms.val()) > 0; var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0;
$(this).each(function(i) { $(this).each(function(i) {
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass); $(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
}); });
@ -52,13 +52,14 @@
$(this).filter(":last").after('<div class="' + options.addCssClass + '"><a href="javascript:void(0)">' + options.addText + "</a></div>"); $(this).filter(":last").after('<div class="' + options.addCssClass + '"><a href="javascript:void(0)">' + options.addText + "</a></div>");
addButton = $(this).filter(":last").next().find("a"); addButton = $(this).filter(":last").next().find("a");
} }
addButton.click(function() { addButton.click(function(e) {
e.preventDefault();
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS"); var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS");
var template = $("#" + options.prefix + "-empty"); var template = $("#" + options.prefix + "-empty");
var row = template.clone(true); var row = template.clone(true);
row.removeClass(options.emptyCssClass) row.removeClass(options.emptyCssClass)
.addClass(options.formCssClass) .addClass(options.formCssClass)
.attr("id", options.prefix + "-" + nextIndex); .attr("id", options.prefix + "-" + nextIndex);
if (row.is("tr")) { if (row.is("tr")) {
// If the forms are laid out in table rows, insert // If the forms are laid out in table rows, insert
// the remove button into the last table cell: // the remove button into the last table cell:
@ -78,14 +79,15 @@
// Insert the new form when it has been fully edited // Insert the new form when it has been fully edited
row.insertBefore($(template)); row.insertBefore($(template));
// Update number of total forms // Update number of total forms
$(totalForms).val(parseInt(totalForms.val()) + 1); $(totalForms).val(parseInt(totalForms.val(), 10) + 1);
nextIndex += 1; nextIndex += 1;
// Hide add button in case we've hit the max, except we want to add infinitely // Hide add button in case we've hit the max, except we want to add infinitely
if ((maxForms.val() != '') && (maxForms.val()-totalForms.val()) <= 0) { if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) {
addButton.parent().hide(); addButton.parent().hide();
} }
// The delete button of each row triggers a bunch of other things // The delete button of each row triggers a bunch of other things
row.find("a." + options.deleteCssClass).click(function() { row.find("a." + options.deleteCssClass).click(function(e) {
e.preventDefault();
// Remove the parent form containing this button: // Remove the parent form containing this button:
var row = $(this).parents("." + options.formCssClass); var row = $(this).parents("." + options.formCssClass);
row.remove(); row.remove();
@ -98,7 +100,7 @@
var forms = $("." + options.formCssClass); var forms = $("." + options.formCssClass);
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
// Show add button again once we drop below max // Show add button again once we drop below max
if ((maxForms.val() == '') || (maxForms.val()-forms.length) > 0) { if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) {
addButton.parent().show(); addButton.parent().show();
} }
// Also, update names and ids for all remaining form controls // Also, update names and ids for all remaining form controls
@ -110,17 +112,15 @@
updateElementIndex(this, options.prefix, i); updateElementIndex(this, options.prefix, i);
}); });
} }
return false;
}); });
// If a post-add callback was supplied, call it with the added form: // If a post-add callback was supplied, call it with the added form:
if (options.added) { if (options.added) {
options.added(row); options.added(row);
} }
return false;
}); });
} }
return this; return this;
} };
/* Setup plugin defaults */ /* Setup plugin defaults */
$.fn.formset.defaults = { $.fn.formset.defaults = {
prefix: "form", // The form prefix for your django formset prefix: "form", // The form prefix for your django formset
@ -132,5 +132,5 @@
formCssClass: "dynamic-form", // CSS class applied to each form in a formset formCssClass: "dynamic-form", // CSS class applied to each form in a formset
added: null, // Function called each time a new form is added added: null, // Function called each time a new form is added
removed: null // Function called each time a form is deleted removed: null // Function called each time a form is deleted
} };
})(django.jQuery); })(django.jQuery);

View File

@ -1,5 +1,5 @@
(function(b){b.fn.formset=function(c){var a=b.extend({},b.fn.formset.defaults,c),j=function(a,e,d){var i=RegExp("("+e+"-(\\d+|__prefix__))"),e=e+"-"+d;b(a).attr("for")&&b(a).attr("for",b(a).attr("for").replace(i,e));if(a.id)a.id=a.id.replace(i,e);if(a.name)a.name=a.name.replace(i,e)},c=b("#id_"+a.prefix+"-TOTAL_FORMS").attr("autocomplete","off"),g=parseInt(c.val()),f=b("#id_"+a.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off"),c=""==f.val()||0<f.val()-c.val();b(this).each(function(){b(this).not("."+ (function(b){b.fn.formset=function(c){var a=b.extend({},b.fn.formset.defaults,c),j=function(a,f,d){var e=RegExp("("+f+"-(\\d+|__prefix__))"),f=f+"-"+d;b(a).attr("for")&&b(a).attr("for",b(a).attr("for").replace(e,f));a.id&&(a.id=a.id.replace(e,f));a.name&&(a.name=a.name.replace(e,f))},c=b("#id_"+a.prefix+"-TOTAL_FORMS").attr("autocomplete","off"),h=parseInt(c.val(),10),g=b("#id_"+a.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off"),c=""===g.val()||0<g.val()-c.val();b(this).each(function(){b(this).not("."+
a.emptyCssClass).addClass(a.formCssClass)});if(b(this).length&&c){var h;"TR"==b(this).attr("tagName")?(c=this.eq(0).children().length,b(this).parent().append('<tr class="'+a.addCssClass+'"><td colspan="'+c+'"><a href="javascript:void(0)">'+a.addText+"</a></tr>"),h=b(this).parent().find("tr:last a")):(b(this).filter(":last").after('<div class="'+a.addCssClass+'"><a href="javascript:void(0)">'+a.addText+"</a></div>"),h=b(this).filter(":last").next().find("a"));h.click(function(){var c=b("#id_"+a.prefix+ a.emptyCssClass).addClass(a.formCssClass)});if(b(this).length&&c){var i;"TR"==b(this).attr("tagName")?(c=this.eq(0).children().length,b(this).parent().append('<tr class="'+a.addCssClass+'"><td colspan="'+c+'"><a href="javascript:void(0)">'+a.addText+"</a></tr>"),i=b(this).parent().find("tr:last a")):(b(this).filter(":last").after('<div class="'+a.addCssClass+'"><a href="javascript:void(0)">'+a.addText+"</a></div>"),i=b(this).filter(":last").next().find("a"));i.click(function(c){c.preventDefault();
"-TOTAL_FORMS"),e=b("#"+a.prefix+"-empty"),d=e.clone(!0);d.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+g);d.is("tr")?d.children(":last").append('<div><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></div>"):d.is("ul")||d.is("ol")?d.append('<li><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></li>"):d.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+ var f=b("#id_"+a.prefix+"-TOTAL_FORMS"),c=b("#"+a.prefix+"-empty"),d=c.clone(true);d.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+h);d.is("tr")?d.children(":last").append('<div><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></div>"):d.is("ul")||d.is("ol")?d.append('<li><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></li>"):d.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+
"</a></span>");d.find("*").each(function(){j(this,a.prefix,c.val())});d.insertBefore(b(e));b(c).val(parseInt(c.val())+1);g+=1;""!=f.val()&&0>=f.val()-c.val()&&h.parent().hide();d.find("a."+a.deleteCssClass).click(function(){var c=b(this).parents("."+a.formCssClass);c.remove();g-=1;a.removed&&a.removed(c);c=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(c.length);(""==f.val()||0<f.val()-c.length)&&h.parent().show();for(var d=0,e=c.length;d<e;d++)j(b(c).get(d),a.prefix,d),b(c.get(d)).find("*").each(function(){j(this, a.deleteText+"</a></span>");d.find("*").each(function(){j(this,a.prefix,f.val())});d.insertBefore(b(c));b(f).val(parseInt(f.val(),10)+1);h=h+1;g.val()!==""&&g.val()-f.val()<=0&&i.parent().hide();d.find("a."+a.deleteCssClass).click(function(e){e.preventDefault();e=b(this).parents("."+a.formCssClass);e.remove();h=h-1;a.removed&&a.removed(e);e=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(e.length);(g.val()===""||g.val()-e.length>0)&&i.parent().show();for(var c=0,d=e.length;c<d;c++){j(b(e).get(c),
a.prefix,d)});return!1});a.added&&a.added(d);return!1})}return this};b.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null}})(django.jQuery); a.prefix,c);b(e.get(c)).find("*").each(function(){j(this,a.prefix,c)})}});a.added&&a.added(d)})}return this};b.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null}})(django.jQuery);

View File

@ -15,8 +15,12 @@
{% if app_list %} {% if app_list %}
{% for app in app_list %} {% for app in app_list %}
<div class="module"> <div class="module">
<table summary="{% blocktrans with name=app.name %}Models available in the {{ name }} application.{% endblocktrans %}"> <table>
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with name=app.name %}{{ name }}{% endblocktrans %}</a></caption> <caption>
<a href="{{ app.app_url }}" class="section" title="{% blocktrans with name=app.name %}Models in the {{ name }} application{% endblocktrans %}">
{% blocktrans with name=app.name %}{{ name }}{% endblocktrans %}
</a>
</caption>
{% for model in app.models %} {% for model in app.models %}
<tr> <tr>
{% if model.admin_url %} {% if model.admin_url %}

View File

@ -1,4 +1,5 @@
import operator import operator
from functools import reduce
from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured
from django.core.paginator import InvalidPage from django.core.paginator import InvalidPage

View File

@ -80,7 +80,7 @@ class Command(BaseCommand):
if default_username and username == '': if default_username and username == '':
username = default_username username = default_username
if not RE_VALID_USERNAME.match(username): if not RE_VALID_USERNAME.match(username):
sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n") self.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.")
username = None username = None
continue continue
try: try:
@ -88,7 +88,7 @@ class Command(BaseCommand):
except User.DoesNotExist: except User.DoesNotExist:
break break
else: else:
sys.stderr.write("Error: That username is already taken.\n") self.stderr.write("Error: That username is already taken.")
username = None username = None
# Get an email # Get an email
@ -98,7 +98,7 @@ class Command(BaseCommand):
try: try:
is_valid_email(email) is_valid_email(email)
except exceptions.ValidationError: except exceptions.ValidationError:
sys.stderr.write("Error: That e-mail address is invalid.\n") self.stderr.write("Error: That e-mail address is invalid.")
email = None email = None
else: else:
break break
@ -109,19 +109,19 @@ class Command(BaseCommand):
password = getpass.getpass() password = getpass.getpass()
password2 = getpass.getpass('Password (again): ') password2 = getpass.getpass('Password (again): ')
if password != password2: if password != password2:
sys.stderr.write("Error: Your passwords didn't match.\n") self.stderr.write("Error: Your passwords didn't match.")
password = None password = None
continue continue
if password.strip() == '': if password.strip() == '':
sys.stderr.write("Error: Blank passwords aren't allowed.\n") self.stderr.write("Error: Blank passwords aren't allowed.")
password = None password = None
continue continue
break break
except KeyboardInterrupt: except KeyboardInterrupt:
sys.stderr.write("\nOperation cancelled.\n") self.stderr.write("\nOperation cancelled.")
sys.exit(1) sys.exit(1)
User.objects.db_manager(database).create_superuser(username, email, password) User.objects.db_manager(database).create_superuser(username, email, password)
if verbosity >= 1: if verbosity >= 1:
self.stdout.write("Superuser created successfully.\n") self.stdout.write("Superuser created successfully.")

View File

@ -2,6 +2,7 @@ from StringIO import StringIO
from django.contrib.auth import models, management from django.contrib.auth import models, management
from django.contrib.auth.management.commands import changepassword from django.contrib.auth.management.commands import changepassword
from django.core.management.base import CommandError
from django.test import TestCase from django.test import TestCase
@ -56,16 +57,10 @@ class ChangepasswordManagementCommandTestCase(TestCase):
def test_that_max_tries_exits_1(self): def test_that_max_tries_exits_1(self):
""" """
A CommandError should be thrown by handle() if the user enters in A CommandError should be thrown by handle() if the user enters in
mismatched passwords three times. This should be caught by execute() and mismatched passwords three times.
converted to a SystemExit
""" """
command = changepassword.Command() command = changepassword.Command()
command._get_pass = lambda *args: args or 'foo' command._get_pass = lambda *args: args or 'foo'
self.assertRaises( with self.assertRaises(CommandError):
SystemExit, command.execute("joe", stdout=self.stdout, stderr=self.stderr)
command.execute,
"joe",
stdout=self.stdout,
stderr=self.stderr
)

View File

@ -0,0 +1,11 @@
[
{
"pk": 1,
"model": "sites.site",
"fields": {
"domain": "example.com",
"name": "example.com"
}
}
]

View File

@ -18,9 +18,10 @@ from django.test.utils import override_settings
TEMPLATE_DIRS=( TEMPLATE_DIRS=(
os.path.join(os.path.dirname(__file__), 'templates'), os.path.join(os.path.dirname(__file__), 'templates'),
), ),
SITE_ID=1,
) )
class FlatpageCSRFTests(TestCase): class FlatpageCSRFTests(TestCase):
fixtures = ['sample_flatpages'] fixtures = ['sample_flatpages', 'example_site']
urls = 'django.contrib.flatpages.tests.urls' urls = 'django.contrib.flatpages.tests.urls'
def setUp(self): def setUp(self):

View File

@ -5,7 +5,10 @@ from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils import translation from django.utils import translation
@override_settings(SITE_ID=1)
class FlatpageAdminFormTests(TestCase): class FlatpageAdminFormTests(TestCase):
fixtures = ['example_site']
def setUp(self): def setUp(self):
self.form_data = { self.form_data = {
'title': "A test page", 'title': "A test page",
@ -89,5 +92,5 @@ class FlatpageAdminFormTests(TestCase):
self.assertEqual( self.assertEqual(
f.errors, f.errors,
{'sites': [u'This field is required.']}) {'sites': [translation.ugettext(u'This field is required.')]})

View File

@ -19,9 +19,10 @@ from django.test.utils import override_settings
TEMPLATE_DIRS=( TEMPLATE_DIRS=(
os.path.join(os.path.dirname(__file__), 'templates'), os.path.join(os.path.dirname(__file__), 'templates'),
), ),
SITE_ID=1,
) )
class FlatpageMiddlewareTests(TestCase): class FlatpageMiddlewareTests(TestCase):
fixtures = ['sample_flatpages'] fixtures = ['sample_flatpages', 'example_site']
urls = 'django.contrib.flatpages.tests.urls' urls = 'django.contrib.flatpages.tests.urls'
def test_view_flatpage(self): def test_view_flatpage(self):
@ -75,7 +76,7 @@ class FlatpageMiddlewareTests(TestCase):
enable_comments=False, enable_comments=False,
registration_required=False, registration_required=False,
) )
fp.sites.add(1) fp.sites.add(settings.SITE_ID)
response = self.client.get('/some.very_special~chars-here/') response = self.client.get('/some.very_special~chars-here/')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -96,9 +97,10 @@ class FlatpageMiddlewareTests(TestCase):
TEMPLATE_DIRS=( TEMPLATE_DIRS=(
os.path.join(os.path.dirname(__file__), 'templates'), os.path.join(os.path.dirname(__file__), 'templates'),
), ),
SITE_ID=1,
) )
class FlatpageMiddlewareAppendSlashTests(TestCase): class FlatpageMiddlewareAppendSlashTests(TestCase):
fixtures = ['sample_flatpages'] fixtures = ['sample_flatpages', 'example_site']
urls = 'django.contrib.flatpages.tests.urls' urls = 'django.contrib.flatpages.tests.urls'
def test_redirect_view_flatpage(self): def test_redirect_view_flatpage(self):
@ -130,7 +132,7 @@ class FlatpageMiddlewareAppendSlashTests(TestCase):
enable_comments=False, enable_comments=False,
registration_required=False, registration_required=False,
) )
fp.sites.add(1) fp.sites.add(settings.SITE_ID)
response = self.client.get('/some.very_special~chars-here') response = self.client.get('/some.very_special~chars-here')
self.assertRedirects(response, '/some.very_special~chars-here/', status_code=301) self.assertRedirects(response, '/some.very_special~chars-here/', status_code=301)
@ -144,7 +146,7 @@ class FlatpageMiddlewareAppendSlashTests(TestCase):
enable_comments=False, enable_comments=False,
registration_required=False, registration_required=False,
) )
fp.sites.add(1) fp.sites.add(settings.SITE_ID)
response = self.client.get('/') response = self.client.get('/')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)

View File

@ -18,6 +18,7 @@ from django.test.utils import override_settings
TEMPLATE_DIRS=( TEMPLATE_DIRS=(
os.path.join(os.path.dirname(__file__), 'templates'), os.path.join(os.path.dirname(__file__), 'templates'),
), ),
SITE_ID=1,
) )
class FlatpageTemplateTagTests(TestCase): class FlatpageTemplateTagTests(TestCase):
fixtures = ['sample_flatpages'] fixtures = ['sample_flatpages']

View File

@ -19,9 +19,10 @@ from django.test.utils import override_settings
TEMPLATE_DIRS=( TEMPLATE_DIRS=(
os.path.join(os.path.dirname(__file__), 'templates'), os.path.join(os.path.dirname(__file__), 'templates'),
), ),
SITE_ID=1,
) )
class FlatpageViewTests(TestCase): class FlatpageViewTests(TestCase):
fixtures = ['sample_flatpages'] fixtures = ['sample_flatpages', 'example_site']
urls = 'django.contrib.flatpages.tests.urls' urls = 'django.contrib.flatpages.tests.urls'
def test_view_flatpage(self): def test_view_flatpage(self):
@ -85,9 +86,10 @@ class FlatpageViewTests(TestCase):
TEMPLATE_DIRS=( TEMPLATE_DIRS=(
os.path.join(os.path.dirname(__file__), 'templates'), os.path.join(os.path.dirname(__file__), 'templates'),
), ),
SITE_ID=1,
) )
class FlatpageViewAppendSlashTests(TestCase): class FlatpageViewAppendSlashTests(TestCase):
fixtures = ['sample_flatpages'] fixtures = ['sample_flatpages', 'example_site']
urls = 'django.contrib.flatpages.tests.urls' urls = 'django.contrib.flatpages.tests.urls'
def test_redirect_view_flatpage(self): def test_redirect_view_flatpage(self):
@ -119,7 +121,7 @@ class FlatpageViewAppendSlashTests(TestCase):
enable_comments=False, enable_comments=False,
registration_required=False, registration_required=False,
) )
fp.sites.add(1) fp.sites.add(settings.SITE_ID)
response = self.client.get('/flatpage_root/some.very_special~chars-here') response = self.client.get('/flatpage_root/some.very_special~chars-here')
self.assertRedirects(response, '/flatpage_root/some.very_special~chars-here/', status_code=301) self.assertRedirects(response, '/flatpage_root/some.very_special~chars-here/', status_code=301)

View File

@ -156,9 +156,6 @@ class PreviewTests(TestCase):
class FormHmacTests(unittest.TestCase): class FormHmacTests(unittest.TestCase):
"""
Same as SecurityHashTests, but with form_hmac
"""
def test_textfield_hash(self): def test_textfield_hash(self):
""" """
@ -166,8 +163,8 @@ class FormHmacTests(unittest.TestCase):
leading/trailing whitespace so as to be friendly to broken browsers that leading/trailing whitespace so as to be friendly to broken browsers that
submit it (usually in textareas). submit it (usually in textareas).
""" """
f1 = HashTestForm({'name': 'joe', 'bio': 'Nothing notable.'}) f1 = HashTestForm({'name': u'joe', 'bio': u'Nothing notable.'})
f2 = HashTestForm({'name': ' joe', 'bio': 'Nothing notable. '}) f2 = HashTestForm({'name': u' joe', 'bio': u'Nothing notable. '})
hash1 = utils.form_hmac(f1) hash1 = utils.form_hmac(f1)
hash2 = utils.form_hmac(f2) hash2 = utils.form_hmac(f2)
self.assertEqual(hash1, hash2) self.assertEqual(hash1, hash2)
@ -269,10 +266,10 @@ class WizardTests(TestCase):
Form should advance if the hash is present and good, as calculated using Form should advance if the hash is present and good, as calculated using
current method. current method.
""" """
data = {"0-field": "test", data = {"0-field": u"test",
"1-field": "test2", "1-field": u"test2",
"hash_0": "7e9cea465f6a10a6fb47fcea65cb9a76350c9a5c", "hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
"wizard_step": "1"} "wizard_step": u"1"}
response = self.client.post('/wizard1/', data) response = self.client.post('/wizard1/', data)
self.assertEqual(2, response.context['step0']) self.assertEqual(2, response.context['step0'])
@ -294,18 +291,18 @@ class WizardTests(TestCase):
reached[0] = True reached[0] = True
wizard = WizardWithProcessStep([WizardPageOneForm]) wizard = WizardWithProcessStep([WizardPageOneForm])
data = {"0-field": "test", data = {"0-field": u"test",
"1-field": "test2", "1-field": u"test2",
"hash_0": "7e9cea465f6a10a6fb47fcea65cb9a76350c9a5c", "hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
"wizard_step": "1"} "wizard_step": u"1"}
wizard(DummyRequest(POST=data)) wizard(DummyRequest(POST=data))
self.assertTrue(reached[0]) self.assertTrue(reached[0])
data = {"0-field": "test", data = {"0-field": u"test",
"1-field": "test2", "1-field": u"test2",
"hash_0": "7e9cea465f6a10a6fb47fcea65cb9a76350c9a5c", "hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
"hash_1": "d5b434e3934cc92fee4bd2964c4ebc06f81d362d", "hash_1": u"1e6f6315da42e62f33a30640ec7e007ad3fbf1a1",
"wizard_step": "2"} "wizard_step": u"2"}
self.assertRaises(http.Http404, wizard, DummyRequest(POST=data)) self.assertRaises(http.Http404, wizard, DummyRequest(POST=data))
def test_14498(self): def test_14498(self):
@ -324,10 +321,10 @@ class WizardTests(TestCase):
wizard = WizardWithProcessStep([WizardPageOneForm, wizard = WizardWithProcessStep([WizardPageOneForm,
WizardPageTwoForm, WizardPageTwoForm,
WizardPageThreeForm]) WizardPageThreeForm])
data = {"0-field": "test", data = {"0-field": u"test",
"1-field": "test2", "1-field": u"test2",
"hash_0": "7e9cea465f6a10a6fb47fcea65cb9a76350c9a5c", "hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
"wizard_step": "1"} "wizard_step": u"1"}
wizard(DummyRequest(POST=data)) wizard(DummyRequest(POST=data))
self.assertTrue(reached[0]) self.assertTrue(reached[0])
@ -348,10 +345,10 @@ class WizardTests(TestCase):
wizard = Wizard([WizardPageOneForm, wizard = Wizard([WizardPageOneForm,
WizardPageTwoForm]) WizardPageTwoForm])
data = {"0-field": "test", data = {"0-field": u"test",
"1-field": "test2", "1-field": u"test2",
"hash_0": "7e9cea465f6a10a6fb47fcea65cb9a76350c9a5c", "hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
"wizard_step": "1"} "wizard_step": u"1"}
wizard(DummyRequest(POST=data)) wizard(DummyRequest(POST=data))
self.assertTrue(reached[0]) self.assertTrue(reached[0])
@ -374,10 +371,10 @@ class WizardTests(TestCase):
wizard = WizardWithProcessStep([WizardPageOneForm, wizard = WizardWithProcessStep([WizardPageOneForm,
WizardPageTwoForm, WizardPageTwoForm,
WizardPageThreeForm]) WizardPageThreeForm])
data = {"0-field": "test", data = {"0-field": u"test",
"1-field": "test2", "1-field": u"test2",
"hash_0": "7e9cea465f6a10a6fb47fcea65cb9a76350c9a5c", "hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
"wizard_step": "1"} "wizard_step": u"1"}
wizard(DummyRequest(POST=data)) wizard(DummyRequest(POST=data))
self.assertTrue(reached[0]) self.assertTrue(reached[0])

View File

@ -120,7 +120,7 @@ class NamedWizardTests(object):
self.assertEqual(response.context['wizard']['steps'].current, 'form2') self.assertEqual(response.context['wizard']['steps'].current, 'form2')
post_data = self.wizard_step_data[1] post_data = self.wizard_step_data[1]
post_data['form2-file1'] = open(__file__) post_data['form2-file1'] = open(__file__, 'rb')
response = self.client.post( response = self.client.post(
reverse(self.wizard_urlname, reverse(self.wizard_urlname,
kwargs={'step': response.context['wizard']['steps'].current}), kwargs={'step': response.context['wizard']['steps'].current}),
@ -147,7 +147,7 @@ class NamedWizardTests(object):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
all_data = response.context['form_list'] all_data = response.context['form_list']
self.assertEqual(all_data[1]['file1'].read(), open(__file__).read()) self.assertEqual(all_data[1]['file1'].read(), open(__file__, 'rb').read())
del all_data[1]['file1'] del all_data[1]['file1']
self.assertEqual(all_data, [ self.assertEqual(all_data, [
{'name': u'Pony', 'thirsty': True, 'user': self.testuser}, {'name': u'Pony', 'thirsty': True, 'user': self.testuser},
@ -168,7 +168,7 @@ class NamedWizardTests(object):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
post_data = self.wizard_step_data[1] post_data = self.wizard_step_data[1]
post_data['form2-file1'] = open(__file__) post_data['form2-file1'] = open(__file__, 'rb')
response = self.client.post( response = self.client.post(
reverse(self.wizard_urlname, reverse(self.wizard_urlname,
kwargs={'step': response.context['wizard']['steps'].current}), kwargs={'step': response.context['wizard']['steps'].current}),
@ -180,7 +180,9 @@ class NamedWizardTests(object):
response = self.client.get(step2_url) response = self.client.get(step2_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['wizard']['steps'].current, 'form2') self.assertEqual(response.context['wizard']['steps'].current, 'form2')
self.assertEqual(response.context['wizard']['form'].files['form2-file1'].read(), open(__file__).read()) self.assertEqual(
response.context['wizard']['form'].files['form2-file1'].read(),
open(__file__, 'rb').read())
response = self.client.post( response = self.client.post(
reverse(self.wizard_urlname, reverse(self.wizard_urlname,
@ -197,7 +199,7 @@ class NamedWizardTests(object):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
all_data = response.context['all_cleaned_data'] all_data = response.context['all_cleaned_data']
self.assertEqual(all_data['file1'].read(), open(__file__).read()) self.assertEqual(all_data['file1'].read(), open(__file__, 'rb').read())
del all_data['file1'] del all_data['file1']
self.assertEqual( self.assertEqual(
all_data, all_data,
@ -221,7 +223,7 @@ class NamedWizardTests(object):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
post_data = self.wizard_step_data[1] post_data = self.wizard_step_data[1]
post_data['form2-file1'] = open(__file__) post_data['form2-file1'] = open(__file__, 'rb')
response = self.client.post( response = self.client.post(
reverse(self.wizard_urlname, reverse(self.wizard_urlname,
kwargs={'step': response.context['wizard']['steps'].current}), kwargs={'step': response.context['wizard']['steps'].current}),

View File

@ -80,7 +80,7 @@ class WizardTests(object):
self.assertEqual(response.context['wizard']['steps'].current, 'form2') self.assertEqual(response.context['wizard']['steps'].current, 'form2')
post_data = self.wizard_step_data[1] post_data = self.wizard_step_data[1]
post_data['form2-file1'] = open(__file__) post_data['form2-file1'] = open(__file__, 'rb')
response = self.client.post(self.wizard_url, post_data) response = self.client.post(self.wizard_url, post_data)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['wizard']['steps'].current, 'form3') self.assertEqual(response.context['wizard']['steps'].current, 'form3')
@ -93,7 +93,7 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
all_data = response.context['form_list'] all_data = response.context['form_list']
self.assertEqual(all_data[1]['file1'].read(), open(__file__).read()) self.assertEqual(all_data[1]['file1'].read(), open(__file__, 'rb').read())
del all_data[1]['file1'] del all_data[1]['file1']
self.assertEqual(all_data, [ self.assertEqual(all_data, [
{'name': u'Pony', 'thirsty': True, 'user': self.testuser}, {'name': u'Pony', 'thirsty': True, 'user': self.testuser},
@ -110,7 +110,7 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
post_data = self.wizard_step_data[1] post_data = self.wizard_step_data[1]
post_data['form2-file1'] = open(__file__) post_data['form2-file1'] = open(__file__, 'rb')
response = self.client.post(self.wizard_url, post_data) response = self.client.post(self.wizard_url, post_data)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -121,7 +121,7 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
all_data = response.context['all_cleaned_data'] all_data = response.context['all_cleaned_data']
self.assertEqual(all_data['file1'].read(), open(__file__).read()) self.assertEqual(all_data['file1'].read(), open(__file__, 'rb').read())
del all_data['file1'] del all_data['file1']
self.assertEqual(all_data, { self.assertEqual(all_data, {
'name': u'Pony', 'thirsty': True, 'user': self.testuser, 'name': u'Pony', 'thirsty': True, 'user': self.testuser,
@ -138,7 +138,7 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
post_data = self.wizard_step_data[1] post_data = self.wizard_step_data[1]
post_data['form2-file1'] = open(__file__) post_data['form2-file1'] = open(__file__, 'rb')
response = self.client.post(self.wizard_url, post_data) response = self.client.post(self.wizard_url, post_data)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -165,7 +165,7 @@ class WizardTests(object):
self.assertEqual(response.context['wizard']['steps'].current, 'form2') self.assertEqual(response.context['wizard']['steps'].current, 'form2')
post_data = self.wizard_step_data[1] post_data = self.wizard_step_data[1]
post_data['form2-file1'] = open(__file__) post_data['form2-file1'] = open(__file__, 'rb')
response = self.client.post(self.wizard_url, post_data) response = self.client.post(self.wizard_url, post_data)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['wizard']['steps'].current, 'form3') self.assertEqual(response.context['wizard']['steps'].current, 'form3')

View File

@ -1,7 +1,5 @@
try: # Do not try cPickle here (see #18340)
import cPickle as pickle import pickle
except ImportError:
import pickle
from django.utils.crypto import salted_hmac from django.utils.crypto import salted_hmac

View File

@ -36,6 +36,7 @@ class GeoModelAdmin(ModelAdmin):
wms_url = 'http://vmap0.tiles.osgeo.org/wms/vmap0' wms_url = 'http://vmap0.tiles.osgeo.org/wms/vmap0'
wms_layer = 'basic' wms_layer = 'basic'
wms_name = 'OpenLayers WMS' wms_name = 'OpenLayers WMS'
wms_options = {'format': 'image/jpeg'}
debug = False debug = False
widget = OpenLayersWidget widget = OpenLayersWidget
@ -76,6 +77,12 @@ class GeoModelAdmin(ModelAdmin):
class OLMap(self.widget): class OLMap(self.widget):
template = self.map_template template = self.map_template
geom_type = db_field.geom_type geom_type = db_field.geom_type
wms_options = ''
if self.wms_options:
wms_options = ["%s: '%s'" % pair for pair in self.wms_options.items()]
wms_options = ', %s' % ', '.join(wms_options)
params = {'default_lon' : self.default_lon, params = {'default_lon' : self.default_lon,
'default_lat' : self.default_lat, 'default_lat' : self.default_lat,
'default_zoom' : self.default_zoom, 'default_zoom' : self.default_zoom,
@ -106,6 +113,7 @@ class GeoModelAdmin(ModelAdmin):
'wms_url' : self.wms_url, 'wms_url' : self.wms_url,
'wms_layer' : self.wms_layer, 'wms_layer' : self.wms_layer,
'wms_name' : self.wms_name, 'wms_name' : self.wms_name,
'wms_options' : wms_options,
'debug' : self.debug, 'debug' : self.debug,
} }
return OLMap return OLMap

View File

@ -1,5 +1,5 @@
from ctypes import c_void_p from ctypes import c_void_p
from types import NoneType
from django.contrib.gis.gdal.error import GDALException from django.contrib.gis.gdal.error import GDALException
class GDALBase(object): class GDALBase(object):
@ -26,7 +26,7 @@ class GDALBase(object):
# compatible type or None (NULL). # compatible type or None (NULL).
if isinstance(ptr, (int, long)): if isinstance(ptr, (int, long)):
self._ptr = self.ptr_type(ptr) self._ptr = self.ptr_type(ptr)
elif isinstance(ptr, (self.ptr_type, NoneType)): elif ptr is None or isinstance(ptr, self.ptr_type):
self._ptr = ptr self._ptr = ptr
else: else:
raise TypeError('Incompatible pointer type') raise TypeError('Incompatible pointer type')

View File

@ -2,10 +2,12 @@
Module for executing all of the GDAL tests. None Module for executing all of the GDAL tests. None
of these tests require the use of the database. of these tests require the use of the database.
""" """
from __future__ import absolute_import
from django.utils.unittest import TestSuite, TextTestRunner from django.utils.unittest import TestSuite, TextTestRunner
# Importing the GDAL test modules. # Importing the GDAL test modules.
import test_driver, test_ds, test_envelope, test_geom, test_srs from . import test_driver, test_ds, test_envelope, test_geom, test_srs
test_suites = [test_driver.suite(), test_suites = [test_driver.suite(),
test_ds.suite(), test_ds.suite(),

View File

@ -11,8 +11,10 @@
Grab GeoIP.dat.gz and GeoLiteCity.dat.gz, and unzip them in the directory Grab GeoIP.dat.gz and GeoLiteCity.dat.gz, and unzip them in the directory
corresponding to settings.GEOIP_PATH. corresponding to settings.GEOIP_PATH.
""" """
from __future__ import absolute_import
try: try:
from django.contrib.gis.geoip.base import GeoIP, GeoIPException from .base import GeoIP, GeoIPException
HAS_GEOIP = True HAS_GEOIP = True
except: except:
HAS_GEOIP = False HAS_GEOIP = False

View File

@ -1,5 +1,5 @@
from ctypes import c_void_p from ctypes import c_void_p
from types import NoneType
from django.contrib.gis.geos.error import GEOSException from django.contrib.gis.geos.error import GEOSException
# Trying to import GDAL libraries, if available. Have to place in # Trying to import GDAL libraries, if available. Have to place in
@ -41,7 +41,7 @@ class GEOSBase(object):
def _set_ptr(self, ptr): def _set_ptr(self, ptr):
# Only allow the pointer to be set with pointers of the # Only allow the pointer to be set with pointers of the
# compatible type or None (NULL). # compatible type or None (NULL).
if isinstance(ptr, (self.ptr_type, NoneType)): if ptr is None or isinstance(ptr, self.ptr_type):
self._ptr = ptr self._ptr = ptr
else: else:
raise TypeError('Incompatible pointer type') raise TypeError('Incompatible pointer type')

View File

@ -8,6 +8,9 @@ See also http://www.aryehleib.com/MutableLists.html
Author: Aryeh Leib Taurog. Author: Aryeh Leib Taurog.
""" """
from django.utils.functional import total_ordering
@total_ordering
class ListMixin(object): class ListMixin(object):
""" """
A base class which provides complete list interface. A base class which provides complete list interface.
@ -143,20 +146,28 @@ class ListMixin(object):
self.extend(cache) self.extend(cache)
return self return self
def __cmp__(self, other): def __eq__(self, other):
'cmp' for i in range(len(self)):
try:
c = self[i] == other[i]
except IndexError:
# must be other is shorter
return False
if not c:
return False
return True
def __lt__(self, other):
slen = len(self) slen = len(self)
for i in range(slen): for i in range(slen):
try: try:
c = cmp(self[i], other[i]) c = self[i] < other[i]
except IndexError: except IndexError:
# must be other is shorter # must be other is shorter
return 1 return False
else: if c:
# elements not equal return c
if c: return c return slen < len(other)
return cmp(slen, len(other))
### Public list interface Methods ### ### Public list interface Methods ###
## Non-mutating ## ## Non-mutating ##

View File

@ -1,8 +1,10 @@
""" """
GEOS Testing module. GEOS Testing module.
""" """
from __future__ import absolute_import
from django.utils.unittest import TestSuite, TextTestRunner from django.utils.unittest import TestSuite, TextTestRunner
import test_geos, test_io, test_geos_mutation, test_mutable_list from . import test_geos, test_io, test_geos_mutation, test_mutable_list
test_suites = [ test_suites = [
test_geos.suite(), test_geos.suite(),

View File

@ -1,18 +1,23 @@
import ctypes import ctypes
import random import random
import unittest
from django.contrib.gis.geos import * from django.contrib.gis.geos import (GEOSException, GEOSIndexError, GEOSGeometry,
GeometryCollection, Point, MultiPoint, Polygon, MultiPolygon, LinearRing,
LineString, MultiLineString, fromfile, fromstr, geos_version_info)
from django.contrib.gis.geos.base import gdal, numpy, GEOSBase from django.contrib.gis.geos.base import gdal, numpy, GEOSBase
from django.contrib.gis.geos.libgeos import GEOS_PREPARE from django.contrib.gis.geos.libgeos import GEOS_PREPARE
from django.contrib.gis.geometry.test_data import TestDataMixin from django.contrib.gis.geometry.test_data import TestDataMixin
from django.utils import unittest
class GEOSTest(unittest.TestCase, TestDataMixin): class GEOSTest(unittest.TestCase, TestDataMixin):
@property @property
def null_srid(self): def null_srid(self):
""" """
Returns the proper null SRID depending on the GEOS version. Returns the proper null SRID depending on the GEOS version.
See the comments in `test15_srid` for more details. See the comments in `test_srid` for more details.
""" """
info = geos_version_info() info = geos_version_info()
if info['version'] == '3.0.0' and info['release_candidate']: if info['version'] == '3.0.0' and info['release_candidate']:
@ -20,7 +25,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
else: else:
return None return None
def test00_base(self): def test_base(self):
"Tests out the GEOSBase class." "Tests out the GEOSBase class."
# Testing out GEOSBase class, which provides a `ptr` property # Testing out GEOSBase class, which provides a `ptr` property
# that abstracts out access to underlying C pointers. # that abstracts out access to underlying C pointers.
@ -62,19 +67,19 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
self.assertRaises(TypeError, fg1._set_ptr, bad_ptr) self.assertRaises(TypeError, fg1._set_ptr, bad_ptr)
self.assertRaises(TypeError, fg2._set_ptr, bad_ptr) self.assertRaises(TypeError, fg2._set_ptr, bad_ptr)
def test01a_wkt(self): def test_wkt(self):
"Testing WKT output." "Testing WKT output."
for g in self.geometries.wkt_out: for g in self.geometries.wkt_out:
geom = fromstr(g.wkt) geom = fromstr(g.wkt)
self.assertEqual(g.ewkt, geom.wkt) self.assertEqual(g.ewkt, geom.wkt)
def test01b_hex(self): def test_hex(self):
"Testing HEX output." "Testing HEX output."
for g in self.geometries.hex_wkt: for g in self.geometries.hex_wkt:
geom = fromstr(g.wkt) geom = fromstr(g.wkt)
self.assertEqual(g.hex, geom.hex) self.assertEqual(g.hex, geom.hex)
def test01b_hexewkb(self): def test_hexewkb(self):
"Testing (HEX)EWKB output." "Testing (HEX)EWKB output."
from binascii import a2b_hex from binascii import a2b_hex
@ -124,14 +129,14 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
# Redundant sanity check. # Redundant sanity check.
self.assertEqual(4326, GEOSGeometry(hexewkb_2d).srid) self.assertEqual(4326, GEOSGeometry(hexewkb_2d).srid)
def test01c_kml(self): def test_kml(self):
"Testing KML output." "Testing KML output."
for tg in self.geometries.wkt_out: for tg in self.geometries.wkt_out:
geom = fromstr(tg.wkt) geom = fromstr(tg.wkt)
kml = getattr(tg, 'kml', False) kml = getattr(tg, 'kml', False)
if kml: self.assertEqual(kml, geom.kml) if kml: self.assertEqual(kml, geom.kml)
def test01d_errors(self): def test_errors(self):
"Testing the Error handlers." "Testing the Error handlers."
# string-based # string-based
print("\nBEGIN - expecting GEOS_ERROR; safe to ignore.\n") print("\nBEGIN - expecting GEOS_ERROR; safe to ignore.\n")
@ -154,7 +159,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
# None # None
self.assertRaises(TypeError, GEOSGeometry, None) self.assertRaises(TypeError, GEOSGeometry, None)
def test01e_wkb(self): def test_wkb(self):
"Testing WKB output." "Testing WKB output."
from binascii import b2a_hex from binascii import b2a_hex
for g in self.geometries.hex_wkt: for g in self.geometries.hex_wkt:
@ -162,7 +167,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
wkb = geom.wkb wkb = geom.wkb
self.assertEqual(b2a_hex(wkb).upper(), g.hex) self.assertEqual(b2a_hex(wkb).upper(), g.hex)
def test01f_create_hex(self): def test_create_hex(self):
"Testing creation from HEX." "Testing creation from HEX."
for g in self.geometries.hex_wkt: for g in self.geometries.hex_wkt:
geom_h = GEOSGeometry(g.hex) geom_h = GEOSGeometry(g.hex)
@ -170,7 +175,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
geom_t = fromstr(g.wkt) geom_t = fromstr(g.wkt)
self.assertEqual(geom_t.wkt, geom_h.wkt) self.assertEqual(geom_t.wkt, geom_h.wkt)
def test01g_create_wkb(self): def test_create_wkb(self):
"Testing creation from WKB." "Testing creation from WKB."
from binascii import a2b_hex from binascii import a2b_hex
for g in self.geometries.hex_wkt: for g in self.geometries.hex_wkt:
@ -180,7 +185,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
geom_t = fromstr(g.wkt) geom_t = fromstr(g.wkt)
self.assertEqual(geom_t.wkt, geom_h.wkt) self.assertEqual(geom_t.wkt, geom_h.wkt)
def test01h_ewkt(self): def test_ewkt(self):
"Testing EWKT." "Testing EWKT."
srids = (-1, 32140) srids = (-1, 32140)
for srid in srids: for srid in srids:
@ -191,9 +196,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
self.assertEqual(srid, poly.shell.srid) self.assertEqual(srid, poly.shell.srid)
self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export
def test01i_json(self): @unittest.skipUnless(gdal.HAS_GDAL and gdal.GEOJSON, "gdal >= 1.5 is required")
def test_json(self):
"Testing GeoJSON input/output (via GDAL)." "Testing GeoJSON input/output (via GDAL)."
if not gdal or not gdal.GEOJSON: return
for g in self.geometries.json_geoms: for g in self.geometries.json_geoms:
geom = GEOSGeometry(g.wkt) geom = GEOSGeometry(g.wkt)
if not hasattr(g, 'not_equal'): if not hasattr(g, 'not_equal'):
@ -201,7 +206,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
self.assertEqual(g.json, geom.geojson) self.assertEqual(g.json, geom.geojson)
self.assertEqual(GEOSGeometry(g.wkt), GEOSGeometry(geom.json)) self.assertEqual(GEOSGeometry(g.wkt), GEOSGeometry(geom.json))
def test01k_fromfile(self): def test_fromfile(self):
"Testing the fromfile() factory." "Testing the fromfile() factory."
from io import BytesIO from io import BytesIO
ref_pnt = GEOSGeometry('POINT(5 23)') ref_pnt = GEOSGeometry('POINT(5 23)')
@ -218,7 +223,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
pnt = fromfile(fh) pnt = fromfile(fh)
self.assertEqual(ref_pnt, pnt) self.assertEqual(ref_pnt, pnt)
def test01k_eq(self): def test_eq(self):
"Testing equivalence." "Testing equivalence."
p = fromstr('POINT(5 23)') p = fromstr('POINT(5 23)')
self.assertEqual(p, p.wkt) self.assertEqual(p, p.wkt)
@ -233,7 +238,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
self.assertNotEqual(g, {'foo' : 'bar'}) self.assertNotEqual(g, {'foo' : 'bar'})
self.assertNotEqual(g, False) self.assertNotEqual(g, False)
def test02a_points(self): def test_points(self):
"Testing Point objects." "Testing Point objects."
prev = fromstr('POINT(0 0)') prev = fromstr('POINT(0 0)')
for p in self.geometries.points: for p in self.geometries.points:
@ -288,7 +293,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
prev = pnt # setting the previous geometry prev = pnt # setting the previous geometry
def test02b_multipoints(self): def test_multipoints(self):
"Testing MultiPoint objects." "Testing MultiPoint objects."
for mp in self.geometries.multipoints: for mp in self.geometries.multipoints:
mpnt = fromstr(mp.wkt) mpnt = fromstr(mp.wkt)
@ -307,7 +312,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
self.assertEqual(p.empty, False) self.assertEqual(p.empty, False)
self.assertEqual(p.valid, True) self.assertEqual(p.valid, True)
def test03a_linestring(self): def test_linestring(self):
"Testing LineString objects." "Testing LineString objects."
prev = fromstr('POINT(0 0)') prev = fromstr('POINT(0 0)')
for l in self.geometries.linestrings: for l in self.geometries.linestrings:
@ -333,7 +338,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
self.assertEqual(ls.wkt, LineString(*tuple(Point(tup) for tup in ls.tuple)).wkt) # Point individual arguments self.assertEqual(ls.wkt, LineString(*tuple(Point(tup) for tup in ls.tuple)).wkt) # Point individual arguments
if numpy: self.assertEqual(ls, LineString(numpy.array(ls.tuple))) # as numpy array if numpy: self.assertEqual(ls, LineString(numpy.array(ls.tuple))) # as numpy array
def test03b_multilinestring(self): def test_multilinestring(self):
"Testing MultiLineString objects." "Testing MultiLineString objects."
prev = fromstr('POINT(0 0)') prev = fromstr('POINT(0 0)')
for l in self.geometries.multilinestrings: for l in self.geometries.multilinestrings:
@ -357,7 +362,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
self.assertEqual(ml.wkt, MultiLineString(*tuple(s.clone() for s in ml)).wkt) self.assertEqual(ml.wkt, MultiLineString(*tuple(s.clone() for s in ml)).wkt)
self.assertEqual(ml, MultiLineString(*tuple(LineString(s.tuple) for s in ml))) self.assertEqual(ml, MultiLineString(*tuple(LineString(s.tuple) for s in ml)))
def test04_linearring(self): def test_linearring(self):
"Testing LinearRing objects." "Testing LinearRing objects."
for rr in self.geometries.linearrings: for rr in self.geometries.linearrings:
lr = fromstr(rr.wkt) lr = fromstr(rr.wkt)
@ -373,14 +378,15 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
self.assertEqual(lr, LinearRing([list(tup) for tup in lr.tuple])) self.assertEqual(lr, LinearRing([list(tup) for tup in lr.tuple]))
if numpy: self.assertEqual(lr, LinearRing(numpy.array(lr.tuple))) if numpy: self.assertEqual(lr, LinearRing(numpy.array(lr.tuple)))
def test05a_polygons(self): def test_polygons_from_bbox(self):
"Testing Polygon objects." "Testing `from_bbox` class method."
# Testing `from_bbox` class method
bbox = (-180, -90, 180, 90) bbox = (-180, -90, 180, 90)
p = Polygon.from_bbox( bbox ) p = Polygon.from_bbox(bbox)
self.assertEqual(bbox, p.extent) self.assertEqual(bbox, p.extent)
def test_polygons(self):
"Testing Polygon objects."
prev = fromstr('POINT(0 0)') prev = fromstr('POINT(0 0)')
for p in self.geometries.polygons: for p in self.geometries.polygons:
# Creating the Polygon, testing its properties. # Creating the Polygon, testing its properties.
@ -437,7 +443,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
self.assertEqual(poly.wkt, Polygon(*tuple(r for r in poly)).wkt) self.assertEqual(poly.wkt, Polygon(*tuple(r for r in poly)).wkt)
self.assertEqual(poly.wkt, Polygon(*tuple(LinearRing(r.tuple) for r in poly)).wkt) self.assertEqual(poly.wkt, Polygon(*tuple(LinearRing(r.tuple) for r in poly)).wkt)
def test05b_multipolygons(self): def test_multipolygons(self):
"Testing MultiPolygon objects." "Testing MultiPolygon objects."
print("\nBEGIN - expecting GEOS_NOTICE; safe to ignore.\n") print("\nBEGIN - expecting GEOS_NOTICE; safe to ignore.\n")
prev = fromstr('POINT (0 0)') prev = fromstr('POINT (0 0)')
@ -460,7 +466,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
print("\nEND - expecting GEOS_NOTICE; safe to ignore.\n") print("\nEND - expecting GEOS_NOTICE; safe to ignore.\n")
def test06a_memory_hijinks(self): def test_memory_hijinks(self):
"Testing Geometry __del__() on rings and polygons." "Testing Geometry __del__() on rings and polygons."
#### Memory issues with rings and polygons #### Memory issues with rings and polygons
@ -483,7 +489,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
# Access to these rings is OK since they are clones. # Access to these rings is OK since they are clones.
s1, s2 = str(ring1), str(ring2) s1, s2 = str(ring1), str(ring2)
def test08_coord_seq(self): def test_coord_seq(self):
"Testing Coordinate Sequence objects." "Testing Coordinate Sequence objects."
for p in self.geometries.polygons: for p in self.geometries.polygons:
if p.ext_ring_cs: if p.ext_ring_cs:
@ -510,7 +516,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
cs[i] = tset cs[i] = tset
self.assertEqual(tset[j], cs[i][j]) self.assertEqual(tset[j], cs[i][j])
def test09_relate_pattern(self): def test_relate_pattern(self):
"Testing relate() and relate_pattern()." "Testing relate() and relate_pattern()."
g = fromstr('POINT (0 0)') g = fromstr('POINT (0 0)')
self.assertRaises(GEOSException, g.relate_pattern, 0, 'invalid pattern, yo') self.assertRaises(GEOSException, g.relate_pattern, 0, 'invalid pattern, yo')
@ -520,7 +526,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
self.assertEqual(rg.result, a.relate_pattern(b, rg.pattern)) self.assertEqual(rg.result, a.relate_pattern(b, rg.pattern))
self.assertEqual(rg.pattern, a.relate(b)) self.assertEqual(rg.pattern, a.relate(b))
def test10_intersection(self): def test_intersection(self):
"Testing intersects() and intersection()." "Testing intersects() and intersection()."
for i in xrange(len(self.geometries.topology_geoms)): for i in xrange(len(self.geometries.topology_geoms)):
a = fromstr(self.geometries.topology_geoms[i].wkt_a) a = fromstr(self.geometries.topology_geoms[i].wkt_a)
@ -533,7 +539,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
a &= b # testing __iand__ a &= b # testing __iand__
self.assertEqual(i1, a) self.assertEqual(i1, a)
def test11_union(self): def test_union(self):
"Testing union()." "Testing union()."
for i in xrange(len(self.geometries.topology_geoms)): for i in xrange(len(self.geometries.topology_geoms)):
a = fromstr(self.geometries.topology_geoms[i].wkt_a) a = fromstr(self.geometries.topology_geoms[i].wkt_a)
@ -545,7 +551,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
a |= b # testing __ior__ a |= b # testing __ior__
self.assertEqual(u1, a) self.assertEqual(u1, a)
def test12_difference(self): def test_difference(self):
"Testing difference()." "Testing difference()."
for i in xrange(len(self.geometries.topology_geoms)): for i in xrange(len(self.geometries.topology_geoms)):
a = fromstr(self.geometries.topology_geoms[i].wkt_a) a = fromstr(self.geometries.topology_geoms[i].wkt_a)
@ -557,7 +563,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
a -= b # testing __isub__ a -= b # testing __isub__
self.assertEqual(d1, a) self.assertEqual(d1, a)
def test13_symdifference(self): def test_symdifference(self):
"Testing sym_difference()." "Testing sym_difference()."
for i in xrange(len(self.geometries.topology_geoms)): for i in xrange(len(self.geometries.topology_geoms)):
a = fromstr(self.geometries.topology_geoms[i].wkt_a) a = fromstr(self.geometries.topology_geoms[i].wkt_a)
@ -569,7 +575,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
a ^= b # testing __ixor__ a ^= b # testing __ixor__
self.assertEqual(d1, a) self.assertEqual(d1, a)
def test14_buffer(self): def test_buffer(self):
"Testing buffer()." "Testing buffer()."
for bg in self.geometries.buffer_geoms: for bg in self.geometries.buffer_geoms:
g = fromstr(bg.wkt) g = fromstr(bg.wkt)
@ -597,7 +603,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
self.assertAlmostEqual(exp_ring[k][0], buf_ring[k][0], 9) self.assertAlmostEqual(exp_ring[k][0], buf_ring[k][0], 9)
self.assertAlmostEqual(exp_ring[k][1], buf_ring[k][1], 9) self.assertAlmostEqual(exp_ring[k][1], buf_ring[k][1], 9)
def test15_srid(self): def test_srid(self):
"Testing the SRID property and keyword." "Testing the SRID property and keyword."
# Testing SRID keyword on Point # Testing SRID keyword on Point
pnt = Point(5, 23, srid=4326) pnt = Point(5, 23, srid=4326)
@ -635,7 +641,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
p3 = fromstr(p1.hex, srid=-1) # -1 is intended. p3 = fromstr(p1.hex, srid=-1) # -1 is intended.
self.assertEqual(-1, p3.srid) self.assertEqual(-1, p3.srid)
def test16_mutable_geometries(self): def test_mutable_geometries(self):
"Testing the mutability of Polygons and Geometry Collections." "Testing the mutability of Polygons and Geometry Collections."
### Testing the mutability of Polygons ### ### Testing the mutability of Polygons ###
for p in self.geometries.polygons: for p in self.geometries.polygons:
@ -699,7 +705,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
#self.assertEqual((3.14, 2.71), mpoly[0].shell[0]) #self.assertEqual((3.14, 2.71), mpoly[0].shell[0])
#del mpoly #del mpoly
def test17_threed(self): def test_threed(self):
"Testing three-dimensional geometries." "Testing three-dimensional geometries."
# Testing a 3D Point # Testing a 3D Point
pnt = Point(2, 3, 8) pnt = Point(2, 3, 8)
@ -715,7 +721,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
ls[0] = (1.,2.,3.) ls[0] = (1.,2.,3.)
self.assertEqual((1.,2.,3.), ls[0]) self.assertEqual((1.,2.,3.), ls[0])
def test18_distance(self): def test_distance(self):
"Testing the distance() function." "Testing the distance() function."
# Distance to self should be 0. # Distance to self should be 0.
pnt = Point(0, 0) pnt = Point(0, 0)
@ -733,7 +739,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
ls2 = LineString((5, 2), (6, 1), (7, 0)) ls2 = LineString((5, 2), (6, 1), (7, 0))
self.assertEqual(3, ls1.distance(ls2)) self.assertEqual(3, ls1.distance(ls2))
def test19_length(self): def test_length(self):
"Testing the length property." "Testing the length property."
# Points have 0 length. # Points have 0 length.
pnt = Point(0, 0) pnt = Point(0, 0)
@ -751,7 +757,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
mpoly = MultiPolygon(poly.clone(), poly) mpoly = MultiPolygon(poly.clone(), poly)
self.assertEqual(8.0, mpoly.length) self.assertEqual(8.0, mpoly.length)
def test20a_emptyCollections(self): def test_emptyCollections(self):
"Testing empty geometries and collections." "Testing empty geometries and collections."
gc1 = GeometryCollection([]) gc1 = GeometryCollection([])
gc2 = fromstr('GEOMETRYCOLLECTION EMPTY') gc2 = fromstr('GEOMETRYCOLLECTION EMPTY')
@ -789,7 +795,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
else: else:
self.assertRaises(GEOSIndexError, g.__getitem__, 0) self.assertRaises(GEOSIndexError, g.__getitem__, 0)
def test20b_collections_of_collections(self): def test_collections_of_collections(self):
"Testing GeometryCollection handling of other collections." "Testing GeometryCollection handling of other collections."
# Creating a GeometryCollection WKT string composed of other # Creating a GeometryCollection WKT string composed of other
# collections and polygons. # collections and polygons.
@ -808,9 +814,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
# And, they should be equal. # And, they should be equal.
self.assertEqual(gc1, gc2) self.assertEqual(gc1, gc2)
def test21_test_gdal(self): @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required")
def test_gdal(self):
"Testing `ogr` and `srs` properties." "Testing `ogr` and `srs` properties."
if not gdal.HAS_GDAL: return
g1 = fromstr('POINT(5 23)') g1 = fromstr('POINT(5 23)')
self.assertEqual(True, isinstance(g1.ogr, gdal.OGRGeometry)) self.assertEqual(True, isinstance(g1.ogr, gdal.OGRGeometry))
self.assertEqual(g1.srs, None) self.assertEqual(g1.srs, None)
@ -821,7 +827,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
self.assertEqual(g2.hex, g2.ogr.hex) self.assertEqual(g2.hex, g2.ogr.hex)
self.assertEqual('WGS 84', g2.srs.name) self.assertEqual('WGS 84', g2.srs.name)
def test22_copy(self): def test_copy(self):
"Testing use with the Python `copy` module." "Testing use with the Python `copy` module."
import copy import copy
poly = GEOSGeometry('POLYGON((0 0, 0 23, 23 23, 23 0, 0 0), (5 5, 5 10, 10 10, 10 5, 5 5))') poly = GEOSGeometry('POLYGON((0 0, 0 23, 23 23, 23 0, 0 0), (5 5, 5 10, 10 10, 10 5, 5 5))')
@ -830,9 +836,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
self.assertNotEqual(poly._ptr, cpy1._ptr) self.assertNotEqual(poly._ptr, cpy1._ptr)
self.assertNotEqual(poly._ptr, cpy2._ptr) self.assertNotEqual(poly._ptr, cpy2._ptr)
def test23_transform(self): @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required")
def test_transform(self):
"Testing `transform` method." "Testing `transform` method."
if not gdal.HAS_GDAL: return
orig = GEOSGeometry('POINT (-104.609 38.255)', 4326) orig = GEOSGeometry('POINT (-104.609 38.255)', 4326)
trans = GEOSGeometry('POINT (992385.4472045 481455.4944650)', 2774) trans = GEOSGeometry('POINT (992385.4472045 481455.4944650)', 2774)
@ -855,7 +861,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
self.assertAlmostEqual(trans.x, p.x, prec) self.assertAlmostEqual(trans.x, p.x, prec)
self.assertAlmostEqual(trans.y, p.y, prec) self.assertAlmostEqual(trans.y, p.y, prec)
def test23_transform_noop(self): def test_transform_noop(self):
""" Testing `transform` method (SRID match) """ """ Testing `transform` method (SRID match) """
# transform() should no-op if source & dest SRIDs match, # transform() should no-op if source & dest SRIDs match,
# regardless of whether GDAL is available. # regardless of whether GDAL is available.
@ -890,7 +896,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
finally: finally:
gdal.HAS_GDAL = old_has_gdal gdal.HAS_GDAL = old_has_gdal
def test23_transform_nosrid(self): def test_transform_nosrid(self):
""" Testing `transform` method (no SRID or negative SRID) """ """ Testing `transform` method (no SRID or negative SRID) """
g = GEOSGeometry('POINT (-104.609 38.255)', srid=None) g = GEOSGeometry('POINT (-104.609 38.255)', srid=None)
@ -905,7 +911,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
g = GEOSGeometry('POINT (-104.609 38.255)', srid=-1) g = GEOSGeometry('POINT (-104.609 38.255)', srid=-1)
self.assertRaises(GEOSException, g.transform, 2774, clone=True) self.assertRaises(GEOSException, g.transform, 2774, clone=True)
def test23_transform_nogdal(self): def test_transform_nogdal(self):
""" Testing `transform` method (GDAL not available) """ """ Testing `transform` method (GDAL not available) """
old_has_gdal = gdal.HAS_GDAL old_has_gdal = gdal.HAS_GDAL
try: try:
@ -919,7 +925,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
finally: finally:
gdal.HAS_GDAL = old_has_gdal gdal.HAS_GDAL = old_has_gdal
def test24_extent(self): def test_extent(self):
"Testing `extent` method." "Testing `extent` method."
# The xmin, ymin, xmax, ymax of the MultiPoint should be returned. # The xmin, ymin, xmax, ymax of the MultiPoint should be returned.
mp = MultiPoint(Point(5, 23), Point(0, 0), Point(10, 50)) mp = MultiPoint(Point(5, 23), Point(0, 0), Point(10, 50))
@ -935,7 +941,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
xmax, ymax = max(x), max(y) xmax, ymax = max(x), max(y)
self.assertEqual((xmin, ymin, xmax, ymax), poly.extent) self.assertEqual((xmin, ymin, xmax, ymax), poly.extent)
def test25_pickle(self): def test_pickle(self):
"Testing pickling and unpickling support." "Testing pickling and unpickling support."
# Using both pickle and cPickle -- just 'cause. # Using both pickle and cPickle -- just 'cause.
import pickle, cPickle import pickle, cPickle
@ -958,9 +964,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
self.assertEqual(geom, tmpg) self.assertEqual(geom, tmpg)
if not no_srid: self.assertEqual(geom.srid, tmpg.srid) if not no_srid: self.assertEqual(geom.srid, tmpg.srid)
def test26_prepared(self): @unittest.skipUnless(GEOS_PREPARE, "geos >= 3.1.0 is required")
def test_prepared(self):
"Testing PreparedGeometry support." "Testing PreparedGeometry support."
if not GEOS_PREPARE: return
# Creating a simple multipolygon and getting a prepared version. # Creating a simple multipolygon and getting a prepared version.
mpoly = GEOSGeometry('MULTIPOLYGON(((0 0,0 5,5 5,5 0,0 0)),((5 5,5 10,10 10,10 5,5 5)))') mpoly = GEOSGeometry('MULTIPOLYGON(((0 0,0 5,5 5,5 0,0 0)),((5 5,5 10,10 10,10 5,5 5)))')
prep = mpoly.prepared prep = mpoly.prepared
@ -974,7 +980,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
self.assertEqual(mpoly.intersects(pnt), prep.intersects(pnt)) self.assertEqual(mpoly.intersects(pnt), prep.intersects(pnt))
self.assertEqual(c, prep.covers(pnt)) self.assertEqual(c, prep.covers(pnt))
def test26_line_merge(self): def test_line_merge(self):
"Testing line merge support" "Testing line merge support"
ref_geoms = (fromstr('LINESTRING(1 1, 1 1, 3 3)'), ref_geoms = (fromstr('LINESTRING(1 1, 1 1, 3 3)'),
fromstr('MULTILINESTRING((1 1, 3 3), (3 3, 4 2))'), fromstr('MULTILINESTRING((1 1, 3 3), (3 3, 4 2))'),
@ -985,10 +991,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
for geom, merged in zip(ref_geoms, ref_merged): for geom, merged in zip(ref_geoms, ref_merged):
self.assertEqual(merged, geom.merged) self.assertEqual(merged, geom.merged)
def test27_valid_reason(self): @unittest.skipUnless(GEOS_PREPARE, "geos >= 3.1.0 is required")
def test_valid_reason(self):
"Testing IsValidReason support" "Testing IsValidReason support"
# Skipping tests if GEOS < v3.1.
if not GEOS_PREPARE: return
g = GEOSGeometry("POINT(0 0)") g = GEOSGeometry("POINT(0 0)")
self.assertTrue(g.valid) self.assertTrue(g.valid)
@ -1005,7 +1010,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
print("\nEND - expecting GEOS_NOTICE; safe to ignore.\n") print("\nEND - expecting GEOS_NOTICE; safe to ignore.\n")
def test28_geos_version(self): def test_geos_version(self):
"Testing the GEOS version regular expression." "Testing the GEOS version regular expression."
from django.contrib.gis.geos.libgeos import version_regex from django.contrib.gis.geos.libgeos import version_regex
versions = [ ('3.0.0rc4-CAPI-1.3.3', '3.0.0'), versions = [ ('3.0.0rc4-CAPI-1.3.3', '3.0.0'),

View File

@ -1,5 +1,7 @@
from django.utils.safestring import mark_safe
from django.contrib.gis.geos import fromstr, Point, LineString, LinearRing, Polygon from django.contrib.gis.geos import fromstr, Point, LineString, LinearRing, Polygon
from django.utils.functional import total_ordering
from django.utils.safestring import mark_safe
class GEvent(object): class GEvent(object):
""" """
@ -166,6 +168,7 @@ class GPolyline(GOverlayBase):
return '%s, "%s", %s, %s' % (self.latlngs, self.color, self.weight, self.opacity) return '%s, "%s", %s, %s' % (self.latlngs, self.color, self.weight, self.opacity)
@total_ordering
class GIcon(object): class GIcon(object):
""" """
Creates a GIcon object to pass into a Gmarker object. Creates a GIcon object to pass into a Gmarker object.
@ -231,8 +234,11 @@ class GIcon(object):
self.iconanchor = iconanchor self.iconanchor = iconanchor
self.infowindowanchor = infowindowanchor self.infowindowanchor = infowindowanchor
def __cmp__(self, other): def __eq__(self, other):
return cmp(self.varname, other.varname) return self.varname == other.varname
def __lt__(self, other):
return self.varname < other.varname
def __hash__(self): def __hash__(self):
# XOR with hash of GIcon type so that hash('varname') won't # XOR with hash of GIcon type so that hash('varname') won't

View File

@ -38,6 +38,8 @@ and Geoff Biggs' PhD work on dimensioned units for robotics.
__all__ = ['A', 'Area', 'D', 'Distance'] __all__ = ['A', 'Area', 'D', 'Distance']
from decimal import Decimal from decimal import Decimal
from django.utils.functional import total_ordering
class MeasureBase(object): class MeasureBase(object):
def default_units(self, kwargs): def default_units(self, kwargs):
""" """
@ -84,6 +86,7 @@ class MeasureBase(object):
else: else:
raise Exception('Could not find a unit keyword associated with "%s"' % unit_str) raise Exception('Could not find a unit keyword associated with "%s"' % unit_str)
@total_ordering
class Distance(MeasureBase): class Distance(MeasureBase):
UNITS = { UNITS = {
'chain' : 20.1168, 'chain' : 20.1168,
@ -178,9 +181,15 @@ class Distance(MeasureBase):
def __str__(self): def __str__(self):
return '%s %s' % (getattr(self, self._default_unit), self._default_unit) return '%s %s' % (getattr(self, self._default_unit), self._default_unit)
def __cmp__(self, other): def __eq__(self, other):
if isinstance(other, Distance): if isinstance(other, Distance):
return cmp(self.m, other.m) return self.m == other.m
else:
return NotImplemented
def __lt__(self, other):
if isinstance(other, Distance):
return self.m < other.m
else: else:
return NotImplemented return NotImplemented
@ -244,6 +253,7 @@ class Distance(MeasureBase):
def __nonzero__(self): def __nonzero__(self):
return bool(self.m) return bool(self.m)
@total_ordering
class Area(MeasureBase): class Area(MeasureBase):
# Getting the square units values and the alias dictionary. # Getting the square units values and the alias dictionary.
UNITS = dict([('sq_%s' % k, v ** 2) for k, v in Distance.UNITS.items()]) UNITS = dict([('sq_%s' % k, v ** 2) for k, v in Distance.UNITS.items()])
@ -267,9 +277,15 @@ class Area(MeasureBase):
def __str__(self): def __str__(self):
return '%s %s' % (getattr(self, self._default_unit), self._default_unit) return '%s %s' % (getattr(self, self._default_unit), self._default_unit)
def __cmp__(self, other): def __eq__(self, other):
if isinstance(other, Area): if isinstance(other, Area):
return cmp(self.sq_m, other.sq_m) return self.sq_m == other.sq_m
else:
return NotImplemented
def __lt__(self, other):
if isinstance(other, Area):
return self.sq_m < other.sq_m
else: else:
return NotImplemented return NotImplemented

View File

@ -111,7 +111,7 @@ OpenLayers.Projection.addTransform("EPSG:4326", "EPSG:3857", OpenLayers.Layer.Sp
// The admin map for this geometry field. // The admin map for this geometry field.
{{ module }}.map = new OpenLayers.Map('{{ id }}_map', options); {{ module }}.map = new OpenLayers.Map('{{ id }}_map', options);
// Base Layer // Base Layer
{{ module }}.layers.base = {% block base_layer %}new OpenLayers.Layer.WMS( "{{ wms_name }}", "{{ wms_url }}", {layers: '{{ wms_layer }}'} );{% endblock %} {{ module }}.layers.base = {% block base_layer %}new OpenLayers.Layer.WMS("{{ wms_name }}", "{{ wms_url }}", {layers: '{{ wms_layer }}'{{ wms_options|safe }}});{% endblock %}
{{ module }}.map.addLayer({{ module }}.layers.base); {{ module }}.map.addLayer({{ module }}.layers.base);
{% block extra_layers %}{% endblock %} {% block extra_layers %}{% endblock %}
{% if is_linestring %}OpenLayers.Feature.Vector.style["default"]["strokeWidth"] = 3; // Default too thin for linestrings. {% endif %} {% if is_linestring %}OpenLayers.Feature.Vector.style["default"]["strokeWidth"] = 3; // Default too thin for linestrings. {% endif %}

View File

@ -2,6 +2,7 @@ from __future__ import absolute_import
from django.test import TestCase from django.test import TestCase
from django.contrib.gis import admin from django.contrib.gis import admin
from django.contrib.gis.geos import Point
from .models import City from .models import City
@ -14,3 +15,21 @@ class GeoAdminTest(TestCase):
admin_js = geoadmin.media.render_js() admin_js = geoadmin.media.render_js()
self.assertTrue(any([geoadmin.openlayers_url in js for js in admin_js])) self.assertTrue(any([geoadmin.openlayers_url in js for js in admin_js]))
def test_olmap_OSM_rendering(self):
geoadmin = admin.site._registry[City]
result = geoadmin.get_map_widget(City._meta.get_field('point'))(
).render('point', Point(-79.460734, 40.18476))
self.assertIn(
"""geodjango_point.layers.base = new OpenLayers.Layer.OSM("OpenStreetMap (Mapnik)");""",
result)
def test_olmap_WMS_rendering(self):
admin.site.unregister(City)
admin.site.register(City, admin.GeoModelAdmin)
geoadmin = admin.site._registry[City]
result = geoadmin.get_map_widget(City._meta.get_field('point'))(
).render('point', Point(-79.460734, 40.18476))
self.assertIn(
"""geodjango_point.layers.base = new OpenLayers.Layer.WMS("OpenLayers WMS", "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: \'basic\', format: 'image/jpeg'});""",
result)

View File

@ -17,6 +17,7 @@ class CountyFeat(models.Model):
class City(models.Model): class City(models.Model):
name = models.CharField(max_length=25) name = models.CharField(max_length=25)
name_txt = models.TextField(default='')
population = models.IntegerField() population = models.IntegerField()
density = models.DecimalField(max_digits=7, decimal_places=1) density = models.DecimalField(max_digits=7, decimal_places=1)
dt = models.DateField() dt = models.DateField()

View File

@ -4,11 +4,11 @@ import os
from copy import copy from copy import copy
from decimal import Decimal from decimal import Decimal
from django.utils.unittest import TestCase
from django.contrib.gis.gdal import DataSource from django.contrib.gis.gdal import DataSource
from django.contrib.gis.tests.utils import mysql from django.contrib.gis.tests.utils import mysql
from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError, InvalidDecimal, MissingForeignKey from django.contrib.gis.utils.layermapping import (LayerMapping, LayerMapError,
InvalidDecimal, MissingForeignKey)
from django.test import TestCase
from .models import ( from .models import (
City, County, CountyFeat, Interstate, ICity1, ICity2, Invalid, State, City, County, CountyFeat, Interstate, ICity1, ICity2, Invalid, State,
@ -28,7 +28,7 @@ STATES = ['Texas', 'Texas', 'Texas', 'Hawaii', 'Colorado']
class LayerMapTest(TestCase): class LayerMapTest(TestCase):
def test01_init(self): def test_init(self):
"Testing LayerMapping initialization." "Testing LayerMapping initialization."
# Model field that does not exist. # Model field that does not exist.
@ -46,22 +46,14 @@ class LayerMapTest(TestCase):
# Incrementing through the bad mapping dictionaries and # Incrementing through the bad mapping dictionaries and
# ensuring that a LayerMapError is raised. # ensuring that a LayerMapError is raised.
for bad_map in (bad1, bad2, bad3): for bad_map in (bad1, bad2, bad3):
try: with self.assertRaises(LayerMapError):
lm = LayerMapping(City, city_shp, bad_map) lm = LayerMapping(City, city_shp, bad_map)
except LayerMapError:
pass
else:
self.fail('Expected a LayerMapError.')
# A LookupError should be thrown for bogus encodings. # A LookupError should be thrown for bogus encodings.
try: with self.assertRaises(LookupError):
lm = LayerMapping(City, city_shp, city_mapping, encoding='foobar') lm = LayerMapping(City, city_shp, city_mapping, encoding='foobar')
except LookupError:
pass
else:
self.fail('Expected a LookupError')
def test02_simple_layermap(self): def test_simple_layermap(self):
"Test LayerMapping import of a simple point shapefile." "Test LayerMapping import of a simple point shapefile."
# Setting up for the LayerMapping. # Setting up for the LayerMapping.
lm = LayerMapping(City, city_shp, city_mapping) lm = LayerMapping(City, city_shp, city_mapping)
@ -85,18 +77,14 @@ class LayerMapTest(TestCase):
self.assertAlmostEqual(pnt1.x, pnt2.x, 5) self.assertAlmostEqual(pnt1.x, pnt2.x, 5)
self.assertAlmostEqual(pnt1.y, pnt2.y, 5) self.assertAlmostEqual(pnt1.y, pnt2.y, 5)
def test03_layermap_strict(self): def test_layermap_strict(self):
"Testing the `strict` keyword, and import of a LineString shapefile." "Testing the `strict` keyword, and import of a LineString shapefile."
# When the `strict` keyword is set an error encountered will force # When the `strict` keyword is set an error encountered will force
# the importation to stop. # the importation to stop.
try: with self.assertRaises(InvalidDecimal):
lm = LayerMapping(Interstate, inter_shp, inter_mapping) lm = LayerMapping(Interstate, inter_shp, inter_mapping)
lm.save(silent=True, strict=True) lm.save(silent=True, strict=True)
except InvalidDecimal: Interstate.objects.all().delete()
# No transactions for geoms on MySQL; delete added features.
if mysql: Interstate.objects.all().delete()
else:
self.fail('Should have failed on strict import with invalid decimal values.')
# This LayerMapping should work b/c `strict` is not set. # This LayerMapping should work b/c `strict` is not set.
lm = LayerMapping(Interstate, inter_shp, inter_mapping) lm = LayerMapping(Interstate, inter_shp, inter_mapping)
@ -137,7 +125,7 @@ class LayerMapTest(TestCase):
qs = CountyFeat.objects.filter(name=name) qs = CountyFeat.objects.filter(name=name)
self.assertEqual(n, qs.count()) self.assertEqual(n, qs.count())
def test04_layermap_unique_multigeometry_fk(self): def test_layermap_unique_multigeometry_fk(self):
"Testing the `unique`, and `transform`, geometry collection conversion, and ForeignKey mappings." "Testing the `unique`, and `transform`, geometry collection conversion, and ForeignKey mappings."
# All the following should work. # All the following should work.
try: try:
@ -176,8 +164,9 @@ class LayerMapTest(TestCase):
self.assertRaises(MissingForeignKey, lm.save, silent=True, strict=True) self.assertRaises(MissingForeignKey, lm.save, silent=True, strict=True)
# Now creating the state models so the ForeignKey mapping may work. # Now creating the state models so the ForeignKey mapping may work.
co, hi, tx = State(name='Colorado'), State(name='Hawaii'), State(name='Texas') State.objects.bulk_create([
co.save(), hi.save(), tx.save() State(name='Colorado'), State(name='Hawaii'), State(name='Texas')
])
# If a mapping is specified as a collection, all OGR fields that # If a mapping is specified as a collection, all OGR fields that
# are not collections will be converted into them. For example, # are not collections will be converted into them. For example,
@ -203,16 +192,19 @@ class LayerMapTest(TestCase):
# The county helper is called to ensure integrity of County models. # The county helper is called to ensure integrity of County models.
self.county_helper() self.county_helper()
def test05_test_fid_range_step(self): def test_test_fid_range_step(self):
"Tests the `fid_range` keyword and the `step` keyword of .save()." "Tests the `fid_range` keyword and the `step` keyword of .save()."
# Function for clearing out all the counties before testing. # Function for clearing out all the counties before testing.
def clear_counties(): County.objects.all().delete() def clear_counties(): County.objects.all().delete()
State.objects.bulk_create([
State(name='Colorado'), State(name='Hawaii'), State(name='Texas')
])
# Initializing the LayerMapping object to use in these tests. # Initializing the LayerMapping object to use in these tests.
lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name') lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name')
# Bad feature id ranges should raise a type error. # Bad feature id ranges should raise a type error.
clear_counties()
bad_ranges = (5.0, 'foo', co_shp) bad_ranges = (5.0, 'foo', co_shp)
for bad in bad_ranges: for bad in bad_ranges:
self.assertRaises(TypeError, lm.save, fid_range=bad) self.assertRaises(TypeError, lm.save, fid_range=bad)
@ -241,8 +233,10 @@ class LayerMapTest(TestCase):
self.assertEqual(2, qs.count()) self.assertEqual(2, qs.count())
hi, co = tuple(qs) hi, co = tuple(qs)
hi_idx, co_idx = tuple(map(NAMES.index, ('Honolulu', 'Pueblo'))) hi_idx, co_idx = tuple(map(NAMES.index, ('Honolulu', 'Pueblo')))
self.assertEqual('Pueblo', co.name); self.assertEqual(NUMS[co_idx], len(co.mpoly)) self.assertEqual('Pueblo', co.name)
self.assertEqual('Honolulu', hi.name); self.assertEqual(NUMS[hi_idx], len(hi.mpoly)) self.assertEqual(NUMS[co_idx], len(co.mpoly))
self.assertEqual('Honolulu', hi.name)
self.assertEqual(NUMS[hi_idx], len(hi.mpoly))
# Testing the `step` keyword -- should get the same counties # Testing the `step` keyword -- should get the same counties
# regardless of we use a step that divides equally, that is odd, # regardless of we use a step that divides equally, that is odd,
@ -252,7 +246,7 @@ class LayerMapTest(TestCase):
lm.save(step=st, strict=True) lm.save(step=st, strict=True)
self.county_helper(county_feat=False) self.county_helper(county_feat=False)
def test06_model_inheritance(self): def test_model_inheritance(self):
"Tests LayerMapping on inherited models. See #12093." "Tests LayerMapping on inherited models. See #12093."
icity_mapping = {'name' : 'Name', icity_mapping = {'name' : 'Name',
'population' : 'Population', 'population' : 'Population',
@ -272,9 +266,18 @@ class LayerMapTest(TestCase):
self.assertEqual(6, ICity1.objects.count()) self.assertEqual(6, ICity1.objects.count())
self.assertEqual(3, ICity2.objects.count()) self.assertEqual(3, ICity2.objects.count())
def test07_invalid_layer(self): def test_invalid_layer(self):
"Tests LayerMapping on invalid geometries. See #15378." "Tests LayerMapping on invalid geometries. See #15378."
invalid_mapping = {'point': 'POINT'} invalid_mapping = {'point': 'POINT'}
lm = LayerMapping(Invalid, invalid_shp, invalid_mapping, lm = LayerMapping(Invalid, invalid_shp, invalid_mapping,
source_srs=4326) source_srs=4326)
lm.save(silent=True) lm.save(silent=True)
def test_textfield(self):
"Tests that String content fits also in a TextField"
mapping = copy(city_mapping)
mapping['name_txt'] = 'Name'
lm = LayerMapping(City, city_shp, mapping)
lm.save(silent=True, strict=True)
self.assertEqual(City.objects.count(), 3)
self.assertEqual(City.objects.all().order_by('name_txt')[0].name_txt, "Houston")

View File

@ -332,7 +332,7 @@ class LayerMapping(object):
val = unicode(ogr_field.value, self.encoding) val = unicode(ogr_field.value, self.encoding)
else: else:
val = ogr_field.value val = ogr_field.value
if len(val) > model_field.max_length: if model_field.max_length and len(val) > model_field.max_length:
raise InvalidString('%s model field maximum string length is %s, given %s characters.' % raise InvalidString('%s model field maximum string length is %s, given %s characters.' %
(model_field.name, model_field.max_length, len(val))) (model_field.name, model_field.max_length, len(val)))
elif isinstance(ogr_field, OFTReal) and isinstance(model_field, models.DecimalField): elif isinstance(ogr_field, OFTReal) and isinstance(model_field, models.DecimalField):

View File

@ -190,7 +190,7 @@ class HumanizeTests(TestCase):
orig_humanize_datetime = humanize.datetime orig_humanize_datetime = humanize.datetime
orig_timesince_datetime = timesince.datetime orig_timesince_datetime = timesince.datetime
humanize.datetime = MockDateTime humanize.datetime = MockDateTime
timesince.datetime = new.module("mock_datetime") timesince.datetime = new.module(b"mock_datetime")
timesince.datetime.datetime = MockDateTime timesince.datetime.datetime = MockDateTime
try: try:

View File

@ -2,7 +2,7 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
PROVINCE_CHOICES = ( PROVINCE_CHOICES = (
('01', _('Arava')), ('01', _('Araba')),
('02', _('Albacete')), ('02', _('Albacete')),
('03', _('Alacant')), ('03', _('Alacant')),
('04', _('Almeria')), ('04', _('Almeria')),

View File

@ -1,5 +1,5 @@
from django import http from django import http
from django.conf import settings from django.conf import settings, global_settings
from django.contrib.messages import constants, utils, get_level, set_level from django.contrib.messages import constants, utils, get_level, set_level
from django.contrib.messages.api import MessageFailure from django.contrib.messages.api import MessageFailure
from django.contrib.messages.storage import default_storage, base from django.contrib.messages.storage import default_storage, base
@ -57,6 +57,7 @@ class BaseTest(TestCase):
def setUp(self): def setUp(self):
self.settings_override = override_settings_tags( self.settings_override = override_settings_tags(
TEMPLATE_DIRS = (), TEMPLATE_DIRS = (),
TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS,
MESSAGE_TAGS = '', MESSAGE_TAGS = '',
MESSAGE_STORAGE = '%s.%s' % (self.storage_class.__module__, MESSAGE_STORAGE = '%s.%s' % (self.storage_class.__module__,
self.storage_class.__name__), self.storage_class.__name__),

View File

@ -1,6 +1,3 @@
import os
from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.test import TestCase from django.test import TestCase
@ -13,16 +10,9 @@ class SitemapTestsBase(TestCase):
def setUp(self): def setUp(self):
self.base_url = '%s://%s' % (self.protocol, self.domain) self.base_url = '%s://%s' % (self.protocol, self.domain)
self.old_USE_L10N = settings.USE_L10N
self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS
settings.TEMPLATE_DIRS = (
os.path.join(os.path.dirname(__file__), 'templates'),
)
self.old_Site_meta_installed = Site._meta.installed self.old_Site_meta_installed = Site._meta.installed
# Create a user that will double as sitemap content # Create a user that will double as sitemap content
User.objects.create_user('testuser', 'test@example.com', 's3krit') User.objects.create_user('testuser', 'test@example.com', 's3krit')
def tearDown(self): def tearDown(self):
settings.USE_L10N = self.old_USE_L10N
settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS
Site._meta.installed = self.old_Site_meta_installed Site._meta.installed = self.old_Site_meta_installed

View File

@ -1,15 +1,19 @@
import os
from datetime import date from datetime import date
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.sitemaps import Sitemap, GenericSitemap from django.contrib.sitemaps import Sitemap, GenericSitemap
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.test.utils import override_settings
from django.utils.unittest import skipUnless from django.utils.unittest import skipUnless
from django.utils.formats import localize from django.utils.formats import localize
from django.utils.translation import activate, deactivate from django.utils.translation import activate, deactivate
from .base import SitemapTestsBase from .base import SitemapTestsBase
class HTTPSitemapTests(SitemapTestsBase): class HTTPSitemapTests(SitemapTestsBase):
def test_simple_sitemap_index(self): def test_simple_sitemap_index(self):
@ -21,6 +25,9 @@ class HTTPSitemapTests(SitemapTestsBase):
</sitemapindex> </sitemapindex>
""" % self.base_url) """ % self.base_url)
@override_settings(
TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'templates'),)
)
def test_simple_sitemap_custom_index(self): def test_simple_sitemap_custom_index(self):
"A simple sitemap index can be rendered with a custom template" "A simple sitemap index can be rendered with a custom template"
response = self.client.get('/simple/custom-index.xml') response = self.client.get('/simple/custom-index.xml')
@ -49,6 +56,9 @@ class HTTPSitemapTests(SitemapTestsBase):
</urlset> </urlset>
""" % (self.base_url, date.today())) """ % (self.base_url, date.today()))
@override_settings(
TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'templates'),)
)
def test_simple_custom_sitemap(self): def test_simple_custom_sitemap(self):
"A simple sitemap can be rendered with a custom template" "A simple sitemap can be rendered with a custom template"
response = self.client.get('/simple/custom-sitemap.xml') response = self.client.get('/simple/custom-sitemap.xml')
@ -60,10 +70,9 @@ class HTTPSitemapTests(SitemapTestsBase):
""" % (self.base_url, date.today())) """ % (self.base_url, date.today()))
@skipUnless(settings.USE_I18N, "Internationalization is not enabled") @skipUnless(settings.USE_I18N, "Internationalization is not enabled")
@override_settings(USE_L10N=True)
def test_localized_priority(self): def test_localized_priority(self):
"The priority value should not be localized (Refs #14164)" "The priority value should not be localized (Refs #14164)"
# Localization should be active
settings.USE_L10N = True
activate('fr') activate('fr')
self.assertEqual(u'0,3', localize(0.3)) self.assertEqual(u'0,3', localize(0.3))

View File

@ -4,7 +4,7 @@ from optparse import make_option
from django.core.files.storage import FileSystemStorage from django.core.files.storage import FileSystemStorage
from django.core.management.base import CommandError, NoArgsCommand from django.core.management.base import CommandError, NoArgsCommand
from django.utils.encoding import smart_str, smart_unicode from django.utils.encoding import smart_unicode
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
from django.contrib.staticfiles import finders, storage from django.contrib.staticfiles import finders, storage
@ -178,15 +178,12 @@ Type 'yes' to continue, or 'no' to cancel: """
', %s post-processed' ', %s post-processed'
% post_processed_count or ''), % post_processed_count or ''),
} }
self.stdout.write(smart_str(summary)) self.stdout.write(summary)
def log(self, msg, level=2): def log(self, msg, level=2):
""" """
Small log helper Small log helper
""" """
msg = smart_str(msg)
if not msg.endswith("\n"):
msg += "\n"
if self.verbosity >= level: if self.verbosity >= level:
self.stdout.write(msg) self.stdout.write(msg)

View File

@ -23,9 +23,7 @@ class Command(LabelCommand):
result = [result] result = [result]
output = u'\n '.join( output = u'\n '.join(
(smart_unicode(os.path.realpath(path)) for path in result)) (smart_unicode(os.path.realpath(path)) for path in result))
self.stdout.write( self.stdout.write(u"Found '%s' here:\n %s" % (path, output))
smart_str(u"Found '%s' here:\n %s\n" % (path, output)))
else: else:
if verbosity >= 1: if verbosity >= 1:
self.stderr.write( self.stderr.write("No matching file found for '%s'." % path)
smart_str("No matching file found for '%s'.\n" % path))

View File

@ -64,6 +64,17 @@ class CachedFilesMixin(object):
compiled = re.compile(pattern) compiled = re.compile(pattern)
self._patterns.setdefault(extension, []).append(compiled) self._patterns.setdefault(extension, []).append(compiled)
def file_hash(self, name, content=None):
"""
Retuns a hash of the file with the given name and optional content.
"""
if content is None:
return None
md5 = hashlib.md5()
for chunk in content.chunks():
md5.update(chunk)
return md5.hexdigest()[:12]
def hashed_name(self, name, content=None): def hashed_name(self, name, content=None):
parsed_name = urlsplit(unquote(name)) parsed_name = urlsplit(unquote(name))
clean_name = parsed_name.path.strip() clean_name = parsed_name.path.strip()
@ -78,13 +89,11 @@ class CachedFilesMixin(object):
return name return name
path, filename = os.path.split(clean_name) path, filename = os.path.split(clean_name)
root, ext = os.path.splitext(filename) root, ext = os.path.splitext(filename)
# Get the MD5 hash of the file file_hash = self.file_hash(clean_name, content)
md5 = hashlib.md5() if file_hash is not None:
for chunk in content.chunks(): file_hash = u".%s" % file_hash
md5.update(chunk) hashed_name = os.path.join(path, u"%s%s%s" %
md5sum = md5.hexdigest()[:12] (root, file_hash, ext))
hashed_name = os.path.join(path, u"%s.%s%s" %
(root, md5sum, ext))
unparsed_name = list(parsed_name) unparsed_name = list(parsed_name)
unparsed_name[2] = hashed_name unparsed_name[2] = hashed_name
# Special casing for a @font-face hack, like url(myfont.eot?#iefix") # Special casing for a @font-face hack, like url(myfont.eot?#iefix")

View File

@ -22,7 +22,7 @@ def csrf(request):
# In order to be able to provide debugging info in the # In order to be able to provide debugging info in the
# case of misconfiguration, we use a sentinel value # case of misconfiguration, we use a sentinel value
# instead of returning an empty dict. # instead of returning an empty dict.
return 'NOTPROVIDED' return b'NOTPROVIDED'
else: else:
return token return token
_get_val = lazy(_get_val, str) _get_val = lazy(_get_val, str)

View File

@ -1,6 +1,7 @@
""" """
Global Django exception and warning classes. Global Django exception and warning classes.
""" """
from functools import reduce
class DjangoRuntimeWarning(RuntimeWarning): class DjangoRuntimeWarning(RuntimeWarning):
pass pass

View File

@ -12,7 +12,8 @@ class File(FileProxyMixin):
if name is None: if name is None:
name = getattr(file, 'name', None) name = getattr(file, 'name', None)
self.name = name self.name = name
self.mode = getattr(file, 'mode', None) if hasattr(file, 'mode'):
self.mode = file.mode
def __str__(self): def __str__(self):
return smart_str(self.name or '') return smart_str(self.name or '')
@ -125,7 +126,7 @@ class ContentFile(File):
A File-like object that takes just raw content, rather than an actual file. A File-like object that takes just raw content, rather than an actual file.
""" """
def __init__(self, content, name=None): def __init__(self, content, name=None):
content = content or '' content = content or b''
super(ContentFile, self).__init__(BytesIO(content), name=name) super(ContentFile, self).__init__(BytesIO(content), name=name)
self.size = len(content) self.size = len(content)

View File

@ -30,8 +30,8 @@ class UploadedFile(File):
self.charset = charset self.charset = charset
def __repr__(self): def __repr__(self):
return "<%s: %s (%s)>" % ( return smart_str("<%s: %s (%s)>" % (
self.__class__.__name__, smart_str(self.name), self.content_type) self.__class__.__name__, self.name, self.content_type))
def _get_name(self): def _get_name(self):
return self._name return self._name

View File

@ -78,14 +78,14 @@ class LimitedStream(object):
def __init__(self, stream, limit, buf_size=64 * 1024 * 1024): def __init__(self, stream, limit, buf_size=64 * 1024 * 1024):
self.stream = stream self.stream = stream
self.remaining = limit self.remaining = limit
self.buffer = '' self.buffer = b''
self.buf_size = buf_size self.buf_size = buf_size
def _read_limited(self, size=None): def _read_limited(self, size=None):
if size is None or size > self.remaining: if size is None or size > self.remaining:
size = self.remaining size = self.remaining
if size == 0: if size == 0:
return '' return b''
result = self.stream.read(size) result = self.stream.read(size)
self.remaining -= len(result) self.remaining -= len(result)
return result return result
@ -93,17 +93,17 @@ class LimitedStream(object):
def read(self, size=None): def read(self, size=None):
if size is None: if size is None:
result = self.buffer + self._read_limited() result = self.buffer + self._read_limited()
self.buffer = '' self.buffer = b''
elif size < len(self.buffer): elif size < len(self.buffer):
result = self.buffer[:size] result = self.buffer[:size]
self.buffer = self.buffer[size:] self.buffer = self.buffer[size:]
else: # size >= len(self.buffer) else: # size >= len(self.buffer)
result = self.buffer + self._read_limited(size - len(self.buffer)) result = self.buffer + self._read_limited(size - len(self.buffer))
self.buffer = '' self.buffer = b''
return result return result
def readline(self, size=None): def readline(self, size=None):
while '\n' not in self.buffer and \ while b'\n' not in self.buffer and \
(size is None or len(self.buffer) < size): (size is None or len(self.buffer) < size):
if size: if size:
# since size is not None here, len(self.buffer) < size # since size is not None here, len(self.buffer) < size

View File

@ -45,6 +45,29 @@ def handle_default_options(options):
sys.path.insert(0, options.pythonpath) sys.path.insert(0, options.pythonpath)
class OutputWrapper(object):
"""
Wrapper around stdout/stderr
"""
def __init__(self, out, style_func=None, ending='\n'):
self._out = out
self.style_func = None
if hasattr(out, 'isatty') and out.isatty():
self.style_func = style_func
self.ending = ending
def __getattr__(self, name):
return getattr(self._out, name)
def write(self, msg, style_func=None, ending=None):
ending = ending is None and self.ending or ending
if ending and not msg.endswith(ending):
msg += ending
style_func = [f for f in (style_func, self.style_func, lambda x:x)
if f is not None][0]
self._out.write(smart_str(style_func(msg)))
class BaseCommand(object): class BaseCommand(object):
""" """
The base class from which all management commands ultimately The base class from which all management commands ultimately
@ -74,8 +97,9 @@ class BaseCommand(object):
output and, if the command is intended to produce a block of output and, if the command is intended to produce a block of
SQL statements, will be wrapped in ``BEGIN`` and ``COMMIT``. SQL statements, will be wrapped in ``BEGIN`` and ``COMMIT``.
4. If ``handle()`` raised a ``CommandError``, ``execute()`` will 4. If ``handle()`` or ``execute()`` raised any exception (e.g.
instead print an error message to ``stderr``. ``CommandError``), ``run_from_argv()`` will instead print an error
message to ``stderr``.
Thus, the ``handle()`` method is typically the starting point for Thus, the ``handle()`` method is typically the starting point for
subclasses; many built-in commands and command types either place subclasses; many built-in commands and command types either place
@ -187,46 +211,42 @@ class BaseCommand(object):
def run_from_argv(self, argv): def run_from_argv(self, argv):
""" """
Set up any environment changes requested (e.g., Python path Set up any environment changes requested (e.g., Python path
and Django settings), then run this command. and Django settings), then run this command. If the
command raises a ``CommandError``, intercept it and print it sensibly
to stderr.
""" """
parser = self.create_parser(argv[0], argv[1]) parser = self.create_parser(argv[0], argv[1])
options, args = parser.parse_args(argv[2:]) options, args = parser.parse_args(argv[2:])
handle_default_options(options) handle_default_options(options)
self.execute(*args, **options.__dict__) try:
self.execute(*args, **options.__dict__)
except Exception as e:
if options.traceback:
self.stderr.write(traceback.format_exc())
self.stderr.write('%s: %s' % (e.__class__.__name__, e))
sys.exit(1)
def execute(self, *args, **options): def execute(self, *args, **options):
""" """
Try to execute this command, performing model validation if Try to execute this command, performing model validation if
needed (as controlled by the attribute needed (as controlled by the attribute
``self.requires_model_validation``, except if force-skipped). If the ``self.requires_model_validation``, except if force-skipped).
command raises a ``CommandError``, intercept it and print it sensibly
to stderr.
""" """
show_traceback = options.get('traceback', False)
# Switch to English, because django-admin.py creates database content # Switch to English, because django-admin.py creates database content
# like permissions, and those shouldn't contain any translations. # like permissions, and those shouldn't contain any translations.
# But only do this if we can assume we have a working settings file, # But only do this if we can assume we have a working settings file,
# because django.utils.translation requires settings. # because django.utils.translation requires settings.
saved_lang = None saved_lang = None
self.stdout = OutputWrapper(options.get('stdout', sys.stdout))
self.stderr = OutputWrapper(options.get('stderr', sys.stderr), self.style.ERROR)
if self.can_import_settings: if self.can_import_settings:
try: from django.utils import translation
from django.utils import translation saved_lang = translation.get_language()
saved_lang = translation.get_language() translation.activate('en-us')
translation.activate('en-us')
except ImportError as e:
# If settings should be available, but aren't,
# raise the error and quit.
if show_traceback:
traceback.print_exc()
else:
sys.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e)))
sys.exit(1)
try: try:
self.stdout = options.get('stdout', sys.stdout)
self.stderr = options.get('stderr', sys.stderr)
if self.requires_model_validation and not options.get('skip_validation'): if self.requires_model_validation and not options.get('skip_validation'):
self.validate() self.validate()
output = self.handle(*args, **options) output = self.handle(*args, **options)
@ -237,16 +257,10 @@ class BaseCommand(object):
from django.db import connections, DEFAULT_DB_ALIAS from django.db import connections, DEFAULT_DB_ALIAS
connection = connections[options.get('database', DEFAULT_DB_ALIAS)] connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
if connection.ops.start_transaction_sql(): if connection.ops.start_transaction_sql():
self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()) + '\n') self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()))
self.stdout.write(output) self.stdout.write(output)
if self.output_transaction: if self.output_transaction:
self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;") + '\n') self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;"))
except CommandError as e:
if show_traceback:
traceback.print_exc()
else:
self.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e)))
sys.exit(1)
finally: finally:
if saved_lang is not None: if saved_lang is not None:
translation.activate(saved_lang) translation.activate(saved_lang)
@ -266,7 +280,7 @@ class BaseCommand(object):
error_text = s.read() error_text = s.read()
raise CommandError("One or more models did not validate:\n%s" % error_text) raise CommandError("One or more models did not validate:\n%s" % error_text)
if display_num_errors: if display_num_errors:
self.stdout.write("%s error%s found\n" % (num_errors, num_errors != 1 and 's' or '')) self.stdout.write("%s error%s found" % (num_errors, num_errors != 1 and 's' or ''))
def handle(self, *args, **options): def handle(self, *args, **options):
""" """

View File

@ -5,7 +5,7 @@ from optparse import make_option
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
def has_bom(fn): def has_bom(fn):
with open(fn, 'r') as f: with open(fn, 'rb') as f:
sample = f.read(4) sample = f.read(4)
return sample[:3] == '\xef\xbb\xbf' or \ return sample[:3] == '\xef\xbb\xbf' or \
sample.startswith(codecs.BOM_UTF16_LE) or \ sample.startswith(codecs.BOM_UTF16_LE) or \

View File

@ -1,7 +1,7 @@
from optparse import make_option from optparse import make_option
from django.core.cache.backends.db import BaseDatabaseCache from django.core.cache.backends.db import BaseDatabaseCache
from django.core.management.base import LabelCommand from django.core.management.base import LabelCommand, CommandError
from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS
from django.db.utils import DatabaseError from django.db.utils import DatabaseError
@ -55,11 +55,10 @@ class Command(LabelCommand):
try: try:
curs.execute("\n".join(full_statement)) curs.execute("\n".join(full_statement))
except DatabaseError as e: except DatabaseError as e:
self.stderr.write(
self.style.ERROR("Cache table '%s' could not be created.\nThe error was: %s.\n" %
(tablename, e)))
transaction.rollback_unless_managed(using=db) transaction.rollback_unless_managed(using=db)
else: raise CommandError(
for statement in index_output: "Cache table '%s' could not be created.\nThe error was: %s." %
curs.execute(statement) (tablename, e))
transaction.commit_unless_managed(using=db) for statement in index_output:
curs.execute(statement)
transaction.commit_unless_managed(using=db)

View File

@ -97,20 +97,24 @@ class Command(BaseCommand):
except KeyError: except KeyError:
raise CommandError("Unknown serialization format: %s" % format) raise CommandError("Unknown serialization format: %s" % format)
# Now collate the objects to be serialized. def get_objects():
objects = [] # Collate the objects to be serialized.
for model in sort_dependencies(app_list.items()): for model in sort_dependencies(app_list.items()):
if model in excluded_models: if model in excluded_models:
continue continue
if not model._meta.proxy and router.allow_syncdb(using, model): if not model._meta.proxy and router.allow_syncdb(using, model):
if use_base_manager: if use_base_manager:
objects.extend(model._base_manager.using(using).all()) objects = model._base_manager
else: else:
objects.extend(model._default_manager.using(using).all()) objects = model._default_manager
for obj in objects.using(using).\
order_by(model._meta.pk.name).iterator():
yield obj
try: try:
return serializers.serialize(format, objects, indent=indent, self.stdout.ending = None
use_natural_keys=use_natural_keys) serializers.serialize(format, get_objects(), indent=indent,
use_natural_keys=use_natural_keys, stream=self.stdout)
except Exception as e: except Exception as e:
if show_traceback: if show_traceback:
raise raise

View File

@ -26,6 +26,8 @@ class Command(NoArgsCommand):
def handle_inspection(self, options): def handle_inspection(self, options):
connection = connections[options.get('database')] connection = connections[options.get('database')]
# 'table_name_filter' is a stealth option
table_name_filter = options.get('table_name_filter')
table2model = lambda table_name: table_name.title().replace('_', '').replace(' ', '').replace('-', '') table2model = lambda table_name: table_name.title().replace('_', '').replace(' ', '').replace('-', '')
@ -43,6 +45,9 @@ class Command(NoArgsCommand):
yield '' yield ''
known_models = [] known_models = []
for table_name in connection.introspection.table_names(cursor): for table_name in connection.introspection.table_names(cursor):
if table_name_filter is not None and callable(table_name_filter):
if not table_name_filter(table_name):
continue
yield 'class %s(models.Model):' % table2model(table_name) yield 'class %s(models.Model):' % table2model(table_name)
known_models.append(table2model(table_name)) known_models.append(table2model(table_name))
try: try:

View File

@ -34,11 +34,11 @@ class Command(BaseCommand):
using = options.get('database') using = options.get('database')
connection = connections[using] connection = connections[using]
self.style = no_style()
if not len(fixture_labels): if not len(fixture_labels):
self.stderr.write( self.stderr.write(
self.style.ERROR("No database fixture specified. Please provide the path of at least one fixture in the command line.\n") "No database fixture specified. Please provide the path of at "
"least one fixture in the command line."
) )
return return
@ -124,11 +124,11 @@ class Command(BaseCommand):
if formats: if formats:
if verbosity >= 2: if verbosity >= 2:
self.stdout.write("Loading '%s' fixtures...\n" % fixture_name) self.stdout.write("Loading '%s' fixtures..." % fixture_name)
else: else:
self.stderr.write( self.stderr.write(
self.style.ERROR("Problem installing fixture '%s': %s is not a known serialization format.\n" % "Problem installing fixture '%s': %s is not a known serialization format." %
(fixture_name, format))) (fixture_name, format))
if commit: if commit:
transaction.rollback(using=using) transaction.rollback(using=using)
transaction.leave_transaction_management(using=using) transaction.leave_transaction_management(using=using)
@ -141,7 +141,7 @@ class Command(BaseCommand):
for fixture_dir in fixture_dirs: for fixture_dir in fixture_dirs:
if verbosity >= 2: if verbosity >= 2:
self.stdout.write("Checking %s for fixtures...\n" % humanize(fixture_dir)) self.stdout.write("Checking %s for fixtures..." % humanize(fixture_dir))
label_found = False label_found = False
for combo in product([using, None], formats, compression_formats): for combo in product([using, None], formats, compression_formats):
@ -154,7 +154,7 @@ class Command(BaseCommand):
) )
if verbosity >= 3: if verbosity >= 3:
self.stdout.write("Trying %s for %s fixture '%s'...\n" % \ self.stdout.write("Trying %s for %s fixture '%s'..." % \
(humanize(fixture_dir), file_name, fixture_name)) (humanize(fixture_dir), file_name, fixture_name))
full_path = os.path.join(fixture_dir, file_name) full_path = os.path.join(fixture_dir, file_name)
open_method = compression_types[compression_format] open_method = compression_types[compression_format]
@ -162,13 +162,13 @@ class Command(BaseCommand):
fixture = open_method(full_path, 'r') fixture = open_method(full_path, 'r')
except IOError: except IOError:
if verbosity >= 2: if verbosity >= 2:
self.stdout.write("No %s fixture '%s' in %s.\n" % \ self.stdout.write("No %s fixture '%s' in %s." % \
(format, fixture_name, humanize(fixture_dir))) (format, fixture_name, humanize(fixture_dir)))
else: else:
try: try:
if label_found: if label_found:
self.stderr.write(self.style.ERROR("Multiple fixtures named '%s' in %s. Aborting.\n" % self.stderr.write("Multiple fixtures named '%s' in %s. Aborting." %
(fixture_name, humanize(fixture_dir)))) (fixture_name, humanize(fixture_dir)))
if commit: if commit:
transaction.rollback(using=using) transaction.rollback(using=using)
transaction.leave_transaction_management(using=using) transaction.leave_transaction_management(using=using)
@ -178,7 +178,7 @@ class Command(BaseCommand):
objects_in_fixture = 0 objects_in_fixture = 0
loaded_objects_in_fixture = 0 loaded_objects_in_fixture = 0
if verbosity >= 2: if verbosity >= 2:
self.stdout.write("Installing %s fixture '%s' from %s.\n" % \ self.stdout.write("Installing %s fixture '%s' from %s." % \
(format, fixture_name, humanize(fixture_dir))) (format, fixture_name, humanize(fixture_dir)))
objects = serializers.deserialize(format, fixture, using=using) objects = serializers.deserialize(format, fixture, using=using)
@ -209,8 +209,8 @@ class Command(BaseCommand):
# error was encountered during fixture loading. # error was encountered during fixture loading.
if objects_in_fixture == 0: if objects_in_fixture == 0:
self.stderr.write( self.stderr.write(
self.style.ERROR("No fixture data found for '%s'. (File format may be invalid.)\n" % "No fixture data found for '%s'. (File format may be invalid.)" %
(fixture_name))) (fixture_name))
if commit: if commit:
transaction.rollback(using=using) transaction.rollback(using=using)
transaction.leave_transaction_management(using=using) transaction.leave_transaction_management(using=using)
@ -231,16 +231,16 @@ class Command(BaseCommand):
traceback.print_exc() traceback.print_exc()
else: else:
self.stderr.write( self.stderr.write(
self.style.ERROR("Problem installing fixture '%s': %s\n" % "Problem installing fixture '%s': %s" %
(full_path, ''.join(traceback.format_exception(sys.exc_type, (full_path, ''.join(traceback.format_exception(sys.exc_type,
sys.exc_value, sys.exc_traceback))))) sys.exc_value, sys.exc_traceback))))
return return
# If we found even one object in a fixture, we need to reset the # If we found even one object in a fixture, we need to reset the
# database sequences. # database sequences.
if loaded_object_count > 0: if loaded_object_count > 0:
sequence_sql = connection.ops.sequence_reset_sql(self.style, models) sequence_sql = connection.ops.sequence_reset_sql(no_style(), models)
if sequence_sql: if sequence_sql:
if verbosity >= 2: if verbosity >= 2:
self.stdout.write("Resetting sequences\n") self.stdout.write("Resetting sequences\n")
@ -253,10 +253,10 @@ class Command(BaseCommand):
if verbosity >= 1: if verbosity >= 1:
if fixture_object_count == loaded_object_count: if fixture_object_count == loaded_object_count:
self.stdout.write("Installed %d object(s) from %d fixture(s)\n" % ( self.stdout.write("Installed %d object(s) from %d fixture(s)" % (
loaded_object_count, fixture_count)) loaded_object_count, fixture_count))
else: else:
self.stdout.write("Installed %d object(s) (of %d) from %d fixture(s)\n" % ( self.stdout.write("Installed %d object(s) (of %d) from %d fixture(s)" % (
loaded_object_count, fixture_object_count, fixture_count)) loaded_object_count, fixture_object_count, fixture_count))
# Close the DB connection. This is required as a workaround for an # Close the DB connection. This is required as a workaround for an

View File

@ -120,12 +120,12 @@ class Command(BaseCommand):
error_text = ERRORS[e.args[0].args[0]] error_text = ERRORS[e.args[0].args[0]]
except (AttributeError, KeyError): except (AttributeError, KeyError):
error_text = str(e) error_text = str(e)
sys.stderr.write(self.style.ERROR("Error: %s" % error_text) + '\n') sys.stderr.write("Error: %s" % error_text)
# Need to use an OS exit because sys.exit doesn't work in a thread # Need to use an OS exit because sys.exit doesn't work in a thread
os._exit(1) os._exit(1)
except KeyboardInterrupt: except KeyboardInterrupt:
if shutdown_message: if shutdown_message:
self.stdout.write("%s\n" % shutdown_message) self.stdout.write(shutdown_message)
sys.exit(0) sys.exit(0)

View File

@ -16,7 +16,6 @@ from os import path
import django import django
from django.template import Template, Context from django.template import Template, Context
from django.utils import archive from django.utils import archive
from django.utils.encoding import smart_str
from django.utils._os import rmtree_errorhandler from django.utils._os import rmtree_errorhandler
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.core.management.commands.makemessages import handle_extensions from django.core.management.commands.makemessages import handle_extensions
@ -166,11 +165,10 @@ class TemplateCommand(BaseCommand):
shutil.copymode(old_path, new_path) shutil.copymode(old_path, new_path)
self.make_writeable(new_path) self.make_writeable(new_path)
except OSError: except OSError:
notice = self.style.NOTICE( self.stderr.write(
"Notice: Couldn't set permission bits on %s. You're " "Notice: Couldn't set permission bits on %s. You're "
"probably using an uncommon filesystem setup. No " "probably using an uncommon filesystem setup. No "
"problem.\n" % new_path) "problem." % new_path, self.style.NOTICE)
sys.stderr.write(smart_str(notice))
if self.paths_to_remove: if self.paths_to_remove:
if self.verbosity >= 2: if self.verbosity >= 2:

View File

@ -39,6 +39,7 @@ class Serializer(object):
self.use_natural_keys = options.pop("use_natural_keys", False) self.use_natural_keys = options.pop("use_natural_keys", False)
self.start_serialization() self.start_serialization()
self.first = True
for obj in queryset: for obj in queryset:
self.start_object(obj) self.start_object(obj)
# Use the concrete parent class' _meta instead of the object's _meta # Use the concrete parent class' _meta instead of the object's _meta
@ -57,6 +58,8 @@ class Serializer(object):
if self.selected_fields is None or field.attname in self.selected_fields: if self.selected_fields is None or field.attname in self.selected_fields:
self.handle_m2m_field(obj, field) self.handle_m2m_field(obj, field)
self.end_object(obj) self.end_object(obj)
if self.first:
self.first = False
self.end_serialization() self.end_serialization()
return self.getvalue() return self.getvalue()

View File

@ -21,13 +21,38 @@ class Serializer(PythonSerializer):
""" """
internal_use_only = False internal_use_only = False
def end_serialization(self): def start_serialization(self):
if json.__version__.split('.') >= ['2', '1', '3']: if json.__version__.split('.') >= ['2', '1', '3']:
# Use JS strings to represent Python Decimal instances (ticket #16850) # Use JS strings to represent Python Decimal instances (ticket #16850)
self.options.update({'use_decimal': False}) self.options.update({'use_decimal': False})
json.dump(self.objects, self.stream, cls=DjangoJSONEncoder, **self.options) self._current = None
self.json_kwargs = self.options.copy()
self.json_kwargs.pop('stream', None)
self.json_kwargs.pop('fields', None)
self.stream.write("[")
def end_serialization(self):
if self.options.get("indent"):
self.stream.write("\n")
self.stream.write("]")
if self.options.get("indent"):
self.stream.write("\n")
def end_object(self, obj):
# self._current has the field data
indent = self.options.get("indent")
if not self.first:
self.stream.write(",")
if not indent:
self.stream.write(" ")
if indent:
self.stream.write("\n")
json.dump(self.get_dump_object(obj), self.stream,
cls=DjangoJSONEncoder, **self.json_kwargs)
self._current = None
def getvalue(self): def getvalue(self):
# overwrite PythonSerializer.getvalue() with base Serializer.getvalue()
if callable(getattr(self.stream, 'getvalue', None)): if callable(getattr(self.stream, 'getvalue', None)):
return self.stream.getvalue() return self.stream.getvalue()

View File

@ -27,13 +27,16 @@ class Serializer(base.Serializer):
self._current = {} self._current = {}
def end_object(self, obj): def end_object(self, obj):
self.objects.append({ self.objects.append(self.get_dump_object(obj))
"model" : smart_unicode(obj._meta),
"pk" : smart_unicode(obj._get_pk_val(), strings_only=True),
"fields" : self._current
})
self._current = None self._current = None
def get_dump_object(self, obj):
return {
"pk": smart_unicode(obj._get_pk_val(), strings_only=True),
"model": smart_unicode(obj._meta),
"fields": self._current
}
def handle_field(self, obj, field): def handle_field(self, obj, field):
value = field._get_val_from_obj(obj) value = field._get_val_from_obj(obj)
# Protected types (i.e., primitives like None, numbers, dates, # Protected types (i.e., primitives like None, numbers, dates,

View File

@ -146,25 +146,23 @@ class WSGIRequestHandler(simple_server.WSGIRequestHandler, object):
env['PATH_INFO'] = urllib.unquote(path) env['PATH_INFO'] = urllib.unquote(path)
env['QUERY_STRING'] = query env['QUERY_STRING'] = query
env['REMOTE_ADDR'] = self.client_address[0] env['REMOTE_ADDR'] = self.client_address[0]
env['CONTENT_TYPE'] = self.headers.get('content-type', 'text/plain')
if self.headers.typeheader is None: length = self.headers.get('content-length')
env['CONTENT_TYPE'] = self.headers.type
else:
env['CONTENT_TYPE'] = self.headers.typeheader
length = self.headers.getheader('content-length')
if length: if length:
env['CONTENT_LENGTH'] = length env['CONTENT_LENGTH'] = length
for h in self.headers.headers: for key, value in self.headers.items():
k,v = h.split(':',1) key = key.replace('-','_').upper()
k=k.replace('-','_').upper(); v=v.strip() value = value.strip()
if k in env: if key in env:
continue # skip content length, type,etc. # Skip content length, type, etc.
if 'HTTP_'+k in env: continue
env['HTTP_'+k] += ','+v # comma-separate multiple headers if 'HTTP_' + key in env:
# Comma-separate multiple headers
env['HTTP_' + key] += ',' + value
else: else:
env['HTTP_'+k] = v env['HTTP_' + key] = value
return env return env
def log_message(self, format, *args): def log_message(self, format, *args):

View File

@ -306,10 +306,8 @@ class RegexURLResolver(LocaleRegexProvider):
tried.append([pattern]) tried.append([pattern])
else: else:
if sub_match: if sub_match:
sub_match_dict = dict([(smart_str(k), v) for k, v in match.groupdict().items()]) sub_match_dict = dict(match.groupdict(), **self.default_kwargs)
sub_match_dict.update(self.default_kwargs) sub_match_dict.update(sub_match.kwargs)
for k, v in sub_match.kwargs.iteritems():
sub_match_dict[smart_str(k)] = v
return ResolverMatch(sub_match.func, sub_match.args, sub_match_dict, sub_match.url_name, self.app_name or sub_match.app_name, [self.namespace] + sub_match.namespaces) return ResolverMatch(sub_match.func, sub_match.args, sub_match_dict, sub_match.url_name, self.app_name or sub_match.app_name, [self.namespace] + sub_match.namespaces)
tried.append([pattern]) tried.append([pattern])
raise Resolver404({'tried': tried, 'path': new_path}) raise Resolver404({'tried': tried, 'path': new_path})

View File

@ -399,6 +399,9 @@ class BaseDatabaseFeatures(object):
# in the SQL standard. # in the SQL standard.
supports_tablespaces = False supports_tablespaces = False
# Does the backend reset sequences between tests?
supports_sequence_reset = True
# Features that need to be confirmed at runtime # Features that need to be confirmed at runtime
# Cache whether the confirmation has been performed. # Cache whether the confirmation has been performed.
_confirmed = False _confirmed = False
@ -414,10 +417,11 @@ class BaseDatabaseFeatures(object):
def confirm(self): def confirm(self):
"Perform manual checks of any database features that might vary between installs" "Perform manual checks of any database features that might vary between installs"
self._confirmed = True if not self._confirmed:
self.supports_transactions = self._supports_transactions() self._confirmed = True
self.supports_stddev = self._supports_stddev() self.supports_transactions = self._supports_transactions()
self.can_introspect_foreign_keys = self._can_introspect_foreign_keys() self.supports_stddev = self._supports_stddev()
self.can_introspect_foreign_keys = self._can_introspect_foreign_keys()
def _supports_transactions(self): def _supports_transactions(self):
"Confirm support for transactions" "Confirm support for transactions"
@ -439,8 +443,9 @@ class BaseDatabaseFeatures(object):
try: try:
self.connection.ops.check_aggregate_support(StdDevPop()) self.connection.ops.check_aggregate_support(StdDevPop())
return True
except NotImplementedError: except NotImplementedError:
self.supports_stddev = False return False
def _can_introspect_foreign_keys(self): def _can_introspect_foreign_keys(self):
"Confirm support for introspected foreign keys" "Confirm support for introspected foreign keys"

View File

@ -418,11 +418,20 @@ class DatabaseWrapper(BaseDatabaseWrapper):
@cached_property @cached_property
def mysql_version(self): def mysql_version(self):
if not self.server_version: if not self.server_version:
new_connection = False
if not self._valid_connection(): if not self._valid_connection():
# Ensure we have a connection with the DB by using a temporary
# cursor
new_connection = True
self.cursor().close() self.cursor().close()
m = server_version_re.match(self.connection.get_server_info()) server_info = self.connection.get_server_info()
if new_connection:
# Make sure we close the connection
self.connection.close()
self.connection = None
m = server_version_re.match(server_info)
if not m: if not m:
raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_info()) raise Exception('Unable to determine MySQL version from version string %r' % server_info)
self.server_version = tuple([int(x) for x in m.groups()]) self.server_version = tuple([int(x) for x in m.groups()])
return self.server_version return self.server_version

View File

@ -83,6 +83,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
ignores_nulls_in_unique_constraints = False ignores_nulls_in_unique_constraints = False
has_bulk_insert = True has_bulk_insert = True
supports_tablespaces = True supports_tablespaces = True
supports_sequence_reset = False
class DatabaseOperations(BaseDatabaseOperations): class DatabaseOperations(BaseDatabaseOperations):
compiler_module = "django.db.backends.oracle.compiler" compiler_module = "django.db.backends.oracle.compiler"

View File

@ -51,15 +51,15 @@ def adapt_datetime_with_timezone_support(value):
default_timezone = timezone.get_default_timezone() default_timezone = timezone.get_default_timezone()
value = timezone.make_aware(value, default_timezone) value = timezone.make_aware(value, default_timezone)
value = value.astimezone(timezone.utc).replace(tzinfo=None) value = value.astimezone(timezone.utc).replace(tzinfo=None)
return value.isoformat(" ") return value.isoformat(b" ")
Database.register_converter("bool", lambda s: str(s) == '1') Database.register_converter(b"bool", lambda s: str(s) == '1')
Database.register_converter("time", parse_time) Database.register_converter(b"time", parse_time)
Database.register_converter("date", parse_date) Database.register_converter(b"date", parse_date)
Database.register_converter("datetime", parse_datetime_with_timezone_support) Database.register_converter(b"datetime", parse_datetime_with_timezone_support)
Database.register_converter("timestamp", parse_datetime_with_timezone_support) Database.register_converter(b"timestamp", parse_datetime_with_timezone_support)
Database.register_converter("TIMESTAMP", parse_datetime_with_timezone_support) Database.register_converter(b"TIMESTAMP", parse_datetime_with_timezone_support)
Database.register_converter("decimal", util.typecast_decimal) Database.register_converter(b"decimal", util.typecast_decimal)
Database.register_adapter(datetime.datetime, adapt_datetime_with_timezone_support) Database.register_adapter(datetime.datetime, adapt_datetime_with_timezone_support)
Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal) Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal)
if Database.version_info >= (2, 4, 1): if Database.version_info >= (2, 4, 1):

View File

@ -57,11 +57,11 @@ class ModelBase(type):
new_class.add_to_class('_meta', Options(meta, **kwargs)) new_class.add_to_class('_meta', Options(meta, **kwargs))
if not abstract: if not abstract:
new_class.add_to_class('DoesNotExist', subclass_exception('DoesNotExist', new_class.add_to_class('DoesNotExist', subclass_exception(b'DoesNotExist',
tuple(x.DoesNotExist tuple(x.DoesNotExist
for x in parents if hasattr(x, '_meta') and not x._meta.abstract) for x in parents if hasattr(x, '_meta') and not x._meta.abstract)
or (ObjectDoesNotExist,), module)) or (ObjectDoesNotExist,), module))
new_class.add_to_class('MultipleObjectsReturned', subclass_exception('MultipleObjectsReturned', new_class.add_to_class('MultipleObjectsReturned', subclass_exception(b'MultipleObjectsReturned',
tuple(x.MultipleObjectsReturned tuple(x.MultipleObjectsReturned
for x in parents if hasattr(x, '_meta') and not x._meta.abstract) for x in parents if hasattr(x, '_meta') and not x._meta.abstract)
or (MultipleObjectsReturned,), module)) or (MultipleObjectsReturned,), module))
@ -404,7 +404,6 @@ class Model(object):
# and as a result, the super call will cause an infinite recursion. # and as a result, the super call will cause an infinite recursion.
# See #10547 and #12121. # See #10547 and #12121.
defers = [] defers = []
pk_val = None
if self._deferred: if self._deferred:
from django.db.models.query_utils import deferred_class_factory from django.db.models.query_utils import deferred_class_factory
factory = deferred_class_factory factory = deferred_class_factory
@ -412,12 +411,7 @@ class Model(object):
if isinstance(self.__class__.__dict__.get(field.attname), if isinstance(self.__class__.__dict__.get(field.attname),
DeferredAttribute): DeferredAttribute):
defers.append(field.attname) defers.append(field.attname)
if pk_val is None: model = self._meta.proxy_for_model
# The pk_val and model values are the same for all
# DeferredAttribute classes, so we only need to do this
# once.
obj = self.__class__.__dict__[field.attname]
model = obj.model_ref()
else: else:
factory = simple_class_factory factory = simple_class_factory
return (model_unpickle, (model, defers, factory), data) return (model_unpickle, (model, defers, factory), data)

View File

@ -1,3 +1,4 @@
import collections
import copy import copy
import datetime import datetime
import decimal import decimal
@ -16,7 +17,7 @@ from django.utils.functional import curry, total_ordering
from django.utils.text import capfirst from django.utils.text import capfirst
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode, force_unicode, smart_str from django.utils.encoding import smart_unicode, force_unicode
from django.utils.ipv6 import clean_ipv6_address from django.utils.ipv6 import clean_ipv6_address
class NOT_PROVIDED: class NOT_PROVIDED:
@ -436,7 +437,7 @@ class Field(object):
return bound_field_class(self, fieldmapping, original) return bound_field_class(self, fieldmapping, original)
def _get_choices(self): def _get_choices(self):
if hasattr(self._choices, 'next'): if isinstance(self._choices, collections.Iterator):
choices, self._choices = tee(self._choices) choices, self._choices = tee(self._choices)
return choices return choices
else: else:
@ -529,7 +530,7 @@ class AutoField(Field):
try: try:
return int(value) return int(value)
except (TypeError, ValueError): except (TypeError, ValueError):
msg = self.error_messages['invalid'] % str(value) msg = self.error_messages['invalid'] % value
raise exceptions.ValidationError(msg) raise exceptions.ValidationError(msg)
def validate(self, value, model_instance): def validate(self, value, model_instance):
@ -581,7 +582,7 @@ class BooleanField(Field):
return True return True
if value in ('f', 'False', '0'): if value in ('f', 'False', '0'):
return False return False
msg = self.error_messages['invalid'] % str(value) msg = self.error_messages['invalid'] % value
raise exceptions.ValidationError(msg) raise exceptions.ValidationError(msg)
def get_prep_lookup(self, lookup_type, value): def get_prep_lookup(self, lookup_type, value):
@ -677,7 +678,7 @@ class DateField(Field):
return value return value
if isinstance(value, datetime.datetime): if isinstance(value, datetime.datetime):
if settings.USE_TZ and timezone.is_aware(value): if settings.USE_TZ and timezone.is_aware(value):
# Convert aware datetimes to the current time zone # Convert aware datetimes to the default time zone
# before casting them to dates (#17742). # before casting them to dates (#17742).
default_timezone = timezone.get_default_timezone() default_timezone = timezone.get_default_timezone()
value = timezone.make_naive(value, default_timezone) value = timezone.make_naive(value, default_timezone)
@ -685,8 +686,6 @@ class DateField(Field):
if isinstance(value, datetime.date): if isinstance(value, datetime.date):
return value return value
value = smart_str(value)
try: try:
parsed = parse_date(value) parsed = parse_date(value)
if parsed is not None: if parsed is not None:
@ -778,8 +777,6 @@ class DateTimeField(DateField):
value = timezone.make_aware(value, default_timezone) value = timezone.make_aware(value, default_timezone)
return value return value
value = smart_str(value)
try: try:
parsed = parse_datetime(value) parsed = parse_datetime(value)
if parsed is not None: if parsed is not None:
@ -861,7 +858,7 @@ class DecimalField(Field):
try: try:
return decimal.Decimal(value) return decimal.Decimal(value)
except decimal.InvalidOperation: except decimal.InvalidOperation:
msg = self.error_messages['invalid'] % str(value) msg = self.error_messages['invalid'] % value
raise exceptions.ValidationError(msg) raise exceptions.ValidationError(msg)
def _format(self, value): def _format(self, value):
@ -966,7 +963,7 @@ class FloatField(Field):
try: try:
return float(value) return float(value)
except (TypeError, ValueError): except (TypeError, ValueError):
msg = self.error_messages['invalid'] % str(value) msg = self.error_messages['invalid'] % value
raise exceptions.ValidationError(msg) raise exceptions.ValidationError(msg)
def formfield(self, **kwargs): def formfield(self, **kwargs):
@ -1001,7 +998,7 @@ class IntegerField(Field):
try: try:
return int(value) return int(value)
except (TypeError, ValueError): except (TypeError, ValueError):
msg = self.error_messages['invalid'] % str(value) msg = self.error_messages['invalid'] % value
raise exceptions.ValidationError(msg) raise exceptions.ValidationError(msg)
def formfield(self, **kwargs): def formfield(self, **kwargs):
@ -1106,7 +1103,7 @@ class NullBooleanField(Field):
return True return True
if value in ('f', 'False', '0'): if value in ('f', 'False', '0'):
return False return False
msg = self.error_messages['invalid'] % str(value) msg = self.error_messages['invalid'] % value
raise exceptions.ValidationError(msg) raise exceptions.ValidationError(msg)
def get_prep_lookup(self, lookup_type, value): def get_prep_lookup(self, lookup_type, value):
@ -1227,8 +1224,6 @@ class TimeField(Field):
# database backend (e.g. Oracle), so we'll be accommodating. # database backend (e.g. Oracle), so we'll be accommodating.
return value.time() return value.time()
value = smart_str(value)
try: try:
parsed = parse_time(value) parsed = parse_time(value)
if parsed is not None: if parsed is not None:

View File

@ -3,6 +3,7 @@ import os
from django import forms from django import forms
from django.db.models.fields import Field from django.db.models.fields import Field
from django.core.exceptions import ValidationError
from django.core.files.base import File from django.core.files.base import File
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from django.core.files.images import ImageFile from django.core.files.images import ImageFile
@ -204,6 +205,11 @@ class FileDescriptor(object):
instance.__dict__[self.field.name] = value instance.__dict__[self.field.name] = value
class FileField(Field): class FileField(Field):
default_error_messages = {
'max_length': _(u'Filename is %(extra)d characters too long.')
}
# The class to wrap instance attributes in. Accessing the file object off # The class to wrap instance attributes in. Accessing the file object off
# the instance will always return an instance of attr_class. # the instance will always return an instance of attr_class.
attr_class = FieldFile attr_class = FieldFile
@ -226,6 +232,21 @@ class FileField(Field):
kwargs['max_length'] = kwargs.get('max_length', 100) kwargs['max_length'] = kwargs.get('max_length', 100)
super(FileField, self).__init__(verbose_name, name, **kwargs) super(FileField, self).__init__(verbose_name, name, **kwargs)
def validate(self, value, model_instance):
"""
Validates that the generated file name still fits within max_length.
"""
# The generated file name stored in the database is generally longer
# than the uploaded file name. Using the length of generated name in
# the error message would be confusing. However, in the common case
# (ie. upload_to='path/to/upload/dir'), the length of the generated
# name equals the length of the uploaded name plus a constant. Thus
# we can tell the user how much shorter the name should be (roughly).
length = len(self.generate_filename(model_instance, value.name))
if self.max_length and length > self.max_length:
error_values = {'extra': length - self.max_length}
raise ValidationError(self.error_messages['max_length'] % error_values)
def get_internal_type(self): def get_internal_type(self):
return "FileField" return "FileField"

View File

@ -237,13 +237,18 @@ class SingleRelatedObjectDescriptor(object):
return self.related.model._base_manager.using(db) return self.related.model._base_manager.using(db)
def get_prefetch_query_set(self, instances): def get_prefetch_query_set(self, instances):
vals = set(instance._get_pk_val() for instance in instances) rel_obj_attr = attrgetter(self.related.field.attname)
params = {'%s__pk__in' % self.related.field.name: vals} instance_attr = lambda obj: obj._get_pk_val()
return (self.get_query_set(instance=instances[0]).filter(**params), instances_dict = dict((instance_attr(inst), inst) for inst in instances)
attrgetter(self.related.field.attname), params = {'%s__pk__in' % self.related.field.name: instances_dict.keys()}
lambda obj: obj._get_pk_val(), qs = self.get_query_set(instance=instances[0]).filter(**params)
True, # Since we're going to assign directly in the cache,
self.cache_name) # we must manage the reverse relation cache manually.
rel_obj_cache_name = self.related.field.get_cache_name()
for rel_obj in qs:
instance = instances_dict[rel_obj_attr(rel_obj)]
setattr(rel_obj, rel_obj_cache_name, instance)
return qs, rel_obj_attr, instance_attr, True, self.cache_name
def __get__(self, instance, instance_type=None): def __get__(self, instance, instance_type=None):
if instance is None: if instance is None:
@ -324,17 +329,23 @@ class ReverseSingleRelatedObjectDescriptor(object):
return QuerySet(self.field.rel.to).using(db) return QuerySet(self.field.rel.to).using(db)
def get_prefetch_query_set(self, instances): def get_prefetch_query_set(self, instances):
vals = set(getattr(instance, self.field.attname) for instance in instances) rel_obj_attr = attrgetter(self.field.rel.field_name)
instance_attr = attrgetter(self.field.attname)
instances_dict = dict((instance_attr(inst), inst) for inst in instances)
other_field = self.field.rel.get_related_field() other_field = self.field.rel.get_related_field()
if other_field.rel: if other_field.rel:
params = {'%s__pk__in' % self.field.rel.field_name: vals} params = {'%s__pk__in' % self.field.rel.field_name: instances_dict.keys()}
else: else:
params = {'%s__in' % self.field.rel.field_name: vals} params = {'%s__in' % self.field.rel.field_name: instances_dict.keys()}
return (self.get_query_set(instance=instances[0]).filter(**params), qs = self.get_query_set(instance=instances[0]).filter(**params)
attrgetter(self.field.rel.field_name), # Since we're going to assign directly in the cache,
attrgetter(self.field.attname), # we must manage the reverse relation cache manually.
True, if not self.field.rel.multiple:
self.cache_name) rel_obj_cache_name = self.field.related.get_cache_name()
for rel_obj in qs:
instance = instances_dict[rel_obj_attr(rel_obj)]
setattr(rel_obj, rel_obj_cache_name, instance)
return qs, rel_obj_attr, instance_attr, True, self.cache_name
def __get__(self, instance, instance_type=None): def __get__(self, instance, instance_type=None):
if instance is None: if instance is None:
@ -467,18 +478,24 @@ class ForeignRelatedObjectsDescriptor(object):
return self.instance._prefetched_objects_cache[rel_field.related_query_name()] return self.instance._prefetched_objects_cache[rel_field.related_query_name()]
except (AttributeError, KeyError): except (AttributeError, KeyError):
db = self._db or router.db_for_read(self.model, instance=self.instance) db = self._db or router.db_for_read(self.model, instance=self.instance)
return super(RelatedManager, self).get_query_set().using(db).filter(**self.core_filters) qs = super(RelatedManager, self).get_query_set().using(db).filter(**self.core_filters)
qs._known_related_object = (rel_field.name, self.instance)
return qs
def get_prefetch_query_set(self, instances): def get_prefetch_query_set(self, instances):
rel_obj_attr = attrgetter(rel_field.get_attname())
instance_attr = attrgetter(attname)
instances_dict = dict((instance_attr(inst), inst) for inst in instances)
db = self._db or router.db_for_read(self.model, instance=instances[0]) db = self._db or router.db_for_read(self.model, instance=instances[0])
query = {'%s__%s__in' % (rel_field.name, attname): query = {'%s__%s__in' % (rel_field.name, attname): instances_dict.keys()}
set(getattr(obj, attname) for obj in instances)}
qs = super(RelatedManager, self).get_query_set().using(db).filter(**query) qs = super(RelatedManager, self).get_query_set().using(db).filter(**query)
return (qs, # Since we just bypassed this class' get_query_set(), we must manage
attrgetter(rel_field.get_attname()), # the reverse relation manually.
attrgetter(attname), for rel_obj in qs:
False, instance = instances_dict[rel_obj_attr(rel_obj)]
rel_field.related_query_name()) setattr(rel_obj, rel_field.name, instance)
cache_name = rel_field.related_query_name()
return qs, rel_obj_attr, instance_attr, False, cache_name
def add(self, *objs): def add(self, *objs):
for obj in objs: for obj in objs:

View File

@ -41,6 +41,7 @@ class QuerySet(object):
self._for_write = False self._for_write = False
self._prefetch_related_lookups = [] self._prefetch_related_lookups = []
self._prefetch_done = False self._prefetch_done = False
self._known_related_object = None # (attname, rel_obj)
######################## ########################
# PYTHON MAGIC METHODS # # PYTHON MAGIC METHODS #
@ -282,9 +283,10 @@ class QuerySet(object):
init_list.append(field.attname) init_list.append(field.attname)
model_cls = deferred_class_factory(self.model, skip) model_cls = deferred_class_factory(self.model, skip)
# Cache db and model outside the loop # Cache db, model and known_related_object outside the loop
db = self.db db = self.db
model = self.model model = self.model
kro_attname, kro_instance = self._known_related_object or (None, None)
compiler = self.query.get_compiler(using=db) compiler = self.query.get_compiler(using=db)
if fill_cache: if fill_cache:
klass_info = get_klass_info(model, max_depth=max_depth, klass_info = get_klass_info(model, max_depth=max_depth,
@ -294,12 +296,12 @@ class QuerySet(object):
obj, _ = get_cached_row(row, index_start, db, klass_info, obj, _ = get_cached_row(row, index_start, db, klass_info,
offset=len(aggregate_select)) offset=len(aggregate_select))
else: else:
# Omit aggregates in object creation.
row_data = row[index_start:aggregate_start]
if skip: if skip:
row_data = row[index_start:aggregate_start]
obj = model_cls(**dict(zip(init_list, row_data))) obj = model_cls(**dict(zip(init_list, row_data)))
else: else:
# Omit aggregates in object creation. obj = model(*row_data)
obj = model(*row[index_start:aggregate_start])
# Store the source database of the object # Store the source database of the object
obj._state.db = db obj._state.db = db
@ -313,7 +315,11 @@ class QuerySet(object):
# Add the aggregates to the model # Add the aggregates to the model
if aggregate_select: if aggregate_select:
for i, aggregate in enumerate(aggregate_select): for i, aggregate in enumerate(aggregate_select):
setattr(obj, aggregate, row[i+aggregate_start]) setattr(obj, aggregate, row[i + aggregate_start])
# Add the known related object to the model, if there is one
if kro_instance:
setattr(obj, kro_attname, kro_instance)
yield obj yield obj
@ -864,6 +870,7 @@ class QuerySet(object):
c = klass(model=self.model, query=query, using=self._db) c = klass(model=self.model, query=query, using=self._db)
c._for_write = self._for_write c._for_write = self._for_write
c._prefetch_related_lookups = self._prefetch_related_lookups[:] c._prefetch_related_lookups = self._prefetch_related_lookups[:]
c._known_related_object = self._known_related_object
c.__dict__.update(kwargs) c.__dict__.update(kwargs)
if setup and hasattr(c, '_setup_query'): if setup and hasattr(c, '_setup_query'):
c._setup_query() c._setup_query()
@ -1781,9 +1788,7 @@ def prefetch_one_level(instances, prefetcher, attname):
rel_obj_cache = {} rel_obj_cache = {}
for rel_obj in all_related_objects: for rel_obj in all_related_objects:
rel_attr_val = rel_obj_attr(rel_obj) rel_attr_val = rel_obj_attr(rel_obj)
if rel_attr_val not in rel_obj_cache: rel_obj_cache.setdefault(rel_attr_val, []).append(rel_obj)
rel_obj_cache[rel_attr_val] = []
rel_obj_cache[rel_attr_val].append(rel_obj)
for obj in instances: for obj in instances:
instance_attr_val = instance_attr(obj) instance_attr_val = instance_attr(obj)

View File

@ -6,8 +6,6 @@ large and/or so that they can be used by other modules without getting into
circular import difficulties. circular import difficulties.
""" """
import weakref
from django.db.backends import util from django.db.backends import util
from django.utils import tree from django.utils import tree
@ -70,8 +68,6 @@ class DeferredAttribute(object):
""" """
def __init__(self, field_name, model): def __init__(self, field_name, model):
self.field_name = field_name self.field_name = field_name
self.model_ref = weakref.ref(model)
self.loaded = False
def __get__(self, instance, owner): def __get__(self, instance, owner):
""" """
@ -79,27 +75,32 @@ class DeferredAttribute(object):
Returns the cached value. Returns the cached value.
""" """
from django.db.models.fields import FieldDoesNotExist from django.db.models.fields import FieldDoesNotExist
non_deferred_model = instance._meta.proxy_for_model
opts = non_deferred_model._meta
assert instance is not None assert instance is not None
cls = self.model_ref()
data = instance.__dict__ data = instance.__dict__
if data.get(self.field_name, self) is self: if data.get(self.field_name, self) is self:
# self.field_name is the attname of the field, but only() takes the # self.field_name is the attname of the field, but only() takes the
# actual name, so we need to translate it here. # actual name, so we need to translate it here.
try: try:
cls._meta.get_field_by_name(self.field_name) f = opts.get_field_by_name(self.field_name)[0]
name = self.field_name
except FieldDoesNotExist: except FieldDoesNotExist:
name = [f.name for f in cls._meta.fields f = [f for f in opts.fields
if f.attname == self.field_name][0] if f.attname == self.field_name][0]
# We use only() instead of values() here because we want the name = f.name
# various data coersion methods (to_python(), etc.) to be called # Lets see if the field is part of the parent chain. If so we
# here. # might be able to reuse the already loaded value. Refs #18343.
val = getattr( val = self._check_parent_chain(instance, name)
cls._base_manager.filter(pk=instance.pk).only(name).using( if val is None:
instance._state.db).get(), # We use only() instead of values() here because we want the
self.field_name # various data coersion methods (to_python(), etc.) to be
) # called here.
val = getattr(
non_deferred_model._base_manager.only(name).using(
instance._state.db).get(pk=instance.pk),
self.field_name
)
data[self.field_name] = val data[self.field_name] = val
return data[self.field_name] return data[self.field_name]
@ -110,6 +111,20 @@ class DeferredAttribute(object):
""" """
instance.__dict__[self.field_name] = value instance.__dict__[self.field_name] = value
def _check_parent_chain(self, instance, name):
"""
Check if the field value can be fetched from a parent field already
loaded in the instance. This can be done if the to-be fetched
field is a primary key field.
"""
opts = instance._meta
f = opts.get_field_by_name(name)[0]
link_field = opts.get_ancestor_link(f.model)
if f.primary_key and f != link_field:
return getattr(instance, link_field.attname)
return None
def select_related_descend(field, restricted, requested, reverse=False): def select_related_descend(field, restricted, requested, reverse=False):
""" """
Returns True if this field should be used to descend deeper for Returns True if this field should be used to descend deeper for

View File

@ -455,6 +455,9 @@ class SQLCompiler(object):
alias = self.query.get_initial_alias() alias = self.query.get_initial_alias()
field, target, opts, joins, _, _ = self.query.setup_joins(pieces, field, target, opts, joins, _, _ = self.query.setup_joins(pieces,
opts, alias, False) opts, alias, False)
# We will later on need to promote those joins that were added to the
# query afresh above.
joins_to_promote = [j for j in joins if self.query.alias_refcount[j] < 2]
alias = joins[-1] alias = joins[-1]
col = target.column col = target.column
if not field.rel: if not field.rel:
@ -466,8 +469,9 @@ class SQLCompiler(object):
# Must use left outer joins for nullable fields and their relations. # Must use left outer joins for nullable fields and their relations.
# Ordering or distinct must not affect the returned set, and INNER # Ordering or distinct must not affect the returned set, and INNER
# JOINS for nullable fields could do this. # JOINS for nullable fields could do this.
self.query.promote_alias_chain(joins, if joins_to_promote:
self.query.alias_map[joins[0]].join_type == self.query.LOUTER) self.query.promote_alias_chain(joins_to_promote,
self.query.alias_map[joins_to_promote[0]].join_type == self.query.LOUTER)
return field, col, alias, joins, opts return field, col, alias, joins, opts
def _final_join_removal(self, col, alias): def _final_join_removal(self, col, alias):
@ -1015,6 +1019,12 @@ class SQLUpdateCompiler(SQLCompiler):
query.extra = {} query.extra = {}
query.select = [] query.select = []
query.add_fields([query.model._meta.pk.name]) query.add_fields([query.model._meta.pk.name])
# Recheck the count - it is possible that fiddling with the select
# fields above removes tables from the query. Refs #18304.
count = query.count_active_tables()
if not self.query.related_updates and count == 1:
return
must_pre_select = count > 1 and not self.connection.features.update_can_self_select must_pre_select = count > 1 and not self.connection.features.update_can_self_select
# Now we adjust the current query: reset the where clause and get rid # Now we adjust the current query: reset the where clause and get rid

View File

@ -4,6 +4,7 @@ Code to manage the creation and SQL rendering of 'where' constraints.
from __future__ import absolute_import from __future__ import absolute_import
import collections
import datetime import datetime
from itertools import repeat from itertools import repeat
@ -49,7 +50,7 @@ class WhereNode(tree.Node):
return return
obj, lookup_type, value = data obj, lookup_type, value = data
if hasattr(value, '__iter__') and hasattr(value, 'next'): if isinstance(value, collections.Iterator):
# Consume any generators immediately, so that we can determine # Consume any generators immediately, so that we can determine
# emptiness and transform any non-empty values correctly. # emptiness and transform any non-empty values correctly.
value = list(value) value = list(value)

View File

@ -155,12 +155,12 @@ class BoundMethodWeakref(object):
def __nonzero__( self ): def __nonzero__( self ):
"""Whether we are still a valid reference""" """Whether we are still a valid reference"""
return self() is not None return self() is not None
def __cmp__( self, other ): def __eq__(self, other):
"""Compare with another reference""" """Compare with another reference"""
if not isinstance (other,self.__class__): if not isinstance(other, self.__class__):
return cmp( self.__class__, type(other) ) return self.__class__ == type(other)
return cmp( self.key, other.key) return self.key == other.key
def __call__(self): def __call__(self):
"""Return a strong reference to the bound method """Return a strong reference to the bound method

View File

@ -446,7 +446,7 @@ class RegexField(CharField):
def _set_regex(self, regex): def _set_regex(self, regex):
if isinstance(regex, basestring): if isinstance(regex, basestring):
regex = re.compile(regex) regex = re.compile(regex, re.UNICODE)
self._regex = regex self._regex = regex
if hasattr(self, '_regex_validator') and self._regex_validator in self.validators: if hasattr(self, '_regex_validator') and self._regex_validator in self.validators:
self.validators.remove(self._regex_validator) self.validators.remove(self._regex_validator)
@ -932,12 +932,16 @@ class FilePathField(ChoiceField):
self.choices.append((f, f.replace(path, "", 1))) self.choices.append((f, f.replace(path, "", 1)))
if self.allow_folders: if self.allow_folders:
for f in dirs: for f in dirs:
if f == '__pycache__':
continue
if self.match is None or self.match_re.search(f): if self.match is None or self.match_re.search(f):
f = os.path.join(root, f) f = os.path.join(root, f)
self.choices.append((f, f.replace(path, "", 1))) self.choices.append((f, f.replace(path, "", 1)))
else: else:
try: try:
for f in sorted(os.listdir(self.path)): for f in sorted(os.listdir(self.path)):
if f == '__pycache__':
continue
full_file = os.path.join(self.path, f) full_file = os.path.join(self.path, f)
if (((self.allow_files and os.path.isfile(full_file)) or if (((self.allow_files and os.path.isfile(full_file)) or
(self.allow_folders and os.path.isdir(full_file))) and (self.allow_folders and os.path.isdir(full_file))) and

View File

@ -388,10 +388,10 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
parent = (object,) parent = (object,)
if hasattr(form, 'Meta'): if hasattr(form, 'Meta'):
parent = (form.Meta, object) parent = (form.Meta, object)
Meta = type('Meta', parent, attrs) Meta = type(b'Meta', parent, attrs)
# Give this new form class a reasonable name. # Give this new form class a reasonable name.
class_name = model.__name__ + 'Form' class_name = model.__name__ + b'Form'
# Class attributes for the new form class. # Class attributes for the new form class.
form_class_attrs = { form_class_attrs = {

View File

@ -18,7 +18,7 @@ _cookie_encodes_correctly = Cookie.SimpleCookie().value_encode(';') == (';', '"\
# See ticket #13007, http://bugs.python.org/issue2193 and http://trac.edgewall.org/ticket/2256 # See ticket #13007, http://bugs.python.org/issue2193 and http://trac.edgewall.org/ticket/2256
_tc = Cookie.SimpleCookie() _tc = Cookie.SimpleCookie()
try: try:
_tc.load('foo:bar=1') _tc.load(b'foo:bar=1')
_cookie_allows_colon_in_names = True _cookie_allows_colon_in_names = True
except Cookie.CookieError: except Cookie.CookieError:
_cookie_allows_colon_in_names = False _cookie_allows_colon_in_names = False
@ -650,8 +650,8 @@ class HttpResponse(object):
def _get_content(self): def _get_content(self):
if self.has_header('Content-Encoding'): if self.has_header('Content-Encoding'):
return ''.join([str(e) for e in self._container]) return b''.join([str(e) for e in self._container])
return ''.join([smart_str(e, self._charset) for e in self._container]) return b''.join([smart_str(e, self._charset) for e in self._container])
def _set_content(self, value): def _set_content(self, value):
if hasattr(value, '__iter__'): if hasattr(value, '__iter__'):

View File

@ -268,7 +268,7 @@ class LazyStream(object):
""" """
self._producer = producer self._producer = producer
self._empty = False self._empty = False
self._leftover = '' self._leftover = b''
self.length = length self.length = length
self.position = 0 self.position = 0
self._remaining = length self._remaining = length
@ -282,7 +282,7 @@ class LazyStream(object):
remaining = (size is not None and [size] or [self._remaining])[0] remaining = (size is not None and [size] or [self._remaining])[0]
# do the whole thing in one shot if no limit was provided. # do the whole thing in one shot if no limit was provided.
if remaining is None: if remaining is None:
yield ''.join(self) yield b''.join(self)
return return
# otherwise do some bookkeeping to return exactly enough # otherwise do some bookkeeping to return exactly enough
@ -298,7 +298,7 @@ class LazyStream(object):
remaining -= len(emitting) remaining -= len(emitting)
yield emitting yield emitting
out = ''.join(parts()) out = b''.join(parts())
return out return out
def next(self): def next(self):
@ -311,7 +311,7 @@ class LazyStream(object):
""" """
if self._leftover: if self._leftover:
output = self._leftover output = self._leftover
self._leftover = '' self._leftover = b''
else: else:
output = next(self._producer) output = next(self._producer)
self._unget_history = [] self._unget_history = []
@ -341,7 +341,7 @@ class LazyStream(object):
return return
self._update_unget_history(len(bytes)) self._update_unget_history(len(bytes))
self.position -= len(bytes) self.position -= len(bytes)
self._leftover = ''.join([bytes, self._leftover]) self._leftover = b''.join([bytes, self._leftover])
def _update_unget_history(self, num_bytes): def _update_unget_history(self, num_bytes):
""" """
@ -459,7 +459,7 @@ class BoundaryIter(object):
if not chunks: if not chunks:
raise StopIteration() raise StopIteration()
chunk = ''.join(chunks) chunk = b''.join(chunks)
boundary = self._find_boundary(chunk, len(chunk) < self._rollback) boundary = self._find_boundary(chunk, len(chunk) < self._rollback)
if boundary: if boundary:
@ -495,9 +495,9 @@ class BoundaryIter(object):
end = index end = index
next = index + len(self._boundary) next = index + len(self._boundary)
# backup over CRLF # backup over CRLF
if data[max(0,end-1)] == '\n': if data[max(0,end-1)] == b'\n':
end -= 1 end -= 1
if data[max(0,end-1)] == '\r': if data[max(0,end-1)] == b'\r':
end -= 1 end -= 1
return end, next return end, next
@ -531,7 +531,7 @@ def parse_boundary_stream(stream, max_header_size):
# 'find' returns the top of these four bytes, so we'll # 'find' returns the top of these four bytes, so we'll
# need to munch them later to prevent them from polluting # need to munch them later to prevent them from polluting
# the payload. # the payload.
header_end = chunk.find('\r\n\r\n') header_end = chunk.find(b'\r\n\r\n')
def _parse_header(line): def _parse_header(line):
main_value_pair, params = parse_header(line) main_value_pair, params = parse_header(line)
@ -557,7 +557,7 @@ def parse_boundary_stream(stream, max_header_size):
outdict = {} outdict = {}
# Eliminate blank lines # Eliminate blank lines
for line in header.split('\r\n'): for line in header.split(b'\r\n'):
# This terminology ("main value" and "dictionary of # This terminology ("main value" and "dictionary of
# parameters") is from the Python docs. # parameters") is from the Python docs.
try: try:
@ -580,7 +580,7 @@ def parse_boundary_stream(stream, max_header_size):
class Parser(object): class Parser(object):
def __init__(self, stream, boundary): def __init__(self, stream, boundary):
self._stream = stream self._stream = stream
self._separator = '--' + boundary self._separator = b'--' + boundary
def __iter__(self): def __iter__(self):
boundarystream = InterBoundaryIter(self._stream, self._separator) boundarystream = InterBoundaryIter(self._stream, self._separator)
@ -590,27 +590,27 @@ class Parser(object):
def parse_header(line): def parse_header(line):
""" Parse the header into a key-value. """ """ Parse the header into a key-value. """
plist = _parse_header_params(';' + line) plist = _parse_header_params(b';' + line)
key = plist.pop(0).lower() key = plist.pop(0).lower()
pdict = {} pdict = {}
for p in plist: for p in plist:
i = p.find('=') i = p.find(b'=')
if i >= 0: if i >= 0:
name = p[:i].strip().lower() name = p[:i].strip().lower()
value = p[i+1:].strip() value = p[i+1:].strip()
if len(value) >= 2 and value[0] == value[-1] == '"': if len(value) >= 2 and value[0] == value[-1] == b'"':
value = value[1:-1] value = value[1:-1]
value = value.replace('\\\\', '\\').replace('\\"', '"') value = value.replace(b'\\\\', b'\\').replace(b'\\"', b'"')
pdict[name] = value pdict[name] = value
return key, pdict return key, pdict
def _parse_header_params(s): def _parse_header_params(s):
plist = [] plist = []
while s[:1] == ';': while s[:1] == b';':
s = s[1:] s = s[1:]
end = s.find(';') end = s.find(b';')
while end > 0 and s.count('"', 0, end) % 2: while end > 0 and s.count(b'"', 0, end) % 2:
end = s.find(';', end + 1) end = s.find(b';', end + 1)
if end < 0: if end < 0:
end = len(s) end = len(s)
f = s[:end] f = s[:end]

View File

@ -52,7 +52,7 @@ class Loader(BaseLoader):
def load_template_source(self, template_name, template_dirs=None): def load_template_source(self, template_name, template_dirs=None):
for filepath in self.get_template_sources(template_name, template_dirs): for filepath in self.get_template_sources(template_name, template_dirs):
try: try:
with open(filepath) as fp: with open(filepath, 'rb') as fp:
return (fp.read().decode(settings.FILE_CHARSET), filepath) return (fp.read().decode(settings.FILE_CHARSET), filepath)
except IOError: except IOError:
pass pass

View File

@ -34,7 +34,7 @@ class Loader(BaseLoader):
tried = [] tried = []
for filepath in self.get_template_sources(template_name, template_dirs): for filepath in self.get_template_sources(template_name, template_dirs):
try: try:
with open(filepath) as fp: with open(filepath, 'rb') as fp:
return (fp.read().decode(settings.FILE_CHARSET), filepath) return (fp.read().decode(settings.FILE_CHARSET), filepath)
except IOError: except IOError:
tried.append(filepath) tried.append(filepath)

View File

@ -148,7 +148,7 @@ class BlockTranslateNode(Node):
context.pop() context.pop()
try: try:
result = result % data result = result % data
except KeyError: except (KeyError, ValueError):
with translation.override(None): with translation.override(None):
result = self.render(context) result = self.render(context)
return result return result

View File

@ -155,7 +155,6 @@ def encode_file(boundary, key, file):
] ]
class RequestFactory(object): class RequestFactory(object):
""" """
Class that lets you create mock Request objects for use in testing. Class that lets you create mock Request objects for use in testing.
@ -227,7 +226,7 @@ class RequestFactory(object):
return urllib.unquote(parsed[2]) return urllib.unquote(parsed[2])
def get(self, path, data={}, **extra): def get(self, path, data={}, **extra):
"Construct a GET request" "Construct a GET request."
parsed = urlparse(path) parsed = urlparse(path)
r = { r = {
@ -270,49 +269,39 @@ class RequestFactory(object):
r.update(extra) r.update(extra)
return self.request(**r) return self.request(**r)
def options(self, path, data={}, **extra): def options(self, path, data='', content_type='application/octet-stream',
"Constrict an OPTIONS request" **extra):
"Construct an OPTIONS request."
return self.generic('OPTIONS', path, data, content_type, **extra)
parsed = urlparse(path) def put(self, path, data='', content_type='application/octet-stream',
r = {
'PATH_INFO': self._get_path(parsed),
'QUERY_STRING': urlencode(data, doseq=True) or parsed[4],
'REQUEST_METHOD': 'OPTIONS',
}
r.update(extra)
return self.request(**r)
def put(self, path, data={}, content_type=MULTIPART_CONTENT,
**extra): **extra):
"Construct a PUT request." "Construct a PUT request."
return self.generic('PUT', path, data, content_type, **extra)
put_data = self._encode_data(data, content_type) def delete(self, path, data='', content_type='application/octet-stream',
**extra):
"Construct a DELETE request."
return self.generic('DELETE', path, data, content_type, **extra)
def generic(self, method, path,
data='', content_type='application/octet-stream', **extra):
parsed = urlparse(path) parsed = urlparse(path)
data = smart_str(data, settings.DEFAULT_CHARSET)
r = { r = {
'CONTENT_LENGTH': len(put_data),
'CONTENT_TYPE': content_type,
'PATH_INFO': self._get_path(parsed), 'PATH_INFO': self._get_path(parsed),
'QUERY_STRING': parsed[4], 'QUERY_STRING': parsed[4],
'REQUEST_METHOD': 'PUT', 'REQUEST_METHOD': method,
'wsgi.input': FakePayload(put_data),
} }
if data:
r.update({
'CONTENT_LENGTH': len(data),
'CONTENT_TYPE': content_type,
'wsgi.input': FakePayload(data),
})
r.update(extra) r.update(extra)
return self.request(**r) return self.request(**r)
def delete(self, path, data={}, **extra):
"Construct a DELETE request."
parsed = urlparse(path)
r = {
'PATH_INFO': self._get_path(parsed),
'QUERY_STRING': urlencode(data, doseq=True) or parsed[4],
'REQUEST_METHOD': 'DELETE',
}
r.update(extra)
return self.request(**r)
class Client(RequestFactory): class Client(RequestFactory):
""" """
A class that can act as a client for testing purposes. A class that can act as a client for testing purposes.
@ -445,30 +434,35 @@ class Client(RequestFactory):
response = self._handle_redirects(response, **extra) response = self._handle_redirects(response, **extra)
return response return response
def options(self, path, data={}, follow=False, **extra): def options(self, path, data='', content_type='application/octet-stream',
follow=False, **extra):
""" """
Request a response from the server using OPTIONS. Request a response from the server using OPTIONS.
""" """
response = super(Client, self).options(path, data=data, **extra) response = super(Client, self).options(path,
data=data, content_type=content_type, **extra)
if follow: if follow:
response = self._handle_redirects(response, **extra) response = self._handle_redirects(response, **extra)
return response return response
def put(self, path, data={}, content_type=MULTIPART_CONTENT, def put(self, path, data='', content_type='application/octet-stream',
follow=False, **extra): follow=False, **extra):
""" """
Send a resource to the server using PUT. Send a resource to the server using PUT.
""" """
response = super(Client, self).put(path, data=data, content_type=content_type, **extra) response = super(Client, self).put(path,
data=data, content_type=content_type, **extra)
if follow: if follow:
response = self._handle_redirects(response, **extra) response = self._handle_redirects(response, **extra)
return response return response
def delete(self, path, data={}, follow=False, **extra): def delete(self, path, data='', content_type='application/octet-stream',
follow=False, **extra):
""" """
Send a DELETE request to the server. Send a DELETE request to the server.
""" """
response = super(Client, self).delete(path, data=data, **extra) response = super(Client, self).delete(path,
data=data, content_type=content_type, **extra)
if follow: if follow:
response = self._handle_redirects(response, **extra) response = self._handle_redirects(response, **extra)
return response return response

View File

@ -23,7 +23,7 @@ import time
from django.conf import settings from django.conf import settings
from django.core.cache import get_cache from django.core.cache import get_cache
from django.utils.encoding import smart_str, iri_to_uri, force_unicode from django.utils.encoding import iri_to_uri, force_unicode
from django.utils.http import http_date from django.utils.http import http_date
from django.utils.timezone import get_current_timezone_name from django.utils.timezone import get_current_timezone_name
from django.utils.translation import get_language from django.utils.translation import get_language
@ -53,7 +53,7 @@ def patch_cache_control(response, **kwargs):
if t[1] is True: if t[1] is True:
return t[0] return t[0]
else: else:
return t[0] + '=' + smart_str(t[1]) return '%s=%s' % (t[0], t[1])
if response.has_header('Cache-Control'): if response.has_header('Cache-Control'):
cc = cc_delim_re.split(response['Cache-Control']) cc = cc_delim_re.split(response['Cache-Control'])

View File

@ -8,6 +8,7 @@ import hashlib
import binascii import binascii
import operator import operator
import time import time
from functools import reduce
# Use the system PRNG if possible # Use the system PRNG if possible
import random import random
@ -23,8 +24,8 @@ except NotImplementedError:
from django.conf import settings from django.conf import settings
_trans_5c = "".join([chr(x ^ 0x5C) for x in xrange(256)]) _trans_5c = b"".join([chr(x ^ 0x5C) for x in xrange(256)])
_trans_36 = "".join([chr(x ^ 0x36) for x in xrange(256)]) _trans_36 = b"".join([chr(x ^ 0x36) for x in xrange(256)])
def salted_hmac(key_salt, value, secret=None): def salted_hmac(key_salt, value, secret=None):
@ -148,11 +149,11 @@ def pbkdf2(password, salt, iterations, dklen=0, digest=None):
def F(i): def F(i):
def U(): def U():
u = salt + struct.pack('>I', i) u = salt + struct.pack(b'>I', i)
for j in xrange(int(iterations)): for j in xrange(int(iterations)):
u = _fast_hmac(password, u, digest).digest() u = _fast_hmac(password, u, digest).digest()
yield _bin_to_long(u) yield _bin_to_long(u)
return _long_to_bin(reduce(operator.xor, U()), hex_format_string) return _long_to_bin(reduce(operator.xor, U()), hex_format_string)
T = [F(x) for x in range(1, l + 1)] T = [F(x) for x in range(1, l + 1)]
return ''.join(T[:-1]) + T[-1][:r] return b''.join(T[:-1]) + T[-1][:r]

View File

@ -1,4 +1,3 @@
import types
import urllib import urllib
import locale import locale
import datetime import datetime
@ -45,7 +44,7 @@ def is_protected_type(obj):
force_unicode(strings_only=True). force_unicode(strings_only=True).
""" """
return isinstance(obj, ( return isinstance(obj, (
types.NoneType, type(None),
int, long, int, long,
datetime.datetime, datetime.date, datetime.time, datetime.datetime, datetime.date, datetime.time,
float, Decimal) float, Decimal)
@ -107,7 +106,7 @@ def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
If strings_only is True, don't convert (some) non-string-like objects. If strings_only is True, don't convert (some) non-string-like objects.
""" """
if strings_only and isinstance(s, (types.NoneType, int)): if strings_only and (s is None or isinstance(s, int)):
return s return s
if isinstance(s, Promise): if isinstance(s, Promise):
return unicode(s).encode(encoding, errors) return unicode(s).encode(encoding, errors)
@ -154,7 +153,7 @@ def iri_to_uri(iri):
# converted. # converted.
if iri is None: if iri is None:
return iri return iri
return urllib.quote(smart_str(iri), safe="/#%[]=:;$&()+,!?*@'~") return urllib.quote(smart_str(iri), safe=b"/#%[]=:;$&()+,!?*@'~")
def filepath_to_uri(path): def filepath_to_uri(path):
"""Convert an file system path to a URI portion that is suitable for """Convert an file system path to a URI portion that is suitable for
@ -173,7 +172,7 @@ def filepath_to_uri(path):
return path return path
# I know about `os.sep` and `os.altsep` but I want to leave # I know about `os.sep` and `os.altsep` but I want to leave
# some flexibility for hardcoding separators. # some flexibility for hardcoding separators.
return urllib.quote(smart_str(path).replace("\\", "/"), safe="/~!*()'") return urllib.quote(smart_str(path).replace("\\", "/"), safe=b"/~!*()'")
# The encoding of the default system locale but falls back to the # The encoding of the default system locale but falls back to the
# given fallback encoding if the encoding is unsupported by python or could # given fallback encoding if the encoding is unsupported by python or could

View File

@ -58,6 +58,7 @@ def lazy(func, *resultclasses):
function is evaluated on every access. function is evaluated on every access.
""" """
@total_ordering
class __proxy__(Promise): class __proxy__(Promise):
""" """
Encapsulate a function call and act as a proxy for methods that are Encapsulate a function call and act as a proxy for methods that are
@ -124,17 +125,23 @@ def lazy(func, *resultclasses):
def __str_cast(self): def __str_cast(self):
return str(func(*self.__args, **self.__kw)) return str(func(*self.__args, **self.__kw))
def __cmp__(self, rhs): def __cast(self):
if self._delegate_str: if self._delegate_str:
s = str(func(*self.__args, **self.__kw)) return self.__str_cast()
elif self._delegate_unicode: elif self._delegate_unicode:
s = unicode(func(*self.__args, **self.__kw)) return self.__unicode_cast()
else: else:
s = func(*self.__args, **self.__kw) return func(*self.__args, **self.__kw)
if isinstance(rhs, Promise):
return -cmp(rhs, s) def __eq__(self, other):
else: if isinstance(other, Promise):
return cmp(s, rhs) other = other.__cast()
return self.__cast() == other
def __lt__(self, other):
if isinstance(other, Promise):
other = other.__cast()
return self.__cast() < other
def __mod__(self, rhs): def __mod__(self, rhs):
if self._delegate_str: if self._delegate_str:

View File

@ -116,7 +116,7 @@ def smart_urlquote(url):
# contains a % not followed by two hexadecimal digits. See #9655. # contains a % not followed by two hexadecimal digits. See #9655.
if '%' not in url or unquoted_percents_re.search(url): if '%' not in url or unquoted_percents_re.search(url):
# See http://bugs.python.org/issue2637 # See http://bugs.python.org/issue2637
url = urllib.quote(smart_str(url), safe='!*\'();:@&=+$,/?#[]~') url = urllib.quote(smart_str(url), safe=b'!*\'();:@&=+$,/?#[]~')
return force_unicode(url) return force_unicode(url)

View File

@ -155,9 +155,20 @@ class SafeExceptionReporterFilter(ExceptionReporterFilter):
Replaces the values of variables marked as sensitive with Replaces the values of variables marked as sensitive with
stars (*********). stars (*********).
""" """
func_name = tb_frame.f_code.co_name # Loop through the frame's callers to see if the sensitive_variables
func = tb_frame.f_globals.get(func_name) # decorator was used.
sensitive_variables = getattr(func, 'sensitive_variables', []) current_frame = tb_frame.f_back
sensitive_variables = None
while current_frame is not None:
if (current_frame.f_code.co_name == 'sensitive_variables_wrapper'
and 'sensitive_variables_wrapper' in current_frame.f_locals):
# The sensitive_variables decorator was used, so we take note
# of the sensitive variables' names.
wrapper = current_frame.f_locals['sensitive_variables_wrapper']
sensitive_variables = getattr(wrapper, 'sensitive_variables', None)
break
current_frame = current_frame.f_back
cleansed = [] cleansed = []
if self.is_active(request) and sensitive_variables: if self.is_active(request) and sensitive_variables:
if sensitive_variables == '__ALL__': if sensitive_variables == '__ALL__':
@ -333,7 +344,7 @@ class ExceptionReporter(object):
source = source.splitlines() source = source.splitlines()
if source is None: if source is None:
try: try:
with open(filename) as fp: with open(filename, 'rb') as fp:
source = fp.readlines() source = fp.readlines()
except (OSError, IOError): except (OSError, IOError):
pass pass

View File

@ -26,13 +26,13 @@ def sensitive_variables(*variables):
""" """
def decorator(func): def decorator(func):
@functools.wraps(func) @functools.wraps(func)
def wrapper(*args, **kwargs): def sensitive_variables_wrapper(*args, **kwargs):
if variables: if variables:
wrapper.sensitive_variables = variables sensitive_variables_wrapper.sensitive_variables = variables
else: else:
wrapper.sensitive_variables = '__ALL__' sensitive_variables_wrapper.sensitive_variables = '__ALL__'
return func(*args, **kwargs) return func(*args, **kwargs)
return wrapper return sensitive_variables_wrapper
return decorator return decorator
@ -61,11 +61,11 @@ def sensitive_post_parameters(*parameters):
""" """
def decorator(view): def decorator(view):
@functools.wraps(view) @functools.wraps(view)
def wrapper(request, *args, **kwargs): def sensitive_post_parameters_wrapper(request, *args, **kwargs):
if parameters: if parameters:
request.sensitive_post_parameters = parameters request.sensitive_post_parameters = parameters
else: else:
request.sensitive_post_parameters = '__ALL__' request.sensitive_post_parameters = '__ALL__'
return view(request, *args, **kwargs) return view(request, *args, **kwargs)
return wrapper return sensitive_post_parameters_wrapper
return decorator return decorator

View File

@ -79,14 +79,22 @@ class View(object):
return handler(request, *args, **kwargs) return handler(request, *args, **kwargs)
def http_method_not_allowed(self, request, *args, **kwargs): def http_method_not_allowed(self, request, *args, **kwargs):
allowed_methods = [m for m in self.http_method_names if hasattr(self, m)]
logger.warning('Method Not Allowed (%s): %s', request.method, request.path, logger.warning('Method Not Allowed (%s): %s', request.method, request.path,
extra={ extra={
'status_code': 405, 'status_code': 405,
'request': self.request 'request': self.request
} }
) )
return http.HttpResponseNotAllowed(allowed_methods) return http.HttpResponseNotAllowed(self._allowed_methods())
def options(self, request, *args, **kwargs):
response = http.HttpResponse()
response['Allow'] = ', '.join(self._allowed_methods())
response['Content-Length'] = 0
return response
def _allowed_methods(self):
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
class TemplateResponseMixin(object): class TemplateResponseMixin(object):

View File

@ -23,7 +23,9 @@ class YearMixin(object):
return self.year_format return self.year_format
def get_year(self): def get_year(self):
"Return the year for which this view should display data" """
Return the year for which this view should display data.
"""
year = self.year year = self.year
if year is None: if year is None:
try: try:
@ -35,6 +37,32 @@ class YearMixin(object):
raise Http404(_(u"No year specified")) raise Http404(_(u"No year specified"))
return year return year
def get_next_year(self, date):
"""
Get the next valid year.
"""
return _get_next_prev(self, date, is_previous=False, period='year')
def get_previous_year(self, date):
"""
Get the previous valid year.
"""
return _get_next_prev(self, date, is_previous=True, period='year')
def _get_next_year(self, date):
"""
Return the start date of the next interval.
The interval is defined by start date <= item date < next start date.
"""
return date.replace(year=date.year + 1, month=1, day=1)
def _get_current_year(self, date):
"""
Return the start date of the current interval.
"""
return date.replace(month=1, day=1)
class MonthMixin(object): class MonthMixin(object):
month_format = '%b' month_format = '%b'
@ -48,7 +76,9 @@ class MonthMixin(object):
return self.month_format return self.month_format
def get_month(self): def get_month(self):
"Return the month for which this view should display data" """
Return the month for which this view should display data.
"""
month = self.month month = self.month
if month is None: if month is None:
try: try:
@ -64,20 +94,30 @@ class MonthMixin(object):
""" """
Get the next valid month. Get the next valid month.
""" """
# next must be the first day of the next month. return _get_next_prev(self, date, is_previous=False, period='month')
if date.month == 12:
next = date.replace(year=date.year + 1, month=1, day=1)
else:
next = date.replace(month=date.month + 1, day=1)
return _get_next_prev(self, next, is_previous=False, period='month')
def get_previous_month(self, date): def get_previous_month(self, date):
""" """
Get the previous valid month. Get the previous valid month.
""" """
# prev must be the last day of the previous month. return _get_next_prev(self, date, is_previous=True, period='month')
prev = date.replace(day=1) - datetime.timedelta(days=1)
return _get_next_prev(self, prev, is_previous=True, period='month') def _get_next_month(self, date):
"""
Return the start date of the next interval.
The interval is defined by start date <= item date < next start date.
"""
if date.month == 12:
return date.replace(year=date.year + 1, month=1, day=1)
else:
return date.replace(month=date.month + 1, day=1)
def _get_current_month(self, date):
"""
Return the start date of the previous interval.
"""
return date.replace(day=1)
class DayMixin(object): class DayMixin(object):
@ -92,7 +132,9 @@ class DayMixin(object):
return self.day_format return self.day_format
def get_day(self): def get_day(self):
"Return the day for which this view should display data" """
Return the day for which this view should display data.
"""
day = self.day day = self.day
if day is None: if day is None:
try: try:
@ -108,15 +150,27 @@ class DayMixin(object):
""" """
Get the next valid day. Get the next valid day.
""" """
next = date + datetime.timedelta(days=1) return _get_next_prev(self, date, is_previous=False, period='day')
return _get_next_prev(self, next, is_previous=False, period='day')
def get_previous_day(self, date): def get_previous_day(self, date):
""" """
Get the previous valid day. Get the previous valid day.
""" """
prev = date - datetime.timedelta(days=1) return _get_next_prev(self, date, is_previous=True, period='day')
return _get_next_prev(self, prev, is_previous=True, period='day')
def _get_next_day(self, date):
"""
Return the start date of the next interval.
The interval is defined by start date <= item date < next start date.
"""
return date + datetime.timedelta(days=1)
def _get_current_day(self, date):
"""
Return the start date of the current interval.
"""
return date
class WeekMixin(object): class WeekMixin(object):
@ -131,7 +185,9 @@ class WeekMixin(object):
return self.week_format return self.week_format
def get_week(self): def get_week(self):
"Return the week for which this view should display data" """
Return the week for which this view should display data
"""
week = self.week week = self.week
if week is None: if week is None:
try: try:
@ -147,19 +203,34 @@ class WeekMixin(object):
""" """
Get the next valid week. Get the next valid week.
""" """
# next must be the first day of the next week. return _get_next_prev(self, date, is_previous=False, period='week')
next = date + datetime.timedelta(days=7 - self._get_weekday(date))
return _get_next_prev(self, next, is_previous=False, period='week')
def get_previous_week(self, date): def get_previous_week(self, date):
""" """
Get the previous valid week. Get the previous valid week.
""" """
# prev must be the last day of the previous week. return _get_next_prev(self, date, is_previous=True, period='week')
prev = date - datetime.timedelta(days=self._get_weekday(date) + 1)
return _get_next_prev(self, prev, is_previous=True, period='week') def _get_next_week(self, date):
"""
Return the start date of the next interval.
The interval is defined by start date <= item date < next start date.
"""
return date + datetime.timedelta(days=7 - self._get_weekday(date))
def _get_current_week(self, date):
"""
Return the start date of the current interval.
"""
return date - datetime.timedelta(self._get_weekday(date))
def _get_weekday(self, date): def _get_weekday(self, date):
"""
Return the weekday for a given date.
The first day according to the week format is 0 and the last day is 6.
"""
week_format = self.get_week_format() week_format = self.get_week_format()
if week_format == '%W': # week starts on Monday if week_format == '%W': # week starts on Monday
return date.weekday() return date.weekday()
@ -168,6 +239,7 @@ class WeekMixin(object):
else: else:
raise ValueError("unknown week format: %s" % week_format) raise ValueError("unknown week format: %s" % week_format)
class DateMixin(object): class DateMixin(object):
""" """
Mixin class for views manipulating date-based data. Mixin class for views manipulating date-based data.
@ -255,7 +327,7 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View):
""" """
raise NotImplementedError('A DateView must provide an implementation of get_dated_items()') raise NotImplementedError('A DateView must provide an implementation of get_dated_items()')
def get_dated_queryset(self, **lookup): def get_dated_queryset(self, ordering=None, **lookup):
""" """
Get a queryset properly filtered according to `allow_future` and any Get a queryset properly filtered according to `allow_future` and any
extra lookup kwargs. extra lookup kwargs.
@ -266,14 +338,17 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View):
allow_empty = self.get_allow_empty() allow_empty = self.get_allow_empty()
paginate_by = self.get_paginate_by(qs) paginate_by = self.get_paginate_by(qs)
if ordering is not None:
qs = qs.order_by(ordering)
if not allow_future: if not allow_future:
now = timezone.now() if self.uses_datetime_field else datetime.date.today() now = timezone.now() if self.uses_datetime_field else timezone_today()
qs = qs.filter(**{'%s__lte' % date_field: now}) qs = qs.filter(**{'%s__lte' % date_field: now})
if not allow_empty: if not allow_empty:
# When pagination is enabled, it's better to do a cheap query # When pagination is enabled, it's better to do a cheap query
# than to load the unpaginated queryset in memory. # than to load the unpaginated queryset in memory.
is_empty = not bool(qs) if paginate_by is None else not qs.exists() is_empty = len(qs) == 0 if paginate_by is None else not qs.exists()
if is_empty: if is_empty:
raise Http404(_(u"No %(verbose_name_plural)s available") % { raise Http404(_(u"No %(verbose_name_plural)s available") % {
'verbose_name_plural': force_unicode(qs.model._meta.verbose_name_plural) 'verbose_name_plural': force_unicode(qs.model._meta.verbose_name_plural)
@ -310,15 +385,13 @@ class BaseArchiveIndexView(BaseDateListView):
""" """
Return (date_list, items, extra_context) for this request. Return (date_list, items, extra_context) for this request.
""" """
qs = self.get_dated_queryset() qs = self.get_dated_queryset(ordering='-%s' % self.get_date_field())
date_list = self.get_date_list(qs, 'year') date_list = self.get_date_list(qs, 'year')
if date_list: if not date_list:
object_list = qs.order_by('-' + self.get_date_field()) qs = qs.none()
else:
object_list = qs.none()
return (date_list, object_list, {}) return (date_list, qs, {})
class ArchiveIndexView(MultipleObjectTemplateResponseMixin, BaseArchiveIndexView): class ArchiveIndexView(MultipleObjectTemplateResponseMixin, BaseArchiveIndexView):
@ -344,23 +417,25 @@ class BaseYearArchiveView(YearMixin, BaseDateListView):
date = _date_from_string(year, self.get_year_format()) date = _date_from_string(year, self.get_year_format())
since = self._make_date_lookup_arg(date) since = self._make_date_lookup_arg(date)
until = self._make_date_lookup_arg(datetime.date(date.year + 1, 1, 1)) until = self._make_date_lookup_arg(self._get_next_year(date))
lookup_kwargs = { lookup_kwargs = {
'%s__gte' % date_field: since, '%s__gte' % date_field: since,
'%s__lt' % date_field: until, '%s__lt' % date_field: until,
} }
qs = self.get_dated_queryset(**lookup_kwargs) qs = self.get_dated_queryset(ordering='-%s' % date_field, **lookup_kwargs)
date_list = self.get_date_list(qs, 'month') date_list = self.get_date_list(qs, 'month')
if self.get_make_object_list(): if not self.get_make_object_list():
object_list = qs.order_by('-' + date_field)
else:
# We need this to be a queryset since parent classes introspect it # We need this to be a queryset since parent classes introspect it
# to find information about the model. # to find information about the model.
object_list = qs.none() qs = qs.none()
return (date_list, object_list, {'year': year}) return (date_list, qs, {
'year': date,
'next_year': self.get_next_year(date),
'previous_year': self.get_previous_year(date),
})
def get_make_object_list(self): def get_make_object_list(self):
""" """
@ -392,12 +467,8 @@ class BaseMonthArchiveView(YearMixin, MonthMixin, BaseDateListView):
date = _date_from_string(year, self.get_year_format(), date = _date_from_string(year, self.get_year_format(),
month, self.get_month_format()) month, self.get_month_format())
# Construct a date-range lookup.
since = self._make_date_lookup_arg(date) since = self._make_date_lookup_arg(date)
if date.month == 12: until = self._make_date_lookup_arg(self._get_next_month(date))
until = self._make_date_lookup_arg(datetime.date(date.year + 1, 1, 1))
else:
until = self._make_date_lookup_arg(datetime.date(date.year, date.month + 1, 1))
lookup_kwargs = { lookup_kwargs = {
'%s__gte' % date_field: since, '%s__gte' % date_field: since,
'%s__lt' % date_field: until, '%s__lt' % date_field: until,
@ -442,9 +513,8 @@ class BaseWeekArchiveView(YearMixin, WeekMixin, BaseDateListView):
week_start, '%w', week_start, '%w',
week, week_format) week, week_format)
# Construct a date-range lookup.
since = self._make_date_lookup_arg(date) since = self._make_date_lookup_arg(date)
until = self._make_date_lookup_arg(date + datetime.timedelta(days=7)) until = self._make_date_lookup_arg(self._get_next_week(date))
lookup_kwargs = { lookup_kwargs = {
'%s__gte' % date_field: since, '%s__gte' % date_field: since,
'%s__lt' % date_field: until, '%s__lt' % date_field: until,
@ -585,22 +655,22 @@ def _date_from_string(year, year_format, month='', month_format='', day='', day_
}) })
def _get_next_prev(generic_view, naive_result, is_previous, period): def _get_next_prev(generic_view, date, is_previous, period):
""" """
Helper: Get the next or the previous valid date. The idea is to allow Helper: Get the next or the previous valid date. The idea is to allow
links on month/day views to never be 404s by never providing a date links on month/day views to never be 404s by never providing a date
that'll be invalid for the given view. that'll be invalid for the given view.
This is a bit complicated since it handles both next and previous months This is a bit complicated since it handles different intervals of time,
and days (for MonthArchiveView and DayArchiveView); hence the coupling to generic_view. hence the coupling to generic_view.
However in essence the logic comes down to: However in essence the logic comes down to:
* If allow_empty and allow_future are both true, this is easy: just * If allow_empty and allow_future are both true, this is easy: just
return the naive result (just the next/previous day or month, return the naive result (just the next/previous day/week/month,
reguardless of object existence.) reguardless of object existence.)
* If allow_empty is true, allow_future is false, and the naive month * If allow_empty is true, allow_future is false, and the naive result
isn't in the future, then return it; otherwise return None. isn't in the future, then return it; otherwise return None.
* If allow_empty is false and allow_future is true, return the next * If allow_empty is false and allow_future is true, return the next
@ -616,9 +686,23 @@ def _get_next_prev(generic_view, naive_result, is_previous, period):
allow_empty = generic_view.get_allow_empty() allow_empty = generic_view.get_allow_empty()
allow_future = generic_view.get_allow_future() allow_future = generic_view.get_allow_future()
# If allow_empty is True the naive value will be valid get_current = getattr(generic_view, '_get_current_%s' % period)
get_next = getattr(generic_view, '_get_next_%s' % period)
# Bounds of the current interval
start, end = get_current(date), get_next(date)
# If allow_empty is True, the naive result will be valid
if allow_empty: if allow_empty:
result = naive_result if is_previous:
result = get_current(start - datetime.timedelta(days=1))
else:
result = end
if allow_future or result <= timezone_today():
return result
else:
return None
# Otherwise, we'll need to go to the database to look for an object # Otherwise, we'll need to go to the database to look for an object
# whose date_field is at least (greater than/less than) the given # whose date_field is at least (greater than/less than) the given
@ -627,12 +711,22 @@ def _get_next_prev(generic_view, naive_result, is_previous, period):
# Construct a lookup and an ordering depending on whether we're doing # Construct a lookup and an ordering depending on whether we're doing
# a previous date or a next date lookup. # a previous date or a next date lookup.
if is_previous: if is_previous:
lookup = {'%s__lte' % date_field: generic_view._make_date_lookup_arg(naive_result)} lookup = {'%s__lt' % date_field: generic_view._make_date_lookup_arg(start)}
ordering = '-%s' % date_field ordering = '-%s' % date_field
else: else:
lookup = {'%s__gte' % date_field: generic_view._make_date_lookup_arg(naive_result)} lookup = {'%s__gte' % date_field: generic_view._make_date_lookup_arg(end)}
ordering = date_field ordering = date_field
# Filter out objects in the future if appropriate.
if not allow_future:
# Fortunately, to match the implementation of allow_future,
# we need __lte, which doesn't conflict with __lt above.
if generic_view.uses_datetime_field:
now = timezone.now()
else:
now = timezone_today()
lookup['%s__lte' % date_field] = now
qs = generic_view.get_queryset().filter(**lookup).order_by(ordering) qs = generic_view.get_queryset().filter(**lookup).order_by(ordering)
# Snag the first object from the queryset; if it doesn't exist that # Snag the first object from the queryset; if it doesn't exist that
@ -640,26 +734,23 @@ def _get_next_prev(generic_view, naive_result, is_previous, period):
try: try:
result = getattr(qs[0], date_field) result = getattr(qs[0], date_field)
except IndexError: except IndexError:
result = None return None
# Convert datetimes to a dates # Convert datetimes to dates in the current time zone.
if result and generic_view.uses_datetime_field: if generic_view.uses_datetime_field:
if settings.USE_TZ: if settings.USE_TZ:
result = timezone.localtime(result) result = timezone.localtime(result)
result = result.date() result = result.date()
if result: # Return the first day of the period.
if period == 'month': return get_current(result)
# first day of the month
result = result.replace(day=1)
elif period == 'week':
# monday of the week
result = result - datetime.timedelta(days=generic_view._get_weekday(result))
elif period != 'day':
raise ValueError('invalid period: %s' % period)
# Check against future dates.
if result and (allow_future or result < datetime.date.today()): def timezone_today():
return result """
Return the current date in the current time zone.
"""
if settings.USE_TZ:
return timezone.localtime(timezone.now()).date()
else: else:
return None return datetime.date.today()

View File

@ -1,6 +1,5 @@
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.http import Http404 from django.http import Http404
from django.utils.encoding import smart_str
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic.base import TemplateResponseMixin, ContextMixin, View from django.views.generic.base import TemplateResponseMixin, ContextMixin, View
@ -81,7 +80,7 @@ class SingleObjectMixin(ContextMixin):
if self.context_object_name: if self.context_object_name:
return self.context_object_name return self.context_object_name
elif hasattr(obj, '_meta'): elif hasattr(obj, '_meta'):
return smart_str(obj._meta.object_name.lower()) return obj._meta.object_name.lower()
else: else:
return None return None
@ -108,7 +107,7 @@ class SingleObjectTemplateResponseMixin(TemplateResponseMixin):
def get_template_names(self): def get_template_names(self):
""" """
Return a list of template names to be used for the request. Must return Return a list of template names to be used for the request. Must return
a list. May not be called if get_template is overridden. a list. May not be called if render_to_response is overridden.
""" """
try: try:
names = super(SingleObjectTemplateResponseMixin, self).get_template_names() names = super(SingleObjectTemplateResponseMixin, self).get_template_names()

View File

@ -1,7 +1,6 @@
from django.core.paginator import Paginator, InvalidPage from django.core.paginator import Paginator, InvalidPage
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.http import Http404 from django.http import Http404
from django.utils.encoding import smart_str
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic.base import TemplateResponseMixin, ContextMixin, View from django.views.generic.base import TemplateResponseMixin, ContextMixin, View
@ -16,7 +15,7 @@ class MultipleObjectMixin(ContextMixin):
def get_queryset(self): def get_queryset(self):
""" """
Get the list of items for this view. This must be an interable, and may Get the list of items for this view. This must be an iterable, and may
be a queryset (in which qs-specific behavior will be enabled). be a queryset (in which qs-specific behavior will be enabled).
""" """
if self.queryset is not None: if self.queryset is not None:
@ -77,7 +76,7 @@ class MultipleObjectMixin(ContextMixin):
if self.context_object_name: if self.context_object_name:
return self.context_object_name return self.context_object_name
elif hasattr(object_list, 'model'): elif hasattr(object_list, 'model'):
return smart_str('%s_list' % object_list.model._meta.object_name.lower()) return '%s_list' % object_list.model._meta.object_name.lower()
else: else:
return None return None
@ -113,9 +112,19 @@ class BaseListView(MultipleObjectMixin, View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset() self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty() allow_empty = self.get_allow_empty()
if not allow_empty and len(self.object_list) == 0:
raise Http404(_(u"Empty list and '%(class_name)s.allow_empty' is False.") if not allow_empty:
% {'class_name': self.__class__.__name__}) # When pagination is enabled and object_list is a queryset,
# it's better to do a cheap query than to load the unpaginated
# queryset in memory.
if (self.get_paginate_by(self.object_list) is not None
and hasattr(self.object_list, 'exists')):
is_empty = not self.object_list.exists()
else:
is_empty = len(self.object_list) == 0
if is_empty:
raise Http404(_(u"Empty list and '%(class_name)s.allow_empty' is False.")
% {'class_name': self.__class__.__name__})
context = self.get_context_data(object_list=self.object_list) context = self.get_context_data(object_list=self.object_list)
return self.render_to_response(context) return self.render_to_response(context)
@ -126,7 +135,7 @@ class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
def get_template_names(self): def get_template_names(self):
""" """
Return a list of template names to be used for the request. Must return Return a list of template names to be used for the request. Must return
a list. May not be called if get_template is overridden. a list. May not be called if render_to_response is overridden.
""" """
try: try:
names = super(MultipleObjectTemplateResponseMixin, self).get_template_names() names = super(MultipleObjectTemplateResponseMixin, self).get_template_names()

View File

@ -61,7 +61,7 @@ look like this:
poll.opened = False poll.opened = False
poll.save() poll.save()
self.stdout.write('Successfully closed poll "%s"\n' % poll_id) self.stdout.write('Successfully closed poll "%s"' % poll_id)
.. note:: .. note::
When you are using management commands and wish to provide console When you are using management commands and wish to provide console
@ -317,8 +317,12 @@ Exception class indicating a problem while executing a management
command. command.
If this exception is raised during the execution of a management If this exception is raised during the execution of a management
command, it will be caught and turned into a nicely-printed error command from a command line console, it will be caught and turned into a
message to the appropriate output stream (i.e., stderr); as a nicely-printed error message to the appropriate output stream (i.e., stderr);
result, raising this exception (with a sensible description of the as a result, raising this exception (with a sensible description of the
error) is the preferred way to indicate that something has gone error) is the preferred way to indicate that something has gone
wrong in the execution of a command. wrong in the execution of a command.
If a management command is called from code through
:ref:`call_command <call-command>`, it's up to you to catch the exception
when needed.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -13,9 +13,19 @@ It'll consist of two parts:
* An admin site that lets you add, change and delete polls. * An admin site that lets you add, change and delete polls.
We'll assume you have :doc:`Django installed </intro/install>` already. You can We'll assume you have :doc:`Django installed </intro/install>` already. You can
tell Django is installed by running the Python interactive interpreter and tell Django is installed and which version by running the following command:
typing ``import django``. If that command runs successfully, with no errors,
Django is installed. .. code-block:: bash
python -c "import django; print(django.get_version())"
You should see either the version of your Django installation or an error
telling "No module named django". Check also that the version number matches
the version of this tutorial. If they don't match, you can refer to the
tutorial for your version of Django or update Django to the newest version.
See :doc:`How to install Django </topics/install>` for advice on how to remove
older versions of Django and install a newer one.
.. admonition:: Where to get help: .. admonition:: Where to get help:
@ -339,9 +349,10 @@ The first step in writing a database Web app in Django is to define your models
the :ref:`DRY Principle <dry>`. The goal is to define your data model in one the :ref:`DRY Principle <dry>`. The goal is to define your data model in one
place and automatically derive things from it. place and automatically derive things from it.
In our simple poll app, we'll create two models: polls and choices. A poll has In our simple poll app, we'll create two models: ``Poll`` and ``Choice``.
a question and a publication date. A choice has two fields: the text of the A ``Poll`` has a question and a publication date. A ``Choice`` has two fields:
choice and a vote tally. Each choice is associated with a poll. the text of the choice and a vote tally. Each ``Choice`` is associated with a
``Poll``.
These concepts are represented by simple Python classes. Edit the These concepts are represented by simple Python classes. Edit the
:file:`polls/models.py` file so it looks like this:: :file:`polls/models.py` file so it looks like this::
@ -354,7 +365,7 @@ These concepts are represented by simple Python classes. Edit the
class Choice(models.Model): class Choice(models.Model):
poll = models.ForeignKey(Poll) poll = models.ForeignKey(Poll)
choice = models.CharField(max_length=200) choice_text = models.CharField(max_length=200)
votes = models.IntegerField() votes = models.IntegerField()
The code is straightforward. Each model is represented by a class that The code is straightforward. Each model is represented by a class that
@ -384,8 +395,8 @@ Some :class:`~django.db.models.Field` classes have required elements.
schema, but in validation, as we'll soon see. schema, but in validation, as we'll soon see.
Finally, note a relationship is defined, using Finally, note a relationship is defined, using
:class:`~django.db.models.ForeignKey`. That tells Django each Choice is related :class:`~django.db.models.ForeignKey`. That tells Django each ``Choice`` is related
to a single Poll. Django supports all the common database relationships: to a single ``Poll``. Django supports all the common database relationships:
many-to-ones, many-to-manys and one-to-ones. many-to-ones, many-to-manys and one-to-ones.
.. _`Python path`: http://docs.python.org/tutorial/modules.html#the-module-search-path .. _`Python path`: http://docs.python.org/tutorial/modules.html#the-module-search-path
@ -397,7 +408,7 @@ That small bit of model code gives Django a lot of information. With it, Django
is able to: is able to:
* Create a database schema (``CREATE TABLE`` statements) for this app. * Create a database schema (``CREATE TABLE`` statements) for this app.
* Create a Python database-access API for accessing Poll and Choice objects. * Create a Python database-access API for accessing ``Poll`` and ``Choice`` objects.
But first we need to tell our project that the ``polls`` app is installed. But first we need to tell our project that the ``polls`` app is installed.
@ -446,7 +457,7 @@ statements for the polls app):
CREATE TABLE "polls_choice" ( CREATE TABLE "polls_choice" (
"id" serial NOT NULL PRIMARY KEY, "id" serial NOT NULL PRIMARY KEY,
"poll_id" integer NOT NULL REFERENCES "polls_poll" ("id") DEFERRABLE INITIALLY DEFERRED, "poll_id" integer NOT NULL REFERENCES "polls_poll" ("id") DEFERRABLE INITIALLY DEFERRED,
"choice" varchar(200) NOT NULL, "choice_text" varchar(200) NOT NULL,
"votes" integer NOT NULL "votes" integer NOT NULL
); );
COMMIT; COMMIT;
@ -597,7 +608,7 @@ of this object. Let's fix that by editing the polls model (in the
class Choice(models.Model): class Choice(models.Model):
# ... # ...
def __unicode__(self): def __unicode__(self):
return self.choice return self.choice_text
It's important to add :meth:`~django.db.models.Model.__unicode__` methods to It's important to add :meth:`~django.db.models.Model.__unicode__` methods to
your models, not only for your own sanity when dealing with the interactive your models, not only for your own sanity when dealing with the interactive
@ -678,7 +689,7 @@ Save these changes and start a new Python interactive shell by running
True True
# Give the Poll a couple of Choices. The create call constructs a new # Give the Poll a couple of Choices. The create call constructs a new
# choice object, does the INSERT statement, adds the choice to the set # Choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object. Django creates # of available choices and returns the new Choice object. Django creates
# a set to hold the "other side" of a ForeignKey relation # a set to hold the "other side" of a ForeignKey relation
# (e.g. a poll's choices) which can be accessed via the API. # (e.g. a poll's choices) which can be accessed via the API.
@ -689,11 +700,11 @@ Save these changes and start a new Python interactive shell by running
[] []
# Create three choices. # Create three choices.
>>> p.choice_set.create(choice='Not much', votes=0) >>> p.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much> <Choice: Not much>
>>> p.choice_set.create(choice='The sky', votes=0) >>> p.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky> <Choice: The sky>
>>> c = p.choice_set.create(choice='Just hacking again', votes=0) >>> c = p.choice_set.create(choice_text='Just hacking again', votes=0)
# Choice objects have API access to their related Poll objects. # Choice objects have API access to their related Poll objects.
>>> c.poll >>> c.poll
@ -713,7 +724,7 @@ Save these changes and start a new Python interactive shell by running
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>] [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
# Let's delete one of the choices. Use delete() for that. # Let's delete one of the choices. Use delete() for that.
>>> c = p.choice_set.filter(choice__startswith='Just hacking') >>> c = p.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete() >>> c.delete()
For more information on model relations, see :doc:`Accessing related objects For more information on model relations, see :doc:`Accessing related objects

View File

@ -276,11 +276,11 @@ in that window and click "Save," Django will save the poll to the database and
dynamically add it as the selected choice on the "Add choice" form you're dynamically add it as the selected choice on the "Add choice" form you're
looking at. looking at.
But, really, this is an inefficient way of adding Choice objects to the system. But, really, this is an inefficient way of adding ``Choice`` objects to the system.
It'd be better if you could add a bunch of Choices directly when you create the It'd be better if you could add a bunch of Choices directly when you create the
Poll object. Let's make that happen. ``Poll`` object. Let's make that happen.
Remove the ``register()`` call for the Choice model. Then, edit the ``Poll`` Remove the ``register()`` call for the ``Choice`` model. Then, edit the ``Poll``
registration code to read:: registration code to read::
class ChoiceInline(admin.StackedInline): class ChoiceInline(admin.StackedInline):
@ -296,7 +296,7 @@ registration code to read::
admin.site.register(Poll, PollAdmin) admin.site.register(Poll, PollAdmin)
This tells Django: "Choice objects are edited on the Poll admin page. By This tells Django: "``Choice`` objects are edited on the ``Poll`` admin page. By
default, provide enough fields for 3 choices." default, provide enough fields for 3 choices."
Load the "Add poll" page to see how that looks, you may need to restart your development server: Load the "Add poll" page to see how that looks, you may need to restart your development server:
@ -309,7 +309,7 @@ by ``extra`` -- and each time you come back to the "Change" page for an
already-created object, you get another three extra slots. already-created object, you get another three extra slots.
One small problem, though. It takes a lot of screen space to display all the One small problem, though. It takes a lot of screen space to display all the
fields for entering related Choice objects. For that reason, Django offers a fields for entering related ``Choice`` objects. For that reason, Django offers a
tabular way of displaying inline related objects; you just need to change tabular way of displaying inline related objects; you just need to change
the ``ChoiceInline`` declaration to read:: the ``ChoiceInline`` declaration to read::
@ -397,7 +397,7 @@ search terms, Django will search the ``question`` field. You can use as many
fields as you'd like -- although because it uses a ``LIKE`` query behind the fields as you'd like -- although because it uses a ``LIKE`` query behind the
scenes, keep it reasonable, to keep your database happy. scenes, keep it reasonable, to keep your database happy.
Finally, because Poll objects have dates, it'd be convenient to be able to Finally, because ``Poll`` objects have dates, it'd be convenient to be able to
drill down by date. Add this line:: drill down by date. Add this line::
date_hierarchy = 'pub_date' date_hierarchy = 'pub_date'

View File

@ -400,7 +400,7 @@ like:
<h1>{{ poll.question }}</h1> <h1>{{ poll.question }}</h1>
<ul> <ul>
{% for choice in poll.choice_set.all %} {% for choice in poll.choice_set.all %}
<li>{{ choice.choice }}</li> <li>{{ choice.choice_text }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
@ -412,7 +412,7 @@ list-index lookup.
Method-calling happens in the :ttag:`{% for %}<for>` loop: Method-calling happens in the :ttag:`{% for %}<for>` loop:
``poll.choice_set.all`` is interpreted as the Python code ``poll.choice_set.all`` is interpreted as the Python code
``poll.choice_set.all()``, which returns an iterable of Choice objects and is ``poll.choice_set.all()``, which returns an iterable of ``Choice`` objects and is
suitable for use in the :ttag:`{% for %}<for>` tag. suitable for use in the :ttag:`{% for %}<for>` tag.
See the :doc:`template guide </topics/templates>` for more about templates. See the :doc:`template guide </topics/templates>` for more about templates.

Some files were not shown because too many files have changed in this diff Show More