Merge recent changes from master.

This commit is contained in:
Russell Keith-Magee 2012-09-09 08:22:26 +08:00
commit 19526563b5
131 changed files with 1842 additions and 1159 deletions

View File

@ -506,6 +506,7 @@ answer newbie questions, and generally made Django that much better:
Johan C. Stöver <johan@nilling.nl>
Nowell Strite <http://nowell.strite.org/>
Thomas Stromberg <tstromberg@google.com>
Travis Swicegood <travis@domain51.com>
Pascal Varet
SuperJared
Radek Švarz <http://www.svarz.cz/translate/>

View File

@ -7,7 +7,6 @@ a list of all possible variables.
"""
import os
import re
import time # Needed for Windows
import warnings
@ -26,7 +25,7 @@ class LazySettings(LazyObject):
The user can manually configure settings prior to using them. Otherwise,
Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE.
"""
def _setup(self):
def _setup(self, name):
"""
Load the settings module pointed to by the environment variable. This
is used the first time we need any settings at all, if the user has not
@ -37,12 +36,21 @@ class LazySettings(LazyObject):
if not settings_module: # If it's set but is an empty string.
raise KeyError
except KeyError:
# NOTE: This is arguably an EnvironmentError, but that causes
# problems with Python's interactive help.
raise ImportError("Settings cannot be imported, because environment variable %s is undefined." % ENVIRONMENT_VARIABLE)
raise ImproperlyConfigured(
"Requested setting %s, but settings are not configured. "
"You must either define the environment variable %s "
"or call settings.configure() before accessing settings."
% (name, ENVIRONMENT_VARIABLE))
self._wrapped = Settings(settings_module)
def __getattr__(self, name):
if self._wrapped is empty:
self._setup(name)
return getattr(self._wrapped, name)
def configure(self, default_settings=global_settings, **options):
"""
Called to manually configure the settings. The 'default_settings'

View File

@ -14,9 +14,10 @@ from django.core.exceptions import PermissionDenied, ValidationError
from django.core.paginator import Paginator
from django.core.urlresolvers import reverse
from django.db import models, transaction, router
from django.db.models.constants import LOOKUP_SEP
from django.db.models.related import RelatedObject
from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
from django.db.models.sql.constants import LOOKUP_SEP, QUERY_TERMS
from django.db.models.sql.constants import QUERY_TERMS
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.template.response import SimpleTemplateResponse, TemplateResponse
@ -1456,8 +1457,10 @@ class InlineModelAdmin(BaseModelAdmin):
return request.user.has_perm(
self.opts.app_label + '.' + self.opts.get_delete_permission())
class StackedInline(InlineModelAdmin):
template = 'admin/edit_inline/stacked.html'
class TabularInline(InlineModelAdmin):
template = 'admin/edit_inline/tabular.html'

View File

@ -9,128 +9,264 @@
* All rights reserved.
*
* Spiced up with Code from Zain Memon's GSoC project 2009
* and modified for Django by Jannis Leidel
* and modified for Django by Jannis Leidel, Travis Swicegood and Julien Phalip.
*
* Licensed under the New BSD License
* See: http://www.opensource.org/licenses/bsd-license.php
*/
(function($) {
$.fn.formset = function(opts) {
var options = $.extend({}, $.fn.formset.defaults, opts);
var updateElementIndex = function(el, prefix, ndx) {
var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))");
var replacement = prefix + "-" + ndx;
if ($(el).attr("for")) {
$(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
}
if (el.id) {
el.id = el.id.replace(id_regex, replacement);
}
if (el.name) {
el.name = el.name.replace(id_regex, replacement);
}
};
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off");
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,
$.fn.formset = function(opts) {
var options = $.extend({}, $.fn.formset.defaults, opts);
var $this = $(this);
var $parent = $this.parent();
var updateElementIndex = function(el, prefix, ndx) {
var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))");
var replacement = prefix + "-" + ndx;
if ($(el).attr("for")) {
$(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
}
if (el.id) {
el.id = el.id.replace(id_regex, replacement);
}
if (el.name) {
el.name = el.name.replace(id_regex, replacement);
}
};
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off");
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;
$(this).each(function(i) {
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
});
if ($(this).length && showAddButton) {
var addButton;
if ($(this).attr("tagName") == "TR") {
// If forms are laid out as table rows, insert the
// "add" button in a new table row:
var numCols = this.eq(-1).children().length;
$(this).parent().append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a href="javascript:void(0)">' + options.addText + "</a></tr>");
addButton = $(this).parent().find("tr:last a");
} else {
// Otherwise, insert it immediately after the last form:
$(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(e) {
e.preventDefault();
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS");
var template = $("#" + options.prefix + "-empty");
var row = template.clone(true);
row.removeClass(options.emptyCssClass)
.addClass(options.formCssClass)
.attr("id", options.prefix + "-" + nextIndex);
if (row.is("tr")) {
// If the forms are laid out in table rows, insert
// the remove button into the last table cell:
row.children(":last").append('<div><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></div>");
} else if (row.is("ul") || row.is("ol")) {
// If they're laid out as an ordered/unordered list,
// insert an <li> after the last list item:
row.append('<li><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></li>");
} else {
// Otherwise, just insert the remove button as the
// last child element of the form's container:
row.children(":first").append('<span><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText + "</a></span>");
}
row.find("*").each(function() {
updateElementIndex(this, options.prefix, totalForms.val());
});
// Insert the new form when it has been fully edited
row.insertBefore($(template));
// Update number of total forms
$(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) {
addButton.parent().hide();
}
// The delete button of each row triggers a bunch of other things
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();
nextIndex -= 1;
// If a post-delete callback was provided, call it with the deleted form:
if (options.removed) {
options.removed(row);
}
// Update the TOTAL_FORMS form count.
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) {
addButton.parent().show();
}
// Also, update names and ids for all remaining form controls
// so they remain in sequence:
for (var i=0, formCount=forms.length; i<formCount; i++)
{
updateElementIndex($(forms).get(i), options.prefix, i);
$(forms.get(i)).find("*").each(function() {
updateElementIndex(this, options.prefix, i);
});
}
});
// If a post-add callback was supplied, call it with the added form:
if (options.added) {
options.added(row);
}
});
}
return this;
};
/* Setup plugin defaults */
$.fn.formset.defaults = {
prefix: "form", // The form prefix for your django formset
addText: "add another", // Text for the add link
deleteText: "remove", // Text for the delete link
addCssClass: "add-row", // CSS class applied to the add link
deleteCssClass: "delete-row", // CSS class applied to the delete link
emptyCssClass: "empty-row", // CSS class applied to the empty row
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
};
var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0;
$this.each(function(i) {
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
});
if ($this.length && showAddButton) {
var addButton;
if ($this.attr("tagName") == "TR") {
// If forms are laid out as table rows, insert the
// "add" button in a new table row:
var numCols = this.eq(-1).children().length;
$parent.append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a href="javascript:void(0)">' + options.addText + "</a></tr>");
addButton = $parent.find("tr:last a");
} else {
// Otherwise, insert it immediately after the last form:
$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(e) {
e.preventDefault();
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS");
var template = $("#" + options.prefix + "-empty");
var row = template.clone(true);
row.removeClass(options.emptyCssClass)
.addClass(options.formCssClass)
.attr("id", options.prefix + "-" + nextIndex);
if (row.is("tr")) {
// If the forms are laid out in table rows, insert
// the remove button into the last table cell:
row.children(":last").append('<div><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></div>");
} else if (row.is("ul") || row.is("ol")) {
// If they're laid out as an ordered/unordered list,
// insert an <li> after the last list item:
row.append('<li><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></li>");
} else {
// Otherwise, just insert the remove button as the
// last child element of the form's container:
row.children(":first").append('<span><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText + "</a></span>");
}
row.find("*").each(function() {
updateElementIndex(this, options.prefix, totalForms.val());
});
// Insert the new form when it has been fully edited
row.insertBefore($(template));
// Update number of total forms
$(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) {
addButton.parent().hide();
}
// The delete button of each row triggers a bunch of other things
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();
nextIndex -= 1;
// If a post-delete callback was provided, call it with the deleted form:
if (options.removed) {
options.removed(row);
}
// Update the TOTAL_FORMS form count.
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) {
addButton.parent().show();
}
// Also, update names and ids for all remaining form controls
// so they remain in sequence:
for (var i=0, formCount=forms.length; i<formCount; i++)
{
updateElementIndex($(forms).get(i), options.prefix, i);
$(forms.get(i)).find("*").each(function() {
updateElementIndex(this, options.prefix, i);
});
}
});
// If a post-add callback was supplied, call it with the added form:
if (options.added) {
options.added(row);
}
});
}
return this;
};
/* Setup plugin defaults */
$.fn.formset.defaults = {
prefix: "form", // The form prefix for your django formset
addText: "add another", // Text for the add link
deleteText: "remove", // Text for the delete link
addCssClass: "add-row", // CSS class applied to the add link
deleteCssClass: "delete-row", // CSS class applied to the delete link
emptyCssClass: "empty-row", // CSS class applied to the empty row
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
};
// Tabular inlines ---------------------------------------------------------
$.fn.tabularFormset = function(options) {
var $rows = $(this);
var alternatingRows = function(row) {
$($rows.selector).not(".add-row").removeClass("row1 row2")
.filter(":even").addClass("row1").end()
.filter(":odd").addClass("row2");
};
var reinitDateTimeShortCuts = function() {
// Reinitialize the calendar and clock widgets by force
if (typeof DateTimeShortcuts != "undefined") {
$(".datetimeshortcuts").remove();
DateTimeShortcuts.init();
}
};
var updateSelectFilter = function() {
// If any SelectFilter widgets are a part of the new form,
// instantiate a new SelectFilter instance for it.
if (typeof SelectFilter != 'undefined'){
$('.selectfilter').each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], false, options.adminStaticPrefix );
});
$('.selectfilterstacked').each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], true, options.adminStaticPrefix );
});
}
};
var initPrepopulatedFields = function(row) {
row.find('.prepopulated_field').each(function() {
var field = $(this),
input = field.find('input, select, textarea'),
dependency_list = input.data('dependency_list') || [],
dependencies = [];
$.each(dependency_list, function(i, field_name) {
dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id'));
});
if (dependencies.length) {
input.prepopulate(dependencies, input.attr('maxlength'));
}
});
};
$rows.formset({
prefix: options.prefix,
addText: options.addText,
formCssClass: "dynamic-" + options.prefix,
deleteCssClass: "inline-deletelink",
deleteText: options.deleteText,
emptyCssClass: "empty-form",
removed: alternatingRows,
added: function(row) {
initPrepopulatedFields(row);
reinitDateTimeShortCuts();
updateSelectFilter();
alternatingRows(row);
}
});
return $rows;
};
// Stacked inlines ---------------------------------------------------------
$.fn.stackedFormset = function(options) {
var $rows = $(this);
var updateInlineLabel = function(row) {
$($rows.selector).find(".inline_label").each(function(i) {
var count = i + 1;
$(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
});
};
var reinitDateTimeShortCuts = function() {
// Reinitialize the calendar and clock widgets by force, yuck.
if (typeof DateTimeShortcuts != "undefined") {
$(".datetimeshortcuts").remove();
DateTimeShortcuts.init();
}
};
var updateSelectFilter = function() {
// If any SelectFilter widgets were added, instantiate a new instance.
if (typeof SelectFilter != "undefined"){
$(".selectfilter").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], false, options.adminStaticPrefix);
});
$(".selectfilterstacked").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], true, options.adminStaticPrefix);
});
}
};
var initPrepopulatedFields = function(row) {
row.find('.prepopulated_field').each(function() {
var field = $(this),
input = field.find('input, select, textarea'),
dependency_list = input.data('dependency_list') || [],
dependencies = [];
$.each(dependency_list, function(i, field_name) {
dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id'));
});
if (dependencies.length) {
input.prepopulate(dependencies, input.attr('maxlength'));
}
});
};
$rows.formset({
prefix: options.prefix,
addText: options.addText,
formCssClass: "dynamic-" + options.prefix,
deleteCssClass: "inline-deletelink",
deleteText: options.deleteText,
emptyCssClass: "empty-form",
removed: updateInlineLabel,
added: (function(row) {
initPrepopulatedFields(row);
reinitDateTimeShortCuts();
updateSelectFilter();
updateInlineLabel(row);
})
});
return $rows;
};
})(django.jQuery);

View File

@ -1,5 +1,9 @@
(function(b){b.fn.formset=function(g){var a=b.extend({},b.fn.formset.defaults,g),k=function(c,f,e){var d=RegExp("("+f+"-(\\d+|__prefix__))");f=f+"-"+e;b(c).attr("for")&&b(c).attr("for",b(c).attr("for").replace(d,f));if(c.id)c.id=c.id.replace(d,f);if(c.name)c.name=c.name.replace(d,f)};g=b("#id_"+a.prefix+"-TOTAL_FORMS").attr("autocomplete","off");var l=parseInt(g.val(),10),h=b("#id_"+a.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off");g=h.val()===""||h.val()-g.val()>0;b(this).each(function(){b(this).not("."+
a.emptyCssClass).addClass(a.formCssClass)});if(b(this).length&&g){var j;if(b(this).attr("tagName")=="TR"){g=this.eq(-1).children().length;b(this).parent().append('<tr class="'+a.addCssClass+'"><td colspan="'+g+'"><a href="javascript:void(0)">'+a.addText+"</a></tr>");j=b(this).parent().find("tr:last a")}else{b(this).filter(":last").after('<div class="'+a.addCssClass+'"><a href="javascript:void(0)">'+a.addText+"</a></div>");j=b(this).filter(":last").next().find("a")}j.click(function(c){c.preventDefault();
var f=b("#id_"+a.prefix+"-TOTAL_FORMS");c=b("#"+a.prefix+"-empty");var e=c.clone(true);e.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+l);if(e.is("tr"))e.children(":last").append('<div><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></div>");else e.is("ul")||e.is("ol")?e.append('<li><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></li>"):e.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+
a.deleteText+"</a></span>");e.find("*").each(function(){k(this,a.prefix,f.val())});e.insertBefore(b(c));b(f).val(parseInt(f.val(),10)+1);l+=1;h.val()!==""&&h.val()-f.val()<=0&&j.parent().hide();e.find("a."+a.deleteCssClass).click(function(d){d.preventDefault();d=b(this).parents("."+a.formCssClass);d.remove();l-=1;a.removed&&a.removed(d);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);if(h.val()===""||h.val()-d.length>0)j.parent().show();for(var i=0,m=d.length;i<m;i++){k(b(d).get(i),
a.prefix,i);b(d.get(i)).find("*").each(function(){k(this,a.prefix,i)})}});a.added&&a.added(e)})}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(d){var a=b.extend({},b.fn.formset.defaults,d),c=b(this),d=c.parent(),i=function(a,e,g){var d=RegExp("("+e+"-(\\d+|__prefix__))"),e=e+"-"+g;b(a).attr("for")&&b(a).attr("for",b(a).attr("for").replace(d,e));a.id&&(a.id=a.id.replace(d,e));a.name&&(a.name=a.name.replace(d,e))},f=b("#id_"+a.prefix+"-TOTAL_FORMS").attr("autocomplete","off"),g=parseInt(f.val(),10),e=b("#id_"+a.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off"),f=""===e.val()||0<e.val()-f.val();c.each(function(){b(this).not("."+
a.emptyCssClass).addClass(a.formCssClass)});if(c.length&&f){var h;"TR"==c.attr("tagName")?(c=this.eq(-1).children().length,d.append('<tr class="'+a.addCssClass+'"><td colspan="'+c+'"><a href="javascript:void(0)">'+a.addText+"</a></tr>"),h=d.find("tr:last a")):(c.filter(":last").after('<div class="'+a.addCssClass+'"><a href="javascript:void(0)">'+a.addText+"</a></div>"),h=c.filter(":last").next().find("a"));h.click(function(d){d.preventDefault();var f=b("#id_"+a.prefix+"-TOTAL_FORMS"),d=b("#"+a.prefix+
"-empty"),c=d.clone(true);c.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+g);c.is("tr")?c.children(":last").append('<div><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></div>"):c.is("ul")||c.is("ol")?c.append('<li><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></li>"):c.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></span>");c.find("*").each(function(){i(this,
a.prefix,f.val())});c.insertBefore(b(d));b(f).val(parseInt(f.val(),10)+1);g=g+1;e.val()!==""&&e.val()-f.val()<=0&&h.parent().hide();c.find("a."+a.deleteCssClass).click(function(d){d.preventDefault();d=b(this).parents("."+a.formCssClass);d.remove();g=g-1;a.removed&&a.removed(d);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);(e.val()===""||e.val()-d.length>0)&&h.parent().show();for(var c=0,f=d.length;c<f;c++){i(b(d).get(c),a.prefix,c);b(d.get(c)).find("*").each(function(){i(this,
a.prefix,c)})}});a.added&&a.added(c)})}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};b.fn.tabularFormset=function(d){var a=b(this),c=function(){b(a.selector).not(".add-row").removeClass("row1 row2").filter(":even").addClass("row1").end().filter(":odd").addClass("row2")};a.formset({prefix:d.prefix,addText:d.addText,formCssClass:"dynamic-"+
d.prefix,deleteCssClass:"inline-deletelink",deleteText:d.deleteText,emptyCssClass:"empty-form",removed:c,added:function(a){a.find(".prepopulated_field").each(function(){var d=b(this).find("input, select, textarea"),c=d.data("dependency_list")||[],e=[];b.each(c,function(d,b){e.push("#"+a.find(".field-"+b).find("input, select, textarea").attr("id"))});e.length&&d.prepopulate(e,d.attr("maxlength"))});"undefined"!=typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),DateTimeShortcuts.init());"undefined"!=
typeof SelectFilter&&(b(".selectfilter").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],false,d.adminStaticPrefix)}),b(".selectfilterstacked").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],true,d.adminStaticPrefix)}));c(a)}});return a};b.fn.stackedFormset=function(d){var a=b(this),c=function(){b(a.selector).find(".inline_label").each(function(a){a+=1;b(this).html(b(this).html().replace(/(#\d+)/g,"#"+a))})};a.formset({prefix:d.prefix,
addText:d.addText,formCssClass:"dynamic-"+d.prefix,deleteCssClass:"inline-deletelink",deleteText:d.deleteText,emptyCssClass:"empty-form",removed:c,added:function(a){a.find(".prepopulated_field").each(function(){var d=b(this).find("input, select, textarea"),c=d.data("dependency_list")||[],e=[];b.each(c,function(d,b){e.push("#"+a.find(".form-row .field-"+b).find("input, select, textarea").attr("id"))});e.length&&d.prepopulate(e,d.attr("maxlength"))});"undefined"!=typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),
DateTimeShortcuts.init());"undefined"!=typeof SelectFilter&&(b(".selectfilter").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],false,d.adminStaticPrefix)}),b(".selectfilterstacked").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],true,d.adminStaticPrefix)}));c(a)}});return a}})(django.jQuery);

View File

