Merge branch 'master' of https://github.com/django/django
5
AUTHORS
|
@ -93,7 +93,7 @@ answer newbie questions, and generally made Django that much better:
|
|||
Simon Blanchard
|
||||
David Blewett <david@dawninglight.net>
|
||||
Matt Boersma <matt@sprout.org>
|
||||
boobsd@gmail.com
|
||||
Artem Gnilov <boobsd@gmail.com>
|
||||
Matías Bordese
|
||||
Nate Bragg <jonathan.bragg@alum.rpi.edu>
|
||||
Sean Brant
|
||||
|
@ -211,7 +211,6 @@ answer newbie questions, and generally made Django that much better:
|
|||
Dimitris Glezos <dimitris@glezos.com>
|
||||
glin@seznam.cz
|
||||
martin.glueck@gmail.com
|
||||
Artyom Gnilov <boobsd@gmail.com>
|
||||
Ben Godfrey <http://aftnn.org>
|
||||
GomoX <gomo@datafull.com>
|
||||
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>
|
||||
Mike Malone <mjmalone@gmail.com>
|
||||
Martin Maney <http://www.chipy.org/Martin_Maney>
|
||||
Michael Manfre <mmanfre@gmail.com>
|
||||
masonsimon+django@gmail.com
|
||||
Manuzhai
|
||||
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>
|
||||
oggie rob <oz.robharvey@gmail.com>
|
||||
oggy <ognjen.maric@gmail.com>
|
||||
Jens Page
|
||||
Jay Parlar <parlar@gmail.com>
|
||||
Carlos Eduardo de Paula <carlosedp@gmail.com>
|
||||
John Paulett <john@paulett.org>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
var options = $.extend({}, $.fn.actions.defaults, opts);
|
||||
var actionCheckboxes = $(this);
|
||||
var list_editable_changed = false;
|
||||
checker = function(checked) {
|
||||
var checker = function(checked) {
|
||||
if (checked) {
|
||||
showQuestion();
|
||||
} else {
|
||||
|
@ -11,7 +11,7 @@
|
|||
}
|
||||
$(actionCheckboxes).attr("checked", checked)
|
||||
.parent().parent().toggleClass(options.selectedClass, checked);
|
||||
}
|
||||
},
|
||||
updateCounter = function() {
|
||||
var sel = $(actionCheckboxes).filter(":checked").length;
|
||||
$(options.counterContainer).html(interpolate(
|
||||
|
@ -29,30 +29,30 @@
|
|||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
},
|
||||
showQuestion = function() {
|
||||
$(options.acrossClears).hide();
|
||||
$(options.acrossQuestions).show();
|
||||
$(options.allContainer).hide();
|
||||
}
|
||||
},
|
||||
showClear = function() {
|
||||
$(options.acrossClears).show();
|
||||
$(options.acrossQuestions).hide();
|
||||
$(options.actionContainer).toggleClass(options.selectedClass);
|
||||
$(options.allContainer).show();
|
||||
$(options.counterContainer).hide();
|
||||
}
|
||||
},
|
||||
reset = function() {
|
||||
$(options.acrossClears).hide();
|
||||
$(options.acrossQuestions).hide();
|
||||
$(options.allContainer).hide();
|
||||
$(options.counterContainer).show();
|
||||
}
|
||||
},
|
||||
clearAcross = function() {
|
||||
reset();
|
||||
$(options.acrossInput).val(0);
|
||||
$(options.actionContainer).removeClass(options.selectedClass);
|
||||
}
|
||||
};
|
||||
// Show counter by default
|
||||
$(options.counterContainer).show();
|
||||
// Check state of checkboxes and reinit state if needed
|
||||
|
@ -81,9 +81,9 @@
|
|||
});
|
||||
lastChecked = null;
|
||||
$(actionCheckboxes).click(function(event) {
|
||||
if (!event) { var event = window.event; }
|
||||
if (!event) { event = window.event; }
|
||||
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;
|
||||
$(lastChecked).attr("checked", target.checked)
|
||||
.parent().parent().toggleClass(options.selectedClass, target.checked);
|
||||
|
@ -124,7 +124,7 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
/* Setup plugin defaults */
|
||||
$.fn.actions.defaults = {
|
||||
actionContainer: "div.actions",
|
||||
|
@ -135,5 +135,5 @@
|
|||
acrossClears: "div.actions span.clear",
|
||||
allToggle: "#action-toggle",
|
||||
selectedClass: "selected"
|
||||
}
|
||||
};
|
||||
})(django.jQuery);
|
||||
|
|
|
@ -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=
|
||||
!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.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);
|
||||
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);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)});
|
||||
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",
|
||||
acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"}})(django.jQuery);
|
||||
(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();
|
||||
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);
|
||||
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",
|
||||
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."))});
|
||||
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."))})};
|
||||
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);
|
||||
|
|
|
@ -31,11 +31,11 @@
|
|||
}
|
||||
};
|
||||
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");
|
||||
// only show the add button if we are allowed to add more items,
|
||||
// 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).not("." + options.emptyCssClass).addClass(options.formCssClass);
|
||||
});
|
||||
|
@ -52,7 +52,8 @@
|
|||
$(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.click(function() {
|
||||
addButton.click(function(e) {
|
||||
e.preventDefault();
|
||||
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS");
|
||||
var template = $("#" + options.prefix + "-empty");
|
||||
var row = template.clone(true);
|
||||
|
@ -78,14 +79,15 @@
|
|||
// Insert the new form when it has been fully edited
|
||||
row.insertBefore($(template));
|
||||
// Update number of total forms
|
||||
$(totalForms).val(parseInt(totalForms.val()) + 1);
|
||||
$(totalForms).val(parseInt(totalForms.val(), 10) + 1);
|
||||
nextIndex += 1;
|
||||
// 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();
|
||||
}
|
||||
// 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:
|
||||
var row = $(this).parents("." + options.formCssClass);
|
||||
row.remove();
|
||||
|
@ -98,7 +100,7 @@
|
|||
var forms = $("." + options.formCssClass);
|
||||
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
|
||||
// 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();
|
||||
}
|
||||
// Also, update names and ids for all remaining form controls
|
||||
|
@ -110,17 +112,15 @@
|
|||
updateElementIndex(this, options.prefix, i);
|
||||
});
|
||||
}
|
||||
return false;
|
||||
});
|
||||
// If a post-add callback was supplied, call it with the added form:
|
||||
if (options.added) {
|
||||
options.added(row);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
};
|
||||
/* Setup plugin defaults */
|
||||
$.fn.formset.defaults = {
|
||||
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
|
||||
added: null, // Function called each time a new form is added
|
||||
removed: null // Function called each time a form is deleted
|
||||
}
|
||||
};
|
||||
})(django.jQuery);
|
||||
|
|
|
@ -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("."+
|
||||
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+
|
||||
"-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+
|
||||
"</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.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);
|
||||
(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 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();
|
||||
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.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,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);
|
||||
|
|
|
@ -15,8 +15,12 @@
|
|||
{% if app_list %}
|
||||
{% for app in app_list %}
|
||||
<div class="module">
|
||||
<table summary="{% blocktrans with name=app.name %}Models available in the {{ name }} application.{% endblocktrans %}">
|
||||
<caption><a href="{{ app.app_url }}" class="section">{% blocktrans with name=app.name %}{{ name }}{% endblocktrans %}</a></caption>
|
||||
<table>
|
||||
<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 %}
|
||||
<tr>
|
||||
{% if model.admin_url %}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import operator
|
||||
from functools import reduce
|
||||
|
||||
from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured
|
||||
from django.core.paginator import InvalidPage
|
||||
|
|
|
@ -80,7 +80,7 @@ class Command(BaseCommand):
|
|||
if default_username and username == '':
|
||||
username = default_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
|
||||
continue
|
||||
try:
|
||||
|
@ -88,7 +88,7 @@ class Command(BaseCommand):
|
|||
except User.DoesNotExist:
|
||||
break
|
||||
else:
|
||||
sys.stderr.write("Error: That username is already taken.\n")
|
||||
self.stderr.write("Error: That username is already taken.")
|
||||
username = None
|
||||
|
||||
# Get an email
|
||||
|
@ -98,7 +98,7 @@ class Command(BaseCommand):
|
|||
try:
|
||||
is_valid_email(email)
|
||||
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
|
||||
else:
|
||||
break
|
||||
|
@ -109,19 +109,19 @@ class Command(BaseCommand):
|
|||
password = getpass.getpass()
|
||||
password2 = getpass.getpass('Password (again): ')
|
||||
if password != password2:
|
||||
sys.stderr.write("Error: Your passwords didn't match.\n")
|
||||
self.stderr.write("Error: Your passwords didn't match.")
|
||||
password = None
|
||||
continue
|
||||
if password.strip() == '':
|
||||
sys.stderr.write("Error: Blank passwords aren't allowed.\n")
|
||||
self.stderr.write("Error: Blank passwords aren't allowed.")
|
||||
password = None
|
||||
continue
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
sys.stderr.write("\nOperation cancelled.\n")
|
||||
self.stderr.write("\nOperation cancelled.")
|
||||
sys.exit(1)
|
||||
|
||||
User.objects.db_manager(database).create_superuser(username, email, password)
|
||||
if verbosity >= 1:
|
||||
self.stdout.write("Superuser created successfully.\n")
|
||||
self.stdout.write("Superuser created successfully.")
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ from StringIO import StringIO
|
|||
|
||||
from django.contrib.auth import models, management
|
||||
from django.contrib.auth.management.commands import changepassword
|
||||
from django.core.management.base import CommandError
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
|
@ -56,16 +57,10 @@ class ChangepasswordManagementCommandTestCase(TestCase):
|
|||
def test_that_max_tries_exits_1(self):
|
||||
"""
|
||||
A CommandError should be thrown by handle() if the user enters in
|
||||
mismatched passwords three times. This should be caught by execute() and
|
||||
converted to a SystemExit
|
||||
mismatched passwords three times.
|
||||
"""
|
||||
command = changepassword.Command()
|
||||
command._get_pass = lambda *args: args or 'foo'
|
||||
|
||||
self.assertRaises(
|
||||
SystemExit,
|
||||
command.execute,
|
||||
"joe",
|
||||
stdout=self.stdout,
|
||||
stderr=self.stderr
|
||||
)
|
||||
with self.assertRaises(CommandError):
|
||||
command.execute("joe", stdout=self.stdout, stderr=self.stderr)
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
[
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "sites.site",
|
||||
"fields": {
|
||||
"domain": "example.com",
|
||||
"name": "example.com"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -18,9 +18,10 @@ from django.test.utils import override_settings
|
|||
TEMPLATE_DIRS=(
|
||||
os.path.join(os.path.dirname(__file__), 'templates'),
|
||||
),
|
||||
SITE_ID=1,
|
||||
)
|
||||
class FlatpageCSRFTests(TestCase):
|
||||
fixtures = ['sample_flatpages']
|
||||
fixtures = ['sample_flatpages', 'example_site']
|
||||
urls = 'django.contrib.flatpages.tests.urls'
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
@ -5,7 +5,10 @@ from django.test import TestCase
|
|||
from django.test.utils import override_settings
|
||||
from django.utils import translation
|
||||
|
||||
@override_settings(SITE_ID=1)
|
||||
class FlatpageAdminFormTests(TestCase):
|
||||
fixtures = ['example_site']
|
||||
|
||||
def setUp(self):
|
||||
self.form_data = {
|
||||
'title': "A test page",
|
||||
|
@ -89,5 +92,5 @@ class FlatpageAdminFormTests(TestCase):
|
|||
|
||||
self.assertEqual(
|
||||
f.errors,
|
||||
{'sites': [u'This field is required.']})
|
||||
{'sites': [translation.ugettext(u'This field is required.')]})
|
||||
|
||||
|
|
|
@ -19,9 +19,10 @@ from django.test.utils import override_settings
|
|||
TEMPLATE_DIRS=(
|
||||
os.path.join(os.path.dirname(__file__), 'templates'),
|
||||
),
|
||||
SITE_ID=1,
|
||||
)
|
||||
class FlatpageMiddlewareTests(TestCase):
|
||||
fixtures = ['sample_flatpages']
|
||||
fixtures = ['sample_flatpages', 'example_site']
|
||||
urls = 'django.contrib.flatpages.tests.urls'
|
||||
|
||||
def test_view_flatpage(self):
|
||||
|
@ -75,7 +76,7 @@ class FlatpageMiddlewareTests(TestCase):
|
|||
enable_comments=False,
|
||||
registration_required=False,
|
||||
)
|
||||
fp.sites.add(1)
|
||||
fp.sites.add(settings.SITE_ID)
|
||||
|
||||
response = self.client.get('/some.very_special~chars-here/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
@ -96,9 +97,10 @@ class FlatpageMiddlewareTests(TestCase):
|
|||
TEMPLATE_DIRS=(
|
||||
os.path.join(os.path.dirname(__file__), 'templates'),
|
||||
),
|
||||
SITE_ID=1,
|
||||
)
|
||||
class FlatpageMiddlewareAppendSlashTests(TestCase):
|
||||
fixtures = ['sample_flatpages']
|
||||
fixtures = ['sample_flatpages', 'example_site']
|
||||
urls = 'django.contrib.flatpages.tests.urls'
|
||||
|
||||
def test_redirect_view_flatpage(self):
|
||||
|
@ -130,7 +132,7 @@ class FlatpageMiddlewareAppendSlashTests(TestCase):
|
|||
enable_comments=False,
|
||||
registration_required=False,
|
||||
)
|
||||
fp.sites.add(1)
|
||||
fp.sites.add(settings.SITE_ID)
|
||||
|
||||
response = self.client.get('/some.very_special~chars-here')
|
||||
self.assertRedirects(response, '/some.very_special~chars-here/', status_code=301)
|
||||
|
@ -144,7 +146,7 @@ class FlatpageMiddlewareAppendSlashTests(TestCase):
|
|||
enable_comments=False,
|
||||
registration_required=False,
|
||||
)
|
||||
fp.sites.add(1)
|
||||
fp.sites.add(settings.SITE_ID)
|
||||
|
||||
response = self.client.get('/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
|
|
@ -18,6 +18,7 @@ from django.test.utils import override_settings
|
|||
TEMPLATE_DIRS=(
|
||||
os.path.join(os.path.dirname(__file__), 'templates'),
|
||||
),
|
||||
SITE_ID=1,
|
||||
)
|
||||
class FlatpageTemplateTagTests(TestCase):
|
||||
fixtures = ['sample_flatpages']
|
||||
|
|
|
@ -19,9 +19,10 @@ from django.test.utils import override_settings
|
|||
TEMPLATE_DIRS=(
|
||||
os.path.join(os.path.dirname(__file__), 'templates'),
|
||||
),
|
||||
SITE_ID=1,
|
||||
)
|
||||
class FlatpageViewTests(TestCase):
|
||||
fixtures = ['sample_flatpages']
|
||||
fixtures = ['sample_flatpages', 'example_site']
|
||||
urls = 'django.contrib.flatpages.tests.urls'
|
||||
|
||||
def test_view_flatpage(self):
|
||||
|
@ -85,9 +86,10 @@ class FlatpageViewTests(TestCase):
|
|||
TEMPLATE_DIRS=(
|
||||
os.path.join(os.path.dirname(__file__), 'templates'),
|
||||
),
|
||||
SITE_ID=1,
|
||||
)
|
||||
class FlatpageViewAppendSlashTests(TestCase):
|
||||
fixtures = ['sample_flatpages']
|
||||
fixtures = ['sample_flatpages', 'example_site']
|
||||
urls = 'django.contrib.flatpages.tests.urls'
|
||||
|
||||
def test_redirect_view_flatpage(self):
|
||||
|
@ -119,7 +121,7 @@ class FlatpageViewAppendSlashTests(TestCase):
|
|||
enable_comments=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')
|
||||
self.assertRedirects(response, '/flatpage_root/some.very_special~chars-here/', status_code=301)
|
||||
|
|
|
@ -156,9 +156,6 @@ class PreviewTests(TestCase):
|
|||
|
||||
|
||||
class FormHmacTests(unittest.TestCase):
|
||||
"""
|
||||
Same as SecurityHashTests, but with form_hmac
|
||||
"""
|
||||
|
||||
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
|
||||
submit it (usually in textareas).
|
||||
"""
|
||||
f1 = HashTestForm({'name': 'joe', 'bio': 'Nothing notable.'})
|
||||
f2 = HashTestForm({'name': ' joe', 'bio': 'Nothing notable. '})
|
||||
f1 = HashTestForm({'name': u'joe', 'bio': u'Nothing notable.'})
|
||||
f2 = HashTestForm({'name': u' joe', 'bio': u'Nothing notable. '})
|
||||
hash1 = utils.form_hmac(f1)
|
||||
hash2 = utils.form_hmac(f2)
|
||||
self.assertEqual(hash1, hash2)
|
||||
|
@ -269,10 +266,10 @@ class WizardTests(TestCase):
|
|||
Form should advance if the hash is present and good, as calculated using
|
||||
current method.
|
||||
"""
|
||||
data = {"0-field": "test",
|
||||
"1-field": "test2",
|
||||
"hash_0": "7e9cea465f6a10a6fb47fcea65cb9a76350c9a5c",
|
||||
"wizard_step": "1"}
|
||||
data = {"0-field": u"test",
|
||||
"1-field": u"test2",
|
||||
"hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
|
||||
"wizard_step": u"1"}
|
||||
response = self.client.post('/wizard1/', data)
|
||||
self.assertEqual(2, response.context['step0'])
|
||||
|
||||
|
@ -294,18 +291,18 @@ class WizardTests(TestCase):
|
|||
reached[0] = True
|
||||
|
||||
wizard = WizardWithProcessStep([WizardPageOneForm])
|
||||
data = {"0-field": "test",
|
||||
"1-field": "test2",
|
||||
"hash_0": "7e9cea465f6a10a6fb47fcea65cb9a76350c9a5c",
|
||||
"wizard_step": "1"}
|
||||
data = {"0-field": u"test",
|
||||
"1-field": u"test2",
|
||||
"hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
|
||||
"wizard_step": u"1"}
|
||||
wizard(DummyRequest(POST=data))
|
||||
self.assertTrue(reached[0])
|
||||
|
||||
data = {"0-field": "test",
|
||||
"1-field": "test2",
|
||||
"hash_0": "7e9cea465f6a10a6fb47fcea65cb9a76350c9a5c",
|
||||
"hash_1": "d5b434e3934cc92fee4bd2964c4ebc06f81d362d",
|
||||
"wizard_step": "2"}
|
||||
data = {"0-field": u"test",
|
||||
"1-field": u"test2",
|
||||
"hash_0": "cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
|
||||
"hash_1": u"1e6f6315da42e62f33a30640ec7e007ad3fbf1a1",
|
||||
"wizard_step": u"2"}
|
||||
self.assertRaises(http.Http404, wizard, DummyRequest(POST=data))
|
||||
|
||||
def test_14498(self):
|
||||
|
@ -324,10 +321,10 @@ class WizardTests(TestCase):
|
|||
wizard = WizardWithProcessStep([WizardPageOneForm,
|
||||
WizardPageTwoForm,
|
||||
WizardPageThreeForm])
|
||||
data = {"0-field": "test",
|
||||
"1-field": "test2",
|
||||
"hash_0": "7e9cea465f6a10a6fb47fcea65cb9a76350c9a5c",
|
||||
"wizard_step": "1"}
|
||||
data = {"0-field": u"test",
|
||||
"1-field": u"test2",
|
||||
"hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
|
||||
"wizard_step": u"1"}
|
||||
wizard(DummyRequest(POST=data))
|
||||
self.assertTrue(reached[0])
|
||||
|
||||
|
@ -348,10 +345,10 @@ class WizardTests(TestCase):
|
|||
wizard = Wizard([WizardPageOneForm,
|
||||
WizardPageTwoForm])
|
||||
|
||||
data = {"0-field": "test",
|
||||
"1-field": "test2",
|
||||
"hash_0": "7e9cea465f6a10a6fb47fcea65cb9a76350c9a5c",
|
||||
"wizard_step": "1"}
|
||||
data = {"0-field": u"test",
|
||||
"1-field": u"test2",
|
||||
"hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
|
||||
"wizard_step": u"1"}
|
||||
wizard(DummyRequest(POST=data))
|
||||
self.assertTrue(reached[0])
|
||||
|
||||
|
@ -374,10 +371,10 @@ class WizardTests(TestCase):
|
|||
wizard = WizardWithProcessStep([WizardPageOneForm,
|
||||
WizardPageTwoForm,
|
||||
WizardPageThreeForm])
|
||||
data = {"0-field": "test",
|
||||
"1-field": "test2",
|
||||
"hash_0": "7e9cea465f6a10a6fb47fcea65cb9a76350c9a5c",
|
||||
"wizard_step": "1"}
|
||||
data = {"0-field": u"test",
|
||||
"1-field": u"test2",
|
||||
"hash_0": u"cd13b1db3e8f55174bc5745a1b1a53408d4fd1ca",
|
||||
"wizard_step": u"1"}
|
||||
wizard(DummyRequest(POST=data))
|
||||
self.assertTrue(reached[0])
|
||||
|
||||
|
|
|
@ -120,7 +120,7 @@ class NamedWizardTests(object):
|
|||
self.assertEqual(response.context['wizard']['steps'].current, 'form2')
|
||||
|
||||
post_data = self.wizard_step_data[1]
|
||||
post_data['form2-file1'] = open(__file__)
|
||||
post_data['form2-file1'] = open(__file__, 'rb')
|
||||
response = self.client.post(
|
||||
reverse(self.wizard_urlname,
|
||||
kwargs={'step': response.context['wizard']['steps'].current}),
|
||||
|
@ -147,7 +147,7 @@ class NamedWizardTests(object):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
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']
|
||||
self.assertEqual(all_data, [
|
||||
{'name': u'Pony', 'thirsty': True, 'user': self.testuser},
|
||||
|
@ -168,7 +168,7 @@ class NamedWizardTests(object):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
post_data = self.wizard_step_data[1]
|
||||
post_data['form2-file1'] = open(__file__)
|
||||
post_data['form2-file1'] = open(__file__, 'rb')
|
||||
response = self.client.post(
|
||||
reverse(self.wizard_urlname,
|
||||
kwargs={'step': response.context['wizard']['steps'].current}),
|
||||
|
@ -180,7 +180,9 @@ class NamedWizardTests(object):
|
|||
response = self.client.get(step2_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
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(
|
||||
reverse(self.wizard_urlname,
|
||||
|
@ -197,7 +199,7 @@ class NamedWizardTests(object):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
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']
|
||||
self.assertEqual(
|
||||
all_data,
|
||||
|
@ -221,7 +223,7 @@ class NamedWizardTests(object):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
post_data = self.wizard_step_data[1]
|
||||
post_data['form2-file1'] = open(__file__)
|
||||
post_data['form2-file1'] = open(__file__, 'rb')
|
||||
response = self.client.post(
|
||||
reverse(self.wizard_urlname,
|
||||
kwargs={'step': response.context['wizard']['steps'].current}),
|
||||
|
|
|
@ -80,7 +80,7 @@ class WizardTests(object):
|
|||
self.assertEqual(response.context['wizard']['steps'].current, 'form2')
|
||||
|
||||
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)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.context['wizard']['steps'].current, 'form3')
|
||||
|
@ -93,7 +93,7 @@ class WizardTests(object):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
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']
|
||||
self.assertEqual(all_data, [
|
||||
{'name': u'Pony', 'thirsty': True, 'user': self.testuser},
|
||||
|
@ -110,7 +110,7 @@ class WizardTests(object):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
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)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
@ -121,7 +121,7 @@ class WizardTests(object):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
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']
|
||||
self.assertEqual(all_data, {
|
||||
'name': u'Pony', 'thirsty': True, 'user': self.testuser,
|
||||
|
@ -138,7 +138,7 @@ class WizardTests(object):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
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)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
@ -165,7 +165,7 @@ class WizardTests(object):
|
|||
self.assertEqual(response.context['wizard']['steps'].current, 'form2')
|
||||
|
||||
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)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.context['wizard']['steps'].current, 'form3')
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
import pickle
|
||||
# Do not try cPickle here (see #18340)
|
||||
import pickle
|
||||
|
||||
from django.utils.crypto import salted_hmac
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ class GeoModelAdmin(ModelAdmin):
|
|||
wms_url = 'http://vmap0.tiles.osgeo.org/wms/vmap0'
|
||||
wms_layer = 'basic'
|
||||
wms_name = 'OpenLayers WMS'
|
||||
wms_options = {'format': 'image/jpeg'}
|
||||
debug = False
|
||||
widget = OpenLayersWidget
|
||||
|
||||
|
@ -76,6 +77,12 @@ class GeoModelAdmin(ModelAdmin):
|
|||
class OLMap(self.widget):
|
||||
template = self.map_template
|
||||
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,
|
||||
'default_lat' : self.default_lat,
|
||||
'default_zoom' : self.default_zoom,
|
||||
|
@ -106,6 +113,7 @@ class GeoModelAdmin(ModelAdmin):
|
|||
'wms_url' : self.wms_url,
|
||||
'wms_layer' : self.wms_layer,
|
||||
'wms_name' : self.wms_name,
|
||||
'wms_options' : wms_options,
|
||||
'debug' : self.debug,
|
||||
}
|
||||
return OLMap
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from ctypes import c_void_p
|
||||
from types import NoneType
|
||||
|
||||
from django.contrib.gis.gdal.error import GDALException
|
||||
|
||||
class GDALBase(object):
|
||||
|
@ -26,7 +26,7 @@ class GDALBase(object):
|
|||
# compatible type or None (NULL).
|
||||
if isinstance(ptr, (int, long)):
|
||||
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
|
||||
else:
|
||||
raise TypeError('Incompatible pointer type')
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
Module for executing all of the GDAL tests. None
|
||||
of these tests require the use of the database.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.utils.unittest import TestSuite, TextTestRunner
|
||||
|
||||
# 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_ds.suite(),
|
||||
|
|
|
@ -11,8 +11,10 @@
|
|||
Grab GeoIP.dat.gz and GeoLiteCity.dat.gz, and unzip them in the directory
|
||||
corresponding to settings.GEOIP_PATH.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
try:
|
||||
from django.contrib.gis.geoip.base import GeoIP, GeoIPException
|
||||
from .base import GeoIP, GeoIPException
|
||||
HAS_GEOIP = True
|
||||
except:
|
||||
HAS_GEOIP = False
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from ctypes import c_void_p
|
||||
from types import NoneType
|
||||
|
||||
from django.contrib.gis.geos.error import GEOSException
|
||||
|
||||
# Trying to import GDAL libraries, if available. Have to place in
|
||||
|
@ -41,7 +41,7 @@ class GEOSBase(object):
|
|||
def _set_ptr(self, ptr):
|
||||
# Only allow the pointer to be set with pointers of the
|
||||
# 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
|
||||
else:
|
||||
raise TypeError('Incompatible pointer type')
|
||||
|
|
|
@ -8,6 +8,9 @@ See also http://www.aryehleib.com/MutableLists.html
|
|||
|
||||
Author: Aryeh Leib Taurog.
|
||||
"""
|
||||
from django.utils.functional import total_ordering
|
||||
|
||||
@total_ordering
|
||||
class ListMixin(object):
|
||||
"""
|
||||
A base class which provides complete list interface.
|
||||
|
@ -143,20 +146,28 @@ class ListMixin(object):
|
|||
self.extend(cache)
|
||||
return self
|
||||
|
||||
def __cmp__(self, other):
|
||||
'cmp'
|
||||
def __eq__(self, other):
|
||||
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)
|
||||
for i in range(slen):
|
||||
try:
|
||||
c = cmp(self[i], other[i])
|
||||
c = self[i] < other[i]
|
||||
except IndexError:
|
||||
# must be other is shorter
|
||||
return 1
|
||||
else:
|
||||
# elements not equal
|
||||
if c: return c
|
||||
|
||||
return cmp(slen, len(other))
|
||||
return False
|
||||
if c:
|
||||
return c
|
||||
return slen < len(other)
|
||||
|
||||
### Public list interface Methods ###
|
||||
## Non-mutating ##
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
"""
|
||||
GEOS Testing module.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
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_geos.suite(),
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
import ctypes
|
||||
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.libgeos import GEOS_PREPARE
|
||||
from django.contrib.gis.geometry.test_data import TestDataMixin
|
||||
|
||||
from django.utils import unittest
|
||||
|
||||
|
||||
class GEOSTest(unittest.TestCase, TestDataMixin):
|
||||
|
||||
@property
|
||||
def null_srid(self):
|
||||
"""
|
||||
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()
|
||||
if info['version'] == '3.0.0' and info['release_candidate']:
|
||||
|
@ -20,7 +25,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
else:
|
||||
return None
|
||||
|
||||
def test00_base(self):
|
||||
def test_base(self):
|
||||
"Tests out the GEOSBase class."
|
||||
# Testing out GEOSBase class, which provides a `ptr` property
|
||||
# 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, fg2._set_ptr, bad_ptr)
|
||||
|
||||
def test01a_wkt(self):
|
||||
def test_wkt(self):
|
||||
"Testing WKT output."
|
||||
for g in self.geometries.wkt_out:
|
||||
geom = fromstr(g.wkt)
|
||||
self.assertEqual(g.ewkt, geom.wkt)
|
||||
|
||||
def test01b_hex(self):
|
||||
def test_hex(self):
|
||||
"Testing HEX output."
|
||||
for g in self.geometries.hex_wkt:
|
||||
geom = fromstr(g.wkt)
|
||||
self.assertEqual(g.hex, geom.hex)
|
||||
|
||||
def test01b_hexewkb(self):
|
||||
def test_hexewkb(self):
|
||||
"Testing (HEX)EWKB output."
|
||||
from binascii import a2b_hex
|
||||
|
||||
|
@ -124,14 +129,14 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
# Redundant sanity check.
|
||||
self.assertEqual(4326, GEOSGeometry(hexewkb_2d).srid)
|
||||
|
||||
def test01c_kml(self):
|
||||
def test_kml(self):
|
||||
"Testing KML output."
|
||||
for tg in self.geometries.wkt_out:
|
||||
geom = fromstr(tg.wkt)
|
||||
kml = getattr(tg, 'kml', False)
|
||||
if kml: self.assertEqual(kml, geom.kml)
|
||||
|
||||
def test01d_errors(self):
|
||||
def test_errors(self):
|
||||
"Testing the Error handlers."
|
||||
# string-based
|
||||
print("\nBEGIN - expecting GEOS_ERROR; safe to ignore.\n")
|
||||
|
@ -154,7 +159,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
# None
|
||||
self.assertRaises(TypeError, GEOSGeometry, None)
|
||||
|
||||
def test01e_wkb(self):
|
||||
def test_wkb(self):
|
||||
"Testing WKB output."
|
||||
from binascii import b2a_hex
|
||||
for g in self.geometries.hex_wkt:
|
||||
|
@ -162,7 +167,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
wkb = geom.wkb
|
||||
self.assertEqual(b2a_hex(wkb).upper(), g.hex)
|
||||
|
||||
def test01f_create_hex(self):
|
||||
def test_create_hex(self):
|
||||
"Testing creation from HEX."
|
||||
for g in self.geometries.hex_wkt:
|
||||
geom_h = GEOSGeometry(g.hex)
|
||||
|
@ -170,7 +175,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
geom_t = fromstr(g.wkt)
|
||||
self.assertEqual(geom_t.wkt, geom_h.wkt)
|
||||
|
||||
def test01g_create_wkb(self):
|
||||
def test_create_wkb(self):
|
||||
"Testing creation from WKB."
|
||||
from binascii import a2b_hex
|
||||
for g in self.geometries.hex_wkt:
|
||||
|
@ -180,7 +185,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
geom_t = fromstr(g.wkt)
|
||||
self.assertEqual(geom_t.wkt, geom_h.wkt)
|
||||
|
||||
def test01h_ewkt(self):
|
||||
def test_ewkt(self):
|
||||
"Testing EWKT."
|
||||
srids = (-1, 32140)
|
||||
for srid in srids:
|
||||
|
@ -191,9 +196,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
self.assertEqual(srid, poly.shell.srid)
|
||||
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)."
|
||||
if not gdal or not gdal.GEOJSON: return
|
||||
for g in self.geometries.json_geoms:
|
||||
geom = GEOSGeometry(g.wkt)
|
||||
if not hasattr(g, 'not_equal'):
|
||||
|
@ -201,7 +206,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
self.assertEqual(g.json, geom.geojson)
|
||||
self.assertEqual(GEOSGeometry(g.wkt), GEOSGeometry(geom.json))
|
||||
|
||||
def test01k_fromfile(self):
|
||||
def test_fromfile(self):
|
||||
"Testing the fromfile() factory."
|
||||
from io import BytesIO
|
||||
ref_pnt = GEOSGeometry('POINT(5 23)')
|
||||
|
@ -218,7 +223,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
pnt = fromfile(fh)
|
||||
self.assertEqual(ref_pnt, pnt)
|
||||
|
||||
def test01k_eq(self):
|
||||
def test_eq(self):
|
||||
"Testing equivalence."
|
||||
p = fromstr('POINT(5 23)')
|
||||
self.assertEqual(p, p.wkt)
|
||||
|
@ -233,7 +238,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
self.assertNotEqual(g, {'foo' : 'bar'})
|
||||
self.assertNotEqual(g, False)
|
||||
|
||||
def test02a_points(self):
|
||||
def test_points(self):
|
||||
"Testing Point objects."
|
||||
prev = fromstr('POINT(0 0)')
|
||||
for p in self.geometries.points:
|
||||
|
@ -288,7 +293,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
|
||||
prev = pnt # setting the previous geometry
|
||||
|
||||
def test02b_multipoints(self):
|
||||
def test_multipoints(self):
|
||||
"Testing MultiPoint objects."
|
||||
for mp in self.geometries.multipoints:
|
||||
mpnt = fromstr(mp.wkt)
|
||||
|
@ -307,7 +312,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
self.assertEqual(p.empty, False)
|
||||
self.assertEqual(p.valid, True)
|
||||
|
||||
def test03a_linestring(self):
|
||||
def test_linestring(self):
|
||||
"Testing LineString objects."
|
||||
prev = fromstr('POINT(0 0)')
|
||||
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
|
||||
if numpy: self.assertEqual(ls, LineString(numpy.array(ls.tuple))) # as numpy array
|
||||
|
||||
def test03b_multilinestring(self):
|
||||
def test_multilinestring(self):
|
||||
"Testing MultiLineString objects."
|
||||
prev = fromstr('POINT(0 0)')
|
||||
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, MultiLineString(*tuple(LineString(s.tuple) for s in ml)))
|
||||
|
||||
def test04_linearring(self):
|
||||
def test_linearring(self):
|
||||
"Testing LinearRing objects."
|
||||
for rr in self.geometries.linearrings:
|
||||
lr = fromstr(rr.wkt)
|
||||
|
@ -373,14 +378,15 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
self.assertEqual(lr, LinearRing([list(tup) for tup in lr.tuple]))
|
||||
if numpy: self.assertEqual(lr, LinearRing(numpy.array(lr.tuple)))
|
||||
|
||||
def test05a_polygons(self):
|
||||
"Testing Polygon objects."
|
||||
|
||||
# Testing `from_bbox` class method
|
||||
def test_polygons_from_bbox(self):
|
||||
"Testing `from_bbox` class method."
|
||||
bbox = (-180, -90, 180, 90)
|
||||
p = Polygon.from_bbox( bbox )
|
||||
p = Polygon.from_bbox(bbox)
|
||||
self.assertEqual(bbox, p.extent)
|
||||
|
||||
def test_polygons(self):
|
||||
"Testing Polygon objects."
|
||||
|
||||
prev = fromstr('POINT(0 0)')
|
||||
for p in self.geometries.polygons:
|
||||
# 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(LinearRing(r.tuple) for r in poly)).wkt)
|
||||
|
||||
def test05b_multipolygons(self):
|
||||
def test_multipolygons(self):
|
||||
"Testing MultiPolygon objects."
|
||||
print("\nBEGIN - expecting GEOS_NOTICE; safe to ignore.\n")
|
||||
prev = fromstr('POINT (0 0)')
|
||||
|
@ -460,7 +466,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
|
||||
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."
|
||||
#### 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.
|
||||
s1, s2 = str(ring1), str(ring2)
|
||||
|
||||
def test08_coord_seq(self):
|
||||
def test_coord_seq(self):
|
||||
"Testing Coordinate Sequence objects."
|
||||
for p in self.geometries.polygons:
|
||||
if p.ext_ring_cs:
|
||||
|
@ -510,7 +516,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
cs[i] = tset
|
||||
self.assertEqual(tset[j], cs[i][j])
|
||||
|
||||
def test09_relate_pattern(self):
|
||||
def test_relate_pattern(self):
|
||||
"Testing relate() and relate_pattern()."
|
||||
g = fromstr('POINT (0 0)')
|
||||
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.pattern, a.relate(b))
|
||||
|
||||
def test10_intersection(self):
|
||||
def test_intersection(self):
|
||||
"Testing intersects() and intersection()."
|
||||
for i in xrange(len(self.geometries.topology_geoms)):
|
||||
a = fromstr(self.geometries.topology_geoms[i].wkt_a)
|
||||
|
@ -533,7 +539,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
a &= b # testing __iand__
|
||||
self.assertEqual(i1, a)
|
||||
|
||||
def test11_union(self):
|
||||
def test_union(self):
|
||||
"Testing union()."
|
||||
for i in xrange(len(self.geometries.topology_geoms)):
|
||||
a = fromstr(self.geometries.topology_geoms[i].wkt_a)
|
||||
|
@ -545,7 +551,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
a |= b # testing __ior__
|
||||
self.assertEqual(u1, a)
|
||||
|
||||
def test12_difference(self):
|
||||
def test_difference(self):
|
||||
"Testing difference()."
|
||||
for i in xrange(len(self.geometries.topology_geoms)):
|
||||
a = fromstr(self.geometries.topology_geoms[i].wkt_a)
|
||||
|
@ -557,7 +563,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
a -= b # testing __isub__
|
||||
self.assertEqual(d1, a)
|
||||
|
||||
def test13_symdifference(self):
|
||||
def test_symdifference(self):
|
||||
"Testing sym_difference()."
|
||||
for i in xrange(len(self.geometries.topology_geoms)):
|
||||
a = fromstr(self.geometries.topology_geoms[i].wkt_a)
|
||||
|
@ -569,7 +575,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
a ^= b # testing __ixor__
|
||||
self.assertEqual(d1, a)
|
||||
|
||||
def test14_buffer(self):
|
||||
def test_buffer(self):
|
||||
"Testing buffer()."
|
||||
for bg in self.geometries.buffer_geoms:
|
||||
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][1], buf_ring[k][1], 9)
|
||||
|
||||
def test15_srid(self):
|
||||
def test_srid(self):
|
||||
"Testing the SRID property and keyword."
|
||||
# Testing SRID keyword on Point
|
||||
pnt = Point(5, 23, srid=4326)
|
||||
|
@ -635,7 +641,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
p3 = fromstr(p1.hex, srid=-1) # -1 is intended.
|
||||
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 ###
|
||||
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])
|
||||
#del mpoly
|
||||
|
||||
def test17_threed(self):
|
||||
def test_threed(self):
|
||||
"Testing three-dimensional geometries."
|
||||
# Testing a 3D Point
|
||||
pnt = Point(2, 3, 8)
|
||||
|
@ -715,7 +721,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
ls[0] = (1.,2.,3.)
|
||||
self.assertEqual((1.,2.,3.), ls[0])
|
||||
|
||||
def test18_distance(self):
|
||||
def test_distance(self):
|
||||
"Testing the distance() function."
|
||||
# Distance to self should be 0.
|
||||
pnt = Point(0, 0)
|
||||
|
@ -733,7 +739,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
ls2 = LineString((5, 2), (6, 1), (7, 0))
|
||||
self.assertEqual(3, ls1.distance(ls2))
|
||||
|
||||
def test19_length(self):
|
||||
def test_length(self):
|
||||
"Testing the length property."
|
||||
# Points have 0 length.
|
||||
pnt = Point(0, 0)
|
||||
|
@ -751,7 +757,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
mpoly = MultiPolygon(poly.clone(), poly)
|
||||
self.assertEqual(8.0, mpoly.length)
|
||||
|
||||
def test20a_emptyCollections(self):
|
||||
def test_emptyCollections(self):
|
||||
"Testing empty geometries and collections."
|
||||
gc1 = GeometryCollection([])
|
||||
gc2 = fromstr('GEOMETRYCOLLECTION EMPTY')
|
||||
|
@ -789,7 +795,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
else:
|
||||
self.assertRaises(GEOSIndexError, g.__getitem__, 0)
|
||||
|
||||
def test20b_collections_of_collections(self):
|
||||
def test_collections_of_collections(self):
|
||||
"Testing GeometryCollection handling of other collections."
|
||||
# Creating a GeometryCollection WKT string composed of other
|
||||
# collections and polygons.
|
||||
|
@ -808,9 +814,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
# And, they should be equal.
|
||||
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."
|
||||
if not gdal.HAS_GDAL: return
|
||||
g1 = fromstr('POINT(5 23)')
|
||||
self.assertEqual(True, isinstance(g1.ogr, gdal.OGRGeometry))
|
||||
self.assertEqual(g1.srs, None)
|
||||
|
@ -821,7 +827,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
self.assertEqual(g2.hex, g2.ogr.hex)
|
||||
self.assertEqual('WGS 84', g2.srs.name)
|
||||
|
||||
def test22_copy(self):
|
||||
def test_copy(self):
|
||||
"Testing use with the Python `copy` module."
|
||||
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))')
|
||||
|
@ -830,9 +836,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
self.assertNotEqual(poly._ptr, cpy1._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."
|
||||
if not gdal.HAS_GDAL: return
|
||||
orig = GEOSGeometry('POINT (-104.609 38.255)', 4326)
|
||||
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.y, p.y, prec)
|
||||
|
||||
def test23_transform_noop(self):
|
||||
def test_transform_noop(self):
|
||||
""" Testing `transform` method (SRID match) """
|
||||
# transform() should no-op if source & dest SRIDs match,
|
||||
# regardless of whether GDAL is available.
|
||||
|
@ -890,7 +896,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
finally:
|
||||
gdal.HAS_GDAL = old_has_gdal
|
||||
|
||||
def test23_transform_nosrid(self):
|
||||
def test_transform_nosrid(self):
|
||||
""" Testing `transform` method (no SRID or negative SRID) """
|
||||
|
||||
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)
|
||||
self.assertRaises(GEOSException, g.transform, 2774, clone=True)
|
||||
|
||||
def test23_transform_nogdal(self):
|
||||
def test_transform_nogdal(self):
|
||||
""" Testing `transform` method (GDAL not available) """
|
||||
old_has_gdal = gdal.HAS_GDAL
|
||||
try:
|
||||
|
@ -919,7 +925,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
finally:
|
||||
gdal.HAS_GDAL = old_has_gdal
|
||||
|
||||
def test24_extent(self):
|
||||
def test_extent(self):
|
||||
"Testing `extent` method."
|
||||
# The xmin, ymin, xmax, ymax of the MultiPoint should be returned.
|
||||
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)
|
||||
self.assertEqual((xmin, ymin, xmax, ymax), poly.extent)
|
||||
|
||||
def test25_pickle(self):
|
||||
def test_pickle(self):
|
||||
"Testing pickling and unpickling support."
|
||||
# Using both pickle and cPickle -- just 'cause.
|
||||
import pickle, cPickle
|
||||
|
@ -958,9 +964,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
self.assertEqual(geom, tmpg)
|
||||
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."
|
||||
if not GEOS_PREPARE: return
|
||||
# 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)))')
|
||||
prep = mpoly.prepared
|
||||
|
@ -974,7 +980,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
self.assertEqual(mpoly.intersects(pnt), prep.intersects(pnt))
|
||||
self.assertEqual(c, prep.covers(pnt))
|
||||
|
||||
def test26_line_merge(self):
|
||||
def test_line_merge(self):
|
||||
"Testing line merge support"
|
||||
ref_geoms = (fromstr('LINESTRING(1 1, 1 1, 3 3)'),
|
||||
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):
|
||||
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"
|
||||
# Skipping tests if GEOS < v3.1.
|
||||
if not GEOS_PREPARE: return
|
||||
|
||||
g = GEOSGeometry("POINT(0 0)")
|
||||
self.assertTrue(g.valid)
|
||||
|
@ -1005,7 +1010,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
|
|||
|
||||
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."
|
||||
from django.contrib.gis.geos.libgeos import version_regex
|
||||
versions = [ ('3.0.0rc4-CAPI-1.3.3', '3.0.0'),
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from django.utils.safestring import mark_safe
|
||||
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):
|
||||
"""
|
||||
|
@ -166,6 +168,7 @@ class GPolyline(GOverlayBase):
|
|||
return '%s, "%s", %s, %s' % (self.latlngs, self.color, self.weight, self.opacity)
|
||||
|
||||
|
||||
@total_ordering
|
||||
class GIcon(object):
|
||||
"""
|
||||
Creates a GIcon object to pass into a Gmarker object.
|
||||
|
@ -231,8 +234,11 @@ class GIcon(object):
|
|||
self.iconanchor = iconanchor
|
||||
self.infowindowanchor = infowindowanchor
|
||||
|
||||
def __cmp__(self, other):
|
||||
return cmp(self.varname, other.varname)
|
||||
def __eq__(self, other):
|
||||
return self.varname == other.varname
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.varname < other.varname
|
||||
|
||||
def __hash__(self):
|
||||
# XOR with hash of GIcon type so that hash('varname') won't
|
||||
|
|
|
@ -38,6 +38,8 @@ and Geoff Biggs' PhD work on dimensioned units for robotics.
|
|||
__all__ = ['A', 'Area', 'D', 'Distance']
|
||||
from decimal import Decimal
|
||||
|
||||
from django.utils.functional import total_ordering
|
||||
|
||||
class MeasureBase(object):
|
||||
def default_units(self, kwargs):
|
||||
"""
|
||||
|
@ -84,6 +86,7 @@ class MeasureBase(object):
|
|||
else:
|
||||
raise Exception('Could not find a unit keyword associated with "%s"' % unit_str)
|
||||
|
||||
@total_ordering
|
||||
class Distance(MeasureBase):
|
||||
UNITS = {
|
||||
'chain' : 20.1168,
|
||||
|
@ -178,9 +181,15 @@ class Distance(MeasureBase):
|
|||
def __str__(self):
|
||||
return '%s %s' % (getattr(self, self._default_unit), self._default_unit)
|
||||
|
||||
def __cmp__(self, other):
|
||||
def __eq__(self, other):
|
||||
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:
|
||||
return NotImplemented
|
||||
|
||||
|
@ -244,6 +253,7 @@ class Distance(MeasureBase):
|
|||
def __nonzero__(self):
|
||||
return bool(self.m)
|
||||
|
||||
@total_ordering
|
||||
class Area(MeasureBase):
|
||||
# Getting the square units values and the alias dictionary.
|
||||
UNITS = dict([('sq_%s' % k, v ** 2) for k, v in Distance.UNITS.items()])
|
||||
|
@ -267,9 +277,15 @@ class Area(MeasureBase):
|
|||
def __str__(self):
|
||||
return '%s %s' % (getattr(self, self._default_unit), self._default_unit)
|
||||
|
||||
def __cmp__(self, other):
|
||||
def __eq__(self, other):
|
||||
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:
|
||||
return NotImplemented
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ OpenLayers.Projection.addTransform("EPSG:4326", "EPSG:3857", OpenLayers.Layer.Sp
|
|||
// The admin map for this geometry field.
|
||||
{{ module }}.map = new OpenLayers.Map('{{ id }}_map', options);
|
||||
// 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);
|
||||
{% block extra_layers %}{% endblock %}
|
||||
{% if is_linestring %}OpenLayers.Feature.Vector.style["default"]["strokeWidth"] = 3; // Default too thin for linestrings. {% endif %}
|
||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import absolute_import
|
|||
|
||||
from django.test import TestCase
|
||||
from django.contrib.gis import admin
|
||||
from django.contrib.gis.geos import Point
|
||||
|
||||
from .models import City
|
||||
|
||||
|
@ -14,3 +15,21 @@ class GeoAdminTest(TestCase):
|
|||
admin_js = geoadmin.media.render_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)
|
||||
|
|
|
@ -17,6 +17,7 @@ class CountyFeat(models.Model):
|
|||
|
||||
class City(models.Model):
|
||||
name = models.CharField(max_length=25)
|
||||
name_txt = models.TextField(default='')
|
||||
population = models.IntegerField()
|
||||
density = models.DecimalField(max_digits=7, decimal_places=1)
|
||||
dt = models.DateField()
|
||||
|
|
|
@ -4,11 +4,11 @@ import os
|
|||
from copy import copy
|
||||
from decimal import Decimal
|
||||
|
||||
from django.utils.unittest import TestCase
|
||||
|
||||
from django.contrib.gis.gdal import DataSource
|
||||
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 (
|
||||
City, County, CountyFeat, Interstate, ICity1, ICity2, Invalid, State,
|
||||
|
@ -28,7 +28,7 @@ STATES = ['Texas', 'Texas', 'Texas', 'Hawaii', 'Colorado']
|
|||
|
||||
class LayerMapTest(TestCase):
|
||||
|
||||
def test01_init(self):
|
||||
def test_init(self):
|
||||
"Testing LayerMapping initialization."
|
||||
|
||||
# Model field that does not exist.
|
||||
|
@ -46,22 +46,14 @@ class LayerMapTest(TestCase):
|
|||
# Incrementing through the bad mapping dictionaries and
|
||||
# ensuring that a LayerMapError is raised.
|
||||
for bad_map in (bad1, bad2, bad3):
|
||||
try:
|
||||
with self.assertRaises(LayerMapError):
|
||||
lm = LayerMapping(City, city_shp, bad_map)
|
||||
except LayerMapError:
|
||||
pass
|
||||
else:
|
||||
self.fail('Expected a LayerMapError.')
|
||||
|
||||
# A LookupError should be thrown for bogus encodings.
|
||||
try:
|
||||
with self.assertRaises(LookupError):
|
||||
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."
|
||||
# Setting up for the LayerMapping.
|
||||
lm = LayerMapping(City, city_shp, city_mapping)
|
||||
|
@ -85,18 +77,14 @@ class LayerMapTest(TestCase):
|
|||
self.assertAlmostEqual(pnt1.x, pnt2.x, 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."
|
||||
# When the `strict` keyword is set an error encountered will force
|
||||
# the importation to stop.
|
||||
try:
|
||||
with self.assertRaises(InvalidDecimal):
|
||||
lm = LayerMapping(Interstate, inter_shp, inter_mapping)
|
||||
lm.save(silent=True, strict=True)
|
||||
except InvalidDecimal:
|
||||
# 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.')
|
||||
Interstate.objects.all().delete()
|
||||
|
||||
# This LayerMapping should work b/c `strict` is not set.
|
||||
lm = LayerMapping(Interstate, inter_shp, inter_mapping)
|
||||
|
@ -137,7 +125,7 @@ class LayerMapTest(TestCase):
|
|||
qs = CountyFeat.objects.filter(name=name)
|
||||
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."
|
||||
# All the following should work.
|
||||
try:
|
||||
|
@ -176,8 +164,9 @@ class LayerMapTest(TestCase):
|
|||
self.assertRaises(MissingForeignKey, lm.save, silent=True, strict=True)
|
||||
|
||||
# Now creating the state models so the ForeignKey mapping may work.
|
||||
co, hi, tx = State(name='Colorado'), State(name='Hawaii'), State(name='Texas')
|
||||
co.save(), hi.save(), tx.save()
|
||||
State.objects.bulk_create([
|
||||
State(name='Colorado'), State(name='Hawaii'), State(name='Texas')
|
||||
])
|
||||
|
||||
# If a mapping is specified as a collection, all OGR fields that
|
||||
# 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.
|
||||
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()."
|
||||
# Function for clearing out all the counties before testing.
|
||||
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.
|
||||
lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name')
|
||||
|
||||
# Bad feature id ranges should raise a type error.
|
||||
clear_counties()
|
||||
bad_ranges = (5.0, 'foo', co_shp)
|
||||
for bad in bad_ranges:
|
||||
self.assertRaises(TypeError, lm.save, fid_range=bad)
|
||||
|
@ -241,8 +233,10 @@ class LayerMapTest(TestCase):
|
|||
self.assertEqual(2, qs.count())
|
||||
hi, co = tuple(qs)
|
||||
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('Honolulu', hi.name); self.assertEqual(NUMS[hi_idx], len(hi.mpoly))
|
||||
self.assertEqual('Pueblo', co.name)
|
||||
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
|
||||
# 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)
|
||||
self.county_helper(county_feat=False)
|
||||
|
||||
def test06_model_inheritance(self):
|
||||
def test_model_inheritance(self):
|
||||
"Tests LayerMapping on inherited models. See #12093."
|
||||
icity_mapping = {'name' : 'Name',
|
||||
'population' : 'Population',
|
||||
|
@ -272,9 +266,18 @@ class LayerMapTest(TestCase):
|
|||
self.assertEqual(6, ICity1.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."
|
||||
invalid_mapping = {'point': 'POINT'}
|
||||
lm = LayerMapping(Invalid, invalid_shp, invalid_mapping,
|
||||
source_srs=4326)
|
||||
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")
|
||||
|
|
|
@ -332,7 +332,7 @@ class LayerMapping(object):
|
|||
val = unicode(ogr_field.value, self.encoding)
|
||||
else:
|
||||
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.' %
|
||||
(model_field.name, model_field.max_length, len(val)))
|
||||
elif isinstance(ogr_field, OFTReal) and isinstance(model_field, models.DecimalField):
|
||||
|
|
|
@ -190,7 +190,7 @@ class HumanizeTests(TestCase):
|
|||
orig_humanize_datetime = humanize.datetime
|
||||
orig_timesince_datetime = timesince.datetime
|
||||
humanize.datetime = MockDateTime
|
||||
timesince.datetime = new.module("mock_datetime")
|
||||
timesince.datetime = new.module(b"mock_datetime")
|
||||
timesince.datetime.datetime = MockDateTime
|
||||
|
||||
try:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
PROVINCE_CHOICES = (
|
||||
('01', _('Arava')),
|
||||
('01', _('Araba')),
|
||||
('02', _('Albacete')),
|
||||
('03', _('Alacant')),
|
||||
('04', _('Almeria')),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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.api import MessageFailure
|
||||
from django.contrib.messages.storage import default_storage, base
|
||||
|
@ -57,6 +57,7 @@ class BaseTest(TestCase):
|
|||
def setUp(self):
|
||||
self.settings_override = override_settings_tags(
|
||||
TEMPLATE_DIRS = (),
|
||||
TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS,
|
||||
MESSAGE_TAGS = '',
|
||||
MESSAGE_STORAGE = '%s.%s' % (self.storage_class.__module__,
|
||||
self.storage_class.__name__),
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import Site
|
||||
from django.test import TestCase
|
||||
|
@ -13,16 +10,9 @@ class SitemapTestsBase(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
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
|
||||
# Create a user that will double as sitemap content
|
||||
User.objects.create_user('testuser', 'test@example.com', 's3krit')
|
||||
|
||||
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
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
import os
|
||||
from datetime import date
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sitemaps import Sitemap, GenericSitemap
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test.utils import override_settings
|
||||
from django.utils.unittest import skipUnless
|
||||
from django.utils.formats import localize
|
||||
from django.utils.translation import activate, deactivate
|
||||
|
||||
from .base import SitemapTestsBase
|
||||
|
||||
|
||||
class HTTPSitemapTests(SitemapTestsBase):
|
||||
|
||||
def test_simple_sitemap_index(self):
|
||||
|
@ -21,6 +25,9 @@ class HTTPSitemapTests(SitemapTestsBase):
|
|||
</sitemapindex>
|
||||
""" % self.base_url)
|
||||
|
||||
@override_settings(
|
||||
TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'templates'),)
|
||||
)
|
||||
def test_simple_sitemap_custom_index(self):
|
||||
"A simple sitemap index can be rendered with a custom template"
|
||||
response = self.client.get('/simple/custom-index.xml')
|
||||
|
@ -49,6 +56,9 @@ class HTTPSitemapTests(SitemapTestsBase):
|
|||
</urlset>
|
||||
""" % (self.base_url, date.today()))
|
||||
|
||||
@override_settings(
|
||||
TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'templates'),)
|
||||
)
|
||||
def test_simple_custom_sitemap(self):
|
||||
"A simple sitemap can be rendered with a custom template"
|
||||
response = self.client.get('/simple/custom-sitemap.xml')
|
||||
|
@ -60,10 +70,9 @@ class HTTPSitemapTests(SitemapTestsBase):
|
|||
""" % (self.base_url, date.today()))
|
||||
|
||||
@skipUnless(settings.USE_I18N, "Internationalization is not enabled")
|
||||
@override_settings(USE_L10N=True)
|
||||
def test_localized_priority(self):
|
||||
"The priority value should not be localized (Refs #14164)"
|
||||
# Localization should be active
|
||||
settings.USE_L10N = True
|
||||
activate('fr')
|
||||
self.assertEqual(u'0,3', localize(0.3))
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ from optparse import make_option
|
|||
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
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.contrib.staticfiles import finders, storage
|
||||
|
@ -178,15 +178,12 @@ Type 'yes' to continue, or 'no' to cancel: """
|
|||
', %s post-processed'
|
||||
% post_processed_count or ''),
|
||||
}
|
||||
self.stdout.write(smart_str(summary))
|
||||
self.stdout.write(summary)
|
||||
|
||||
def log(self, msg, level=2):
|
||||
"""
|
||||
Small log helper
|
||||
"""
|
||||
msg = smart_str(msg)
|
||||
if not msg.endswith("\n"):
|
||||
msg += "\n"
|
||||
if self.verbosity >= level:
|
||||
self.stdout.write(msg)
|
||||
|
||||
|
|
|
@ -23,9 +23,7 @@ class Command(LabelCommand):
|
|||
result = [result]
|
||||
output = u'\n '.join(
|
||||
(smart_unicode(os.path.realpath(path)) for path in result))
|
||||
self.stdout.write(
|
||||
smart_str(u"Found '%s' here:\n %s\n" % (path, output)))
|
||||
self.stdout.write(u"Found '%s' here:\n %s" % (path, output))
|
||||
else:
|
||||
if verbosity >= 1:
|
||||
self.stderr.write(
|
||||
smart_str("No matching file found for '%s'.\n" % path))
|
||||
self.stderr.write("No matching file found for '%s'." % path)
|
||||
|
|
|
@ -64,6 +64,17 @@ class CachedFilesMixin(object):
|
|||
compiled = re.compile(pattern)
|
||||
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):
|
||||
parsed_name = urlsplit(unquote(name))
|
||||
clean_name = parsed_name.path.strip()
|
||||
|
@ -78,13 +89,11 @@ class CachedFilesMixin(object):
|
|||
return name
|
||||
path, filename = os.path.split(clean_name)
|
||||
root, ext = os.path.splitext(filename)
|
||||
# Get the MD5 hash of the file
|
||||
md5 = hashlib.md5()
|
||||
for chunk in content.chunks():
|
||||
md5.update(chunk)
|
||||
md5sum = md5.hexdigest()[:12]
|
||||
hashed_name = os.path.join(path, u"%s.%s%s" %
|
||||
(root, md5sum, ext))
|
||||
file_hash = self.file_hash(clean_name, content)
|
||||
if file_hash is not None:
|
||||
file_hash = u".%s" % file_hash
|
||||
hashed_name = os.path.join(path, u"%s%s%s" %
|
||||
(root, file_hash, ext))
|
||||
unparsed_name = list(parsed_name)
|
||||
unparsed_name[2] = hashed_name
|
||||
# Special casing for a @font-face hack, like url(myfont.eot?#iefix")
|
||||
|
|
|
@ -22,7 +22,7 @@ def csrf(request):
|
|||
# In order to be able to provide debugging info in the
|
||||
# case of misconfiguration, we use a sentinel value
|
||||
# instead of returning an empty dict.
|
||||
return 'NOTPROVIDED'
|
||||
return b'NOTPROVIDED'
|
||||
else:
|
||||
return token
|
||||
_get_val = lazy(_get_val, str)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""
|
||||
Global Django exception and warning classes.
|
||||
"""
|
||||
from functools import reduce
|
||||
|
||||
class DjangoRuntimeWarning(RuntimeWarning):
|
||||
pass
|
||||
|
|
|
@ -12,7 +12,8 @@ class File(FileProxyMixin):
|
|||
if name is None:
|
||||
name = getattr(file, 'name', None)
|
||||
self.name = name
|
||||
self.mode = getattr(file, 'mode', None)
|
||||
if hasattr(file, 'mode'):
|
||||
self.mode = file.mode
|
||||
|
||||
def __str__(self):
|
||||
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.
|
||||
"""
|
||||
def __init__(self, content, name=None):
|
||||
content = content or ''
|
||||
content = content or b''
|
||||
super(ContentFile, self).__init__(BytesIO(content), name=name)
|
||||
self.size = len(content)
|
||||
|
||||
|
|
|
@ -30,8 +30,8 @@ class UploadedFile(File):
|
|||
self.charset = charset
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s (%s)>" % (
|
||||
self.__class__.__name__, smart_str(self.name), self.content_type)
|
||||
return smart_str("<%s: %s (%s)>" % (
|
||||
self.__class__.__name__, self.name, self.content_type))
|
||||
|
||||
def _get_name(self):
|
||||
return self._name
|
||||
|
|
|
@ -78,14 +78,14 @@ class LimitedStream(object):
|
|||
def __init__(self, stream, limit, buf_size=64 * 1024 * 1024):
|
||||
self.stream = stream
|
||||
self.remaining = limit
|
||||
self.buffer = ''
|
||||
self.buffer = b''
|
||||
self.buf_size = buf_size
|
||||
|
||||
def _read_limited(self, size=None):
|
||||
if size is None or size > self.remaining:
|
||||
size = self.remaining
|
||||
if size == 0:
|
||||
return ''
|
||||
return b''
|
||||
result = self.stream.read(size)
|
||||
self.remaining -= len(result)
|
||||
return result
|
||||
|
@ -93,17 +93,17 @@ class LimitedStream(object):
|
|||
def read(self, size=None):
|
||||
if size is None:
|
||||
result = self.buffer + self._read_limited()
|
||||
self.buffer = ''
|
||||
self.buffer = b''
|
||||
elif size < len(self.buffer):
|
||||
result = self.buffer[:size]
|
||||
self.buffer = self.buffer[size:]
|
||||
else: # size >= len(self.buffer)
|
||||
result = self.buffer + self._read_limited(size - len(self.buffer))
|
||||
self.buffer = ''
|
||||
self.buffer = b''
|
||||
return result
|
||||
|
||||
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):
|
||||
if size:
|
||||
# since size is not None here, len(self.buffer) < size
|
||||
|
|
|
@ -45,6 +45,29 @@ def handle_default_options(options):
|
|||
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):
|
||||
"""
|
||||
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
|
||||
SQL statements, will be wrapped in ``BEGIN`` and ``COMMIT``.
|
||||
|
||||
4. If ``handle()`` raised a ``CommandError``, ``execute()`` will
|
||||
instead print an error message to ``stderr``.
|
||||
4. If ``handle()`` or ``execute()`` raised any exception (e.g.
|
||||
``CommandError``), ``run_from_argv()`` will instead print an error
|
||||
message to ``stderr``.
|
||||
|
||||
Thus, the ``handle()`` method is typically the starting point for
|
||||
subclasses; many built-in commands and command types either place
|
||||
|
@ -187,46 +211,42 @@ class BaseCommand(object):
|
|||
def run_from_argv(self, argv):
|
||||
"""
|
||||
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])
|
||||
options, args = parser.parse_args(argv[2:])
|
||||
handle_default_options(options)
|
||||
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):
|
||||
"""
|
||||
Try to execute this command, performing model validation if
|
||||
needed (as controlled by the attribute
|
||||
``self.requires_model_validation``, except if force-skipped). If the
|
||||
command raises a ``CommandError``, intercept it and print it sensibly
|
||||
to stderr.
|
||||
``self.requires_model_validation``, except if force-skipped).
|
||||
"""
|
||||
show_traceback = options.get('traceback', False)
|
||||
|
||||
# Switch to English, because django-admin.py creates database content
|
||||
# like permissions, and those shouldn't contain any translations.
|
||||
# But only do this if we can assume we have a working settings file,
|
||||
# because django.utils.translation requires settings.
|
||||
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:
|
||||
try:
|
||||
from django.utils import translation
|
||||
saved_lang = translation.get_language()
|
||||
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:
|
||||
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'):
|
||||
self.validate()
|
||||
output = self.handle(*args, **options)
|
||||
|
@ -237,16 +257,10 @@ class BaseCommand(object):
|
|||
from django.db import connections, DEFAULT_DB_ALIAS
|
||||
connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
|
||||
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)
|
||||
if self.output_transaction:
|
||||
self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;") + '\n')
|
||||
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)
|
||||
self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;"))
|
||||
finally:
|
||||
if saved_lang is not None:
|
||||
translation.activate(saved_lang)
|
||||
|
@ -266,7 +280,7 @@ class BaseCommand(object):
|
|||
error_text = s.read()
|
||||
raise CommandError("One or more models did not validate:\n%s" % error_text)
|
||||
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):
|
||||
"""
|
||||
|
|
|
@ -5,7 +5,7 @@ from optparse import make_option
|
|||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
def has_bom(fn):
|
||||
with open(fn, 'r') as f:
|
||||
with open(fn, 'rb') as f:
|
||||
sample = f.read(4)
|
||||
return sample[:3] == '\xef\xbb\xbf' or \
|
||||
sample.startswith(codecs.BOM_UTF16_LE) or \
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from optparse import make_option
|
||||
|
||||
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.utils import DatabaseError
|
||||
|
||||
|
@ -55,11 +55,10 @@ class Command(LabelCommand):
|
|||
try:
|
||||
curs.execute("\n".join(full_statement))
|
||||
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)
|
||||
else:
|
||||
raise CommandError(
|
||||
"Cache table '%s' could not be created.\nThe error was: %s." %
|
||||
(tablename, e))
|
||||
for statement in index_output:
|
||||
curs.execute(statement)
|
||||
transaction.commit_unless_managed(using=db)
|
||||
|
|
|
@ -97,20 +97,24 @@ class Command(BaseCommand):
|
|||
except KeyError:
|
||||
raise CommandError("Unknown serialization format: %s" % format)
|
||||
|
||||
# Now collate the objects to be serialized.
|
||||
objects = []
|
||||
def get_objects():
|
||||
# Collate the objects to be serialized.
|
||||
for model in sort_dependencies(app_list.items()):
|
||||
if model in excluded_models:
|
||||
continue
|
||||
if not model._meta.proxy and router.allow_syncdb(using, model):
|
||||
if use_base_manager:
|
||||
objects.extend(model._base_manager.using(using).all())
|
||||
objects = model._base_manager
|
||||
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:
|
||||
return serializers.serialize(format, objects, indent=indent,
|
||||
use_natural_keys=use_natural_keys)
|
||||
self.stdout.ending = None
|
||||
serializers.serialize(format, get_objects(), indent=indent,
|
||||
use_natural_keys=use_natural_keys, stream=self.stdout)
|
||||
except Exception as e:
|
||||
if show_traceback:
|
||||
raise
|
||||
|
|
|
@ -26,6 +26,8 @@ class Command(NoArgsCommand):
|
|||
|
||||
def handle_inspection(self, options):
|
||||
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('-', '')
|
||||
|
||||
|
@ -43,6 +45,9 @@ class Command(NoArgsCommand):
|
|||
yield ''
|
||||
known_models = []
|
||||
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)
|
||||
known_models.append(table2model(table_name))
|
||||
try:
|
||||
|
|
|
@ -34,11 +34,11 @@ class Command(BaseCommand):
|
|||
using = options.get('database')
|
||||
|
||||
connection = connections[using]
|
||||
self.style = no_style()
|
||||
|
||||
if not len(fixture_labels):
|
||||
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
|
||||
|
||||
|
@ -124,11 +124,11 @@ class Command(BaseCommand):
|
|||
|
||||
if formats:
|
||||
if verbosity >= 2:
|
||||
self.stdout.write("Loading '%s' fixtures...\n" % fixture_name)
|
||||
self.stdout.write("Loading '%s' fixtures..." % fixture_name)
|
||||
else:
|
||||
self.stderr.write(
|
||||
self.style.ERROR("Problem installing fixture '%s': %s is not a known serialization format.\n" %
|
||||
(fixture_name, format)))
|
||||
"Problem installing fixture '%s': %s is not a known serialization format." %
|
||||
(fixture_name, format))
|
||||
if commit:
|
||||
transaction.rollback(using=using)
|
||||
transaction.leave_transaction_management(using=using)
|
||||
|
@ -141,7 +141,7 @@ class Command(BaseCommand):
|
|||
|
||||
for fixture_dir in fixture_dirs:
|
||||
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
|
||||
for combo in product([using, None], formats, compression_formats):
|
||||
|
@ -154,7 +154,7 @@ class Command(BaseCommand):
|
|||
)
|
||||
|
||||
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))
|
||||
full_path = os.path.join(fixture_dir, file_name)
|
||||
open_method = compression_types[compression_format]
|
||||
|
@ -162,13 +162,13 @@ class Command(BaseCommand):
|
|||
fixture = open_method(full_path, 'r')
|
||||
except IOError:
|
||||
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)))
|
||||
else:
|
||||
try:
|
||||
if label_found:
|
||||
self.stderr.write(self.style.ERROR("Multiple fixtures named '%s' in %s. Aborting.\n" %
|
||||
(fixture_name, humanize(fixture_dir))))
|
||||
self.stderr.write("Multiple fixtures named '%s' in %s. Aborting." %
|
||||
(fixture_name, humanize(fixture_dir)))
|
||||
if commit:
|
||||
transaction.rollback(using=using)
|
||||
transaction.leave_transaction_management(using=using)
|
||||
|
@ -178,7 +178,7 @@ class Command(BaseCommand):
|
|||
objects_in_fixture = 0
|
||||
loaded_objects_in_fixture = 0
|
||||
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)))
|
||||
|
||||
objects = serializers.deserialize(format, fixture, using=using)
|
||||
|
@ -209,8 +209,8 @@ class Command(BaseCommand):
|
|||
# error was encountered during fixture loading.
|
||||
if objects_in_fixture == 0:
|
||||
self.stderr.write(
|
||||
self.style.ERROR("No fixture data found for '%s'. (File format may be invalid.)\n" %
|
||||
(fixture_name)))
|
||||
"No fixture data found for '%s'. (File format may be invalid.)" %
|
||||
(fixture_name))
|
||||
if commit:
|
||||
transaction.rollback(using=using)
|
||||
transaction.leave_transaction_management(using=using)
|
||||
|
@ -231,16 +231,16 @@ class Command(BaseCommand):
|
|||
traceback.print_exc()
|
||||
else:
|
||||
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,
|
||||
sys.exc_value, sys.exc_traceback)))))
|
||||
sys.exc_value, sys.exc_traceback))))
|
||||
return
|
||||
|
||||
|
||||
# If we found even one object in a fixture, we need to reset the
|
||||
# database sequences.
|
||||
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 verbosity >= 2:
|
||||
self.stdout.write("Resetting sequences\n")
|
||||
|
@ -253,10 +253,10 @@ class Command(BaseCommand):
|
|||
|
||||
if verbosity >= 1:
|
||||
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))
|
||||
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))
|
||||
|
||||
# Close the DB connection. This is required as a workaround for an
|
||||
|
|
|
@ -120,12 +120,12 @@ class Command(BaseCommand):
|
|||
error_text = ERRORS[e.args[0].args[0]]
|
||||
except (AttributeError, KeyError):
|
||||
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
|
||||
os._exit(1)
|
||||
except KeyboardInterrupt:
|
||||
if shutdown_message:
|
||||
self.stdout.write("%s\n" % shutdown_message)
|
||||
self.stdout.write(shutdown_message)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ from os import path
|
|||
import django
|
||||
from django.template import Template, Context
|
||||
from django.utils import archive
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils._os import rmtree_errorhandler
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.core.management.commands.makemessages import handle_extensions
|
||||
|
@ -166,11 +165,10 @@ class TemplateCommand(BaseCommand):
|
|||
shutil.copymode(old_path, new_path)
|
||||
self.make_writeable(new_path)
|
||||
except OSError:
|
||||
notice = self.style.NOTICE(
|
||||
self.stderr.write(
|
||||
"Notice: Couldn't set permission bits on %s. You're "
|
||||
"probably using an uncommon filesystem setup. No "
|
||||
"problem.\n" % new_path)
|
||||
sys.stderr.write(smart_str(notice))
|
||||
"problem." % new_path, self.style.NOTICE)
|
||||
|
||||
if self.paths_to_remove:
|
||||
if self.verbosity >= 2:
|
||||
|
|
|
@ -39,6 +39,7 @@ class Serializer(object):
|
|||
self.use_natural_keys = options.pop("use_natural_keys", False)
|
||||
|
||||
self.start_serialization()
|
||||
self.first = True
|
||||
for obj in queryset:
|
||||
self.start_object(obj)
|
||||
# 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:
|
||||
self.handle_m2m_field(obj, field)
|
||||
self.end_object(obj)
|
||||
if self.first:
|
||||
self.first = False
|
||||
self.end_serialization()
|
||||
return self.getvalue()
|
||||
|
||||
|
|
|
@ -21,13 +21,38 @@ class Serializer(PythonSerializer):
|
|||
"""
|
||||
internal_use_only = False
|
||||
|
||||
def end_serialization(self):
|
||||
def start_serialization(self):
|
||||
if json.__version__.split('.') >= ['2', '1', '3']:
|
||||
# Use JS strings to represent Python Decimal instances (ticket #16850)
|
||||
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):
|
||||
# overwrite PythonSerializer.getvalue() with base Serializer.getvalue()
|
||||
if callable(getattr(self.stream, 'getvalue', None)):
|
||||
return self.stream.getvalue()
|
||||
|
||||
|
|
|
@ -27,13 +27,16 @@ class Serializer(base.Serializer):
|
|||
self._current = {}
|
||||
|
||||
def end_object(self, obj):
|
||||
self.objects.append({
|
||||
"model" : smart_unicode(obj._meta),
|
||||
"pk" : smart_unicode(obj._get_pk_val(), strings_only=True),
|
||||
"fields" : self._current
|
||||
})
|
||||
self.objects.append(self.get_dump_object(obj))
|
||||
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):
|
||||
value = field._get_val_from_obj(obj)
|
||||
# Protected types (i.e., primitives like None, numbers, dates,
|
||||
|
|
|
@ -146,25 +146,23 @@ class WSGIRequestHandler(simple_server.WSGIRequestHandler, object):
|
|||
env['PATH_INFO'] = urllib.unquote(path)
|
||||
env['QUERY_STRING'] = query
|
||||
env['REMOTE_ADDR'] = self.client_address[0]
|
||||
env['CONTENT_TYPE'] = self.headers.get('content-type', 'text/plain')
|
||||
|
||||
if self.headers.typeheader is None:
|
||||
env['CONTENT_TYPE'] = self.headers.type
|
||||
else:
|
||||
env['CONTENT_TYPE'] = self.headers.typeheader
|
||||
|
||||
length = self.headers.getheader('content-length')
|
||||
length = self.headers.get('content-length')
|
||||
if length:
|
||||
env['CONTENT_LENGTH'] = length
|
||||
|
||||
for h in self.headers.headers:
|
||||
k,v = h.split(':',1)
|
||||
k=k.replace('-','_').upper(); v=v.strip()
|
||||
if k in env:
|
||||
continue # skip content length, type,etc.
|
||||
if 'HTTP_'+k in env:
|
||||
env['HTTP_'+k] += ','+v # comma-separate multiple headers
|
||||
for key, value in self.headers.items():
|
||||
key = key.replace('-','_').upper()
|
||||
value = value.strip()
|
||||
if key in env:
|
||||
# Skip content length, type, etc.
|
||||
continue
|
||||
if 'HTTP_' + key in env:
|
||||
# Comma-separate multiple headers
|
||||
env['HTTP_' + key] += ',' + value
|
||||
else:
|
||||
env['HTTP_'+k] = v
|
||||
env['HTTP_' + key] = value
|
||||
return env
|
||||
|
||||
def log_message(self, format, *args):
|
||||
|
|
|
@ -306,10 +306,8 @@ class RegexURLResolver(LocaleRegexProvider):
|
|||
tried.append([pattern])
|
||||
else:
|
||||
if sub_match:
|
||||
sub_match_dict = dict([(smart_str(k), v) for k, v in match.groupdict().items()])
|
||||
sub_match_dict.update(self.default_kwargs)
|
||||
for k, v in sub_match.kwargs.iteritems():
|
||||
sub_match_dict[smart_str(k)] = v
|
||||
sub_match_dict = dict(match.groupdict(), **self.default_kwargs)
|
||||
sub_match_dict.update(sub_match.kwargs)
|
||||
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])
|
||||
raise Resolver404({'tried': tried, 'path': new_path})
|
||||
|
|
|
@ -399,6 +399,9 @@ class BaseDatabaseFeatures(object):
|
|||
# in the SQL standard.
|
||||
supports_tablespaces = False
|
||||
|
||||
# Does the backend reset sequences between tests?
|
||||
supports_sequence_reset = True
|
||||
|
||||
# Features that need to be confirmed at runtime
|
||||
# Cache whether the confirmation has been performed.
|
||||
_confirmed = False
|
||||
|
@ -414,6 +417,7 @@ class BaseDatabaseFeatures(object):
|
|||
|
||||
def confirm(self):
|
||||
"Perform manual checks of any database features that might vary between installs"
|
||||
if not self._confirmed:
|
||||
self._confirmed = True
|
||||
self.supports_transactions = self._supports_transactions()
|
||||
self.supports_stddev = self._supports_stddev()
|
||||
|
@ -439,8 +443,9 @@ class BaseDatabaseFeatures(object):
|
|||
|
||||
try:
|
||||
self.connection.ops.check_aggregate_support(StdDevPop())
|
||||
return True
|
||||
except NotImplementedError:
|
||||
self.supports_stddev = False
|
||||
return False
|
||||
|
||||
def _can_introspect_foreign_keys(self):
|
||||
"Confirm support for introspected foreign keys"
|
||||
|
|
|
@ -418,11 +418,20 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||
@cached_property
|
||||
def mysql_version(self):
|
||||
if not self.server_version:
|
||||
new_connection = False
|
||||
if not self._valid_connection():
|
||||
# Ensure we have a connection with the DB by using a temporary
|
||||
# cursor
|
||||
new_connection = True
|
||||
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:
|
||||
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()])
|
||||
return self.server_version
|
||||
|
||||
|
|
|
@ -83,6 +83,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
|||
ignores_nulls_in_unique_constraints = False
|
||||
has_bulk_insert = True
|
||||
supports_tablespaces = True
|
||||
supports_sequence_reset = False
|
||||
|
||||
class DatabaseOperations(BaseDatabaseOperations):
|
||||
compiler_module = "django.db.backends.oracle.compiler"
|
||||
|
|
|
@ -51,15 +51,15 @@ def adapt_datetime_with_timezone_support(value):
|
|||
default_timezone = timezone.get_default_timezone()
|
||||
value = timezone.make_aware(value, default_timezone)
|
||||
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("time", parse_time)
|
||||
Database.register_converter("date", parse_date)
|
||||
Database.register_converter("datetime", parse_datetime_with_timezone_support)
|
||||
Database.register_converter("timestamp", parse_datetime_with_timezone_support)
|
||||
Database.register_converter("TIMESTAMP", parse_datetime_with_timezone_support)
|
||||
Database.register_converter("decimal", util.typecast_decimal)
|
||||
Database.register_converter(b"bool", lambda s: str(s) == '1')
|
||||
Database.register_converter(b"time", parse_time)
|
||||
Database.register_converter(b"date", parse_date)
|
||||
Database.register_converter(b"datetime", parse_datetime_with_timezone_support)
|
||||
Database.register_converter(b"timestamp", parse_datetime_with_timezone_support)
|
||||
Database.register_converter(b"TIMESTAMP", parse_datetime_with_timezone_support)
|
||||
Database.register_converter(b"decimal", util.typecast_decimal)
|
||||
Database.register_adapter(datetime.datetime, adapt_datetime_with_timezone_support)
|
||||
Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal)
|
||||
if Database.version_info >= (2, 4, 1):
|
||||
|
|
|
@ -57,11 +57,11 @@ class ModelBase(type):
|
|||
|
||||
new_class.add_to_class('_meta', Options(meta, **kwargs))
|
||||
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
|
||||
for x in parents if hasattr(x, '_meta') and not x._meta.abstract)
|
||||
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
|
||||
for x in parents if hasattr(x, '_meta') and not x._meta.abstract)
|
||||
or (MultipleObjectsReturned,), module))
|
||||
|
@ -404,7 +404,6 @@ class Model(object):
|
|||
# and as a result, the super call will cause an infinite recursion.
|
||||
# See #10547 and #12121.
|
||||
defers = []
|
||||
pk_val = None
|
||||
if self._deferred:
|
||||
from django.db.models.query_utils import deferred_class_factory
|
||||
factory = deferred_class_factory
|
||||
|
@ -412,12 +411,7 @@ class Model(object):
|
|||
if isinstance(self.__class__.__dict__.get(field.attname),
|
||||
DeferredAttribute):
|
||||
defers.append(field.attname)
|
||||
if pk_val is None:
|
||||
# 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()
|
||||
model = self._meta.proxy_for_model
|
||||
else:
|
||||
factory = simple_class_factory
|
||||
return (model_unpickle, (model, defers, factory), data)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import collections
|
||||
import copy
|
||||
import datetime
|
||||
import decimal
|
||||
|
@ -16,7 +17,7 @@ from django.utils.functional import curry, total_ordering
|
|||
from django.utils.text import capfirst
|
||||
from django.utils import timezone
|
||||
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
|
||||
|
||||
class NOT_PROVIDED:
|
||||
|
@ -436,7 +437,7 @@ class Field(object):
|
|||
return bound_field_class(self, fieldmapping, original)
|
||||
|
||||
def _get_choices(self):
|
||||
if hasattr(self._choices, 'next'):
|
||||
if isinstance(self._choices, collections.Iterator):
|
||||
choices, self._choices = tee(self._choices)
|
||||
return choices
|
||||
else:
|
||||
|
@ -529,7 +530,7 @@ class AutoField(Field):
|
|||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError):
|
||||
msg = self.error_messages['invalid'] % str(value)
|
||||
msg = self.error_messages['invalid'] % value
|
||||
raise exceptions.ValidationError(msg)
|
||||
|
||||
def validate(self, value, model_instance):
|
||||
|
@ -581,7 +582,7 @@ class BooleanField(Field):
|
|||
return True
|
||||
if value in ('f', 'False', '0'):
|
||||
return False
|
||||
msg = self.error_messages['invalid'] % str(value)
|
||||
msg = self.error_messages['invalid'] % value
|
||||
raise exceptions.ValidationError(msg)
|
||||
|
||||
def get_prep_lookup(self, lookup_type, value):
|
||||
|
@ -677,7 +678,7 @@ class DateField(Field):
|
|||
return value
|
||||
if isinstance(value, datetime.datetime):
|
||||
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).
|
||||
default_timezone = timezone.get_default_timezone()
|
||||
value = timezone.make_naive(value, default_timezone)
|
||||
|
@ -685,8 +686,6 @@ class DateField(Field):
|
|||
if isinstance(value, datetime.date):
|
||||
return value
|
||||
|
||||
value = smart_str(value)
|
||||
|
||||
try:
|
||||
parsed = parse_date(value)
|
||||
if parsed is not None:
|
||||
|
@ -778,8 +777,6 @@ class DateTimeField(DateField):
|
|||
value = timezone.make_aware(value, default_timezone)
|
||||
return value
|
||||
|
||||
value = smart_str(value)
|
||||
|
||||
try:
|
||||
parsed = parse_datetime(value)
|
||||
if parsed is not None:
|
||||
|
@ -861,7 +858,7 @@ class DecimalField(Field):
|
|||
try:
|
||||
return decimal.Decimal(value)
|
||||
except decimal.InvalidOperation:
|
||||
msg = self.error_messages['invalid'] % str(value)
|
||||
msg = self.error_messages['invalid'] % value
|
||||
raise exceptions.ValidationError(msg)
|
||||
|
||||
def _format(self, value):
|
||||
|
@ -966,7 +963,7 @@ class FloatField(Field):
|
|||
try:
|
||||
return float(value)
|
||||
except (TypeError, ValueError):
|
||||
msg = self.error_messages['invalid'] % str(value)
|
||||
msg = self.error_messages['invalid'] % value
|
||||
raise exceptions.ValidationError(msg)
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
|
@ -1001,7 +998,7 @@ class IntegerField(Field):
|
|||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError):
|
||||
msg = self.error_messages['invalid'] % str(value)
|
||||
msg = self.error_messages['invalid'] % value
|
||||
raise exceptions.ValidationError(msg)
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
|
@ -1106,7 +1103,7 @@ class NullBooleanField(Field):
|
|||
return True
|
||||
if value in ('f', 'False', '0'):
|
||||
return False
|
||||
msg = self.error_messages['invalid'] % str(value)
|
||||
msg = self.error_messages['invalid'] % value
|
||||
raise exceptions.ValidationError(msg)
|
||||
|
||||
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.
|
||||
return value.time()
|
||||
|
||||
value = smart_str(value)
|
||||
|
||||
try:
|
||||
parsed = parse_time(value)
|
||||
if parsed is not None:
|
||||
|
|
|
@ -3,6 +3,7 @@ import os
|
|||
|
||||
from django import forms
|
||||
from django.db.models.fields import Field
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.files.base import File
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.files.images import ImageFile
|
||||
|
@ -204,6 +205,11 @@ class FileDescriptor(object):
|
|||
instance.__dict__[self.field.name] = value
|
||||
|
||||
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 instance will always return an instance of attr_class.
|
||||
attr_class = FieldFile
|
||||
|
@ -226,6 +232,21 @@ class FileField(Field):
|
|||
kwargs['max_length'] = kwargs.get('max_length', 100)
|
||||
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):
|
||||
return "FileField"
|
||||
|
||||
|
|
|
@ -237,13 +237,18 @@ class SingleRelatedObjectDescriptor(object):
|
|||
return self.related.model._base_manager.using(db)
|
||||
|
||||
def get_prefetch_query_set(self, instances):
|
||||
vals = set(instance._get_pk_val() for instance in instances)
|
||||
params = {'%s__pk__in' % self.related.field.name: vals}
|
||||
return (self.get_query_set(instance=instances[0]).filter(**params),
|
||||
attrgetter(self.related.field.attname),
|
||||
lambda obj: obj._get_pk_val(),
|
||||
True,
|
||||
self.cache_name)
|
||||
rel_obj_attr = attrgetter(self.related.field.attname)
|
||||
instance_attr = lambda obj: obj._get_pk_val()
|
||||
instances_dict = dict((instance_attr(inst), inst) for inst in instances)
|
||||
params = {'%s__pk__in' % self.related.field.name: instances_dict.keys()}
|
||||
qs = self.get_query_set(instance=instances[0]).filter(**params)
|
||||
# Since we're going to assign directly in the cache,
|
||||
# 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):
|
||||
if instance is None:
|
||||
|
@ -324,17 +329,23 @@ class ReverseSingleRelatedObjectDescriptor(object):
|
|||
return QuerySet(self.field.rel.to).using(db)
|
||||
|
||||
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()
|
||||
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:
|
||||
params = {'%s__in' % self.field.rel.field_name: vals}
|
||||
return (self.get_query_set(instance=instances[0]).filter(**params),
|
||||
attrgetter(self.field.rel.field_name),
|
||||
attrgetter(self.field.attname),
|
||||
True,
|
||||
self.cache_name)
|
||||
params = {'%s__in' % self.field.rel.field_name: instances_dict.keys()}
|
||||
qs = self.get_query_set(instance=instances[0]).filter(**params)
|
||||
# Since we're going to assign directly in the cache,
|
||||
# we must manage the reverse relation cache manually.
|
||||
if not self.field.rel.multiple:
|
||||
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):
|
||||
if instance is None:
|
||||
|
@ -467,18 +478,24 @@ class ForeignRelatedObjectsDescriptor(object):
|
|||
return self.instance._prefetched_objects_cache[rel_field.related_query_name()]
|
||||
except (AttributeError, KeyError):
|
||||
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):
|
||||
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])
|
||||
query = {'%s__%s__in' % (rel_field.name, attname):
|
||||
set(getattr(obj, attname) for obj in instances)}
|
||||
query = {'%s__%s__in' % (rel_field.name, attname): instances_dict.keys()}
|
||||
qs = super(RelatedManager, self).get_query_set().using(db).filter(**query)
|
||||
return (qs,
|
||||
attrgetter(rel_field.get_attname()),
|
||||
attrgetter(attname),
|
||||
False,
|
||||
rel_field.related_query_name())
|
||||
# Since we just bypassed this class' get_query_set(), we must manage
|
||||
# the reverse relation manually.
|
||||
for rel_obj in qs:
|
||||
instance = instances_dict[rel_obj_attr(rel_obj)]
|
||||
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):
|
||||
for obj in objs:
|
||||
|
|
|
@ -41,6 +41,7 @@ class QuerySet(object):
|
|||
self._for_write = False
|
||||
self._prefetch_related_lookups = []
|
||||
self._prefetch_done = False
|
||||
self._known_related_object = None # (attname, rel_obj)
|
||||
|
||||
########################
|
||||
# PYTHON MAGIC METHODS #
|
||||
|
@ -282,9 +283,10 @@ class QuerySet(object):
|
|||
init_list.append(field.attname)
|
||||
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
|
||||
model = self.model
|
||||
kro_attname, kro_instance = self._known_related_object or (None, None)
|
||||
compiler = self.query.get_compiler(using=db)
|
||||
if fill_cache:
|
||||
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,
|
||||
offset=len(aggregate_select))
|
||||
else:
|
||||
if skip:
|
||||
# Omit aggregates in object creation.
|
||||
row_data = row[index_start:aggregate_start]
|
||||
if skip:
|
||||
obj = model_cls(**dict(zip(init_list, row_data)))
|
||||
else:
|
||||
# Omit aggregates in object creation.
|
||||
obj = model(*row[index_start:aggregate_start])
|
||||
obj = model(*row_data)
|
||||
|
||||
# Store the source database of the object
|
||||
obj._state.db = db
|
||||
|
@ -313,7 +315,11 @@ class QuerySet(object):
|
|||
# Add the aggregates to the model
|
||||
if 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
|
||||
|
||||
|
@ -864,6 +870,7 @@ class QuerySet(object):
|
|||
c = klass(model=self.model, query=query, using=self._db)
|
||||
c._for_write = self._for_write
|
||||
c._prefetch_related_lookups = self._prefetch_related_lookups[:]
|
||||
c._known_related_object = self._known_related_object
|
||||
c.__dict__.update(kwargs)
|
||||
if setup and hasattr(c, '_setup_query'):
|
||||
c._setup_query()
|
||||
|
@ -1781,9 +1788,7 @@ def prefetch_one_level(instances, prefetcher, attname):
|
|||
rel_obj_cache = {}
|
||||
for rel_obj in all_related_objects:
|
||||
rel_attr_val = rel_obj_attr(rel_obj)
|
||||
if rel_attr_val not in rel_obj_cache:
|
||||
rel_obj_cache[rel_attr_val] = []
|
||||
rel_obj_cache[rel_attr_val].append(rel_obj)
|
||||
rel_obj_cache.setdefault(rel_attr_val, []).append(rel_obj)
|
||||
|
||||
for obj in instances:
|
||||
instance_attr_val = instance_attr(obj)
|
||||
|
|
|
@ -6,8 +6,6 @@ large and/or so that they can be used by other modules without getting into
|
|||
circular import difficulties.
|
||||
"""
|
||||
|
||||
import weakref
|
||||
|
||||
from django.db.backends import util
|
||||
from django.utils import tree
|
||||
|
||||
|
@ -70,8 +68,6 @@ class DeferredAttribute(object):
|
|||
"""
|
||||
def __init__(self, field_name, model):
|
||||
self.field_name = field_name
|
||||
self.model_ref = weakref.ref(model)
|
||||
self.loaded = False
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
"""
|
||||
|
@ -79,25 +75,30 @@ class DeferredAttribute(object):
|
|||
Returns the cached value.
|
||||
"""
|
||||
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
|
||||
cls = self.model_ref()
|
||||
data = instance.__dict__
|
||||
if data.get(self.field_name, self) is self:
|
||||
# self.field_name is the attname of the field, but only() takes the
|
||||
# actual name, so we need to translate it here.
|
||||
try:
|
||||
cls._meta.get_field_by_name(self.field_name)
|
||||
name = self.field_name
|
||||
f = opts.get_field_by_name(self.field_name)[0]
|
||||
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]
|
||||
name = f.name
|
||||
# Lets see if the field is part of the parent chain. If so we
|
||||
# might be able to reuse the already loaded value. Refs #18343.
|
||||
val = self._check_parent_chain(instance, name)
|
||||
if val is None:
|
||||
# We use only() instead of values() here because we want the
|
||||
# various data coersion methods (to_python(), etc.) to be called
|
||||
# here.
|
||||
# various data coersion methods (to_python(), etc.) to be
|
||||
# called here.
|
||||
val = getattr(
|
||||
cls._base_manager.filter(pk=instance.pk).only(name).using(
|
||||
instance._state.db).get(),
|
||||
non_deferred_model._base_manager.only(name).using(
|
||||
instance._state.db).get(pk=instance.pk),
|
||||
self.field_name
|
||||
)
|
||||
data[self.field_name] = val
|
||||
|
@ -110,6 +111,20 @@ class DeferredAttribute(object):
|
|||
"""
|
||||
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):
|
||||
"""
|
||||
Returns True if this field should be used to descend deeper for
|
||||
|
|
|
@ -455,6 +455,9 @@ class SQLCompiler(object):
|
|||
alias = self.query.get_initial_alias()
|
||||
field, target, opts, joins, _, _ = self.query.setup_joins(pieces,
|
||||
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]
|
||||
col = target.column
|
||||
if not field.rel:
|
||||
|
@ -466,8 +469,9 @@ class SQLCompiler(object):
|
|||
# Must use left outer joins for nullable fields and their relations.
|
||||
# Ordering or distinct must not affect the returned set, and INNER
|
||||
# JOINS for nullable fields could do this.
|
||||
self.query.promote_alias_chain(joins,
|
||||
self.query.alias_map[joins[0]].join_type == self.query.LOUTER)
|
||||
if joins_to_promote:
|
||||
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
|
||||
|
||||
def _final_join_removal(self, col, alias):
|
||||
|
@ -1015,6 +1019,12 @@ class SQLUpdateCompiler(SQLCompiler):
|
|||
query.extra = {}
|
||||
query.select = []
|
||||
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
|
||||
|
||||
# Now we adjust the current query: reset the where clause and get rid
|
||||
|
|
|
@ -4,6 +4,7 @@ Code to manage the creation and SQL rendering of 'where' constraints.
|
|||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import collections
|
||||
import datetime
|
||||
from itertools import repeat
|
||||
|
||||
|
@ -49,7 +50,7 @@ class WhereNode(tree.Node):
|
|||
return
|
||||
|
||||
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
|
||||
# emptiness and transform any non-empty values correctly.
|
||||
value = list(value)
|
||||
|
|
|
@ -156,11 +156,11 @@ class BoundMethodWeakref(object):
|
|||
"""Whether we are still a valid reference"""
|
||||
return self() is not None
|
||||
|
||||
def __cmp__( self, other ):
|
||||
def __eq__(self, other):
|
||||
"""Compare with another reference"""
|
||||
if not isinstance (other,self.__class__):
|
||||
return cmp( self.__class__, type(other) )
|
||||
return cmp( self.key, other.key)
|
||||
if not isinstance(other, self.__class__):
|
||||
return self.__class__ == type(other)
|
||||
return self.key == other.key
|
||||
|
||||
def __call__(self):
|
||||
"""Return a strong reference to the bound method
|
||||
|
|
|
@ -446,7 +446,7 @@ class RegexField(CharField):
|
|||
|
||||
def _set_regex(self, regex):
|
||||
if isinstance(regex, basestring):
|
||||
regex = re.compile(regex)
|
||||
regex = re.compile(regex, re.UNICODE)
|
||||
self._regex = regex
|
||||
if hasattr(self, '_regex_validator') and self._regex_validator in self.validators:
|
||||
self.validators.remove(self._regex_validator)
|
||||
|
@ -932,12 +932,16 @@ class FilePathField(ChoiceField):
|
|||
self.choices.append((f, f.replace(path, "", 1)))
|
||||
if self.allow_folders:
|
||||
for f in dirs:
|
||||
if f == '__pycache__':
|
||||
continue
|
||||
if self.match is None or self.match_re.search(f):
|
||||
f = os.path.join(root, f)
|
||||
self.choices.append((f, f.replace(path, "", 1)))
|
||||
else:
|
||||
try:
|
||||
for f in sorted(os.listdir(self.path)):
|
||||
if f == '__pycache__':
|
||||
continue
|
||||
full_file = os.path.join(self.path, f)
|
||||
if (((self.allow_files and os.path.isfile(full_file)) or
|
||||
(self.allow_folders and os.path.isdir(full_file))) and
|
||||
|
|
|
@ -388,10 +388,10 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
|
|||
parent = (object,)
|
||||
if hasattr(form, 'Meta'):
|
||||
parent = (form.Meta, object)
|
||||
Meta = type('Meta', parent, attrs)
|
||||
Meta = type(b'Meta', parent, attrs)
|
||||
|
||||
# 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.
|
||||
form_class_attrs = {
|
||||
|
|
|
@ -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
|
||||
_tc = Cookie.SimpleCookie()
|
||||
try:
|
||||
_tc.load('foo:bar=1')
|
||||
_tc.load(b'foo:bar=1')
|
||||
_cookie_allows_colon_in_names = True
|
||||
except Cookie.CookieError:
|
||||
_cookie_allows_colon_in_names = False
|
||||
|
@ -650,8 +650,8 @@ class HttpResponse(object):
|
|||
|
||||
def _get_content(self):
|
||||
if self.has_header('Content-Encoding'):
|
||||
return ''.join([str(e) for e in self._container])
|
||||
return ''.join([smart_str(e, self._charset) for e in self._container])
|
||||
return b''.join([str(e) for e in self._container])
|
||||
return b''.join([smart_str(e, self._charset) for e in self._container])
|
||||
|
||||
def _set_content(self, value):
|
||||
if hasattr(value, '__iter__'):
|
||||
|
|
|
@ -268,7 +268,7 @@ class LazyStream(object):
|
|||
"""
|
||||
self._producer = producer
|
||||
self._empty = False
|
||||
self._leftover = ''
|
||||
self._leftover = b''
|
||||
self.length = length
|
||||
self.position = 0
|
||||
self._remaining = length
|
||||
|
@ -282,7 +282,7 @@ class LazyStream(object):
|
|||
remaining = (size is not None and [size] or [self._remaining])[0]
|
||||
# do the whole thing in one shot if no limit was provided.
|
||||
if remaining is None:
|
||||
yield ''.join(self)
|
||||
yield b''.join(self)
|
||||
return
|
||||
|
||||
# otherwise do some bookkeeping to return exactly enough
|
||||
|
@ -298,7 +298,7 @@ class LazyStream(object):
|
|||
remaining -= len(emitting)
|
||||
yield emitting
|
||||
|
||||
out = ''.join(parts())
|
||||
out = b''.join(parts())
|
||||
return out
|
||||
|
||||
def next(self):
|
||||
|
@ -311,7 +311,7 @@ class LazyStream(object):
|
|||
"""
|
||||
if self._leftover:
|
||||
output = self._leftover
|
||||
self._leftover = ''
|
||||
self._leftover = b''
|
||||
else:
|
||||
output = next(self._producer)
|
||||
self._unget_history = []
|
||||
|
@ -341,7 +341,7 @@ class LazyStream(object):
|
|||
return
|
||||
self._update_unget_history(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):
|
||||
"""
|
||||
|
@ -459,7 +459,7 @@ class BoundaryIter(object):
|
|||
if not chunks:
|
||||
raise StopIteration()
|
||||
|
||||
chunk = ''.join(chunks)
|
||||
chunk = b''.join(chunks)
|
||||
boundary = self._find_boundary(chunk, len(chunk) < self._rollback)
|
||||
|
||||
if boundary:
|
||||
|
@ -495,9 +495,9 @@ class BoundaryIter(object):
|
|||
end = index
|
||||
next = index + len(self._boundary)
|
||||
# backup over CRLF
|
||||
if data[max(0,end-1)] == '\n':
|
||||
if data[max(0,end-1)] == b'\n':
|
||||
end -= 1
|
||||
if data[max(0,end-1)] == '\r':
|
||||
if data[max(0,end-1)] == b'\r':
|
||||
end -= 1
|
||||
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
|
||||
# need to munch them later to prevent them from polluting
|
||||
# the payload.
|
||||
header_end = chunk.find('\r\n\r\n')
|
||||
header_end = chunk.find(b'\r\n\r\n')
|
||||
|
||||
def _parse_header(line):
|
||||
main_value_pair, params = parse_header(line)
|
||||
|
@ -557,7 +557,7 @@ def parse_boundary_stream(stream, max_header_size):
|
|||
outdict = {}
|
||||
|
||||
# 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
|
||||
# parameters") is from the Python docs.
|
||||
try:
|
||||
|
@ -580,7 +580,7 @@ def parse_boundary_stream(stream, max_header_size):
|
|||
class Parser(object):
|
||||
def __init__(self, stream, boundary):
|
||||
self._stream = stream
|
||||
self._separator = '--' + boundary
|
||||
self._separator = b'--' + boundary
|
||||
|
||||
def __iter__(self):
|
||||
boundarystream = InterBoundaryIter(self._stream, self._separator)
|
||||
|
@ -590,27 +590,27 @@ class Parser(object):
|
|||
|
||||
def parse_header(line):
|
||||
""" Parse the header into a key-value. """
|
||||
plist = _parse_header_params(';' + line)
|
||||
plist = _parse_header_params(b';' + line)
|
||||
key = plist.pop(0).lower()
|
||||
pdict = {}
|
||||
for p in plist:
|
||||
i = p.find('=')
|
||||
i = p.find(b'=')
|
||||
if i >= 0:
|
||||
name = p[:i].strip().lower()
|
||||
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.replace('\\\\', '\\').replace('\\"', '"')
|
||||
value = value.replace(b'\\\\', b'\\').replace(b'\\"', b'"')
|
||||
pdict[name] = value
|
||||
return key, pdict
|
||||
|
||||
def _parse_header_params(s):
|
||||
plist = []
|
||||
while s[:1] == ';':
|
||||
while s[:1] == b';':
|
||||
s = s[1:]
|
||||
end = s.find(';')
|
||||
while end > 0 and s.count('"', 0, end) % 2:
|
||||
end = s.find(';', end + 1)
|
||||
end = s.find(b';')
|
||||
while end > 0 and s.count(b'"', 0, end) % 2:
|
||||
end = s.find(b';', end + 1)
|
||||
if end < 0:
|
||||
end = len(s)
|
||||
f = s[:end]
|
||||
|
|
|
@ -52,7 +52,7 @@ class Loader(BaseLoader):
|
|||
def load_template_source(self, template_name, template_dirs=None):
|
||||
for filepath in self.get_template_sources(template_name, template_dirs):
|
||||
try:
|
||||
with open(filepath) as fp:
|
||||
with open(filepath, 'rb') as fp:
|
||||
return (fp.read().decode(settings.FILE_CHARSET), filepath)
|
||||
except IOError:
|
||||
pass
|
||||
|
|
|
@ -34,7 +34,7 @@ class Loader(BaseLoader):
|
|||
tried = []
|
||||
for filepath in self.get_template_sources(template_name, template_dirs):
|
||||
try:
|
||||
with open(filepath) as fp:
|
||||
with open(filepath, 'rb') as fp:
|
||||
return (fp.read().decode(settings.FILE_CHARSET), filepath)
|
||||
except IOError:
|
||||
tried.append(filepath)
|
||||
|
|
|
@ -148,7 +148,7 @@ class BlockTranslateNode(Node):
|
|||
context.pop()
|
||||
try:
|
||||
result = result % data
|
||||
except KeyError:
|
||||
except (KeyError, ValueError):
|
||||
with translation.override(None):
|
||||
result = self.render(context)
|
||||
return result
|
||||
|
|
|
@ -155,7 +155,6 @@ def encode_file(boundary, key, file):
|
|||
]
|
||||
|
||||
|
||||
|
||||
class RequestFactory(object):
|
||||
"""
|
||||
Class that lets you create mock Request objects for use in testing.
|
||||
|
@ -227,7 +226,7 @@ class RequestFactory(object):
|
|||
return urllib.unquote(parsed[2])
|
||||
|
||||
def get(self, path, data={}, **extra):
|
||||
"Construct a GET request"
|
||||
"Construct a GET request."
|
||||
|
||||
parsed = urlparse(path)
|
||||
r = {
|
||||
|
@ -270,49 +269,39 @@ class RequestFactory(object):
|
|||
r.update(extra)
|
||||
return self.request(**r)
|
||||
|
||||
def options(self, path, data={}, **extra):
|
||||
"Constrict an OPTIONS request"
|
||||
def options(self, path, data='', content_type='application/octet-stream',
|
||||
**extra):
|
||||
"Construct an OPTIONS request."
|
||||
return self.generic('OPTIONS', path, data, content_type, **extra)
|
||||
|
||||
parsed = urlparse(path)
|
||||
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,
|
||||
def put(self, path, data='', content_type='application/octet-stream',
|
||||
**extra):
|
||||
"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)
|
||||
data = smart_str(data, settings.DEFAULT_CHARSET)
|
||||
r = {
|
||||
'CONTENT_LENGTH': len(put_data),
|
||||
'CONTENT_TYPE': content_type,
|
||||
'PATH_INFO': self._get_path(parsed),
|
||||
'QUERY_STRING': parsed[4],
|
||||
'REQUEST_METHOD': 'PUT',
|
||||
'wsgi.input': FakePayload(put_data),
|
||||
'REQUEST_METHOD': method,
|
||||
}
|
||||
if data:
|
||||
r.update({
|
||||
'CONTENT_LENGTH': len(data),
|
||||
'CONTENT_TYPE': content_type,
|
||||
'wsgi.input': FakePayload(data),
|
||||
})
|
||||
r.update(extra)
|
||||
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):
|
||||
"""
|
||||
A class that can act as a client for testing purposes.
|
||||
|
@ -445,30 +434,35 @@ class Client(RequestFactory):
|
|||
response = self._handle_redirects(response, **extra)
|
||||
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.
|
||||
"""
|
||||
response = super(Client, self).options(path, data=data, **extra)
|
||||
response = super(Client, self).options(path,
|
||||
data=data, content_type=content_type, **extra)
|
||||
if follow:
|
||||
response = self._handle_redirects(response, **extra)
|
||||
return response
|
||||
|
||||
def put(self, path, data={}, content_type=MULTIPART_CONTENT,
|
||||
def put(self, path, data='', content_type='application/octet-stream',
|
||||
follow=False, **extra):
|
||||
"""
|
||||
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:
|
||||
response = self._handle_redirects(response, **extra)
|
||||
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.
|
||||
"""
|
||||
response = super(Client, self).delete(path, data=data, **extra)
|
||||
response = super(Client, self).delete(path,
|
||||
data=data, content_type=content_type, **extra)
|
||||
if follow:
|
||||
response = self._handle_redirects(response, **extra)
|
||||
return response
|
||||
|
|
|
@ -23,7 +23,7 @@ import time
|
|||
|
||||
from django.conf import settings
|
||||
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.timezone import get_current_timezone_name
|
||||
from django.utils.translation import get_language
|
||||
|
@ -53,7 +53,7 @@ def patch_cache_control(response, **kwargs):
|
|||
if t[1] is True:
|
||||
return t[0]
|
||||
else:
|
||||
return t[0] + '=' + smart_str(t[1])
|
||||
return '%s=%s' % (t[0], t[1])
|
||||
|
||||
if response.has_header('Cache-Control'):
|
||||
cc = cc_delim_re.split(response['Cache-Control'])
|
||||
|
|
|
@ -8,6 +8,7 @@ import hashlib
|
|||
import binascii
|
||||
import operator
|
||||
import time
|
||||
from functools import reduce
|
||||
|
||||
# Use the system PRNG if possible
|
||||
import random
|
||||
|
@ -23,8 +24,8 @@ except NotImplementedError:
|
|||
from django.conf import settings
|
||||
|
||||
|
||||
_trans_5c = "".join([chr(x ^ 0x5C) for x in xrange(256)])
|
||||
_trans_36 = "".join([chr(x ^ 0x36) for x in xrange(256)])
|
||||
_trans_5c = b"".join([chr(x ^ 0x5C) 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):
|
||||
|
@ -148,11 +149,11 @@ def pbkdf2(password, salt, iterations, dklen=0, digest=None):
|
|||
|
||||
def F(i):
|
||||
def U():
|
||||
u = salt + struct.pack('>I', i)
|
||||
u = salt + struct.pack(b'>I', i)
|
||||
for j in xrange(int(iterations)):
|
||||
u = _fast_hmac(password, u, digest).digest()
|
||||
yield _bin_to_long(u)
|
||||
return _long_to_bin(reduce(operator.xor, U()), hex_format_string)
|
||||
|
||||
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]
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import types
|
||||
import urllib
|
||||
import locale
|
||||
import datetime
|
||||
|
@ -45,7 +44,7 @@ def is_protected_type(obj):
|
|||
force_unicode(strings_only=True).
|
||||
"""
|
||||
return isinstance(obj, (
|
||||
types.NoneType,
|
||||
type(None),
|
||||
int, long,
|
||||
datetime.datetime, datetime.date, datetime.time,
|
||||
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 and isinstance(s, (types.NoneType, int)):
|
||||
if strings_only and (s is None or isinstance(s, int)):
|
||||
return s
|
||||
if isinstance(s, Promise):
|
||||
return unicode(s).encode(encoding, errors)
|
||||
|
@ -154,7 +153,7 @@ def iri_to_uri(iri):
|
|||
# converted.
|
||||
if iri is None:
|
||||
return iri
|
||||
return urllib.quote(smart_str(iri), safe="/#%[]=:;$&()+,!?*@'~")
|
||||
return urllib.quote(smart_str(iri), safe=b"/#%[]=:;$&()+,!?*@'~")
|
||||
|
||||
def filepath_to_uri(path):
|
||||
"""Convert an file system path to a URI portion that is suitable for
|
||||
|
@ -173,7 +172,7 @@ def filepath_to_uri(path):
|
|||
return path
|
||||
# I know about `os.sep` and `os.altsep` but I want to leave
|
||||
# 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
|
||||
# given fallback encoding if the encoding is unsupported by python or could
|
||||
|
|
|
@ -58,6 +58,7 @@ def lazy(func, *resultclasses):
|
|||
function is evaluated on every access.
|
||||
"""
|
||||
|
||||
@total_ordering
|
||||
class __proxy__(Promise):
|
||||
"""
|
||||
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):
|
||||
return str(func(*self.__args, **self.__kw))
|
||||
|
||||
def __cmp__(self, rhs):
|
||||
def __cast(self):
|
||||
if self._delegate_str:
|
||||
s = str(func(*self.__args, **self.__kw))
|
||||
return self.__str_cast()
|
||||
elif self._delegate_unicode:
|
||||
s = unicode(func(*self.__args, **self.__kw))
|
||||
return self.__unicode_cast()
|
||||
else:
|
||||
s = func(*self.__args, **self.__kw)
|
||||
if isinstance(rhs, Promise):
|
||||
return -cmp(rhs, s)
|
||||
else:
|
||||
return cmp(s, rhs)
|
||||
return func(*self.__args, **self.__kw)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Promise):
|
||||
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):
|
||||
if self._delegate_str:
|
||||
|
|
|
@ -116,7 +116,7 @@ def smart_urlquote(url):
|
|||
# contains a % not followed by two hexadecimal digits. See #9655.
|
||||
if '%' not in url or unquoted_percents_re.search(url):
|
||||
# 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)
|
||||
|
||||
|
|
|
@ -155,9 +155,20 @@ class SafeExceptionReporterFilter(ExceptionReporterFilter):
|
|||
Replaces the values of variables marked as sensitive with
|
||||
stars (*********).
|
||||
"""
|
||||
func_name = tb_frame.f_code.co_name
|
||||
func = tb_frame.f_globals.get(func_name)
|
||||
sensitive_variables = getattr(func, 'sensitive_variables', [])
|
||||
# Loop through the frame's callers to see if the sensitive_variables
|
||||
# decorator was used.
|
||||
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 = []
|
||||
if self.is_active(request) and sensitive_variables:
|
||||
if sensitive_variables == '__ALL__':
|
||||
|
@ -333,7 +344,7 @@ class ExceptionReporter(object):
|
|||
source = source.splitlines()
|
||||
if source is None:
|
||||
try:
|
||||
with open(filename) as fp:
|
||||
with open(filename, 'rb') as fp:
|
||||
source = fp.readlines()
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
|
|
|
@ -26,13 +26,13 @@ def sensitive_variables(*variables):
|
|||
"""
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
def sensitive_variables_wrapper(*args, **kwargs):
|
||||
if variables:
|
||||
wrapper.sensitive_variables = variables
|
||||
sensitive_variables_wrapper.sensitive_variables = variables
|
||||
else:
|
||||
wrapper.sensitive_variables = '__ALL__'
|
||||
sensitive_variables_wrapper.sensitive_variables = '__ALL__'
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
return sensitive_variables_wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
|
@ -61,11 +61,11 @@ def sensitive_post_parameters(*parameters):
|
|||
"""
|
||||
def decorator(view):
|
||||
@functools.wraps(view)
|
||||
def wrapper(request, *args, **kwargs):
|
||||
def sensitive_post_parameters_wrapper(request, *args, **kwargs):
|
||||
if parameters:
|
||||
request.sensitive_post_parameters = parameters
|
||||
else:
|
||||
request.sensitive_post_parameters = '__ALL__'
|
||||
return view(request, *args, **kwargs)
|
||||
return wrapper
|
||||
return sensitive_post_parameters_wrapper
|
||||
return decorator
|
||||
|
|
|
@ -79,14 +79,22 @@ class View(object):
|
|||
return handler(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,
|
||||
extra={
|
||||
'status_code': 405,
|
||||
'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):
|
||||
|
|
|
@ -23,7 +23,9 @@ class YearMixin(object):
|
|||
return self.year_format
|
||||
|
||||
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
|
||||
if year is None:
|
||||
try:
|
||||
|
@ -35,6 +37,32 @@ class YearMixin(object):
|
|||
raise Http404(_(u"No year specified"))
|
||||
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):
|
||||
month_format = '%b'
|
||||
|
@ -48,7 +76,9 @@ class MonthMixin(object):
|
|||
return self.month_format
|
||||
|
||||
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
|
||||
if month is None:
|
||||
try:
|
||||
|
@ -64,20 +94,30 @@ class MonthMixin(object):
|
|||
"""
|
||||
Get the next valid month.
|
||||
"""
|
||||
# next must be the first day of the next 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')
|
||||
return _get_next_prev(self, date, is_previous=False, period='month')
|
||||
|
||||
def get_previous_month(self, date):
|
||||
"""
|
||||
Get the previous valid month.
|
||||
"""
|
||||
# prev must be the last day of the previous month.
|
||||
prev = date.replace(day=1) - datetime.timedelta(days=1)
|
||||
return _get_next_prev(self, prev, is_previous=True, period='month')
|
||||
return _get_next_prev(self, date, 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):
|
||||
|
@ -92,7 +132,9 @@ class DayMixin(object):
|
|||
return self.day_format
|
||||
|
||||
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
|
||||
if day is None:
|
||||
try:
|
||||
|
@ -108,15 +150,27 @@ class DayMixin(object):
|
|||
"""
|
||||
Get the next valid day.
|
||||
"""
|
||||
next = date + datetime.timedelta(days=1)
|
||||
return _get_next_prev(self, next, is_previous=False, period='day')
|
||||
return _get_next_prev(self, date, is_previous=False, period='day')
|
||||
|
||||
def get_previous_day(self, date):
|
||||
"""
|
||||
Get the previous valid day.
|
||||
"""
|
||||
prev = date - datetime.timedelta(days=1)
|
||||
return _get_next_prev(self, prev, is_previous=True, period='day')
|
||||
return _get_next_prev(self, date, 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):
|
||||
|
@ -131,7 +185,9 @@ class WeekMixin(object):
|
|||
return self.week_format
|
||||
|
||||
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
|
||||
if week is None:
|
||||
try:
|
||||
|
@ -147,19 +203,34 @@ class WeekMixin(object):
|
|||
"""
|
||||
Get the next valid week.
|
||||
"""
|
||||
# next must be the first day of the next week.
|
||||
next = date + datetime.timedelta(days=7 - self._get_weekday(date))
|
||||
return _get_next_prev(self, next, is_previous=False, period='week')
|
||||
return _get_next_prev(self, date, is_previous=False, period='week')
|
||||
|
||||
def get_previous_week(self, date):
|
||||
"""
|
||||
Get the previous valid week.
|
||||
"""
|
||||
# prev must be the last day of the previous week.
|
||||
prev = date - datetime.timedelta(days=self._get_weekday(date) + 1)
|
||||
return _get_next_prev(self, prev, is_previous=True, period='week')
|
||||
return _get_next_prev(self, date, 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):
|
||||
"""
|
||||
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()
|
||||
if week_format == '%W': # week starts on Monday
|
||||
return date.weekday()
|
||||
|
@ -168,6 +239,7 @@ class WeekMixin(object):
|
|||
else:
|
||||
raise ValueError("unknown week format: %s" % week_format)
|
||||
|
||||
|
||||
class DateMixin(object):
|
||||
"""
|
||||
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()')
|
||||
|
||||
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
|
||||
extra lookup kwargs.
|
||||
|
@ -266,14 +338,17 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View):
|
|||
allow_empty = self.get_allow_empty()
|
||||
paginate_by = self.get_paginate_by(qs)
|
||||
|
||||
if ordering is not None:
|
||||
qs = qs.order_by(ordering)
|
||||
|
||||
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})
|
||||
|
||||
if not allow_empty:
|
||||
# When pagination is enabled, it's better to do a cheap query
|
||||
# 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:
|
||||
raise Http404(_(u"No %(verbose_name_plural)s available") % {
|
||||
'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.
|
||||
"""
|
||||
qs = self.get_dated_queryset()
|
||||
qs = self.get_dated_queryset(ordering='-%s' % self.get_date_field())
|
||||
date_list = self.get_date_list(qs, 'year')
|
||||
|
||||
if date_list:
|
||||
object_list = qs.order_by('-' + self.get_date_field())
|
||||
else:
|
||||
object_list = qs.none()
|
||||
if not date_list:
|
||||
qs = qs.none()
|
||||
|
||||
return (date_list, object_list, {})
|
||||
return (date_list, qs, {})
|
||||
|
||||
|
||||
class ArchiveIndexView(MultipleObjectTemplateResponseMixin, BaseArchiveIndexView):
|
||||
|
@ -344,23 +417,25 @@ class BaseYearArchiveView(YearMixin, BaseDateListView):
|
|||
date = _date_from_string(year, self.get_year_format())
|
||||
|
||||
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 = {
|
||||
'%s__gte' % date_field: since,
|
||||
'%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')
|
||||
|
||||
if self.get_make_object_list():
|
||||
object_list = qs.order_by('-' + date_field)
|
||||
else:
|
||||
if not self.get_make_object_list():
|
||||
# We need this to be a queryset since parent classes introspect it
|
||||
# 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):
|
||||
"""
|
||||
|
@ -392,12 +467,8 @@ class BaseMonthArchiveView(YearMixin, MonthMixin, BaseDateListView):
|
|||
date = _date_from_string(year, self.get_year_format(),
|
||||
month, self.get_month_format())
|
||||
|
||||
# Construct a date-range lookup.
|
||||
since = self._make_date_lookup_arg(date)
|
||||
if date.month == 12:
|
||||
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))
|
||||
until = self._make_date_lookup_arg(self._get_next_month(date))
|
||||
lookup_kwargs = {
|
||||
'%s__gte' % date_field: since,
|
||||
'%s__lt' % date_field: until,
|
||||
|
@ -442,9 +513,8 @@ class BaseWeekArchiveView(YearMixin, WeekMixin, BaseDateListView):
|
|||
week_start, '%w',
|
||||
week, week_format)
|
||||
|
||||
# Construct a date-range lookup.
|
||||
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 = {
|
||||
'%s__gte' % date_field: since,
|
||||
'%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
|
||||
links on month/day views to never be 404s by never providing a date
|
||||
that'll be invalid for the given view.
|
||||
|
||||
This is a bit complicated since it handles both next and previous months
|
||||
and days (for MonthArchiveView and DayArchiveView); hence the coupling to generic_view.
|
||||
This is a bit complicated since it handles different intervals of time,
|
||||
hence the coupling to generic_view.
|
||||
|
||||
However in essence the logic comes down to:
|
||||
|
||||
* 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.)
|
||||
|
||||
* 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.
|
||||
|
||||
* 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_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:
|
||||
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
|
||||
# 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
|
||||
# a previous date or a next date lookup.
|
||||
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
|
||||
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
|
||||
|
||||
# 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)
|
||||
|
||||
# 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:
|
||||
result = getattr(qs[0], date_field)
|
||||
except IndexError:
|
||||
result = None
|
||||
return None
|
||||
|
||||
# Convert datetimes to a dates
|
||||
if result and generic_view.uses_datetime_field:
|
||||
# Convert datetimes to dates in the current time zone.
|
||||
if generic_view.uses_datetime_field:
|
||||
if settings.USE_TZ:
|
||||
result = timezone.localtime(result)
|
||||
result = result.date()
|
||||
|
||||
if result:
|
||||
if period == 'month':
|
||||
# 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)
|
||||
# Return the first day of the period.
|
||||
return get_current(result)
|
||||
|
||||
# Check against future dates.
|
||||
if result and (allow_future or result < datetime.date.today()):
|
||||
return result
|
||||
|
||||
def timezone_today():
|
||||
"""
|
||||
Return the current date in the current time zone.
|
||||
"""
|
||||
if settings.USE_TZ:
|
||||
return timezone.localtime(timezone.now()).date()
|
||||
else:
|
||||
return None
|
||||
return datetime.date.today()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
|
||||
from django.http import Http404
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic.base import TemplateResponseMixin, ContextMixin, View
|
||||
|
||||
|
@ -81,7 +80,7 @@ class SingleObjectMixin(ContextMixin):
|
|||
if self.context_object_name:
|
||||
return self.context_object_name
|
||||
elif hasattr(obj, '_meta'):
|
||||
return smart_str(obj._meta.object_name.lower())
|
||||
return obj._meta.object_name.lower()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
@ -108,7 +107,7 @@ class SingleObjectTemplateResponseMixin(TemplateResponseMixin):
|
|||
def get_template_names(self):
|
||||
"""
|
||||
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:
|
||||
names = super(SingleObjectTemplateResponseMixin, self).get_template_names()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from django.core.paginator import Paginator, InvalidPage
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.http import Http404
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic.base import TemplateResponseMixin, ContextMixin, View
|
||||
|
||||
|
@ -16,7 +15,7 @@ class MultipleObjectMixin(ContextMixin):
|
|||
|
||||
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).
|
||||
"""
|
||||
if self.queryset is not None:
|
||||
|
@ -77,7 +76,7 @@ class MultipleObjectMixin(ContextMixin):
|
|||
if self.context_object_name:
|
||||
return self.context_object_name
|
||||
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:
|
||||
return None
|
||||
|
||||
|
@ -113,7 +112,17 @@ class BaseListView(MultipleObjectMixin, View):
|
|||
def get(self, request, *args, **kwargs):
|
||||
self.object_list = self.get_queryset()
|
||||
allow_empty = self.get_allow_empty()
|
||||
if not allow_empty and len(self.object_list) == 0:
|
||||
|
||||
if not allow_empty:
|
||||
# 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)
|
||||
|
@ -126,7 +135,7 @@ class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
|
|||
def get_template_names(self):
|
||||
"""
|
||||
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:
|
||||
names = super(MultipleObjectTemplateResponseMixin, self).get_template_names()
|
||||
|
|
|
@ -61,7 +61,7 @@ look like this:
|
|||
poll.opened = False
|
||||
poll.save()
|
||||
|
||||
self.stdout.write('Successfully closed poll "%s"\n' % poll_id)
|
||||
self.stdout.write('Successfully closed poll "%s"' % poll_id)
|
||||
|
||||
.. note::
|
||||
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.
|
||||
|
||||
If this exception is raised during the execution of a management
|
||||
command, it will be caught and turned into a nicely-printed error
|
||||
message to the appropriate output stream (i.e., stderr); as a
|
||||
result, raising this exception (with a sensible description of the
|
||||
command from a command line console, it will be caught and turned into a
|
||||
nicely-printed error message to the appropriate output stream (i.e., stderr);
|
||||
as a result, raising this exception (with a sensible description of the
|
||||
error) is the preferred way to indicate that something has gone
|
||||
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.
|
||||
|
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
|
@ -13,9 +13,19 @@ It'll consist of two parts:
|
|||
* An admin site that lets you add, change and delete polls.
|
||||
|
||||
We'll assume you have :doc:`Django installed </intro/install>` already. You can
|
||||
tell Django is installed by running the Python interactive interpreter and
|
||||
typing ``import django``. If that command runs successfully, with no errors,
|
||||
Django is installed.
|
||||
tell Django is installed and which version by running the following command:
|
||||
|
||||
.. 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:
|
||||
|
||||
|
@ -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
|
||||
place and automatically derive things from it.
|
||||
|
||||
In our simple poll app, we'll create two models: polls and choices. A poll has
|
||||
a question and a publication date. A choice has two fields: the text of the
|
||||
choice and a vote tally. Each choice is associated with a poll.
|
||||
In our simple poll app, we'll create two models: ``Poll`` and ``Choice``.
|
||||
A ``Poll`` has a question and a publication date. A ``Choice`` has two fields:
|
||||
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
|
||||
: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):
|
||||
poll = models.ForeignKey(Poll)
|
||||
choice = models.CharField(max_length=200)
|
||||
choice_text = models.CharField(max_length=200)
|
||||
votes = models.IntegerField()
|
||||
|
||||
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.
|
||||
|
||||
Finally, note a relationship is defined, using
|
||||
:class:`~django.db.models.ForeignKey`. That tells Django each Choice is related
|
||||
to a single Poll. Django supports all the common database relationships:
|
||||
:class:`~django.db.models.ForeignKey`. That tells Django each ``Choice`` is related
|
||||
to a single ``Poll``. Django supports all the common database relationships:
|
||||
many-to-ones, many-to-manys and one-to-ones.
|
||||
|
||||
.. _`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:
|
||||
|
||||
* 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.
|
||||
|
||||
|
@ -446,7 +457,7 @@ statements for the polls app):
|
|||
CREATE TABLE "polls_choice" (
|
||||
"id" serial NOT NULL PRIMARY KEY,
|
||||
"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
|
||||
);
|
||||
COMMIT;
|
||||
|
@ -597,7 +608,7 @@ of this object. Let's fix that by editing the polls model (in the
|
|||
class Choice(models.Model):
|
||||
# ...
|
||||
def __unicode__(self):
|
||||
return self.choice
|
||||
return self.choice_text
|
||||
|
||||
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
|
||||
|
@ -678,7 +689,7 @@ Save these changes and start a new Python interactive shell by running
|
|||
True
|
||||
|
||||
# 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
|
||||
# a set to hold the "other side" of a ForeignKey relation
|
||||
# (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.
|
||||
>>> p.choice_set.create(choice='Not much', votes=0)
|
||||
>>> p.choice_set.create(choice_text='Not much', votes=0)
|
||||
<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>
|
||||
>>> 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.
|
||||
>>> 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>]
|
||||
|
||||
# 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()
|
||||
|
||||
For more information on model relations, see :doc:`Accessing related objects
|
||||
|
|
|
@ -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
|
||||
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
|
||||
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::
|
||||
|
||||
class ChoiceInline(admin.StackedInline):
|
||||
|
@ -296,7 +296,7 @@ registration code to read::
|
|||
|
||||
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."
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
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
|
||||
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::
|
||||
|
||||
date_hierarchy = 'pub_date'
|
||||
|
|
|
@ -400,7 +400,7 @@ like:
|
|||
<h1>{{ poll.question }}</h1>
|
||||
<ul>
|
||||
{% for choice in poll.choice_set.all %}
|
||||
<li>{{ choice.choice }}</li>
|
||||
<li>{{ choice.choice_text }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
|
@ -412,7 +412,7 @@ list-index lookup.
|
|||
|
||||
Method-calling happens in the :ttag:`{% for %}<for>` loop:
|
||||
``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.
|
||||
|
||||
See the :doc:`template guide </topics/templates>` for more about templates.
|
||||
|
|