@ -20,63 +20,11 @@
<script type="text/javascript">
(function($) {
$(document).ready(function() {
var rows = "#{{ inline_admin_formset.formset.prefix }}-group .inline-related";
var updateInlineLabel = function(row) {
$(rows).find(".inline_label").each(function(i) {
var count = i + 1;
$(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
});
};
var reinitDateTimeShortCuts = function() {
// Reinitialize the calendar and clock widgets by force, yuck.
if (typeof DateTimeShortcuts != "undefined") {
$(".datetimeshortcuts").remove();
DateTimeShortcuts.init();
}
};
var updateSelectFilter = function() {
// If any SelectFilter widgets were added, instantiate a new instance.
if (typeof SelectFilter != "undefined"){
$(".selectfilter").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% static "admin/" %}");
});
$(".selectfilterstacked").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% static "admin/" %}");
});
}
};
var initPrepopulatedFields = function(row) {
row.find('.prepopulated_field').each(function() {
var field = $(this);
var input = field.find('input, select, textarea');
var dependency_list = input.data('dependency_list') || [];
var dependencies = [];
$.each(dependency_list, function(i, field_name) {
dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id'));
});
if (dependencies.length) {
input.prepopulate(dependencies, input.attr('maxlength'));
}
});
};
$(rows).formset({
prefix: "{{ inline_admin_formset.formset.prefix }}",
addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}",
formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
deleteCssClass: "inline-deletelink",
deleteText: "{% trans "Remove" %}",
emptyCssClass: "empty-form",
removed: updateInlineLabel,
added: (function(row) {
initPrepopulatedFields(row);
reinitDateTimeShortCuts();
updateSelectFilter();
updateInlineLabel(row);
})
});
});
$("#{{ inline_admin_formset.formset.prefix }}-group .inline-related").stackedFormset({
prefix: '{{ inline_admin_formset.formset.prefix }}',
adminStaticPrefix: '{% static "admin/" %}',
deleteText: "{% trans "Remove" %}",
addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}"
});
})(django.jQuery);
</script>

View File

@ -67,64 +67,13 @@
</div>
<script type="text/javascript">
(function($) {
$(document).ready(function($) {
var rows = "#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr";
var alternatingRows = function(row) {
$(rows).not(".add-row").removeClass("row1 row2")
.filter(":even").addClass("row1").end()
.filter(rows + ":odd").addClass("row2");
}
var reinitDateTimeShortCuts = function() {
// Reinitialize the calendar and clock widgets by force
if (typeof DateTimeShortcuts != "undefined") {
$(".datetimeshortcuts").remove();
DateTimeShortcuts.init();
}
}
var updateSelectFilter = function() {
// If any SelectFilter widgets are a part of the new form,
// instantiate a new SelectFilter instance for it.
if (typeof SelectFilter != "undefined"){
$(".selectfilter").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% static "admin/" %}");
});
$(".selectfilterstacked").each(function(index, value){
var namearr = value.name.split('-');
SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% static "admin/" %}");
});
}
}
var initPrepopulatedFields = function(row) {
row.find('.prepopulated_field').each(function() {
var field = $(this);
var input = field.find('input, select, textarea');
var dependency_list = input.data('dependency_list') || [];
var dependencies = [];
$.each(dependency_list, function(i, field_name) {
dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id'));
});
if (dependencies.length) {
input.prepopulate(dependencies, input.attr('maxlength'));
}
});
}
$(rows).formset({
prefix: "{{ inline_admin_formset.formset.prefix }}",
addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}",
formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
deleteCssClass: "inline-deletelink",
deleteText: "{% trans "Remove" %}",
emptyCssClass: "empty-form",
removed: alternatingRows,
added: (function(row) {
initPrepopulatedFields(row);
reinitDateTimeShortCuts();
updateSelectFilter();
alternatingRows(row);
})
});
});
$("#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr").tabularFormset({
prefix: "{{ inline_admin_formset.formset.prefix }}",
adminStaticPrefix: '{% static "admin/" %}',
addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
deleteText: "{% trans 'Remove' %}"
});
})(django.jQuery);
</script>

View File

@ -182,7 +182,7 @@ def items_for_result(cl, result, form):
row_class = ''
try:
f, attr, value = lookup_field(field_name, result, cl.model_admin)
except (AttributeError, ObjectDoesNotExist):
except ObjectDoesNotExist:
result_repr = EMPTY_CHANGELIST_VALUE
else:
if f is None:

View File

@ -4,7 +4,7 @@ import datetime
import decimal
from django.db import models
from django.db.models.sql.constants import LOOKUP_SEP
from django.db.models.constants import LOOKUP_SEP
from django.db.models.deletion import Collector
from django.db.models.related import RelatedObject
from django.forms.forms import pretty_name

View File

@ -8,6 +8,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME
from django.core.exceptions import PermissionDenied
from django.utils.decorators import available_attrs
from django.utils.encoding import force_str
from django.shortcuts import resolve_url
def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
@ -23,11 +24,10 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE
if test_func(request.user):
return view_func(request, *args, **kwargs)
path = request.build_absolute_uri()
# urlparse chokes on lazy objects in Python 3
login_url_as_str = force_str(login_url or settings.LOGIN_URL)
resolved_login_url = resolve_url(login_url or settings.LOGIN_URL)
# If the login url is the same scheme and net location then just
# use the path as the "next" url.
login_scheme, login_netloc = urlparse(login_url_as_str)[:2]
login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
current_scheme, current_netloc = urlparse(path)[:2]
if ((not login_scheme or login_scheme == current_scheme) and
(not login_netloc or login_netloc == current_netloc)):

View File

@ -28,7 +28,7 @@ class LoginRequiredTestCase(AuthViewsTestCase):
pass
login_required(normal_view)
def testLoginRequired(self, view_url='/login_required/', login_url=settings.LOGIN_URL):
def testLoginRequired(self, view_url='/login_required/', login_url='/login/'):
"""
Check that login_required works on a simple view wrapped in a
login_required decorator.

View File

@ -1,8 +1,9 @@
from django.conf import settings
from django.contrib.auth.models import (Group, User, SiteProfileNotAvailable,
UserManager)
from django.test import TestCase, skipIfCustomUser
from django.test.utils import override_settings
from django.contrib.auth.models import (Group, User,
SiteProfileNotAvailable, UserManager)
from django.utils import six
@skipIfCustomUser
@ -14,19 +15,19 @@ class ProfileTestCase(TestCase):
# calling get_profile without AUTH_PROFILE_MODULE set
del settings.AUTH_PROFILE_MODULE
with self.assertRaisesRegexp(SiteProfileNotAvailable,
with six.assertRaisesRegex(self, SiteProfileNotAvailable,
"You need to set AUTH_PROFILE_MODULE in your project"):
user.get_profile()
# Bad syntax in AUTH_PROFILE_MODULE:
settings.AUTH_PROFILE_MODULE = 'foobar'
with self.assertRaisesRegexp(SiteProfileNotAvailable,
with six.assertRaisesRegex(self, SiteProfileNotAvailable,
"app_label and model_name should be separated by a dot"):
user.get_profile()
# module that doesn't exist
settings.AUTH_PROFILE_MODULE = 'foo.bar'
with self.assertRaisesRegexp(SiteProfileNotAvailable,
with six.assertRaisesRegex(self, SiteProfileNotAvailable,
"Unable to load the profile model"):
user.get_profile()

View File

@ -7,9 +7,9 @@ from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect, QueryDict
from django.template.response import TemplateResponse
from django.utils.encoding import force_str
from django.utils.http import base36_to_int
from django.utils.translation import ugettext as _
from django.shortcuts import resolve_url
from django.views.decorators.debug import sensitive_post_parameters
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
@ -38,16 +38,16 @@ def login(request, template_name='registration/login.html',
if request.method == "POST":
form = authentication_form(data=request.POST)
if form.is_valid():
netloc = urlparse(redirect_to)[1]
# Use default setting if redirect_to is empty
if not redirect_to:
redirect_to = settings.LOGIN_REDIRECT_URL
redirect_to = resolve_url(redirect_to)
netloc = urlparse(redirect_to)[1]
# Heavier security check -- don't allow redirection to a different
# host.
elif netloc and netloc != request.get_host():
redirect_to = settings.LOGIN_REDIRECT_URL
if netloc and netloc != request.get_host():
redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
# Okay, security checks complete. Log the user in.
auth_login(request, form.get_user())
@ -112,6 +112,7 @@ def logout_then_login(request, login_url=None, current_app=None, extra_context=N
"""
if not login_url:
login_url = settings.LOGIN_URL
login_url = resolve_url(login_url)
return logout(request, login_url, current_app=current_app, extra_context=extra_context)
@ -120,10 +121,9 @@ def redirect_to_login(next, login_url=None,
"""
Redirects the user to the login page, passing the given 'next' page
"""
# urlparse chokes on lazy objects in Python 3
login_url_as_str = force_str(login_url or settings.LOGIN_URL)
resolved_url = resolve_url(login_url or settings.LOGIN_URL)
login_url_parts = list(urlparse(login_url_as_str))
login_url_parts = list(urlparse(resolved_url))
if redirect_field_name:
querystring = QueryDict(login_url_parts[4], mutable=True)
querystring[redirect_field_name] = next
@ -236,7 +236,7 @@ def password_reset_complete(request,
template_name='registration/password_reset_complete.html',
current_app=None, extra_context=None):
context = {
'login_url': settings.LOGIN_URL
'login_url': resolve_url(settings.LOGIN_URL)
}
if extra_context is not None:
context.update(extra_context)

View File

@ -1,5 +1,5 @@
from django.db.models.constants import LOOKUP_SEP
from django.db.models.fields import FieldDoesNotExist
from django.db.models.sql.constants import LOOKUP_SEP
from django.db.models.sql.expressions import SQLEvaluator
from django.db.models.sql.where import Constraint, WhereNode
from django.contrib.gis.db.models.fields import GeometryField

View File

@ -4,6 +4,7 @@ HR-specific Form helpers
"""
from __future__ import absolute_import, unicode_literals
import datetime
import re
from django.contrib.localflavor.hr.hr_choices import (
@ -91,17 +92,16 @@ class HRJMBGField(Field):
dd = int(matches.group('dd'))
mm = int(matches.group('mm'))
yyy = int(matches.group('yyy'))
import datetime
try:
datetime.date(yyy,mm,dd)
except:
datetime.date(yyy, mm, dd)
except ValueError:
raise ValidationError(self.error_messages['date'])
# Validate checksum.
k = matches.group('k')
checksum = 0
for i,j in zip(range(7,1,-1),range(6)):
checksum+=i*(int(value[j])+int(value[13-i]))
for i, j in zip(range(7, 1, -1), range(6)):
checksum += i * (int(value[j]) + int(value[13 - i]))
m = 11 - checksum % 11
if m == 10:
raise ValidationError(self.error_messages['invalid'])

View File

@ -4,6 +4,8 @@ Romanian specific form helpers.
"""
from __future__ import absolute_import, unicode_literals
import datetime
from django.contrib.localflavor.ro.ro_counties import COUNTIES_CHOICES
from django.core.validators import EMPTY_VALUES
from django.forms import ValidationError, Field, RegexField, Select
@ -69,10 +71,9 @@ class ROCNPField(RegexField):
if value in EMPTY_VALUES:
return ''
# check birthdate digits
import datetime
try:
datetime.date(int(value[1:3]),int(value[3:5]),int(value[5:7]))
except:
datetime.date(int(value[1:3]), int(value[3:5]), int(value[5:7]))
except ValueError:
raise ValidationError(self.error_messages['invalid'])
# checksum
key = '279146358279'
@ -118,7 +119,7 @@ class ROCountyField(Field):
# search for county name
normalized_CC = []
for entry in COUNTIES_CHOICES:
normalized_CC.append((entry[0],entry[1].upper()))
normalized_CC.append((entry[0], entry[1].upper()))
for entry in normalized_CC:
if entry[1] == value:
return entry[0]
@ -153,8 +154,8 @@ class ROIBANField(RegexField):
value = super(ROIBANField, self).clean(value)
if value in EMPTY_VALUES:
return ''
value = value.replace('-','')
value = value.replace(' ','')
value = value.replace('-', '')
value = value.replace(' ', '')
value = value.upper()
if value[0:2] != 'RO':
raise ValidationError(self.error_messages['invalid'])
@ -185,10 +186,10 @@ class ROPhoneNumberField(RegexField):
value = super(ROPhoneNumberField, self).clean(value)
if value in EMPTY_VALUES:
return ''
value = value.replace('-','')
value = value.replace('(','')
value = value.replace(')','')
value = value.replace(' ','')
value = value.replace('-', '')
value = value.replace('(', '')
value = value.replace(')', '')
value = value.replace(' ', '')
if len(value) != 10:
raise ValidationError(self.error_messages['invalid'])
return value
@ -202,4 +203,3 @@ class ROPostalCodeField(RegexField):
def __init__(self, max_length=6, min_length=6, *args, **kwargs):
super(ROPostalCodeField, self).__init__(r'^[0-9][0-8][0-9]{4}$',
max_length, min_length, *args, **kwargs)

View File

@ -47,6 +47,9 @@ def markdown(value, arg=''):
they will be silently ignored.
"""
import warnings
warnings.warn('The markdown filter has been deprecated',
category=DeprecationWarning)
try:
import markdown
except ImportError:
@ -72,6 +75,9 @@ def markdown(value, arg=''):
@register.filter(is_safe=True)
def restructuredtext(value):
import warnings
warnings.warn('The restructuredtext filter has been deprecated',
category=DeprecationWarning)
try:
from docutils.core import publish_parts
except ImportError:

View File

@ -1,7 +1,9 @@
# Quick tests for the markup templatetags (django.contrib.markup)
import re
import warnings
from django.template import Template, Context
from django import test
from django.utils import unittest
from django.utils.html import escape
@ -21,7 +23,7 @@ try:
except ImportError:
docutils = None
class Templates(unittest.TestCase):
class Templates(test.TestCase):
textile_content = """Paragraph 1
@ -37,6 +39,13 @@ Paragraph 2 with a link_
.. _link: http://www.example.com/"""
def setUp(self):
self.save_warnings_state()
warnings.filterwarnings('ignore', category=DeprecationWarning, module='django.contrib.markup')
def tearDown(self):
self.restore_warnings_state()
@unittest.skipUnless(textile, 'textile not installed')
def test_textile(self):
t = Template("{% load markup %}{{ textile_content|textile }}")

View File

@ -46,10 +46,10 @@ class CookieStorage(BaseStorage):
Stores messages in a cookie.
"""
cookie_name = 'messages'
# We should be able to store 4K in a cookie, but Internet Explorer
# imposes 4K as the *total* limit for a domain. To allow other
# cookies, we go for 3/4 of 4K.
max_cookie_size = 3072
# uwsgi's default configuration enforces a maximum size of 4kb for all the
# HTTP headers. In order to leave some room for other cookies and headers,
# restrict the session cookie to 1/2 of 4kb. See #18781.
max_cookie_size = 2048
not_finished = '__messagesnotfinished__'
def _get(self, *args, **kwargs):

View File

@ -152,7 +152,7 @@ class BaseTest(TestCase):
cycle.
"""
data = {
'messages': ['Test message %d' % x for x in range(10)],
'messages': ['Test message %d' % x for x in range(5)],
}
show_url = reverse('django.contrib.messages.tests.urls.show')
for level in ('debug', 'info', 'success', 'warning', 'error'):
@ -170,7 +170,7 @@ class BaseTest(TestCase):
@override_settings(MESSAGE_LEVEL=constants.DEBUG)
def test_with_template_response(self):
data = {
'messages': ['Test message %d' % x for x in range(10)],
'messages': ['Test message %d' % x for x in range(5)],
}
show_url = reverse('django.contrib.messages.tests.urls.show_template_response')
for level in self.levels.keys():
@ -194,7 +194,7 @@ class BaseTest(TestCase):
before a GET.
"""
data = {
'messages': ['Test message %d' % x for x in range(10)],
'messages': ['Test message %d' % x for x in range(5)],
}
show_url = reverse('django.contrib.messages.tests.urls.show')
messages = []
@ -226,7 +226,7 @@ class BaseTest(TestCase):
when one attempts to store a message.
"""
data = {
'messages': ['Test message %d' % x for x in range(10)],
'messages': ['Test message %d' % x for x in range(5)],
}
show_url = reverse('django.contrib.messages.tests.urls.show')
for level in ('debug', 'info', 'success', 'warning', 'error'):
@ -251,7 +251,7 @@ class BaseTest(TestCase):
raised if 'fail_silently' = True
"""
data = {
'messages': ['Test message %d' % x for x in range(10)],
'messages': ['Test message %d' % x for x in range(5)],
'fail_silently': True,
}
show_url = reverse('django.contrib.messages.tests.urls.show')

View File

@ -1,4 +1,4 @@
from datetime import datetime, timedelta
from datetime import timedelta
import shutil
import string
import tempfile
@ -302,11 +302,10 @@ class CacheDBSessionTests(SessionTestsMixin, TestCase):
self.assertTrue(self.session.exists(self.session.session_key))
def test_load_overlong_key(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
# Some backends might issue a warning
with warnings.catch_warnings():
self.session._session_key = (string.ascii_letters + string.digits) * 20
self.assertEqual(self.session.load(), {})
self.assertEqual(len(w), 1)
@override_settings(USE_TZ=True)
@ -352,11 +351,10 @@ class CacheSessionTests(SessionTestsMixin, unittest.TestCase):
backend = CacheSession
def test_load_overlong_key(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
# Some backends might issue a warning
with warnings.catch_warnings():
self.session._session_key = (string.ascii_letters + string.digits) * 20
self.assertEqual(self.session.load(), {})
self.assertEqual(len(w), 1)
class SessionMiddlewareTests(unittest.TestCase):

View File

@ -141,7 +141,7 @@ class CacheClass(BaseMemcachedCache):
)
try:
import memcache
except:
except ImportError:
raise InvalidCacheBackendError(
"Memcached cache backend requires either the 'memcache' or 'cmemcache' library"
)

View File

@ -21,7 +21,7 @@ class EmailBackend(BaseEmailBackend):
stream_created = self.open()
for message in email_messages:
self.stream.write('%s\n' % message.message().as_string())
self.stream.write('-'*79)
self.stream.write('-' * 79)
self.stream.write('\n')
self.stream.flush() # flush after each message
if stream_created:

View File

@ -5,6 +5,7 @@ from optparse import OptionParser, NO_DEFAULT
import imp
import warnings
from django.core.exceptions import ImproperlyConfigured
from django.core.management.base import BaseCommand, CommandError, handle_default_options
from django.core.management.color import color_style
from django.utils.importlib import import_module
@ -105,7 +106,7 @@ def get_commands():
try:
from django.conf import settings
apps = settings.INSTALLED_APPS
except (AttributeError, EnvironmentError, ImportError):
except (AttributeError, ImproperlyConfigured):
apps = []
# Find and load the management module for each installed app.

View File

@ -1034,9 +1034,12 @@ class BaseDatabaseIntrospection(object):
def get_primary_key_column(self, cursor, table_name):
"""
Backends can override this to return the column name of the primary key for the given table.
Returns the name of the primary key column for the given table.
"""
raise NotImplementedError
for column in six.iteritems(self.get_indexes(cursor, table_name)):
if column[1]['primary_key']:
return column[0]
return None
def get_indexes(self, cursor, table_name):
"""

View File

@ -1,8 +1,10 @@
import hashlib
import sys
import time
from django.conf import settings
from django.db.utils import load_backend
from django.utils.encoding import force_bytes
from django.utils.six.moves import input
# The prefix to put on the default database name when creating
@ -27,7 +29,10 @@ class BaseDatabaseCreation(object):
Generates a 32-bit digest of a set of arguments that can be used to
shorten identifying names.
"""
return '%x' % (abs(hash(args)) % 4294967296) # 2**32
h = hashlib.md5()
for arg in args:
h.update(force_bytes(arg))
return h.hexdigest()[:8]
def sql_create_model(self, model, style, known_models=set()):
"""

View File

@ -2,7 +2,6 @@ import re
from .base import FIELD_TYPE
from django.db.backends import BaseDatabaseIntrospection
from django.utils import six
foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
@ -88,15 +87,6 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
key_columns.extend(cursor.fetchall())
return key_columns
def get_primary_key_column(self, cursor, table_name):
"""
Returns the name of the primary key column for the given table
"""
for column in six.iteritems(self.get_indexes(cursor, table_name)):
if column[1]['primary_key']:
return column[0]
return None
def get_indexes(self, cursor, table_name):
cursor.execute("SHOW INDEX FROM %s" % self.connection.ops.quote_name(table_name))
# Do a two-pass search for indexes: on first pass check which indexes

View File

@ -412,7 +412,4 @@ def _sqlite_format_dtdelta(dt, conn, days, secs, usecs):
return str(dt)
def _sqlite_regexp(re_pattern, re_string):
try:
return bool(re.search(re_pattern, re_string))
except:
return False
return bool(re.search(re_pattern, re_string))

View File

@ -0,0 +1,7 @@
"""
Constants used across the ORM in general.
"""
# Separator used to split filter strings apart.
LOOKUP_SEP = '__'

View File

@ -8,6 +8,7 @@ import sys
from django.core import exceptions
from django.db import connections, router, transaction, IntegrityError
from django.db.models.constants import LOOKUP_SEP
from django.db.models.fields import AutoField
from django.db.models.query_utils import (Q, select_related_descend,
deferred_class_factory, InvalidQuery)
@ -1613,8 +1614,6 @@ def prefetch_related_objects(result_cache, related_lookups):
Populates prefetched objects caches for a list of results
from a QuerySet
"""
from django.db.models.sql.constants import LOOKUP_SEP
if len(result_cache) == 0:
return # nothing to do

View File

@ -3,8 +3,10 @@ from django.utils.six.moves import zip
from django.core.exceptions import FieldError
from django.db import transaction
from django.db.backends.util import truncate_name
from django.db.models.constants import LOOKUP_SEP
from django.db.models.query_utils import select_related_descend
from django.db.models.sql.constants import *
from django.db.models.sql.constants import (SINGLE, MULTI, ORDER_DIR,
GET_ITERATOR_CHUNK_SIZE)
from django.db.models.sql.datastructures import EmptyResultSet
from django.db.models.sql.expressions import SQLEvaluator
from django.db.models.sql.query import get_order_dir, Query
@ -811,7 +813,7 @@ class SQLCompiler(object):
raise EmptyResultSet
except EmptyResultSet:
if result_type == MULTI:
return empty_iter()
return iter([])
else:
return
@ -1088,13 +1090,6 @@ class SQLDateCompiler(SQLCompiler):
yield date
def empty_iter():
"""
Returns an iterator containing no results.
"""
yield next(iter([]))
def order_modified_iter(cursor, trim, sentinel):
"""
Yields blocks of rows from a cursor. We use this iterator in the special

View File

@ -1,7 +1,13 @@
"""
Constants specific to the SQL storage portion of the ORM.
"""
from collections import namedtuple
import re
# Valid query types (a set is used for speedy lookups).
# Valid query types (a set is used for speedy lookups). These are (currently)
# considered SQL-specific; other storage systems may choose to use different
# lookup types.
QUERY_TERMS = set([
'exact', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte', 'in',
'startswith', 'istartswith', 'endswith', 'iendswith', 'range', 'year',
@ -12,9 +18,6 @@ QUERY_TERMS = set([
# Larger values are slightly faster at the expense of more storage space.
GET_ITERATOR_CHUNK_SIZE = 100
# Separator used to split filter strings apart.
LOOKUP_SEP = '__'
# Constants to make looking up tuple values clearer.
# Join lists (indexes into the tuples that are values in the alias_map
# dictionary in the Query class).

View File

@ -1,6 +1,6 @@
from django.core.exceptions import FieldError
from django.db.models.constants import LOOKUP_SEP
from django.db.models.fields import FieldDoesNotExist
from django.db.models.sql.constants import LOOKUP_SEP
class SQLEvaluator(object):
def __init__(self, expression, query, allow_joins=True):

View File

@ -15,11 +15,12 @@ from django.utils.tree import Node
from django.utils import six
from django.db import connections, DEFAULT_DB_ALIAS
from django.db.models import signals
from django.db.models.constants import LOOKUP_SEP
from django.db.models.expressions import ExpressionNode
from django.db.models.fields import FieldDoesNotExist
from django.db.models.query_utils import InvalidQuery
from django.db.models.sql import aggregates as base_aggregates_module
from django.db.models.sql.constants import *
from django.db.models.sql.constants import (QUERY_TERMS, ORDER_DIR, SINGLE,
ORDER_PATTERN, JoinInfo)
from django.db.models.sql.datastructures import EmptyResultSet, Empty, MultiJoin
from django.db.models.sql.expressions import SQLEvaluator
from django.db.models.sql.where import (WhereNode, Constraint, EverythingNode,
@ -28,6 +29,7 @@ from django.core.exceptions import FieldError
__all__ = ['Query', 'RawQuery']
class RawQuery(object):
"""
A single raw SQL query

View File

@ -3,6 +3,7 @@ Query subclasses which provide extra functionality beyond simple data retrieval.
"""
from django.core.exceptions import FieldError
from django.db.models.constants import LOOKUP_SEP
from django.db.models.fields import DateField, FieldDoesNotExist
from django.db.models.sql.constants import *
from django.db.models.sql.datastructures import Date

View File

@ -507,11 +507,7 @@ class CheckboxInput(Widget):
def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs, type='checkbox', name=name)
try:
result = self.check_test(value)
except: # Silently catch exceptions
result = False
if result:
if self.check_test(value):
final_attrs['checked'] = 'checked'
if not (value is True or value is False or value is None or value == ''):
# Only add the 'value' attribute if a value is non-empty.
@ -525,7 +521,7 @@ class CheckboxInput(Widget):
return False
value = data.get(name)
# Translate true and false strings to boolean values.
values = {'true': True, 'false': False}
values = {'true': True, 'false': False}
if isinstance(value, six.string_types):
value = values.get(value.lower(), value)
return value

View File

@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals
import copy
import datetime
from email.header import Header
import os
import re
import sys
@ -560,31 +561,44 @@ class HttpResponse(object):
else:
__str__ = serialize
def _convert_to_ascii(self, *values):
"""Converts all values to ascii strings."""
for value in values:
if not isinstance(value, six.string_types):
value = str(value)
try:
if six.PY3:
# Ensure string only contains ASCII
value.encode('us-ascii')
def _convert_to_charset(self, value, charset, mime_encode=False):
"""Converts headers key/value to ascii/latin1 native strings.
`charset` must be 'ascii' or 'latin-1'. If `mime_encode` is True and
`value` value can't be represented in the given charset, MIME-encoding
is applied.
"""
if not isinstance(value, (bytes, six.text_type)):
value = str(value)
try:
if six.PY3:
if isinstance(value, str):
# Ensure string is valid in given charset
value.encode(charset)
else:
if isinstance(value, str):
# Ensure string only contains ASCII
value.decode('us-ascii')
else:
# Convert unicode to an ASCII string
value = value.encode('us-ascii')
except UnicodeError as e:
e.reason += ', HTTP response headers must be in US-ASCII format'
# Convert bytestring using given charset
value = value.decode(charset)
else:
if isinstance(value, str):
# Ensure string is valid in given charset
value.decode(charset)
else:
# Convert unicode string to given charset
value = value.encode(charset)
except UnicodeError as e:
if mime_encode:
# Wrapping in str() is a workaround for #12422 under Python 2.
value = str(Header(value, 'utf-8').encode())
else:
e.reason += ', HTTP response headers must be in %s format' % charset
raise
if '\n' in value or '\r' in value:
raise BadHeaderError("Header values can't contain newlines (got %r)" % value)
yield value
if str('\n') in value or str('\r') in value:
raise BadHeaderError("Header values can't contain newlines (got %r)" % value)
return value
def __setitem__(self, header, value):
header, value = self._convert_to_ascii(header, value)
header = self._convert_to_charset(header, 'ascii')
value = self._convert_to_charset(value, 'latin1', mime_encode=True)
self._headers[header.lower()] = (header, value)
def __delitem__(self, header):

View File

@ -68,11 +68,10 @@ class MultiPartParser(object):
if not boundary or not cgi.valid_boundary(boundary):
raise MultiPartParserError('Invalid boundary in multipart: %s' % boundary)
# Content-Length should contain the length of the body we are about
# to receive.
try:
content_length = int(META.get('HTTP_CONTENT_LENGTH', META.get('CONTENT_LENGTH',0)))
content_length = int(META.get('HTTP_CONTENT_LENGTH', META.get('CONTENT_LENGTH', 0)))
except (ValueError, TypeError):
content_length = 0
@ -178,7 +177,7 @@ class MultiPartParser(object):
content_type = meta_data.get('content-type', ('',))[0].strip()
try:
charset = meta_data.get('content-type', (0,{}))[1].get('charset', None)
charset = meta_data.get('content-type', (0, {}))[1].get('charset', None)
except:
charset = None

View File

@ -66,23 +66,7 @@ def redirect(to, *args, **kwargs):
else:
redirect_class = HttpResponseRedirect
# If it's a model, use get_absolute_url()
if hasattr(to, 'get_absolute_url'):
return redirect_class(to.get_absolute_url())
# Next try a reverse URL resolution.
try:
return redirect_class(urlresolvers.reverse(to, args=args, kwargs=kwargs))
except urlresolvers.NoReverseMatch:
# If this is a callable, re-raise.
if callable(to):
raise
# If this doesn't "feel" like a URL, re-raise.
if '/' not in to and '.' not in to:
raise
# Finally, fall back and assume it's a URL
return redirect_class(to)
return redirect_class(resolve_url(to, *args, **kwargs))
def _get_queryset(klass):
"""
@ -128,3 +112,34 @@ def get_list_or_404(klass, *args, **kwargs):
raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
return obj_list
def resolve_url(to, *args, **kwargs):
"""
Return a URL appropriate for the arguments passed.
The arguments could be:
* A model: the model's `get_absolute_url()` function will be called.
* A view name, possibly with arguments: `urlresolvers.reverse()` will
be used to reverse-resolve the name.
* A URL, which will be returned as-is.
"""
# If it's a model, use get_absolute_url()
if hasattr(to, 'get_absolute_url'):
return to.get_absolute_url()
# Next try a reverse URL resolution.
try:
return urlresolvers.reverse(to, args=args, kwargs=kwargs)
except urlresolvers.NoReverseMatch:
# If this is a callable, re-raise.
if callable(to):
raise
# If this doesn't "feel" like a URL, re-raise.
if '/' not in to and '.' not in to:
raise
# Finally, fall back and assume it's a URL
return to

View File

@ -360,7 +360,7 @@ class SimpleTestCase(ut2.TestCase):
args: Extra args.
kwargs: Extra kwargs.
"""
return self.assertRaisesRegexp(expected_exception,
return six.assertRaisesRegex(self, expected_exception,
re.escape(expected_message), callable_obj, *args, **kwargs)
def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None,

View File

@ -1,6 +1,6 @@
import os
import stat
from os.path import join, normcase, normpath, abspath, isabs, sep
from os.path import join, normcase, normpath, abspath, isabs, sep, dirname
from django.utils.encoding import force_text
from django.utils import six
@ -41,13 +41,16 @@ def safe_join(base, *paths):
paths = [force_text(p) for p in paths]
final_path = abspathu(join(base, *paths))
base_path = abspathu(base)
base_path_len = len(base_path)
# Ensure final_path starts with base_path (using normcase to ensure we
# don't false-negative on case insensitive operating systems like Windows)
# and that the next character after the final path is os.sep (or nothing,
# in which case final_path must be equal to base_path).
if not normcase(final_path).startswith(normcase(base_path)) \
or final_path[base_path_len:base_path_len+1] not in ('', sep):
# don't false-negative on case insensitive operating systems like Windows),
# further, one of the following conditions must be true:
# a) The next character is the path separator (to prevent conditions like
# safe_join("/dir", "/../d"))
# b) The final path must be the same as the base path.
# c) The base path must be the most root path (meaning either "/" or "C:\\")
if (not normcase(final_path).startswith(normcase(base_path + sep)) and
normcase(final_path) != normcase(base_path) and
dirname(normcase(base_path)) != normcase(base_path)):
raise ValueError('The joined path (%s) is located outside of the base '
'path component (%s)' % (final_path, base_path))
return final_path

View File

@ -370,13 +370,20 @@ def with_metaclass(meta, base=object):
if PY3:
_iterlists = "lists"
_assertRaisesRegex = "assertRaisesRegex"
else:
_iterlists = "iterlists"
_assertRaisesRegex = "assertRaisesRegexp"
def iterlists(d):
"""Return an iterator over the values of a MultiValueDict."""
return getattr(d, _iterlists)()
def assertRaisesRegex(self, *args, **kwargs):
return getattr(self, _assertRaisesRegex)(*args, **kwargs)
add_move(MovedModule("_dummy_thread", "dummy_thread"))
add_move(MovedModule("_thread", "thread"))

View File

@ -372,6 +372,9 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View):
return qs
def get_date_list_period(self):
"""
Get the aggregation period for the list of dates: 'year', 'month', or 'day'.
"""
return self.date_list_period
def get_date_list(self, queryset, date_type=None):

View File

@ -1,9 +1,8 @@
The documentation in this tree is in plain text files and can be viewed using
any text file viewer.
Technically speaking, it uses ReST (reStructuredText) [1], and the Sphinx
documentation system [2]. This allows it to be built into other forms for
easier viewing and browsing.
It uses ReST (reStructuredText) [1], and the Sphinx documentation system [2].
This allows it to be built into other forms for easier viewing and browsing.
To create an HTML version of the docs:

View File

@ -16,8 +16,9 @@ How do I get started?
What are Django's prerequisites?
--------------------------------
Django requires Python_, specifically Python 2.6.5 - 2.7.x. No other Python
libraries are required for basic Django usage.
Django requires Python, specifically Python 2.6.5 - 2.7.x. No other Python
libraries are required for basic Django usage. Django 1.5 also has
experimental support for Python 3.2 and above.
For a development environment -- if you just want to experiment with Django --
you don't need to have a separate Web server installed; Django comes with its
@ -50,15 +51,12 @@ aren't available under older versions of Python.
Third-party applications for use with Django are, of course, free to set their
own version requirements.
Over the next year or two Django will begin dropping support for older Python
versions as part of a migration which will end with Django running on Python 3
(see below for details).
All else being equal, we recommend that you use the latest 2.x release
(currently Python 2.7). This will let you take advantage of the numerous
improvements and optimizations to the Python language since version 2.6, and
will help ease the process of dropping support for older Python versions on
the road to Python 3.
improvements and optimizations to the Python language since version 2.6.
Generally speaking, we don't recommend running Django on Python 3 yet; see
below for more.
What Python version can I use with Django?
------------------------------------------
@ -71,25 +69,21 @@ Django version Python versions
1.2 2.4, 2.5, 2.6, 2.7
1.3 2.4, 2.5, 2.6, 2.7
**1.4** **2.5, 2.6, 2.7**
*1.5 (future)* *2.6, 2.7, 3.x (experimental)*
*1.5 (future)* *2.6, 2.7* and *3.2, 3.3 (experimental)*
============== ===============
Can I use Django with Python 3?
-------------------------------
Not at the moment. Python 3.0 introduced a number of
backwards-incompatible changes to the Python language, and although
these changes are generally a good thing for Python's future, it will
be a while before most Python software catches up and is able to run
on Python 3.0. For larger Python-based software like Django, the
transition is expected to take at least a year or two (since it
involves dropping support for older Python releases and so must be
done gradually).
Django 1.5 introduces experimental support for Python 3.2 and 3.3. However, we
don't yet suggest that you use Django and Python 3 in production.
In the meantime, Python 2.x releases will be supported and provided
with bug fixes and security updates by the Python development team, so
continuing to use a Python 2.x release during the transition should
not present any risk.
Python 3 support should be considered a "preview". It's offered to bootstrap
the transition of the Django ecosystem to Python 3, and to help you start
porting your apps for future Python 3 compatibility. But we're not yet
confident enough to promise stability in production.
Our current plan is to make Django 1.6 suitable for general use with Python 3.
Will Django run under shared hosting (like TextDrive or Dreamhost)?
-------------------------------------------------------------------

View File

@ -11,7 +11,7 @@ Then, just do this::
>>> from django.db import connection
>>> connection.queries
[{'sql': 'SELECT polls_polls.id,polls_polls.question,polls_polls.pub_date FROM polls_polls',
[{'sql': 'SELECT polls_polls.id, polls_polls.question, polls_polls.pub_date FROM polls_polls',
'time': '0.002'}]
``connection.queries`` is only available if :setting:`DEBUG` is ``True``.

View File

@ -181,10 +181,10 @@ card values plus their suits; 104 characters in total.
Many of Django's model fields accept options that they don't do anything
with. For example, you can pass both
:attr:`~django.db.models.Field.editable` and
:attr:`~django.db.models.Field.auto_now` to a
:attr:`~django.db.models.DateField.auto_now` to a
:class:`django.db.models.DateField` and it will simply ignore the
:attr:`~django.db.models.Field.editable` parameter
(:attr:`~django.db.models.Field.auto_now` being set implies
(:attr:`~django.db.models.DateField.auto_now` being set implies
``editable=False``). No error is raised in this case.
This behavior simplifies the field classes, because they don't need to
@ -516,8 +516,8 @@ for the first time, the ``add`` parameter will be ``True``, otherwise it will be
You only need to override this method if you want to preprocess the value
somehow, just before saving. For example, Django's
:class:`~django.db.models.DateTimeField` uses this method to set the attribute
correctly in the case of :attr:`~django.db.models.Field.auto_now` or
:attr:`~django.db.models.Field.auto_now_add`.
correctly in the case of :attr:`~django.db.models.DateField.auto_now` or
:attr:`~django.db.models.DateField.auto_now_add`.
If you do override this method, you must return the value of the attribute at
the end. You should also update the model's attribute if you make any changes

View File

@ -264,6 +264,9 @@ these changes.
in 1.4. The backward compatibility will be removed --
``HttpRequest.raw_post_data`` will no longer work.
* ``django.contrib.markup`` will be removed following an accelerated
deprecation.
1.7
---

View File

@ -10,11 +10,9 @@ Install Python
--------------
Being a Python Web framework, Django requires Python. It works with any Python
version from 2.6.5 to 2.7 (due to backwards incompatibilities in Python 3.0,
Django does not currently work with Python 3.0; see :doc:`the Django FAQ
</faq/install>` for more information on supported Python versions and the 3.0
transition), these versions of Python include a lightweight database called
SQLite_ so you won't need to set up a database just yet.
version from 2.6.5 to 2.7. It also features experimental support for versions
3.2 and 3.3. All these versions of Python include a lightweight database
called SQLite_ so you won't need to set up a database just yet.
.. _sqlite: http://sqlite.org/

View File

@ -31,7 +31,7 @@ the file ``mysite/news/models.py``::
return self.full_name
class Article(models.Model):
pub_date = models.DateTimeField()
pub_date = models.DateField()
headline = models.CharField(max_length=200)
content = models.TextField()
reporter = models.ForeignKey(Reporter)
@ -96,8 +96,8 @@ access your data. The API is created on the fly, no code generation necessary::
DoesNotExist: Reporter matching query does not exist. Lookup parameters were {'id': 2}
# Create an article.
>>> from datetime import datetime
>>> a = Article(pub_date=datetime.now(), headline='Django is cool',
>>> from datetime import date
>>> a = Article(pub_date=date.today(), headline='Django is cool',
... content='Yeah.', reporter=r)
>>> a.save()
@ -140,7 +140,7 @@ as registering your model in the admin site::
from django.db import models
class Article(models.Model):
pub_date = models.DateTimeField()
pub_date = models.DateField()
headline = models.CharField(max_length=200)
content = models.TextField()
reporter = models.ForeignKey(Reporter)

View File

@ -513,11 +513,12 @@ Here's what happens if a user goes to "/polls/34/" in this system:
further processing.
Now that we've decoupled that, we need to decouple the ``polls.urls``
URLconf by removing the leading "polls/" from each line, and removing the
lines registering the admin site. Your ``polls/urls.py`` file should now look like
URLconf by removing the leading "polls/" from each line, removing the
lines registering the admin site, and removing the ``include`` import which
is no longer used. Your ``polls/urls.py`` file should now look like
this::
from django.conf.urls import patterns, include, url
from django.conf.urls import patterns, url
urlpatterns = patterns('polls.views',
url(r'^$', 'index'),

View File

@ -218,7 +218,7 @@ Read on for details.
First, open the ``polls/urls.py`` URLconf. It looks like this, according to the
tutorial so far::
from django.conf.urls import patterns, include, url
from django.conf.urls import patterns, url
urlpatterns = patterns('polls.views',
url(r'^$', 'index'),
@ -229,7 +229,7 @@ tutorial so far::
Change it like so::
from django.conf.urls import patterns, include, url
from django.conf.urls import patterns, url
from django.views.generic import DetailView, ListView
from polls.models import Poll

View File

@ -8,6 +8,11 @@ themselves or inherited from. They may not provide all the capabilities
required for projects, in which case there are Mixins and Generic class-based
views.
Many of Django's built-in class-based views inherit from other class-based
views or various mixins. Because this inheritence chain is very important, the
ancestor classes are documented under the section title of **Ancestors (MRO)**.
MRO is an acronym for Method Resolution Order.
View
----
@ -20,6 +25,7 @@ View
1. :meth:`dispatch()`
2. :meth:`http_method_not_allowed()`
3. :meth:`options()`
**Example views.py**::
@ -41,8 +47,20 @@ View
url(r'^mine/$', MyView.as_view(), name='my-view'),
)
**Attributes**
.. attribute:: http_method_names = ['get', 'post', 'put', 'delete', 'head', 'options', 'trace']
The default list of HTTP method names that this view will accept.
**Methods**
.. classmethod:: as_view(**initkwargs)
Returns a callable view that takes a request and returns a response::
response = MyView.as_view()(request)
.. method:: dispatch(request, *args, **kwargs)
The ``view`` part of the view -- the method that accepts a ``request``
@ -53,6 +71,11 @@ View
delegated to :meth:`~View.get()`, a ``POST`` to :meth:`~View.post()`,
and so on.
By default, a ``HEAD`` request will be delegated to :meth:`~View.get()`.
If you need to handle ``HEAD`` requests in a different way than ``GET``,
you can override the :meth:`~View.head()` method. See
:ref:`supporting-other-http-methods` for an example.
The default implementation also sets ``request``, ``args`` and
``kwargs`` as instance variables, so any method on the view can know
the full details of the request that was made to invoke the view.
@ -62,14 +85,13 @@ View
If the view was called with a HTTP method it doesn't support, this
method is called instead.
The default implementation returns ``HttpResponseNotAllowed`` with list
of allowed methods in plain text.
The default implementation returns ``HttpResponseNotAllowed`` with a
list of allowed methods in plain text.
.. note::
.. method:: options(request, *args, **kwargs)
Documentation on class-based views is a work in progress. As yet, only the
methods defined directly on the class are documented here, not methods
defined on superclasses.
Handles responding to requests for the OPTIONS HTTP verb. Returns a
list of the allowed HTTP method names for the view.
TemplateView
------------
@ -81,6 +103,8 @@ TemplateView
**Ancestors (MRO)**
This view inherits methods and attributes from the following views:
* :class:`django.views.generic.base.TemplateView`
* :class:`django.views.generic.base.TemplateResponseMixin`
* :class:`django.views.generic.base.View`
@ -116,28 +140,11 @@ TemplateView
url(r'^$', HomePageView.as_view(), name='home'),
)
**Methods and Attributes**
.. attribute:: template_name
The full name of a template to use.
.. method:: get_context_data(**kwargs)
Return a context data dictionary consisting of the contents of
``kwargs`` stored in the context variable ``params``.
**Context**
* ``params``: The dictionary of keyword arguments captured from the URL
pattern that served the view.
.. note::
Documentation on class-based views is a work in progress. As yet, only the
methods defined directly on the class are documented here, not methods
defined on superclasses.
RedirectView
------------
@ -156,6 +163,8 @@ RedirectView
**Ancestors (MRO)**
This view inherits methods and attributes from the following view:
* :class:`django.views.generic.base.View`
**Method Flowchart**
@ -194,7 +203,7 @@ RedirectView
url(r'^go-to-django/$', RedirectView.as_view(url='http://djangoproject.com'), name='go-to-django'),
)
**Methods and Attributes**
**Attributes**
.. attribute:: url
@ -215,6 +224,8 @@ RedirectView
then the query string is discarded. By default, ``query_string`` is
``False``.
**Methods**
.. method:: get_redirect_url(**kwargs)
Constructs the target URL for redirection.
@ -225,9 +236,3 @@ RedirectView
:attr:`~RedirectView.query_string`. Subclasses may implement any
behavior they wish, as long as the method returns a redirect-ready URL
string.
.. note::
Documentation on class-based views is a work in progress. As yet, only the
methods defined directly on the class are documented here, not methods
defined on superclasses.

View File

@ -2,13 +2,15 @@
Generic date views
==================
Date-based generic views (in the module :mod:`django.views.generic.dates`)
are views for displaying drilldown pages for date-based data.
.. module:: django.views.generic.dates
Date-based generic views, provided in :mod:`django.views.generic.dates`, are
views for displaying drilldown pages for date-based data.
ArchiveIndexView
----------------
.. class:: django.views.generic.dates.ArchiveIndexView
.. class:: ArchiveIndexView
A top-level index page showing the "latest" objects, by date. Objects with
a date in the *future* are not included unless you set ``allow_future`` to
@ -36,7 +38,7 @@ ArchiveIndexView
YearArchiveView
---------------
.. class:: django.views.generic.dates.YearArchiveView
.. class:: YearArchiveView
A yearly archive page showing all available months in a given year. Objects
with a date in the *future* are not displayed unless you set
@ -58,13 +60,15 @@ YearArchiveView
A boolean specifying whether to retrieve the full list of objects for
this year and pass those to the template. If ``True``, the list of
objects will be made available to the context. By default, this is
objects will be made available to the context. If ``False``, the
``None`` queryset will be used as the object list. By default, this is
``False``.
.. method:: get_make_object_list()
Determine if an object list will be returned as part of the context. If
``False``, the ``None`` queryset will be used as the object list.
Determine if an object list will be returned as part of the context.
Returns :attr:`~YearArchiveView.make_object_list` by default.
**Context**
@ -80,16 +84,18 @@ YearArchiveView
:class:`datetime.datetime<python:datetime.datetime>` objects, in
ascending order.
* ``year``: A :class:`datetime.date<python:datetime.date>` object
* ``year``: A :class:`~datetime.date` object
representing the given year.
* ``next_year``: A :class:`datetime.date<python:datetime.date>` object
representing the first day of the next year. If the next year is in the
future, this will be ``None``.
* ``next_year``: A :class:`~datetime.date` object
representing the first day of the next year, according to
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
* ``previous_year``: A :class:`datetime.date<python:datetime.date>` object
representing the first day of the previous year. Unlike ``next_year``,
this will never be ``None``.
* ``previous_year``: A :class:`~datetime.date` object
representing the first day of the previous year, according to
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
**Notes**
@ -98,7 +104,7 @@ YearArchiveView
MonthArchiveView
----------------
.. class:: django.views.generic.dates.MonthArchiveView
.. class:: MonthArchiveView
A monthly archive page showing all objects in a given month. Objects with a
date in the *future* are not displayed unless you set ``allow_future`` to
@ -131,16 +137,18 @@ MonthArchiveView
:class:`datetime.datetime<python:datetime.datetime>` objects, in
ascending order.
* ``month``: A :class:`datetime.date<python:datetime.date>` object
* ``month``: A :class:`~datetime.date` object
representing the given month.
* ``next_month``: A :class:`datetime.date<python:datetime.date>` object
representing the first day of the next month. If the next month is in the
future, this will be ``None``.
* ``next_month``: A :class:`~datetime.date` object
representing the first day of the next month, according to
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
* ``previous_month``: A :class:`datetime.date<python:datetime.date>` object
representing the first day of the previous month. Unlike ``next_month``,
this will never be ``None``.
* ``previous_month``: A :class:`~datetime.date` object
representing the first day of the previous month, according to
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
**Notes**
@ -149,7 +157,7 @@ MonthArchiveView
WeekArchiveView
---------------
.. class:: django.views.generic.dates.WeekArchiveView
.. class:: WeekArchiveView
A weekly archive page showing all objects in a given week. Objects with a
date in the *future* are not displayed unless you set ``allow_future`` to
@ -175,16 +183,18 @@ WeekArchiveView
:class:`~django.views.generic.dates.BaseDateListView`), the template's
context will be:
* ``week``: A :class:`datetime.date<python:datetime.date>` object
* ``week``: A :class:`~datetime.date` object
representing the first day of the given week.
* ``next_week``: A :class:`datetime.date<python:datetime.date>` object
representing the first day of the next week. If the next week is in the
future, this will be ``None``.
* ``next_week``: A :class:`~datetime.date` object
representing the first day of the next week, according to
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
* ``previous_week``: A :class:`datetime.date<python:datetime.date>` object
representing the first day of the previous week. Unlike ``next_week``,
this will never be ``None``.
* ``previous_week``: A :class:`~datetime.date` object
representing the first day of the previous week, according to
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
**Notes**
@ -193,7 +203,7 @@ WeekArchiveView
DayArchiveView
--------------
.. class:: django.views.generic.dates.DayArchiveView
.. class:: DayArchiveView
A day archive page showing all objects in a given day. Days in the future
throw a 404 error, regardless of whether any objects exist for future days,
@ -220,24 +230,28 @@ DayArchiveView
:class:`~django.views.generic.dates.BaseDateListView`), the template's
context will be:
* ``day``: A :class:`datetime.date<python:datetime.date>` object
* ``day``: A :class:`~datetime.date` object
representing the given day.
* ``next_day``: A :class:`datetime.date<python:datetime.date>` object
representing the next day. If the next day is in the future, this will be
``None``.
* ``next_day``: A :class:`~datetime.date` object
representing the next day, according to
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
* ``previous_day``: A :class:`datetime.date<python:datetime.date>` object
representing the previous day. Unlike ``next_day``, this will never be
``None``.
* ``previous_day``: A :class:`~datetime.date` object
representing the previous day, according to
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
* ``next_month``: A :class:`datetime.date<python:datetime.date>` object
representing the first day of the next month. If the next month is in the
future, this will be ``None``.
* ``next_month``: A :class:`~datetime.date` object
representing the first day of the next month, according to
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
* ``previous_month``: A :class:`datetime.date<python:datetime.date>` object
representing the first day of the previous month. Unlike ``next_month``,
this will never be ``None``.
* ``previous_month``: A :class:`~datetime.date` object
representing the first day of the previous month, according to
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
**Notes**
@ -246,7 +260,7 @@ DayArchiveView
TodayArchiveView
----------------
.. class:: django.views.generic.dates.TodayArchiveView
.. class:: TodayArchiveView
A day archive page showing all objects for *today*. This is exactly the
same as :class:`django.views.generic.dates.DayArchiveView`, except today's
@ -271,7 +285,7 @@ TodayArchiveView
DateDetailView
--------------
.. class:: django.views.generic.dates.DateDetailView
.. class:: DateDetailView
A page representing an individual object. If the object has a date value in
the future, the view will throw a 404 error by default, unless you set
@ -293,6 +307,22 @@ DateDetailView
.. note::
All of the generic views listed above have matching Base* views that only
differ in that the they do not include the
:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`.
All of the generic views listed above have matching ``Base`` views that
only differ in that the they do not include the
:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`:
.. class:: BaseArchiveIndexView
.. class:: BaseYearArchiveView
.. class:: BaseMonthArchiveView
.. class:: BaseWeekArchiveView
.. class:: BaseDayArchiveView
.. class:: BaseTodayArchiveView
.. class:: BaseDateDetailView

View File

@ -15,6 +15,8 @@ DetailView
**Ancestors (MRO)**
This view inherits methods and attributes from the following views:
* :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin`
* :class:`django.views.generic.base.TemplateResponseMixin`
* :class:`django.views.generic.detail.BaseDetailView`
@ -71,7 +73,9 @@ ListView
objects (usually, but not necessarily a queryset) that the view is
operating upon.
**Mixins**
**Ancestors (MRO)**
This view inherits methods and attributes from the following views:
* :class:`django.views.generic.list.ListView`
* :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin`
@ -90,3 +94,54 @@ ListView
6. :meth:`get_context_data()`
7. :meth:`get()`
8. :meth:`render_to_response()`
**Example views.py**::
from django.views.generic.list import ListView
from django.utils import timezone
from articles.models import Article
class ArticleListView(ListView):
model = Article
def get_context_data(self, **kwargs):
context = super(ArticleListView, self).get_context_data(**kwargs)
context['now'] = timezone.now()
return context
**Example urls.py**::
from django.conf.urls import patterns, url
from article.views import ArticleListView
urlpatterns = patterns('',
url(r'^$', ArticleListView.as_view(), name='article-list'),
)
.. class:: django.views.generic.list.BaseListView
A base view for displaying a list of objects. It is not intended to be used
directly, but rather as a parent class of the
:class:`django.views.generic.list.ListView` or other views representing
lists of objects.
**Ancestors (MRO)**
This view inherits methods and attributes from the following views:
* :class:`django.views.generic.list.MultipleObjectMixin`
* :class:`django.views.generic.base.View`
**Methods**
.. method:: get(request, *args, **kwargs)
Adds :attr:`object_list` to the context. If
:attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty`
is True then display an empty list. If
:attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` is
False then raise a 404 error.

View File

@ -23,7 +23,7 @@ it is safe to store state variables on the instance (i.e., ``self.foo = 3`` is
a thread-safe operation).
A class-based view is deployed into a URL pattern using the
:meth:`~View.as_view()` classmethod::
:meth:`~django.views.generic.base.View.as_view()` classmethod::
urlpatterns = patterns('',
(r'^view/$', MyView.as_view(size=42)),
@ -37,9 +37,10 @@ A class-based view is deployed into a URL pattern using the
is modified, the actions of one user visiting your view could have an
effect on subsequent users visiting the same view.
Any argument passed into :meth:`~View.as_view()` will be assigned onto the
instance that is used to service a request. Using the previous example,
this means that every request on ``MyView`` is able to use ``self.size``.
Any argument passed into :meth:`~django.views.generic.base.View.as_view()` will
be assigned onto the instance that is used to service a request. Using the
previous example, this means that every request on ``MyView`` is able to use
``self.size``.
Base vs Generic views
---------------------

View File

@ -2,11 +2,12 @@
Date-based mixins
=================
.. currentmodule:: django.views.generic.dates
YearMixin
---------
.. class:: django.views.generic.dates.YearMixin
.. class:: YearMixin
A mixin that can be used to retrieve and provide parsing information for a
year component of a date.
@ -20,29 +21,45 @@ YearMixin
.. attribute:: year
**Optional** The value for the year (as a string). By default, set to
**Optional** The value for the year, as a string. By default, set to
``None``, which means the year will be determined using other means.
.. method:: get_year_format()
Returns the :func:`~time.strftime` format to use when parsing the year. Returns
:attr:`YearMixin.year_format` by default.
Returns the :func:`~time.strftime` format to use when parsing the
year. Returns :attr:`~YearMixin.year_format` by default.
.. method:: get_year()
Returns the year for which this view will display data. Tries the
following sources, in order:
Returns the year for which this view will display data, as a string.
Tries the following sources, in order:
* The value of the :attr:`YearMixin.year` attribute.
* The value of the `year` argument captured in the URL pattern
* The value of the `year` argument captured in the URL pattern.
* The value of the `year` GET query argument.
Raises a 404 if no valid year specification can be found.
.. method:: get_next_year(date)
Returns a date object containing the first day of the year after the
date provided. This function can also return ``None`` or raise an
:class:`~django.http.Http404` exception, depending on the values of
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
.. method:: get_previous_year(date)
Returns a date object containing the first day of the year before the
date provided. This function can also return ``None`` or raise an
:class:`~django.http.Http404` exception, depending on the values of
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
MonthMixin
----------
.. class:: django.views.generic.dates.MonthMixin
.. class:: MonthMixin
A mixin that can be used to retrieve and provide parsing information for a
month component of a date.
@ -51,26 +68,26 @@ MonthMixin
.. attribute:: month_format
The :func:`~time.strftime` format to use when parsing the month. By default, this is
``'%b'``.
The :func:`~time.strftime` format to use when parsing the month. By
default, this is ``'%b'``.
.. attribute:: month
**Optional** The value for the month (as a string). By default, set to
**Optional** The value for the month, as a string. By default, set to
``None``, which means the month will be determined using other means.
.. method:: get_month_format()
Returns the :func:`~time.strftime` format to use when parsing the month. Returns
:attr:`MonthMixin.month_format` by default.
Returns the :func:`~time.strftime` format to use when parsing the
month. Returns :attr:`~MonthMixin.month_format` by default.
.. method:: get_month()
Returns the month for which this view will display data. Tries the
following sources, in order:
Returns the month for which this view will display data, as a string.
Tries the following sources, in order:
* The value of the :attr:`MonthMixin.month` attribute.
* The value of the `month` argument captured in the URL pattern
* The value of the `month` argument captured in the URL pattern.
* The value of the `month` GET query argument.
Raises a 404 if no valid month specification can be found.
@ -78,20 +95,23 @@ MonthMixin
.. method:: get_next_month(date)
Returns a date object containing the first day of the month after the
date provided. Returns ``None`` if mixed with a view that sets
``allow_future = False``, and the next month is in the future. If
``allow_empty = False``, returns the next month that contains data.
date provided. This function can also return ``None`` or raise an
:class:`~django.http.Http404` exception, depending on the values of
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
.. method:: get_prev_month(date)
Returns a date object containing the first day of the month before the
date provided. If ``allow_empty = False``, returns the previous month
that contained data.
date provided. This function can also return ``None`` or raise an
:class:`~django.http.Http404` exception, depending on the values of
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
DayMixin
--------
.. class:: django.views.generic.dates.DayMixin
.. class:: DayMixin
A mixin that can be used to retrieve and provide parsing information for a
day component of a date.
@ -100,46 +120,50 @@ DayMixin
.. attribute:: day_format
The :func:`~time.strftime` format to use when parsing the day. By default, this is
``'%d'``.
The :func:`~time.strftime` format to use when parsing the day. By
default, this is ``'%d'``.
.. attribute:: day
**Optional** The value for the day (as a string). By default, set to
**Optional** The value for the day, as a string. By default, set to
``None``, which means the day will be determined using other means.
.. method:: get_day_format()
Returns the :func:`~time.strftime` format to use when parsing the day. Returns
:attr:`DayMixin.day_format` by default.
Returns the :func:`~time.strftime` format to use when parsing the day.
Returns :attr:`~DayMixin.day_format` by default.
.. method:: get_day()
Returns the day for which this view will display data. Tries the
following sources, in order:
Returns the day for which this view will display data, as a string.
Tries the following sources, in order:
* The value of the :attr:`DayMixin.day` attribute.
* The value of the `day` argument captured in the URL pattern
* The value of the `day` argument captured in the URL pattern.
* The value of the `day` GET query argument.
Raises a 404 if no valid day specification can be found.
.. method:: get_next_day(date)
Returns a date object containing the next day after the date provided.
Returns ``None`` if mixed with a view that sets ``allow_future = False``,
and the next day is in the future. If ``allow_empty = False``, returns
the next day that contains data.
Returns a date object containing the next valid day after the date
provided. This function can also return ``None`` or raise an
:class:`~django.http.Http404` exception, depending on the values of
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
.. method:: get_prev_day(date)
Returns a date object containing the previous day. If
``allow_empty = False``, returns the previous day that contained data.
Returns a date object containing the previous valid day. This function
can also return ``None`` or raise an :class:`~django.http.Http404`
exception, depending on the values of
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
WeekMixin
---------
.. class:: django.views.generic.dates.WeekMixin
.. class:: WeekMixin
A mixin that can be used to retrieve and provide parsing information for a
week component of a date.
@ -148,23 +172,24 @@ WeekMixin
.. attribute:: week_format
The :func:`~time.strftime` format to use when parsing the week. By default, this is
``'%U'``.
The :func:`~time.strftime` format to use when parsing the week. By
default, this is ``'%U'``, which means the week starts on Sunday. Set
it to ``'%W'`` if your week starts on Monday.
.. attribute:: week
**Optional** The value for the week (as a string). By default, set to
**Optional** The value for the week, as a string. By default, set to
``None``, which means the week will be determined using other means.
.. method:: get_week_format()
Returns the :func:`~time.strftime` format to use when parsing the week. Returns
:attr:`WeekMixin.week_format` by default.
Returns the :func:`~time.strftime` format to use when parsing the
week. Returns :attr:`~WeekMixin.week_format` by default.
.. method:: get_week()
Returns the week for which this view will display data. Tries the
following sources, in order:
Returns the week for which this view will display data, as a string.
Tries the following sources, in order:
* The value of the :attr:`WeekMixin.week` attribute.
* The value of the `week` argument captured in the URL pattern
@ -172,11 +197,26 @@ WeekMixin
Raises a 404 if no valid week specification can be found.
.. method:: get_next_week(date)
Returns a date object containing the first day of the week after the
date provided. This function can also return ``None`` or raise an
:class:`~django.http.Http404` exception, depending on the values of
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
.. method:: get_prev_week(date)
Returns a date object containing the first day of the week before the
date provided. This function can also return ``None`` or raise an
:class:`~django.http.Http404` exception, depending on the values of
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
DateMixin
---------
.. class:: django.views.generic.dates.DateMixin
.. class:: DateMixin
A mixin class providing common behavior for all date-based views.
@ -186,7 +226,7 @@ DateMixin
The name of the ``DateField`` or ``DateTimeField`` in the
``QuerySet``'s model that the date-based archive should use to
determine the objects on the page.
determine the list of objects to display on the page.
When :doc:`time zone support </topics/i18n/timezones>` is enabled and
``date_field`` is a ``DateTimeField``, dates are assumed to be in the
@ -210,26 +250,26 @@ DateMixin
.. method:: get_date_field()
Returns the name of the field that contains the date data that this
view will operate on. Returns :attr:`DateMixin.date_field` by default.
view will operate on. Returns :attr:`~DateMixin.date_field` by default.
.. method:: get_allow_future()
Determine whether to include "future" objects on this page, where
"future" means objects in which the field specified in ``date_field``
is greater than the current date/time. Returns
:attr:`DateMixin.allow_future` by default.
:attr:`~DateMixin.allow_future` by default.
BaseDateListView
----------------
.. class:: django.views.generic.dates.BaseDateListView
.. class:: BaseDateListView
A base class that provides common behavior for all date-based views. There
won't normally be a reason to instantiate
:class:`~django.views.generic.dates.BaseDateListView`; instantiate one of
the subclasses instead.
While this view (and it's subclasses) are executing, ``self.object_list``
While this view (and its subclasses) are executing, ``self.object_list``
will contain the list of objects that the view is operating upon, and
``self.date_list`` will contain the list of dates for which data is
available.
@ -245,10 +285,18 @@ BaseDateListView
A boolean specifying whether to display the page if no objects are
available. If this is ``True`` and no objects are available, the view
will display an empty page instead of raising a 404. By default, this
is ``False``.
will display an empty page instead of raising a 404.
.. method:: get_dated_items():
This is identical to :attr:`MultipleObjectMixin.allow_empty`, except
for the default value, which is ``False``.
.. attribute:: date_list_period
**Optional** A string defining the aggregation period for
``date_list``. It must be one of ``'year'`` (default), ``'month'``, or
``'day'``.
.. method:: get_dated_items()
Returns a 3-tuple containing (``date_list``, ``object_list``,
``extra_context``).
@ -265,10 +313,17 @@ BaseDateListView
``lookup``. Enforces any restrictions on the queryset, such as
``allow_empty`` and ``allow_future``.
.. method:: get_date_list(queryset, date_type)
.. method:: get_date_list_period()
Returns the list of dates of type ``date_type`` for which
``queryset`` contains entries. For example, ``get_date_list(qs,
'year')`` will return the list of years for which ``qs`` has entries.
See :meth:`~django.db.models.query.QuerySet.dates()` for the
ways that the ``date_type`` argument can be used.
Returns the aggregation period for ``date_list``. Returns
:attr:`~BaseDateListView.date_list_period` by default.
.. method:: get_date_list(queryset, date_type=None)
Returns the list of dates of type ``date_type`` for which ``queryset``
contains entries. For example, ``get_date_list(qs, 'year')`` will
return the list of years for which ``qs`` has entries. If
``date_type`` isn't provided, the result of
:meth:`BaseDateListView.get_date_list_period` is used. See
:meth:`~django.db.models.query.QuerySet.dates()` for the ways that the
``date_type`` argument can be used.

View File

@ -86,7 +86,8 @@ MultipleObjectMixin
.. method:: get_queryset()
Returns the queryset that represents the data this view will display.
Get the list of items for this view. This must be an iterable and may
be a queryset (in which queryset-specific behavior will be enabled).
.. method:: paginate_queryset(queryset, page_size)

View File

@ -9,16 +9,17 @@ ContextMixin
.. versionadded:: 1.5
**classpath**
``django.views.generic.base.ContextMixin``
**Methods**
.. method:: get_context_data(**kwargs)
Returns a dictionary representing the template context. The keyword
arguments provided will make up the returned context.
arguments provided will make up the returned context. Example usage::
def get_context_data(self, **kwargs):
context = super(RandomNumberView, self).get_context_data(**kwargs)
context['number'] = random.randrange(1, 100)
return context
The template context of all class-based generic views include a
``view`` variable that points to the ``View`` instance.
@ -42,7 +43,13 @@ TemplateResponseMixin
suitable context. The template to use is configurable and can be
further customized by subclasses.
**Methods and Attributes**
**Attributes**
.. attribute:: template_name
The full name of a template to use as defined by a string. Not defining
a template_name will raise a
:class:`django.core.exceptions.ImproperlyConfigured` exception.
.. attribute:: response_class
@ -57,12 +64,14 @@ TemplateResponseMixin
instantiation, create a ``TemplateResponse`` subclass and assign it to
``response_class``.
**Methods**
.. method:: render_to_response(context, **response_kwargs)
Returns a ``self.response_class`` instance.
If any keyword arguments are provided, they will be
passed to the constructor of the response class.
If any keyword arguments are provided, they will be passed to the
constructor of the response class.
Calls :meth:`~TemplateResponseMixin.get_template_names()` to obtain the
list of template names that will be searched looking for an existent

View File

@ -32,11 +32,11 @@ A simple example is the best illustration of this. Suppose we have the
following model, which would represent entries in a Weblog::
from django.db import models
class Entry(models.Model):
title = models.CharField(maxlength=250)
body = models.TextField()
pub_date = models.DateTimeField()
pub_date = models.DateField()
enable_comments = models.BooleanField()
Now, suppose that we want the following steps to be applied whenever a
@ -55,11 +55,11 @@ Accomplishing this is fairly straightforward and requires very little
code::
from django.contrib.comments.moderation import CommentModerator, moderator
class EntryModerator(CommentModerator):
email_notification = True
enable_field = 'enable_comments'
moderator.register(Entry, EntryModerator)
The :class:`CommentModerator` class pre-defines a number of useful moderation

View File

@ -80,7 +80,7 @@ geospatial libraries:
Program Description Required Supported Versions
======================== ==================================== ================================ ==========================
:ref:`GEOS <ref-geos>` Geometry Engine Open Source Yes 3.3, 3.2, 3.1, 3.0
`PROJ.4`_ Cartographic Projections library Yes (PostgreSQL and SQLite only) 4.7, 4.6, 4.5, 4.4
`PROJ.4`_ Cartographic Projections library Yes (PostgreSQL and SQLite only) 4.8, 4.7, 4.6, 4.5, 4.4
:ref:`GDAL <ref-gdal>` Geospatial Data Abstraction Library No (but, required for SQLite) 1.9, 1.8, 1.7, 1.6, 1.5
:ref:`GeoIP <ref-geoip>` IP-based geolocation library No 1.4
`PostGIS`__ Spatial extensions for PostgreSQL Yes (PostgreSQL only) 1.5, 1.4, 1.3
@ -140,16 +140,16 @@ internal geometry representation used by GeoDjango (it's behind the "lazy"
geometries). Specifically, the C API library is called (e.g., ``libgeos_c.so``)
directly from Python using ctypes.
First, download GEOS 3.3.0 from the refractions Web site and untar the source
First, download GEOS 3.3.5 from the refractions Web site and untar the source
archive::
$ wget http://download.osgeo.org/geos/geos-3.3.0.tar.bz2
$ tar xjf geos-3.3.0.tar.bz2
$ wget http://download.osgeo.org/geos/geos-3.3.5.tar.bz2
$ tar xjf geos-3.3.5.tar.bz2
Next, change into the directory where GEOS was unpacked, run the configure
script, compile, and install::
$ cd geos-3.3.0
$ cd geos-3.3.5
$ ./configure
$ make
$ sudo make install
@ -203,15 +203,15 @@ reference systems.
First, download the PROJ.4 source code and datum shifting files [#]_::
$ wget http://download.osgeo.org/proj/proj-4.7.0.tar.gz
$ wget http://download.osgeo.org/proj/proj-datumgrid-1.5.zip
$ wget http://download.osgeo.org/proj/proj-4.8.0.tar.gz
$ wget http://download.osgeo.org/proj/proj-datumgrid-1.5.tar.gz
Next, untar the source code archive, and extract the datum shifting files in the
``nad`` subdirectory. This must be done *prior* to configuration::
$ tar xzf proj-4.7.0.tar.gz
$ cd proj-4.7.0/nad
$ unzip ../../proj-datumgrid-1.5.zip
$ tar xzf proj-4.8.0.tar.gz
$ cd proj-4.8.0/nad
$ tar xzf ../../proj-datumgrid-1.5.tar.gz
$ cd ..
Finally, configure, make and install PROJ.4::
@ -239,9 +239,9 @@ installed prior to building PostGIS.
First download the source archive, and extract::
$ wget http://postgis.refractions.net/download/postgis-1.5.2.tar.gz
$ tar xzf postgis-1.5.2.tar.gz
$ cd postgis-1.5.2
$ wget http://postgis.refractions.net/download/postgis-1.5.5.tar.gz
$ tar xzf postgis-1.5.5.tar.gz
$ cd postgis-1.5.5
Next, configure, make and install PostGIS::
@ -959,15 +959,15 @@ Ubuntu & Debian GNU/Linux
Ubuntu
^^^^^^
11.10
~~~~~
11.10 through 12.04
~~~~~~~~~~~~~~~~~~~
In Ubuntu 11.10, PostgreSQL was upgraded to 9.1. The installation commands are:
In Ubuntu 11.10, PostgreSQL was upgraded to 9.1. The installation command is:
.. code-block:: bash
$ sudo apt-get install binutils gdal-bin libproj-dev postgresql-9.1-postgis \
postgresql-server-dev-9.1 python-psycopg2
$ sudo apt-get install binutils gdal-bin libproj-dev \
postgresql-9.1-postgis postgresql-server-dev-9.1 python-psycopg2
.. _ubuntu10:
@ -976,7 +976,7 @@ In Ubuntu 11.10, PostgreSQL was upgraded to 9.1. The installation commands are:
In Ubuntu 10.04, PostgreSQL was upgraded to 8.4 and GDAL was upgraded to 1.6.
Ubuntu 10.04 uses PostGIS 1.4, while Ubuntu 10.10 uses PostGIS 1.5 (with
geography support). The installation commands are:
geography support). The installation command is:
.. code-block:: bash

View File

@ -5,6 +5,9 @@ django.contrib.markup
.. module:: django.contrib.markup
:synopsis: A collection of template filters that implement common markup languages.
.. deprecated:: 1.5
This module has been deprecated.
Django provides template filters that implement the following markup
languages:

View File

@ -5,14 +5,16 @@ The messages framework
.. module:: django.contrib.messages
:synopsis: Provides cookie- and session-based temporary message storage.
Quite commonly in web applications, you may need to display a one-time
notification message (also know as "flash message") to the user after
processing a form or some other types of user input. For this, Django provides
full support for cookie- and session-based messaging, for both anonymous and
authenticated users. The messages framework allows you to temporarily store
messages in one request and retrieve them for display in a subsequent request
(usually the next one). Every message is tagged with a specific ``level`` that
determines its priority (e.g., ``info``, ``warning``, or ``error``).
Quite commonly in web applications, you need to display a one-time
notification message (also known as "flash message") to the user after
processing a form or some other types of user input.
For this, Django provides full support for cookie- and session-based
messaging, for both anonymous and authenticated users. The messages framework
allows you to temporarily store messages in one request and retrieve them for
display in a subsequent request (usually the next one). Every message is
tagged with a specific ``level`` that determines its priority (e.g., ``info``,
``warning``, or ``error``).
Enabling messages
=================
@ -20,32 +22,27 @@ Enabling messages
Messages are implemented through a :doc:`middleware </ref/middleware>`
class and corresponding :doc:`context processor </ref/templates/api>`.
To enable message functionality, do the following:
The default ``settings.py`` created by ``django-admin.py startproject``
already contains all the settings required to enable message functionality:
* Edit the :setting:`MIDDLEWARE_CLASSES` setting and make sure
it contains ``'django.contrib.messages.middleware.MessageMiddleware'``.
* ``'django.contrib.messages'`` is in :setting:`INSTALLED_APPS`.
If you are using a :ref:`storage backend <message-storage-backends>` that
relies on :doc:`sessions </topics/http/sessions>` (the default),
``'django.contrib.sessions.middleware.SessionMiddleware'`` must be
enabled and appear before ``MessageMiddleware`` in your
* :setting:`MIDDLEWARE_CLASSES` contains
``'django.contrib.sessions.middleware.SessionMiddleware'`` and
``'django.contrib.messages.middleware.MessageMiddleware'``.
The default :ref:`storage backend <message-storage-backends>` relies on
:doc:`sessions </topics/http/sessions>`. That's why ``SessionMiddleware``
must be enabled and appear before ``MessageMiddleware`` in
:setting:`MIDDLEWARE_CLASSES`.
* Edit the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting and make sure
it contains ``'django.contrib.messages.context_processors.messages'``.
* :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains
``'django.contrib.messages.context_processors.messages'``.
* Add ``'django.contrib.messages'`` to your :setting:`INSTALLED_APPS`
setting
The default ``settings.py`` created by ``django-admin.py startproject`` has
``MessageMiddleware`` activated and the ``django.contrib.messages`` app
installed. Also, the default value for :setting:`TEMPLATE_CONTEXT_PROCESSORS`
contains ``'django.contrib.messages.context_processors.messages'``.
If you don't want to use messages, you can remove the
``MessageMiddleware`` line from :setting:`MIDDLEWARE_CLASSES`, the ``messages``
context processor from :setting:`TEMPLATE_CONTEXT_PROCESSORS` and
``'django.contrib.messages'`` from your :setting:`INSTALLED_APPS`.
If you don't want to use messages, you can remove
``'django.contrib.messages'`` from your :setting:`INSTALLED_APPS`, the
``MessageMiddleware`` line from :setting:`MIDDLEWARE_CLASSES`, and the
``messages`` context processor from :setting:`TEMPLATE_CONTEXT_PROCESSORS`.
Configuring the message engine
==============================
@ -56,34 +53,35 @@ Storage backends
----------------
The messages framework can use different backends to store temporary messages.
If the default FallbackStorage isn't suitable to your needs, you can change
which backend is being used by adding a `MESSAGE_STORAGE`_ to your
settings, referencing the module and class of the storage class. For
example::
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
Django provides three built-in storage classes:
The value should be the full path of the desired storage class.
.. class:: django.contrib.messages.storage.session.SessionStorage
Three storage classes are available:
This class stores all messages inside of the request's session. Therefore
it requires Django's ``contrib.sessions`` application.
``'django.contrib.messages.storage.session.SessionStorage'``
This class stores all messages inside of the request's session. It
requires Django's ``contrib.sessions`` application.
.. class:: django.contrib.messages.storage.cookie.CookieStorage
``'django.contrib.messages.storage.cookie.CookieStorage'``
This class stores the message data in a cookie (signed with a secret hash
to prevent manipulation) to persist notifications across requests. Old
messages are dropped if the cookie data size would exceed 4096 bytes.
messages are dropped if the cookie data size would exceed 2048 bytes.
``'django.contrib.messages.storage.fallback.FallbackStorage'``
This is the default storage class.
.. class:: django.contrib.messages.storage.fallback.FallbackStorage
This class first uses CookieStorage for all messages, falling back to using
SessionStorage for the messages that could not fit in a single cookie.
This class first uses ``CookieStorage``, and falls back to using
``SessionStorage`` for the messages that could not fit in a single cookie.
It also requires Django's ``contrib.sessions`` application.
Since it is uses SessionStorage, it also requires Django's
``contrib.sessions`` application.
This behavior avoids writing to the session whenever possible. It should
provide the best performance in the general case.
:class:`~django.contrib.messages.storage.fallback.FallbackStorage` is the
default storage class. If it isn't suitable to your needs, you can select
another storage class by setting `MESSAGE_STORAGE`_ to its full import path,
for example::
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
To write your own storage class, subclass the ``BaseStorage`` class in
``django.contrib.messages.storage.base`` and implement the ``_get`` and
@ -97,8 +95,8 @@ to that of the Python logging module. Message levels allow you to group
messages by type so they can be filtered or displayed differently in views and
templates.
The built-in levels (which can be imported from ``django.contrib.messages``
directly) are:
The built-in levels, which can be imported from ``django.contrib.messages``
directly, are:
=========== ========
Constant Purpose

View File

@ -398,11 +398,21 @@ For each field, we describe the default widget used if you don't specify
If no ``input_formats`` argument is provided, the default input formats are::
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
'%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
'%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006'
'%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
'%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
'%Y-%m-%d', # '2006-10-25'
'%m/%d/%Y', # '10/25/2006'
'%m/%d/%y', # '10/25/06'
Additionally, if you specify :setting:`USE_L10N=False<USE_L10N>` in your settings, the
following will also be included in the default input formats::
'%b %m %d', # 'Oct 25 2006'
'%b %d, %Y', # 'Oct 25, 2006'
'%d %b %Y', # '25 Oct 2006'
'%d %b, %Y', # '25 Oct, 2006'
'%B %d %Y', # 'October 25 2006'
'%B %d, %Y', # 'October 25, 2006'
'%d %B %Y', # '25 October 2006'
'%d %B, %Y', # '25 October, 2006'
``DateTimeField``
~~~~~~~~~~~~~~~~~

View File

@ -70,9 +70,8 @@ overridden:
formfield-specific piece of validation and, possibly,
cleaning/normalizing the data.
Just like the general field ``clean()`` method, above, this method
should return the cleaned data, regardless of whether it changed
anything or not.
This method should return the cleaned value obtained from cleaned_data,
regardless of whether it changed anything or not.
* The Form subclass's ``clean()`` method. This method can perform
any validation that requires access to multiple fields from the form at

View File

@ -310,6 +310,10 @@ commonly used groups of widgets:
A callable that takes the value of the CheckBoxInput and returns
``True`` if the checkbox should be checked for that value.
.. versionchanged:: 1.5
Exceptions from ``check_test`` used to be silenced by its caller,
this is no longer the case, they will propagate upwards.
``Select``
~~~~~~~~~~

View File

@ -195,6 +195,14 @@ support tablespaces for indexes, this option is ignored.
The default value for the field. This can be a value or a callable object. If
callable it will be called every time a new object is created.
The default cannot be a mutable object (model instance, list, set, etc.), as a
reference to the same instance of that object would be used as the default
value in all new model instances. Instead, wrap the desired default in a
callable. For example, if you had a custom ``JSONField`` and wanted to specify
a dictionary as the default, use a ``lambda`` as follows::
contact_info = JSONField("ContactInfo", default=lambda:{"email": "to1@example.com"})
``editable``
------------
@ -983,10 +991,10 @@ define the details of how the relation works.
this with functions from the Python ``datetime`` module to limit choices of
objects by date. For example::
limit_choices_to = {'pub_date__lte': datetime.now}
limit_choices_to = {'pub_date__lte': datetime.date.today}
only allows the choice of related objects with a ``pub_date`` before the
current date/time to be chosen.
current date to be chosen.
Instead of a dictionary this can also be a :class:`~django.db.models.Q`
object for more :ref:`complex queries <complex-lookups-with-q>`. However,

View File

@ -135,7 +135,7 @@ access to more than a single field::
raise ValidationError('Draft entries may not have a publication date.')
# Set the pub_date for published items if it hasn't been set already.
if self.status == 'published' and self.pub_date is None:
self.pub_date = datetime.datetime.now()
self.pub_date = datetime.date.today()
Any :exc:`~django.core.exceptions.ValidationError` exceptions raised by
``Model.clean()`` will be stored in a special key error dictionary key,

View File

@ -83,9 +83,10 @@ Django quotes column and table names behind the scenes.
.. attribute:: Options.get_latest_by
The name of a :class:`DateField` or :class:`DateTimeField` in the model.
This specifies the default field to use in your model :class:`Manager`'s
:class:`~QuerySet.latest` method.
The name of an orderable field in the model, typically a :class:`DateField`,
:class:`DateTimeField`, or :class:`IntegerField`. This specifies the default
field to use in your model :class:`Manager`'s :class:`~QuerySet.latest`
method.
Example::

View File

@ -31,6 +31,9 @@ You can evaluate a ``QuerySet`` in the following ways:
for e in Entry.objects.all():
print(e.headline)
Note: Don't use this if all you want to do is determine if at least one
result exists. It's more efficient to use :meth:`~QuerySet.exists`.
* **Slicing.** As explained in :ref:`limiting-querysets`, a ``QuerySet`` can
be sliced, using Python's array-slicing syntax. Slicing an unevaluated
``QuerySet`` usually returns another unevaluated ``QuerySet``, but Django
@ -75,7 +78,7 @@ You can evaluate a ``QuerySet`` in the following ways:
Note: *Don't* use this if all you want to do is determine if at least one
result exists, and don't need the actual objects. It's more efficient to
use :meth:`exists() <QuerySet.exists>` (see below).
use :meth:`~QuerySet.exists` (see below).
.. _pickling QuerySets:
@ -1523,9 +1526,40 @@ exists
Returns ``True`` if the :class:`.QuerySet` contains any results, and ``False``
if not. This tries to perform the query in the simplest and fastest way
possible, but it *does* execute nearly the same query. This means that calling
:meth:`.QuerySet.exists` is faster than ``bool(some_query_set)``, but not by
a large degree. If ``some_query_set`` has not yet been evaluated, but you know
possible, but it *does* execute nearly the same query as a normal
:class:`.QuerySet` query.
:meth:`~.QuerySet.exists` is useful for searches relating to both
object membership in a :class:`.QuerySet` and to the existence of any objects in
a :class:`.QuerySet`, particularly in the context of a large :class:`.QuerySet`.
The most efficient method of finding whether a model with a unique field
(e.g. ``primary_key``) is a member of a :class:`.QuerySet` is::
entry = Entry.objects.get(pk=123)
if some_query_set.filter(pk=entry.pk).exists():
print("Entry contained in queryset")
Which will be faster than the following which requires evaluating and iterating
through the entire queryset::
if entry in some_query_set:
print("Entry contained in QuerySet")
And to find whether a queryset contains any items::
if some_query_set.exists():
print("There is at least one object in some_query_set")
Which will be faster than::
if some_query_set:
print("There is at least one object in some_query_set")
... but not by a large degree (hence needing a large queryset for efficiency
gains).
Additionally, if a ``some_query_set`` has not yet been evaluated, but you know
that it will be at some point, then using ``some_query_set.exists()`` will do
more overall work (one query for the existence check plus an extra one to later
retrieve the results) than simply using ``bool(some_query_set)``, which
@ -1945,6 +1979,17 @@ SQL equivalent::
You can use ``range`` anywhere you can use ``BETWEEN`` in SQL — for dates,
numbers and even characters.
.. warning::
Filtering a ``DateTimeField`` with dates won't include items on the last
day, because the bounds are interpreted as "0am on the given date". If
``pub_date`` was a ``DateTimeField``, the above expression would be turned
into this SQL::
SELECT ... WHERE pub_date BETWEEN '2005-01-01 00:00:00' and '2005-03-31 00:00:00';
Generally speaking, you can't mix dates and datetimes.
.. fieldlookup:: year
year
@ -1958,7 +2003,7 @@ Example::
SQL equivalent::
SELECT ... WHERE pub_date BETWEEN '2005-01-01' AND '2005-12-31 23:59:59.999999';
SELECT ... WHERE pub_date BETWEEN '2005-01-01' AND '2005-12-31';
(The exact SQL syntax varies for each database engine.)

View File

@ -1313,25 +1313,13 @@ The URL where requests are redirected after login when the
This is used by the :func:`~django.contrib.auth.decorators.login_required`
decorator, for example.
.. _`note on LOGIN_REDIRECT_URL setting`:
.. versionchanged:: 1.5
.. note::
You can use :func:`~django.core.urlresolvers.reverse_lazy` to reference
URLs by their name instead of providing a hardcoded value. Assuming a
``urls.py`` with an URLpattern named ``home``::
urlpatterns = patterns('',
url('^welcome/$', 'test_app.views.home', name='home'),
)
You can use :func:`~django.core.urlresolvers.reverse_lazy` like this::
from django.core.urlresolvers import reverse_lazy
LOGIN_REDIRECT_URL = reverse_lazy('home')
This also works fine with localized URLs using
:func:`~django.conf.urls.i18n.i18n_patterns`.
This setting now also accepts view function names and
:ref:`named URL patterns <naming-url-patterns>` which can be used to reduce
configuration duplication since you no longer have to define the URL in two
places (``settings`` and URLconf).
For backward compatibility reasons the default remains unchanged.
.. setting:: LOGIN_URL
@ -1343,8 +1331,13 @@ Default: ``'/accounts/login/'``
The URL where requests are redirected for login, especially when using the
:func:`~django.contrib.auth.decorators.login_required` decorator.
.. note::
See the `note on LOGIN_REDIRECT_URL setting`_
.. versionchanged:: 1.5
This setting now also accepts view function names and
:ref:`named URL patterns <naming-url-patterns>` which can be used to reduce
configuration duplication since you no longer have to define the URL in two
places (``settings`` and URLconf).
For backward compatibility reasons the default remains unchanged.
.. setting:: LOGOUT_URL

View File

@ -121,6 +121,12 @@ Django 1.5 also includes several smaller improvements worth noting:
argument. By default the batch_size is unlimited except for SQLite where
single batch is limited so that 999 parameters per query isn't exceeded.
* The :setting:`LOGIN_URL` and :setting:`LOGIN_REDIRECT_URL` settings now also
accept view function names and
:ref:`named URL patterns <naming-url-patterns>`. This allows you to reduce
configuration duplication. More information can be found in the
:func:`~django.contrib.auth.decorators.login_required` documentation.
Backwards incompatible changes in 1.5
=====================================
@ -358,3 +364,11 @@ the built-in :func:`itertools.product` instead.
The :class:`~django.utils.encoding.StrAndUnicode` mix-in has been deprecated.
Define a ``__str__`` method and apply the
:func:`~django.utils.encoding.python_2_unicode_compatible` decorator instead.
``django.utils.markup``
~~~~~~~~~~~~~~~~~~~~~~~
The markup contrib module has been deprecated and will follow an accelerated
deprecation schedule. Direct use of python markup libraries or 3rd party tag
libraries is preferred to Django maintaining this functionality in the
framework.

View File

@ -951,6 +951,13 @@ The login_required decorator
(r'^accounts/login/$', 'django.contrib.auth.views.login'),
.. versionchanged:: 1.5
As of version 1.5 :setting:`settings.LOGIN_URL <LOGIN_URL>` now also accepts
view function names and :ref:`named URL patterns <naming-url-patterns>`.
This allows you to freely remap your login view within your URLconf
without having to update the setting.
.. function:: views.login(request, [template_name, redirect_field_name, authentication_form])
**URL name:** ``login``

View File

@ -84,6 +84,50 @@ function-like entry to class-based views::
For more information on how to use the built in generic views, consult the next
topic on :doc:`generic class based views</topics/class-based-views/generic-display>`.
.. _supporting-other-http-methods:
Supporting other HTTP methods
-----------------------------
Suppose somebody wants to access our book library over HTTP using the views
as an API. The API client would connect every now and then and download book
data for the books published since last visit. But if no new books appeared
since then, it is a waste of CPU time and bandwidth to fetch the books from the
database, render a full response and send it to the client. It might be
preferable to ask the API when the most recent book was published.
We map the URL to book list view in the URLconf::
from django.conf.urls import patterns
from books.views import BookListView
urlpatterns = patterns('',
(r'^books/$', BookListView.as_view()),
)
And the view::
from django.http import HttpResponse
from django.views.generic import ListView
from books.models import Book
class BookListView(ListView):
model = Book
def head(self, *args, **kwargs):
last_book = self.get_queryset().latest('publication_date')
response = HttpResponse('')
# RFC 1123 date format
response['Last-Modified'] = last_book.publication_date.strftime('%a, %d %b %Y %H:%M:%S GMT')
return response
If the view is accessed from a ``GET`` request, a plain-and-simple object
list is returned in the response (using ``book_list.html`` template). But if
the client issues a ``HEAD`` request, the response has an empty body and
the ``Last-Modified`` header indicates when the most recent book was published.
Based on this information, the client may or may not download the full object
list.
Decorating class-based views
============================

View File

@ -42,8 +42,8 @@ Create a few Reporters::
Create an Article::
>>> from datetime import datetime
>>> a = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter=r)
>>> from datetime import date
>>> a = Article(id=None, headline="This is a test", pub_date=date(2005, 7, 27), reporter=r)
>>> a.save()
>>> a.reporter.id
@ -65,7 +65,7 @@ database, which always returns unicode strings)::
Create an Article via the Reporter object::
>>> new_article = r.article_set.create(headline="John's second story", pub_date=datetime(2005, 7, 29))
>>> new_article = r.article_set.create(headline="John's second story", pub_date=date(2005, 7, 29))
>>> new_article
<Article: John's second story>
>>> new_article.reporter
@ -75,7 +75,7 @@ Create an Article via the Reporter object::
Create a new article, and add it to the article set::
>>> new_article2 = Article(headline="Paul's story", pub_date=datetime(2006, 1, 17))
>>> new_article2 = Article(headline="Paul's story", pub_date=date(2006, 1, 17))
>>> r.article_set.add(new_article2)
>>> new_article2.reporter
<Reporter: John Smith>

View File

@ -201,73 +201,129 @@ An example
write to propagate to the slaves). It also doesn't consider the
interaction of transactions with the database utilization strategy.
So - what does this mean in practice? Say you want ``myapp`` to
exist on the ``other`` database, and you want all other models in a
master/slave relationship between the databases ``master``, ``slave1`` and
``slave2``. To implement this, you would need 2 routers::
So - what does this mean in practice? Let's consider another sample
configuration. This one will have several databases: one for the
``auth`` application, and all other apps using a master/slave setup
with two read slaves. Here are the settings specifying these
databases::
class MyAppRouter(object):
"""A router to control all database operations on models in
the myapp application"""
DATABASES = {
'auth_db': {
'NAME': 'auth_db',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'swordfish',
},
'master': {
'NAME': 'master',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'spam',
},
'slave1': {
'NAME': 'slave1',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'eggs',
},
'slave2': {
'NAME': 'slave2',
'ENGINE': 'django.db.backends.mysql',
'USER': 'mysql_user',
'PASSWORD': 'bacon',
},
}
Now we'll need to handle routing. First we want a router that knows to
send queries for the ``auth`` app to ``auth_db``::
class AuthRouter(object):
"""
A router to control all database operations on models in the
auth application.
"""
def db_for_read(self, model, **hints):
"Point all operations on myapp models to 'other'"
if model._meta.app_label == 'myapp':
return 'other'
"""
Attempts to read auth models go to auth_db.
"""
if model._meta.app_label == 'auth':
return 'auth_db'
return None
def db_for_write(self, model, **hints):
"Point all operations on myapp models to 'other'"
if model._meta.app_label == 'myapp':
return 'other'
"""
Attempts to write auth models go to auth_db.
"""
if model._meta.app_label == 'auth':
return 'auth_db'
return None
def allow_relation(self, obj1, obj2, **hints):
"Allow any relation if a model in myapp is involved"
if obj1._meta.app_label == 'myapp' or obj2._meta.app_label == 'myapp':
return True
"""
Allow relations if a model in the auth app is involved.
"""
if obj1._meta.app_label == 'auth' or \
obj2._meta.app_label == 'auth':
return True
return None
def allow_syncdb(self, db, model):
"Make sure the myapp app only appears on the 'other' db"
if db == 'other':
return model._meta.app_label == 'myapp'
elif model._meta.app_label == 'myapp':
"""
Make sure the auth app only appears in the 'auth_db'
database.
"""
if db == 'auth_db':
return model._meta.app_label == 'auth'
elif model._meta.app_label == 'auth':
return False
return None
class MasterSlaveRouter(object):
"""A router that sets up a simple master/slave configuration"""
And we also want a router that sends all other apps to the
master/slave configuration, and randomly chooses a slave to read
from::
import random
class MasterSlaveRouter(object):
def db_for_read(self, model, **hints):
"Point all read operations to a random slave"
return random.choice(['slave1','slave2'])
"""
Reads go to a randomly-chosen slave.
"""
return random.choice(['slave1', 'slave2'])
def db_for_write(self, model, **hints):
"Point all write operations to the master"
"""
Writes always go to master.
"""
return 'master'
def allow_relation(self, obj1, obj2, **hints):
"Allow any relation between two objects in the db pool"
db_list = ('master','slave1','slave2')
if obj1._state.db in db_list and obj2._state.db in db_list:
"""
Relations between objects are allowed if both objects are
in the master/slave pool.
"""
db_list = ('master', 'slave1', 'slave2')
if obj1.state.db in db_list and obj2.state.db in db_list:
return True
return None
def allow_syncdb(self, db, model):
"Explicitly put all models on all databases."
"""
All non-auth models end up in this pool.
"""
return True
Then, in your settings file, add the following (substituting ``path.to.`` with
the actual python path to the module where you define the routers)::
Finally, in the settings file, we add the following (substituting
``path.to.`` with the actual python path to the module(s) where the
routers are defined)::
DATABASE_ROUTERS = ['path.to.MyAppRouter', 'path.to.MasterSlaveRouter']
DATABASE_ROUTERS = ['path.to.AuthRouter', 'path.to.MasterSlaveRouter']
The order in which routers are processed is significant. Routers will
be queried in the order the are listed in the
:setting:`DATABASE_ROUTERS` setting . In this example, the
``MyAppRouter`` is processed before the ``MasterSlaveRouter``, and as a
result, decisions concerning the models in ``myapp`` are processed
``AuthRouter`` is processed before the ``MasterSlaveRouter``, and as a
result, decisions concerning the models in ``auth`` are processed
before any other decision is made. If the :setting:`DATABASE_ROUTERS`
setting listed the two routers in the other order,
``MasterSlaveRouter.allow_syncdb()`` would be processed first. The
@ -276,11 +332,11 @@ that all models would be available on all databases.
With this setup installed, lets run some Django code::
>>> # This retrieval will be performed on the 'credentials' database
>>> # This retrieval will be performed on the 'auth_db' database
>>> fred = User.objects.get(username='fred')
>>> fred.first_name = 'Frederick'
>>> # This save will also be directed to 'credentials'
>>> # This save will also be directed to 'auth_db'
>>> fred.save()
>>> # These retrieval will be randomly allocated to a slave database

View File

@ -35,8 +35,8 @@ models, which comprise a Weblog application:
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateTimeField()
mod_date = models.DateTimeField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
n_comments = models.IntegerField()
n_pingbacks = models.IntegerField()
@ -233,7 +233,7 @@ refinements together. For example::
>>> Entry.objects.filter(
... headline__startswith='What'
... ).exclude(
... pub_date__gte=datetime.now()
... pub_date__gte=datetime.date.today()
... ).filter(
... pub_date__gte=datetime(2005, 1, 30)
... )
@ -258,8 +258,8 @@ stored, used and reused.
Example::
>> q1 = Entry.objects.filter(headline__startswith="What")
>> q2 = q1.exclude(pub_date__gte=datetime.now())
>> q3 = q1.filter(pub_date__gte=datetime.now())
>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
>> q3 = q1.filter(pub_date__gte=datetime.date.today())
These three ``QuerySets`` are separate. The first is a base
:class:`~django.db.models.query.QuerySet` containing all entries that contain a
@ -282,7 +282,7 @@ actually run the query until the :class:`~django.db.models.query.QuerySet` is
*evaluated*. Take a look at this example::
>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.now())
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")
>>> print(q)
@ -968,11 +968,12 @@ Be aware that the ``update()`` method is converted directly to an SQL
statement. It is a bulk operation for direct updates. It doesn't run any
:meth:`~django.db.models.Model.save` methods on your models, or emit the
``pre_save`` or ``post_save`` signals (which are a consequence of calling
:meth:`~django.db.models.Model.save`). If you want to save every item in a
:class:`~django.db.models.query.QuerySet` and make sure that the
:meth:`~django.db.models.Model.save` method is called on each instance, you
don't need any special function to handle that. Just loop over them and call
:meth:`~django.db.models.Model.save`::
:meth:`~django.db.models.Model.save`), or honor the
:attr:`~django.db.models.DateField.auto_now` field option.
If you want to save every item in a :class:`~django.db.models.query.QuerySet`
and make sure that the :meth:`~django.db.models.Model.save` method is called on
each instance, you don't need any special function to handle that. Just loop
over them and call :meth:`~django.db.models.Model.save`::
for item in my_queryset:
item.save()

View File

@ -311,18 +311,18 @@ model fields:
to exclude from the form.
For example, if you want a form for the ``Author`` model (defined
above) that includes only the ``name`` and ``title`` fields, you would
above) that includes only the ``name`` and ``birth_date`` fields, you would
specify ``fields`` or ``exclude`` like this::
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
fields = ('name', 'title')
fields = ('name', 'birth_date')
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
exclude = ('birth_date',)
exclude = ('title',)
Since the Author model has only 3 fields, 'name', 'title', and
'birth_date', the forms above will contain exactly the same fields.

View File

@ -20,18 +20,18 @@ Overview
========
To design URLs for an app, you create a Python module informally called a
**URLconf** (URL configuration). This module is pure Python code and
is a simple mapping between URL patterns (as simple regular expressions) to
Python callback functions (your views).
**URLconf** (URL configuration). This module is pure Python code and is a
simple mapping between URL patterns (simple regular expressions) to Python
functions (your views).
This mapping can be as short or as long as needed. It can reference other
mappings. And, because it's pure Python code, it can be constructed
dynamically.
.. versionadded:: 1.4
Django also allows to translate URLs according to the active language.
This process is described in the
:ref:`internationalization docs <url-internationalization>`.
Django also provides a way to translate URLs according to the active
language. See the :ref:`internationalization documentation
<url-internationalization>` for more information.
.. _how-django-processes-a-request:
@ -154,11 +154,12 @@ The matching/grouping algorithm
Here's the algorithm the URLconf parser follows, with respect to named groups
vs. non-named groups in a regular expression:
If there are any named arguments, it will use those, ignoring non-named arguments.
Otherwise, it will pass all non-named arguments as positional arguments.
1. If there are any named arguments, it will use those, ignoring non-named
arguments.
In both cases, it will pass any extra keyword arguments as keyword arguments.
See "Passing extra options to view functions" below.
2. Otherwise, it will pass all non-named arguments as positional arguments.
In both cases, any extra keyword arguments that have been given as per `Passing extra options to view functions`_ (below) will also be passed to the view.
What the URLconf searches against
=================================
@ -176,6 +177,44 @@ The URLconf doesn't look at the request method. In other words, all request
methods -- ``POST``, ``GET``, ``HEAD``, etc. -- will be routed to the same
function for the same URL.
Notes on capturing text in URLs
===============================
Each captured argument is sent to the view as a plain Python string, regardless
of what sort of match the regular expression makes. For example, in this
URLconf line::
(r'^articles/(?P<year>\d{4})/$', 'news.views.year_archive'),
...the ``year`` argument to ``news.views.year_archive()`` will be a string, not
an integer, even though the ``\d{4}`` will only match integer strings.
A convenient trick is to specify default parameters for your views' arguments.
Here's an example URLconf and view::
# URLconf
urlpatterns = patterns('',
(r'^blog/$', 'blog.views.page'),
(r'^blog/page(?P<num>\d+)/$', 'blog.views.page'),
)
# View (in blog/views.py)
def page(request, num="1"):
# Output the appropriate page of blog entries, according to num.
In the above example, both URL patterns point to the same view --
``blog.views.page`` -- but the first pattern doesn't capture anything from the
URL. If the first pattern matches, the ``page()`` function will use its
default argument for ``num``, ``"1"``. If the second pattern matches,
``page()`` will use whatever ``num`` value was captured by the regex.
Performance
===========
Each regular expression in a ``urlpatterns`` is compiled the first time it's
accessed. This makes the system blazingly fast.
Syntax of the urlpatterns variable
==================================
@ -209,10 +248,10 @@ The first argument to ``patterns()`` is a string ``prefix``. See
The remaining arguments should be tuples in this format::
(regular expression, Python callback function [, optional dictionary [, optional name]])
(regular expression, Python callback function [, optional_dictionary [, optional_name]])
...where ``optional dictionary`` and ``optional name`` are optional. (See
`Passing extra options to view functions`_ below.)
The ``optional_dictionary`` and ``optional_name`` parameters are described in
`Passing extra options to view functions`_ below.
.. note::
Because `patterns()` is a function call, it accepts a maximum of 255
@ -332,43 +371,6 @@ value should suffice.
See the documentation about :ref:`the 500 (HTTP Internal Server Error) view
<http_internal_server_error_view>` for more information.
Notes on capturing text in URLs
===============================
Each captured argument is sent to the view as a plain Python string, regardless
of what sort of match the regular expression makes. For example, in this
URLconf line::
(r'^articles/(?P<year>\d{4})/$', 'news.views.year_archive'),
...the ``year`` argument to ``news.views.year_archive()`` will be a string, not
an integer, even though the ``\d{4}`` will only match integer strings.
A convenient trick is to specify default parameters for your views' arguments.
Here's an example URLconf and view::
# URLconf
urlpatterns = patterns('',
(r'^blog/$', 'blog.views.page'),
(r'^blog/page(?P<num>\d+)/$', 'blog.views.page'),
)
# View (in blog/views.py)
def page(request, num="1"):
# Output the appropriate page of blog entries, according to num.
In the above example, both URL patterns point to the same view --
``blog.views.page`` -- but the first pattern doesn't capture anything from the
URL. If the first pattern matches, the ``page()`` function will use its
default argument for ``num``, ``"1"``. If the second pattern matches,
``page()`` will use whatever ``num`` value was captured by the regex.
Performance
===========
Each regular expression in a ``urlpatterns`` is compiled the first time it's
accessed. This makes the system blazingly fast.
The view prefix
===============

View File

@ -1251,6 +1251,11 @@ As a convenience, Django comes with a view, :func:`django.views.i18n.set_languag
that sets a user's language preference and redirects to a given URL or, by default,
back to the previous page.
Make sure that the following item is in your
:setting:`TEMPLATE_CONTEXT_PROCESSORS` list in your settings file::
'django.core.context_processors.i18n'
Activate this view by adding the following line to your URLconf::
(r'^i18n/', include('django.conf.urls.i18n')),

View File

@ -9,10 +9,8 @@ Install Python
Being a Python Web framework, Django requires Python.
It works with any Python version from 2.6.5 to 2.7 (due to backwards
incompatibilities in Python 3.0, Django does not currently work with
Python 3.0; see :doc:`the Django FAQ </faq/install>` for more
information on supported Python versions and the 3.0 transition).
It works with any Python version from 2.6.5 to 2.7. It also features
experimental support for versions 3.2 and 3.3.
Get Python at http://www.python.org. If you're running Linux or Mac OS X, you
probably already have it installed.

View File

@ -25,10 +25,11 @@ free to chose another strategy for your own code, especially if you don't need
to stay compatible with Python 2. But authors of pluggable applications are
encouraged to use the same porting strategy as Django itself.
Writing compatible code is much easier if you target Python ≥ 2.6. You will
most likely take advantage of the compatibility functions introduced in Django
1.5, like :mod:`django.utils.six`, so your application will also require
Django ≥ 1.5.
Writing compatible code is much easier if you target Python ≥ 2.6. Django 1.5
introduces compatibility tools such as :mod:`django.utils.six`. For
convenience, forwards-compatible aliases were introduced in Django 1.4.2. If
your application takes advantage of these tools, it will require Django ≥
1.4.2.
Obviously, writing compatible source code adds some overhead, and that can
cause frustration. Django's developers have found that attempting to write
@ -102,6 +103,8 @@ Old name New name
For backwards compatibility, the old names still work on Python 2. Under
Python 3, ``smart_str`` is an alias for ``smart_text``.
For forwards compatibility, the new names work as of Django 1.4.2.
.. note::
:mod:`django.utils.encoding` was deeply refactored in Django 1.5 to
@ -126,6 +129,8 @@ For backwards compatibility, the old names still work on Python 2. Under
Python 3, ``EscapeString`` and ``SafeString`` are aliases for ``EscapeText``
and ``SafeText`` respectively.
For forwards compatibility, the new names work as of Django 1.4.2.
:meth:`__str__` and :meth:`__unicode__` methods
-----------------------------------------------
@ -166,6 +171,8 @@ On Python 3, the decorator is a no-op. On Python 2, it defines appropriate
This technique is the best match for Django's porting philosophy.
For forwards compatibility, this decorator is available as of Django 1.4.2.
Finally, note that :meth:`__repr__` must return a :class:`str` on all versions
of Python.
@ -317,7 +324,8 @@ Writing compatible code with six
six_ is the canonical compatibility library for supporting Python 2 and 3 in
a single codebase. Read its documentation!
:mod:`six` is bundled with Django: you can import it as :mod:`django.utils.six`.
:mod:`six` is bundled with Django as of version 1.4.2. You can import it as
:mod:`django.utils.six`.
Here are the most common changes required to write compatible code.
@ -392,5 +400,12 @@ The version of six bundled with Django includes one extra function:
2 and :meth:`~django.utils.datastructures.MultiValueDict.lists()` on
Python 3.
.. function:: assertRaisesRegex(testcase, *args, **kwargs)
This replaces ``testcase.assertRaisesRegexp`` on Python 2, and
``testcase.assertRaisesRegex`` on Python 3. ``assertRaisesRegexp`` still
exists in current Python3 versions, but issues a warning.
In addition to six' defaults moves, Django's version provides ``thread`` as
``_thread`` and ``dummy_thread`` as ``_dummy_thread``.

View File

@ -76,9 +76,17 @@ POST to your Web site and have another logged in user unwittingly submit that
form. The malicious user would have to know the nonce, which is user specific
(using a cookie).
When deployed with :ref:`HTTPS <security-recommendation-ssl>`,
``CsrfViewMiddleware`` will check that the HTTP referer header is set to a
URL on the same origin (including subdomain and port). Because HTTPS
provides additional security, it is imperative to ensure connections use HTTPS
where it is available by forwarding insecure connection requests and using
HSTS for supported browsers.
Be very careful with marking views with the ``csrf_exempt`` decorator unless
it is absolutely necessary.
SQL injection protection
========================
@ -112,6 +120,8 @@ The middleware is strongly recommended for any site that does not need to have
its pages wrapped in a frame by third party sites, or only needs to allow that
for a small section of the site.
.. _security-recommendation-ssl:
SSL/HTTPS
=========
@ -147,7 +157,15 @@ server, there are some additional steps you may need:
any POST data being accepted over HTTP (which will be fine if you are
redirecting all HTTP traffic to HTTPS).
.. _additional-security-topics:
* Use HTTP Strict Transport Security (HSTS)
HSTS is an HTTP header that informs a browser that all future connections
to a particular site should always use HTTPS. Combined with redirecting
requests over HTTP to HTTPS, this will ensure that connections always enjoy
the added security of SSL provided one successful connection has occurred.
HSTS is usually configured on the web server.
.. _host-headers-virtual-hosting:
Host headers and virtual hosting
================================
@ -158,15 +176,17 @@ Site Scripting attacks, they can be used for Cross-Site Request
Forgery and cache poisoning attacks in some circumstances. We
recommend you ensure your Web server is configured such that:
* It always validates incoming HTTP ``Host`` headers against the expected
host name.
* Disallows requests with no ``Host`` header.
* Is *not* configured with a catch-all virtual host that forwards requests
to a Django application.
* It always validates incoming HTTP ``Host`` headers against the expected
host name.
* Disallows requests with no ``Host`` header.
* Is *not* configured with a catch-all virtual host that forwards requests
to a Django application.
Additionally, as of 1.3.1, Django requires you to explicitly enable support for
the ``X-Forwarded-Host`` header if your configuration requires it.
.. _additional-security-topics:
Additional security topics
==========================

View File

@ -5,7 +5,7 @@ from datetime import datetime
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.fields import Field, FieldDoesNotExist
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
from django.utils.six import PY3
from django.utils import six
from django.utils.translation import ugettext_lazy
from .models import Article
@ -82,7 +82,7 @@ class ModelTest(TestCase):
# Django raises an Article.DoesNotExist exception for get() if the
# parameters don't match any object.
self.assertRaisesRegexp(
six.assertRaisesRegex(self,
ObjectDoesNotExist,
"Article matching query does not exist. Lookup parameters were "
"{'id__exact': 2000}",
@ -91,14 +91,14 @@ class ModelTest(TestCase):
)
# To avoid dict-ordering related errors check only one lookup
# in single assert.
self.assertRaisesRegexp(
six.assertRaisesRegex(self,
ObjectDoesNotExist,
".*'pub_date__year': 2005.*",
Article.objects.get,
pub_date__year=2005,
pub_date__month=8,
)
self.assertRaisesRegexp(
six.assertRaisesRegex(self,
ObjectDoesNotExist,
".*'pub_date__month': 8.*",
Article.objects.get,
@ -106,7 +106,7 @@ class ModelTest(TestCase):
pub_date__month=8,
)
self.assertRaisesRegexp(
six.assertRaisesRegex(self,
ObjectDoesNotExist,
"Article matching query does not exist. Lookup parameters were "
"{'pub_date__week_day': 6}",
@ -168,7 +168,7 @@ class ModelTest(TestCase):
self.assertEqual(a4.headline, 'Fourth article')
# Don't use invalid keyword arguments.
self.assertRaisesRegexp(
six.assertRaisesRegex(self,
TypeError,
"'foo' is an invalid keyword argument for this function",
Article,
@ -259,13 +259,13 @@ class ModelTest(TestCase):
"datetime.datetime(2005, 7, 28, 0, 0)"])
# dates() requires valid arguments.
self.assertRaisesRegexp(
six.assertRaisesRegex(self,
TypeError,
"dates\(\) takes at least 3 arguments \(1 given\)",
Article.objects.dates,
)
self.assertRaisesRegexp(
six.assertRaisesRegex(self,
FieldDoesNotExist,
"Article has no field named 'invalid_field'",
Article.objects.dates,
@ -273,7 +273,7 @@ class ModelTest(TestCase):
"year",
)
self.assertRaisesRegexp(
six.assertRaisesRegex(self,
AssertionError,
"'kind' must be one of 'year', 'month' or 'day'.",
Article.objects.dates,
@ -281,7 +281,7 @@ class ModelTest(TestCase):
"bad_kind",
)
self.assertRaisesRegexp(
six.assertRaisesRegex(self,
AssertionError,
"'order' must be either 'ASC' or 'DESC'.",
Article.objects.dates,
@ -323,7 +323,7 @@ class ModelTest(TestCase):
"<Article: Third article>"])
# Slicing works with longs (Python 2 only -- Python 3 doesn't have longs).
if not PY3:
if not six.PY3:
self.assertEqual(Article.objects.all()[long(0)], a)
self.assertQuerysetEqual(Article.objects.all()[long(1):long(3)],
["<Article: Second article>", "<Article: Third article>"])
@ -369,14 +369,14 @@ class ModelTest(TestCase):
"<Article: Updated article 8>"])
# Also, once you have sliced you can't filter, re-order or combine
self.assertRaisesRegexp(
six.assertRaisesRegex(self,
AssertionError,
"Cannot filter a query once a slice has been taken.",
Article.objects.all()[0:5].filter,
id=a.id,
)
self.assertRaisesRegexp(
six.assertRaisesRegex(self,
AssertionError,
"Cannot reorder a query once a slice has been taken.",
Article.objects.all()[0:5].order_by,
@ -411,7 +411,7 @@ class ModelTest(TestCase):
# An Article instance doesn't have access to the "objects" attribute.
# That's only available on the class.
self.assertRaisesRegexp(
six.assertRaisesRegex(self,
AttributeError,
"Manager isn't accessible via Article instances",
getattr,

View File

@ -1,10 +1,10 @@
from __future__ import absolute_import
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.db.models.loading import get_app
from django.test import TestCase
from django.test.utils import override_settings
from django.utils import six
from .models import Empty
@ -14,12 +14,13 @@ class EmptyModelTests(TestCase):
m = Empty()
self.assertEqual(m.id, None)
m.save()
m2 = Empty.objects.create()
Empty.objects.create()
self.assertEqual(len(Empty.objects.all()), 2)
self.assertTrue(m.id is not None)
existing = Empty(m.id)
existing.save()
class NoModelTests(TestCase):
"""
Test for #7198 to ensure that the proper error message is raised
@ -32,6 +33,6 @@ class NoModelTests(TestCase):
"""
@override_settings(INSTALLED_APPS=("modeltests.empty.no_models",))
def test_no_models(self):
with self.assertRaisesRegexp(ImproperlyConfigured,
with six.assertRaisesRegex(self, ImproperlyConfigured,
'App with label no_models is missing a models.py module.'):
get_app('no_models')

View File

@ -4,7 +4,7 @@ from django.contrib.sites.models import Site
from django.core import management
from django.db import connection, IntegrityError
from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature
from django.utils.six import StringIO
from django.utils import six
from .models import Article, Book, Spy, Tag, Visa
@ -21,16 +21,17 @@ class TestCaseFixtureLoadingTests(TestCase):
'<Article: Poker has no place on ESPN>',
])
class FixtureLoadingTests(TestCase):
def _dumpdata_assert(self, args, output, format='json', natural_keys=False,
use_base_manager=False, exclude_list=[]):
new_io = StringIO()
management.call_command('dumpdata', *args, **{'format':format,
'stdout':new_io,
'stderr':new_io,
'use_natural_keys':natural_keys,
'use_base_manager':use_base_manager,
new_io = six.StringIO()
management.call_command('dumpdata', *args, **{'format': format,
'stdout': new_io,
'stderr': new_io,
'use_natural_keys': natural_keys,
'use_base_manager': use_base_manager,
'exclude': exclude_list})
command_output = new_io.getvalue().strip()
self.assertEqual(command_output, output)
@ -42,8 +43,6 @@ class FixtureLoadingTests(TestCase):
])
def test_loading_and_dumping(self):
new_io = StringIO()
Site.objects.all().delete()
# Load fixture 1. Single JSON file, with two objects.
management.call_command('loaddata', 'fixture1.json', verbosity=0, commit=False)
@ -184,12 +183,12 @@ class FixtureLoadingTests(TestCase):
exclude_list=['fixtures.Article', 'fixtures.Book', 'sites'])
# Excluding a bogus app should throw an error
with self.assertRaisesRegexp(management.CommandError,
with six.assertRaisesRegex(self, management.CommandError,
"Unknown app in excludes: foo_app"):
self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['foo_app'])
# Excluding a bogus model should throw an error
with self.assertRaisesRegexp(management.CommandError,
with six.assertRaisesRegex(self, management.CommandError,
"Unknown model in excludes: fixtures.FooModel"):
self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['fixtures.FooModel'])
@ -199,7 +198,7 @@ class FixtureLoadingTests(TestCase):
self.assertQuerysetEqual(Spy.objects.all(),
['<Spy: Paul>'])
# Use the default manager
self._dumpdata_assert(['fixtures.Spy'],'[{"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": false}}]' % spy1.pk)
self._dumpdata_assert(['fixtures.Spy'], '[{"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": false}}]' % spy1.pk)
# Dump using Django's base manager. Should return all objects,
# even those normally filtered by the manager
self._dumpdata_assert(['fixtures.Spy'], '[{"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": true}}, {"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": false}}]' % (spy2.pk, spy1.pk), use_base_manager=True)
@ -227,7 +226,7 @@ class FixtureLoadingTests(TestCase):
def test_ambiguous_compressed_fixture(self):
# The name "fixture5" is ambigous, so loading it will raise an error
with self.assertRaisesRegexp(management.CommandError,
with six.assertRaisesRegex(self, management.CommandError,
"Multiple fixtures named 'fixture5'"):
management.call_command('loaddata', 'fixture5', verbosity=0, commit=False)
@ -251,7 +250,7 @@ class FixtureLoadingTests(TestCase):
# is closed at the end of each test.
if connection.vendor == 'mysql':
connection.cursor().execute("SET sql_mode = 'TRADITIONAL'")
with self.assertRaisesRegexp(IntegrityError,
with six.assertRaisesRegex(self, IntegrityError,
"Could not load fixtures.Article\(pk=1\): .*$"):
management.call_command('loaddata', 'invalid.json', verbosity=0, commit=False)
@ -290,10 +289,11 @@ class FixtureLoadingTests(TestCase):
self._dumpdata_assert(['fixtures'], """<?xml version="1.0" encoding="utf-8"?>
<django-objects version="1.0"><object pk="1" model="fixtures.category"><field type="CharField" name="title">News Stories</field><field type="TextField" name="description">Latest news stories</field></object><object pk="2" model="fixtures.article"><field type="CharField" name="headline">Poker has no place on ESPN</field><field type="DateTimeField" name="pub_date">2006-06-16T12:00:00</field></object><object pk="3" model="fixtures.article"><field type="CharField" name="headline">Time to reform copyright</field><field type="DateTimeField" name="pub_date">2006-06-16T13:00:00</field></object><object pk="1" model="fixtures.tag"><field type="CharField" name="name">copyright</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="2" model="fixtures.tag"><field type="CharField" name="name">law</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="1" model="fixtures.person"><field type="CharField" name="name">Django Reinhardt</field></object><object pk="2" model="fixtures.person"><field type="CharField" name="name">Stephane Grappelli</field></object><object pk="3" model="fixtures.person"><field type="CharField" name="name">Prince</field></object><object pk="10" model="fixtures.book"><field type="CharField" name="name">Achieving self-awareness of Python programs</field><field to="fixtures.person" name="authors" rel="ManyToManyRel"></field></object></django-objects>""", format='xml', natural_keys=True)
class FixtureTransactionTests(TransactionTestCase):
def _dumpdata_assert(self, args, output, format='json'):
new_io = StringIO()
management.call_command('dumpdata', *args, **{'format':format, 'stdout':new_io})
new_io = six.StringIO()
management.call_command('dumpdata', *args, **{'format': format, 'stdout': new_io})
command_output = new_io.getvalue().strip()
self.assertEqual(command_output, output)
@ -308,7 +308,7 @@ class FixtureTransactionTests(TransactionTestCase):
# Try to load fixture 2 using format discovery; this will fail
# because there are two fixture2's in the fixtures directory
with self.assertRaisesRegexp(management.CommandError,
with six.assertRaisesRegex(self, management.CommandError,
"Multiple fixtures named 'fixture2'"):
management.call_command('loaddata', 'fixture2', verbosity=0)

View File

@ -1,6 +1,7 @@
from __future__ import absolute_import
from django.test import TestCase
from django.utils import six
from .models import Article, Publication
@ -52,7 +53,7 @@ class ManyToManyTests(TestCase):
])
# Adding an object of the wrong type raises TypeError
with self.assertRaisesRegexp(TypeError, "'Publication' instance expected, got <Article.*"):
with six.assertRaisesRegex(self, TypeError, "'Publication' instance expected, got <Article.*"):
a6.publications.add(a5)
# Add a Publication directly via publications.add by using keyword arguments.
p4 = a6.publications.create(title='Highlights for Adults')

View File

@ -70,7 +70,7 @@ class ManyToOneTests(TestCase):
self.assertQuerysetEqual(self.r2.article_set.all(), ["<Article: Paul's story>"])
# Adding an object of the wrong type raises TypeError.
with self.assertRaisesRegexp(TypeError, "'Article' instance expected, got <Reporter.*"):
with six.assertRaisesRegex(self, TypeError, "'Article' instance expected, got <Reporter.*"):
self.r.article_set.add(self.r2)
self.assertQuerysetEqual(self.r.article_set.all(),
[

View File

@ -2,7 +2,7 @@ from __future__ import absolute_import
from datetime import date
from django.db.models.sql.query import InvalidQuery
from django.db.models.query_utils import InvalidQuery
from django.test import TestCase
from .models import Author, Book, Coffee, Reviewer, FriendlyAuthor

View File

@ -1,7 +1,8 @@
from __future__ import absolute_import
from django.test import TestCase
from django.db.models.signals import pre_save, post_save
from django.test import TestCase
from .models import Person, Employee, ProxyEmployee, Profile, Account

View File

@ -3,6 +3,7 @@ from __future__ import unicode_literals
from django.core.exceptions import ValidationError
from django.db import models
from django.utils import six
from django.utils.unittest import TestCase
@ -18,7 +19,7 @@ class ValidationMessagesTest(TestCase):
self._test_validation_messages(f, 'fõo',
["'fõo' value must be an integer."])
# primary_key must be True. Refs #12467.
with self.assertRaisesRegexp(AssertionError,
with six.assertRaisesRegex(self, AssertionError,
"AutoFields must have primary_key=True."):
models.AutoField(primary_key=False)

View File

@ -9,7 +9,7 @@ from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase, RequestFactory
from django.test.utils import override_settings
from django.test.utils import override_settings, six
from django.utils.encoding import force_text
from .models import Book, Department, Employee
@ -18,6 +18,7 @@ from .models import Book, Department, Employee
def select_by(dictlist, key, value):
return [x for x in dictlist if x[key] == value][0]
class DecadeListFilter(SimpleListFilter):
def lookups(self, request, model_admin):
@ -520,7 +521,7 @@ class ListFiltersTests(TestCase):
"""
modeladmin = DecadeFilterBookAdminWithoutTitle(Book, site)
request = self.request_factory.get('/', {})
self.assertRaisesRegexp(ImproperlyConfigured,
six.assertRaisesRegex(self, ImproperlyConfigured,
"The list filter 'DecadeListFilterWithoutTitle' does not specify a 'title'.",
self.get_changelist, request, Book, modeladmin)
@ -530,7 +531,7 @@ class ListFiltersTests(TestCase):
"""
modeladmin = DecadeFilterBookAdminWithoutParameter(Book, site)
request = self.request_factory.get('/', {})
self.assertRaisesRegexp(ImproperlyConfigured,
six.assertRaisesRegex(self, ImproperlyConfigured,
"The list filter 'DecadeListFilterWithoutParameter' does not specify a 'parameter_name'.",
self.get_changelist, request, Book, modeladmin)

View File

@ -8,10 +8,11 @@ from django.test import TestCase
from django.test.utils import override_settings
# local test models
from .admin import InnerInline
from .admin import InnerInline, TitleInline, site
from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person,
OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile,
ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2)
ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2,
Title)
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
@ -408,6 +409,47 @@ class SeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
fixtures = ['admin-views-users.xml']
urls = "regressiontests.admin_inlines.urls"
def test_add_stackeds(self):
"""
Ensure that the "Add another XXX" link correctly adds items to the
stacked formset.
"""
self.admin_login(username='super', password='secret')
self.selenium.get('%s%s' % (self.live_server_url,
'/admin/admin_inlines/holder4/add/'))
inline_id = '#inner4stacked_set-group'
rows_length = lambda: len(self.selenium.find_elements_by_css_selector(
'%s .dynamic-inner4stacked_set' % inline_id))
self.assertEqual(rows_length(), 3)
add_button = self.selenium.find_element_by_link_text(
'Add another Inner4 Stacked')
add_button.click()
self.assertEqual(rows_length(), 4)
def test_delete_stackeds(self):
self.admin_login(username='super', password='secret')
self.selenium.get('%s%s' % (self.live_server_url,
'/admin/admin_inlines/holder4/add/'))
inline_id = '#inner4stacked_set-group'
rows_length = lambda: len(self.selenium.find_elements_by_css_selector(
'%s .dynamic-inner4stacked_set' % inline_id))
self.assertEqual(rows_length(), 3)
add_button = self.selenium.find_element_by_link_text(
'Add another Inner4 Stacked')
add_button.click()
add_button.click()
self.assertEqual(rows_length(), 5, msg="sanity check")
for delete_link in self.selenium.find_elements_by_css_selector(
'%s .inline-deletelink' % inline_id):
delete_link.click()
self.assertEqual(rows_length(), 3)
def test_add_inlines(self):
"""
Ensure that the "Add another XXX" link correctly adds items to the
@ -516,6 +558,21 @@ class SeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
self.assertEqual(len(self.selenium.find_elements_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-2')), 1)
def test_alternating_rows(self):
self.admin_login(username='super', password='secret')
self.selenium.get('%s%s' % (self.live_server_url,
'/admin/admin_inlines/profilecollection/add/'))
# Add a few inlines
self.selenium.find_element_by_link_text('Add another Profile').click()
self.selenium.find_element_by_link_text('Add another Profile').click()
row_selector = 'form#profilecollection_form tr.dynamic-profile_set'
self.assertEqual(len(self.selenium.find_elements_by_css_selector(
"%s.row1" % row_selector)), 2, msg="Expect two row1 styled rows")
self.assertEqual(len(self.selenium.find_elements_by_css_selector(
"%s.row2" % row_selector)), 1, msg="Expect one row2 styled row")
class SeleniumChromeTests(SeleniumFirefoxTests):
webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver'

View File

@ -181,11 +181,11 @@ class DjangoAdminNoSettings(AdminScriptTestCase):
"A series of tests for django-admin.py when there is no settings.py file."
def test_builtin_command(self):
"no settings: django-admin builtin commands fail with an import error when no settings provided"
"no settings: django-admin builtin commands fail with an error when no settings provided"
args = ['sqlall', 'admin_scripts']
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
self.assertOutput(err, 'settings are not configured')
def test_builtin_with_bad_settings(self):
"no settings: django-admin builtin commands fail if settings file (from argument) doesn't exist"
@ -213,11 +213,11 @@ class DjangoAdminDefaultSettings(AdminScriptTestCase):
self.remove_settings('settings.py')
def test_builtin_command(self):
"default: django-admin builtin commands fail with an import error when no settings provided"
"default: django-admin builtin commands fail with an error when no settings provided"
args = ['sqlall', 'admin_scripts']
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
self.assertOutput(err, 'settings are not configured')
def test_builtin_with_settings(self):
"default: django-admin builtin commands succeed if settings are provided as argument"
@ -279,11 +279,11 @@ class DjangoAdminFullPathDefaultSettings(AdminScriptTestCase):
self.remove_settings('settings.py')
def test_builtin_command(self):
"fulldefault: django-admin builtin commands fail with an import error when no settings provided"
"fulldefault: django-admin builtin commands fail with an error when no settings provided"
args = ['sqlall', 'admin_scripts']
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
self.assertOutput(err, 'settings are not configured')
def test_builtin_with_settings(self):
"fulldefault: django-admin builtin commands succeed if a settings file is provided"
@ -345,11 +345,11 @@ class DjangoAdminMinimalSettings(AdminScriptTestCase):
self.remove_settings('settings.py')
def test_builtin_command(self):
"minimal: django-admin builtin commands fail with an import error when no settings provided"
"minimal: django-admin builtin commands fail with an error when no settings provided"
args = ['sqlall', 'admin_scripts']
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
self.assertOutput(err, 'settings are not configured')
def test_builtin_with_settings(self):
"minimal: django-admin builtin commands fail if settings are provided as argument"
@ -411,11 +411,11 @@ class DjangoAdminAlternateSettings(AdminScriptTestCase):
self.remove_settings('alternate_settings.py')
def test_builtin_command(self):
"alternate: django-admin builtin commands fail with an import error when no settings provided"
"alternate: django-admin builtin commands fail with an error when no settings provided"
args = ['sqlall', 'admin_scripts']
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
self.assertOutput(err, 'settings are not configured')
def test_builtin_with_settings(self):
"alternate: django-admin builtin commands succeed if settings are provided as argument"
@ -482,11 +482,11 @@ class DjangoAdminMultipleSettings(AdminScriptTestCase):
self.remove_settings('alternate_settings.py')
def test_builtin_command(self):
"alternate: django-admin builtin commands fail with an import error when no settings provided"
"alternate: django-admin builtin commands fail with an error when no settings provided"
args = ['sqlall', 'admin_scripts']
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
self.assertOutput(err, 'settings are not configured')
def test_builtin_with_settings(self):
"alternate: django-admin builtin commands succeed if settings are provided as argument"
@ -570,11 +570,11 @@ class DjangoAdminSettingsDirectory(AdminScriptTestCase):
self.assertTrue(os.path.exists(os.path.join(app_path, 'api.py')))
def test_builtin_command(self):
"directory: django-admin builtin commands fail with an import error when no settings provided"
"directory: django-admin builtin commands fail with an error when no settings provided"
args = ['sqlall', 'admin_scripts']
out, err = self.run_django_admin(args)
self.assertNoOutput(out)
self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
self.assertOutput(err, 'settings are not configured')
def test_builtin_with_bad_settings(self):
"directory: django-admin builtin commands fail if settings file (from argument) doesn't exist"
@ -621,7 +621,7 @@ class ManageNoSettings(AdminScriptTestCase):
"A series of tests for manage.py when there is no settings.py file."
def test_builtin_command(self):
"no settings: manage.py builtin commands fail with an import error when no settings provided"
"no settings: manage.py builtin commands fail with an error when no settings provided"
args = ['sqlall', 'admin_scripts']
out, err = self.run_manage(args)
self.assertNoOutput(out)
@ -786,7 +786,7 @@ class ManageMinimalSettings(AdminScriptTestCase):
self.remove_settings('settings.py')
def test_builtin_command(self):
"minimal: manage.py builtin commands fail with an import error when no settings provided"
"minimal: manage.py builtin commands fail with an error when no settings provided"
args = ['sqlall', 'admin_scripts']
out, err = self.run_manage(args)
self.assertNoOutput(out)
@ -852,7 +852,7 @@ class ManageAlternateSettings(AdminScriptTestCase):
self.remove_settings('alternate_settings.py')
def test_builtin_command(self):
"alternate: manage.py builtin commands fail with an import error when no default settings provided"
"alternate: manage.py builtin commands fail with an error when no default settings provided"
args = ['sqlall', 'admin_scripts']
out, err = self.run_manage(args)
self.assertNoOutput(out)
@ -895,7 +895,7 @@ class ManageAlternateSettings(AdminScriptTestCase):
args = ['noargs_command']
out, err = self.run_manage(args)
self.assertNoOutput(out)
self.assertOutput(err, "Unknown command: 'noargs_command'")
self.assertOutput(err, "Could not import settings 'regressiontests.settings'")
def test_custom_command_with_settings(self):
"alternate: manage.py can execute user commands if settings are provided as argument"
@ -927,7 +927,7 @@ class ManageMultipleSettings(AdminScriptTestCase):
self.remove_settings('alternate_settings.py')
def test_builtin_command(self):
"multiple: manage.py builtin commands fail with an import error when no settings provided"
"multiple: manage.py builtin commands fail with an error when no settings provided"
args = ['sqlall', 'admin_scripts']
out, err = self.run_manage(args)
self.assertNoOutput(out)
@ -1576,7 +1576,6 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
self.assertOutput(err, "Destination directory '%s' does not exist, please create it first." % testproject_dir)
self.assertFalse(os.path.exists(testproject_dir))
def test_custom_project_template_with_non_ascii_templates(self):
"Ticket 18091: Make sure the startproject management command is able to render templates with non-ASCII content"
template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template')
@ -1588,5 +1587,6 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
self.assertNoOutput(err)
self.assertTrue(os.path.isdir(testproject_dir))
path = os.path.join(testproject_dir, 'ticket-18091-non-ascii-template.txt')
self.assertEqual(codecs.open(path, 'r', 'utf-8').read(),
'Some non-ASCII text for testing ticket #18091:\nüäö €\n')
with codecs.open(path, 'r', 'utf-8') as f:
self.assertEqual(f.read(),
'Some non-ASCII text for testing ticket #18091:\nüäö €\n')

View File

@ -27,11 +27,14 @@ from .models import (Article, Chapter, Account, Media, Child, Parent, Picture,
Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug,
AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod,
AdminOrderedCallable, Report, Color2, UnorderedObject, MainPrepopulated,
RelatedPrepopulated, UndeletableObject)
RelatedPrepopulated, UndeletableObject, Simple)
def callable_year(dt_value):
return dt_value.year
try:
return dt_value.year
except AttributeError:
return None
callable_year.admin_order_field = 'date'
@ -575,6 +578,14 @@ class UndeletableObjectAdmin(admin.ModelAdmin):
return super(UndeletableObjectAdmin, self).change_view(*args, **kwargs)
def callable_on_unknown(obj):
return obj.unknown
class AttributeErrorRaisingAdmin(admin.ModelAdmin):
list_display = [callable_on_unknown, ]
site = admin.AdminSite(name="admin")
site.register(Article, ArticleAdmin)
site.register(CustomArticle, CustomArticleAdmin)
@ -648,6 +659,7 @@ site.register(AdminOrderedModelMethod, AdminOrderedModelMethodAdmin)
site.register(AdminOrderedAdminMethod, AdminOrderedAdminMethodAdmin)
site.register(AdminOrderedCallable, AdminOrderedCallableAdmin)
site.register(Color2, CustomTemplateFilterColorAdmin)
site.register(Simple, AttributeErrorRaisingAdmin)
# Register core models we need in our tests
from django.contrib.auth.models import User, Group

View File

@ -49,3 +49,4 @@ site.register(models.Fabric, base_admin.FabricAdmin)
site.register(models.ChapterXtra1, base_admin.ChapterXtra1Admin)
site.register(User, UserLimitedAdmin)
site.register(models.UndeletableObject, base_admin.UndeletableObjectAdmin)
site.register(models.Simple, base_admin.AttributeErrorRaisingAdmin)

View File

@ -649,3 +649,9 @@ class UndeletableObject(models.Model):
Refs #10057.
"""
name = models.CharField(max_length=255)
class Simple(models.Model):
"""
Simple model with nothing on it for use in testing
"""

View File

@ -46,7 +46,7 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount,
OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField,
AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable,
Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject,
UndeletableObject)
Simple, UndeletableObject)
ERROR_MESSAGE = "Please enter the correct username and password \
@ -579,6 +579,19 @@ class AdminViewBasicTest(TestCase):
(self.urlbit, instance.pk))
self.assertNotContains(response, 'deletelink')
def test_allows_attributeerror_to_bubble_up(self):
"""
Ensure that AttributeErrors are allowed to bubble when raised inside
a change list view.
Requires a model to be created so there's something to be displayed
Refs: #16655, #18593, and #18747
"""
Simple.objects.create()
with self.assertRaises(AttributeError):
self.client.get('/test_admin/%s/admin_views/simple/' % self.urlbit)
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AdminViewFormUrlTest(TestCase):

View File

@ -655,7 +655,7 @@ class ThreadTests(TestCase):
class BackendLoadingTests(TestCase):
def test_old_style_backends_raise_useful_exception(self):
self.assertRaisesRegexp(ImproperlyConfigured,
six.assertRaisesRegex(self, ImproperlyConfigured,
"Try using django.db.backends.sqlite3 instead",
load_backend, 'sqlite3')

View File

@ -15,7 +15,7 @@ import warnings
from django.conf import settings
from django.core import management
from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS
from django.core.cache import get_cache
from django.core.cache.backends.base import (CacheKeyWarning,
InvalidCacheBackendError)
from django.db import router
@ -25,8 +25,7 @@ from django.middleware.cache import (FetchFromCacheMiddleware,
from django.template import Template
from django.template.response import TemplateResponse
from django.test import TestCase, TransactionTestCase, RequestFactory
from django.test.utils import (get_warnings_state, restore_warnings_state,
override_settings)
from django.test.utils import override_settings, six
from django.utils import timezone, translation, unittest
from django.utils.cache import (patch_vary_headers, get_cache_key,
learn_cache_key, patch_cache_control, patch_response_headers)
@ -821,7 +820,7 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase):
self.perform_cull_test(50, 18)
def test_second_call_doesnt_crash(self):
with self.assertRaisesRegexp(management.CommandError,
with six.assertRaisesRegex(self, management.CommandError,
"Cache table 'test cache table' could not be created"):
management.call_command(
'createcachetable',

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