Merge recent changes from master.
This commit is contained in:
commit
19526563b5
1
AUTHORS
1
AUTHORS
|
@ -506,6 +506,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Johan C. Stöver <johan@nilling.nl>
|
Johan C. Stöver <johan@nilling.nl>
|
||||||
Nowell Strite <http://nowell.strite.org/>
|
Nowell Strite <http://nowell.strite.org/>
|
||||||
Thomas Stromberg <tstromberg@google.com>
|
Thomas Stromberg <tstromberg@google.com>
|
||||||
|
Travis Swicegood <travis@domain51.com>
|
||||||
Pascal Varet
|
Pascal Varet
|
||||||
SuperJared
|
SuperJared
|
||||||
Radek Švarz <http://www.svarz.cz/translate/>
|
Radek Švarz <http://www.svarz.cz/translate/>
|
||||||
|
|
|
@ -7,7 +7,6 @@ a list of all possible variables.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import time # Needed for Windows
|
import time # Needed for Windows
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
@ -26,7 +25,7 @@ class LazySettings(LazyObject):
|
||||||
The user can manually configure settings prior to using them. Otherwise,
|
The user can manually configure settings prior to using them. Otherwise,
|
||||||
Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE.
|
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
|
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
|
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.
|
if not settings_module: # If it's set but is an empty string.
|
||||||
raise KeyError
|
raise KeyError
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# NOTE: This is arguably an EnvironmentError, but that causes
|
raise ImproperlyConfigured(
|
||||||
# problems with Python's interactive help.
|
"Requested setting %s, but settings are not configured. "
|
||||||
raise ImportError("Settings cannot be imported, because environment variable %s is undefined." % ENVIRONMENT_VARIABLE)
|
"You must either define the environment variable %s "
|
||||||
|
"or call settings.configure() before accessing settings."
|
||||||
|
% (name, ENVIRONMENT_VARIABLE))
|
||||||
|
|
||||||
self._wrapped = Settings(settings_module)
|
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):
|
def configure(self, default_settings=global_settings, **options):
|
||||||
"""
|
"""
|
||||||
Called to manually configure the settings. The 'default_settings'
|
Called to manually configure the settings. The 'default_settings'
|
||||||
|
|
|
@ -14,9 +14,10 @@ from django.core.exceptions import PermissionDenied, ValidationError
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db import models, transaction, router
|
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.related import RelatedObject
|
||||||
from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
|
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.http import Http404, HttpResponse, HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.template.response import SimpleTemplateResponse, TemplateResponse
|
from django.template.response import SimpleTemplateResponse, TemplateResponse
|
||||||
|
@ -1456,8 +1457,10 @@ class InlineModelAdmin(BaseModelAdmin):
|
||||||
return request.user.has_perm(
|
return request.user.has_perm(
|
||||||
self.opts.app_label + '.' + self.opts.get_delete_permission())
|
self.opts.app_label + '.' + self.opts.get_delete_permission())
|
||||||
|
|
||||||
|
|
||||||
class StackedInline(InlineModelAdmin):
|
class StackedInline(InlineModelAdmin):
|
||||||
template = 'admin/edit_inline/stacked.html'
|
template = 'admin/edit_inline/stacked.html'
|
||||||
|
|
||||||
|
|
||||||
class TabularInline(InlineModelAdmin):
|
class TabularInline(InlineModelAdmin):
|
||||||
template = 'admin/edit_inline/tabular.html'
|
template = 'admin/edit_inline/tabular.html'
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Spiced up with Code from Zain Memon's GSoC project 2009
|
* 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
|
* Licensed under the New BSD License
|
||||||
* See: http://www.opensource.org/licenses/bsd-license.php
|
* See: http://www.opensource.org/licenses/bsd-license.php
|
||||||
|
@ -17,6 +17,8 @@
|
||||||
(function($) {
|
(function($) {
|
||||||
$.fn.formset = function(opts) {
|
$.fn.formset = function(opts) {
|
||||||
var options = $.extend({}, $.fn.formset.defaults, opts);
|
var options = $.extend({}, $.fn.formset.defaults, opts);
|
||||||
|
var $this = $(this);
|
||||||
|
var $parent = $this.parent();
|
||||||
var updateElementIndex = function(el, prefix, ndx) {
|
var updateElementIndex = function(el, prefix, ndx) {
|
||||||
var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))");
|
var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))");
|
||||||
var replacement = prefix + "-" + ndx;
|
var replacement = prefix + "-" + ndx;
|
||||||
|
@ -36,21 +38,21 @@
|
||||||
// only show the add button if we are allowed to add more items,
|
// only show the add button if we are allowed to add more items,
|
||||||
// note that max_num = None translates to a blank string.
|
// note that max_num = None translates to a blank string.
|
||||||
var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0;
|
var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0;
|
||||||
$(this).each(function(i) {
|
$this.each(function(i) {
|
||||||
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
|
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
|
||||||
});
|
});
|
||||||
if ($(this).length && showAddButton) {
|
if ($this.length && showAddButton) {
|
||||||
var addButton;
|
var addButton;
|
||||||
if ($(this).attr("tagName") == "TR") {
|
if ($this.attr("tagName") == "TR") {
|
||||||
// If forms are laid out as table rows, insert the
|
// If forms are laid out as table rows, insert the
|
||||||
// "add" button in a new table row:
|
// "add" button in a new table row:
|
||||||
var numCols = this.eq(-1).children().length;
|
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>");
|
$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");
|
addButton = $parent.find("tr:last a");
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, insert it immediately after the last form:
|
// 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>");
|
$this.filter(":last").after('<div class="' + options.addCssClass + '"><a href="javascript:void(0)">' + options.addText + "</a></div>");
|
||||||
addButton = $(this).filter(":last").next().find("a");
|
addButton = $this.filter(":last").next().find("a");
|
||||||
}
|
}
|
||||||
addButton.click(function(e) {
|
addButton.click(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -121,6 +123,7 @@
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Setup plugin defaults */
|
/* Setup plugin defaults */
|
||||||
$.fn.formset.defaults = {
|
$.fn.formset.defaults = {
|
||||||
prefix: "form", // The form prefix for your django formset
|
prefix: "form", // The form prefix for your django formset
|
||||||
|
@ -133,4 +136,137 @@
|
||||||
added: null, // Function called each time a new form is added
|
added: null, // Function called each time a new form is added
|
||||||
removed: null // Function called each time a form is deleted
|
removed: null // Function called each time a form is deleted
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 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);
|
})(django.jQuery);
|
||||||
|
|
|
@ -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("."+
|
(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(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();
|
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+
|
||||||
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)">'+
|
"-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.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,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,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);
|
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);
|
||||||
|
|
|
@ -20,63 +20,11 @@
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
(function($) {
|
(function($) {
|
||||||
$(document).ready(function() {
|
$("#{{ inline_admin_formset.formset.prefix }}-group .inline-related").stackedFormset({
|
||||||
var rows = "#{{ inline_admin_formset.formset.prefix }}-group .inline-related";
|
prefix: '{{ inline_admin_formset.formset.prefix }}',
|
||||||
var updateInlineLabel = function(row) {
|
adminStaticPrefix: '{% static "admin/" %}',
|
||||||
$(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" %}",
|
deleteText: "{% trans "Remove" %}",
|
||||||
emptyCssClass: "empty-form",
|
addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}"
|
||||||
removed: updateInlineLabel,
|
|
||||||
added: (function(row) {
|
|
||||||
initPrepopulatedFields(row);
|
|
||||||
reinitDateTimeShortCuts();
|
|
||||||
updateSelectFilter();
|
|
||||||
updateInlineLabel(row);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
})(django.jQuery);
|
})(django.jQuery);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -67,64 +67,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
(function($) {
|
(function($) {
|
||||||
$(document).ready(function($) {
|
$("#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr").tabularFormset({
|
||||||
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 }}",
|
prefix: "{{ inline_admin_formset.formset.prefix }}",
|
||||||
addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}",
|
adminStaticPrefix: '{% static "admin/" %}',
|
||||||
formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
|
addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
|
||||||
deleteCssClass: "inline-deletelink",
|
deleteText: "{% trans 'Remove' %}"
|
||||||
deleteText: "{% trans "Remove" %}",
|
|
||||||
emptyCssClass: "empty-form",
|
|
||||||
removed: alternatingRows,
|
|
||||||
added: (function(row) {
|
|
||||||
initPrepopulatedFields(row);
|
|
||||||
reinitDateTimeShortCuts();
|
|
||||||
updateSelectFilter();
|
|
||||||
alternatingRows(row);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
})(django.jQuery);
|
})(django.jQuery);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -182,7 +182,7 @@ def items_for_result(cl, result, form):
|
||||||
row_class = ''
|
row_class = ''
|
||||||
try:
|
try:
|
||||||
f, attr, value = lookup_field(field_name, result, cl.model_admin)
|
f, attr, value = lookup_field(field_name, result, cl.model_admin)
|
||||||
except (AttributeError, ObjectDoesNotExist):
|
except ObjectDoesNotExist:
|
||||||
result_repr = EMPTY_CHANGELIST_VALUE
|
result_repr = EMPTY_CHANGELIST_VALUE
|
||||||
else:
|
else:
|
||||||
if f is None:
|
if f is None:
|
||||||
|
|
|
@ -4,7 +4,7 @@ import datetime
|
||||||
import decimal
|
import decimal
|
||||||
|
|
||||||
from django.db import models
|
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.deletion import Collector
|
||||||
from django.db.models.related import RelatedObject
|
from django.db.models.related import RelatedObject
|
||||||
from django.forms.forms import pretty_name
|
from django.forms.forms import pretty_name
|
||||||
|
|
|
@ -8,6 +8,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.utils.decorators import available_attrs
|
from django.utils.decorators import available_attrs
|
||||||
from django.utils.encoding import force_str
|
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):
|
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):
|
if test_func(request.user):
|
||||||
return view_func(request, *args, **kwargs)
|
return view_func(request, *args, **kwargs)
|
||||||
path = request.build_absolute_uri()
|
path = request.build_absolute_uri()
|
||||||
# urlparse chokes on lazy objects in Python 3
|
resolved_login_url = resolve_url(login_url or settings.LOGIN_URL)
|
||||||
login_url_as_str = force_str(login_url or settings.LOGIN_URL)
|
|
||||||
# If the login url is the same scheme and net location then just
|
# If the login url is the same scheme and net location then just
|
||||||
# use the path as the "next" url.
|
# 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]
|
current_scheme, current_netloc = urlparse(path)[:2]
|
||||||
if ((not login_scheme or login_scheme == current_scheme) and
|
if ((not login_scheme or login_scheme == current_scheme) and
|
||||||
(not login_netloc or login_netloc == current_netloc)):
|
(not login_netloc or login_netloc == current_netloc)):
|
||||||
|
|
|
@ -28,7 +28,7 @@ class LoginRequiredTestCase(AuthViewsTestCase):
|
||||||
pass
|
pass
|
||||||
login_required(normal_view)
|
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
|
Check that login_required works on a simple view wrapped in a
|
||||||
login_required decorator.
|
login_required decorator.
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import (Group, User, SiteProfileNotAvailable,
|
||||||
|
UserManager)
|
||||||
from django.test import TestCase, skipIfCustomUser
|
from django.test import TestCase, skipIfCustomUser
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
from django.contrib.auth.models import (Group, User,
|
from django.utils import six
|
||||||
SiteProfileNotAvailable, UserManager)
|
|
||||||
|
|
||||||
|
|
||||||
@skipIfCustomUser
|
@skipIfCustomUser
|
||||||
|
@ -14,19 +15,19 @@ class ProfileTestCase(TestCase):
|
||||||
|
|
||||||
# calling get_profile without AUTH_PROFILE_MODULE set
|
# calling get_profile without AUTH_PROFILE_MODULE set
|
||||||
del settings.AUTH_PROFILE_MODULE
|
del settings.AUTH_PROFILE_MODULE
|
||||||
with self.assertRaisesRegexp(SiteProfileNotAvailable,
|
with six.assertRaisesRegex(self, SiteProfileNotAvailable,
|
||||||
"You need to set AUTH_PROFILE_MODULE in your project"):
|
"You need to set AUTH_PROFILE_MODULE in your project"):
|
||||||
user.get_profile()
|
user.get_profile()
|
||||||
|
|
||||||
# Bad syntax in AUTH_PROFILE_MODULE:
|
# Bad syntax in AUTH_PROFILE_MODULE:
|
||||||
settings.AUTH_PROFILE_MODULE = 'foobar'
|
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"):
|
"app_label and model_name should be separated by a dot"):
|
||||||
user.get_profile()
|
user.get_profile()
|
||||||
|
|
||||||
# module that doesn't exist
|
# module that doesn't exist
|
||||||
settings.AUTH_PROFILE_MODULE = 'foo.bar'
|
settings.AUTH_PROFILE_MODULE = 'foo.bar'
|
||||||
with self.assertRaisesRegexp(SiteProfileNotAvailable,
|
with six.assertRaisesRegex(self, SiteProfileNotAvailable,
|
||||||
"Unable to load the profile model"):
|
"Unable to load the profile model"):
|
||||||
user.get_profile()
|
user.get_profile()
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,9 @@ from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.http import HttpResponseRedirect, QueryDict
|
from django.http import HttpResponseRedirect, QueryDict
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils.encoding import force_str
|
|
||||||
from django.utils.http import base36_to_int
|
from django.utils.http import base36_to_int
|
||||||
from django.utils.translation import ugettext as _
|
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.debug import sensitive_post_parameters
|
||||||
from django.views.decorators.cache import never_cache
|
from django.views.decorators.cache import never_cache
|
||||||
from django.views.decorators.csrf import csrf_protect
|
from django.views.decorators.csrf import csrf_protect
|
||||||
|
@ -38,16 +38,16 @@ def login(request, template_name='registration/login.html',
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = authentication_form(data=request.POST)
|
form = authentication_form(data=request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
netloc = urlparse(redirect_to)[1]
|
|
||||||
|
|
||||||
# Use default setting if redirect_to is empty
|
# Use default setting if redirect_to is empty
|
||||||
if not redirect_to:
|
if not redirect_to:
|
||||||
redirect_to = settings.LOGIN_REDIRECT_URL
|
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
|
# Heavier security check -- don't allow redirection to a different
|
||||||
# host.
|
# host.
|
||||||
elif netloc and netloc != request.get_host():
|
if netloc and netloc != request.get_host():
|
||||||
redirect_to = settings.LOGIN_REDIRECT_URL
|
redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
|
||||||
|
|
||||||
# Okay, security checks complete. Log the user in.
|
# Okay, security checks complete. Log the user in.
|
||||||
auth_login(request, form.get_user())
|
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:
|
if not login_url:
|
||||||
login_url = settings.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)
|
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
|
Redirects the user to the login page, passing the given 'next' page
|
||||||
"""
|
"""
|
||||||
# urlparse chokes on lazy objects in Python 3
|
resolved_url = resolve_url(login_url or settings.LOGIN_URL)
|
||||||
login_url_as_str = force_str(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:
|
if redirect_field_name:
|
||||||
querystring = QueryDict(login_url_parts[4], mutable=True)
|
querystring = QueryDict(login_url_parts[4], mutable=True)
|
||||||
querystring[redirect_field_name] = next
|
querystring[redirect_field_name] = next
|
||||||
|
@ -236,7 +236,7 @@ def password_reset_complete(request,
|
||||||
template_name='registration/password_reset_complete.html',
|
template_name='registration/password_reset_complete.html',
|
||||||
current_app=None, extra_context=None):
|
current_app=None, extra_context=None):
|
||||||
context = {
|
context = {
|
||||||
'login_url': settings.LOGIN_URL
|
'login_url': resolve_url(settings.LOGIN_URL)
|
||||||
}
|
}
|
||||||
if extra_context is not None:
|
if extra_context is not None:
|
||||||
context.update(extra_context)
|
context.update(extra_context)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
from django.db.models.constants import LOOKUP_SEP
|
||||||
from django.db.models.fields import FieldDoesNotExist
|
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.expressions import SQLEvaluator
|
||||||
from django.db.models.sql.where import Constraint, WhereNode
|
from django.db.models.sql.where import Constraint, WhereNode
|
||||||
from django.contrib.gis.db.models.fields import GeometryField
|
from django.contrib.gis.db.models.fields import GeometryField
|
||||||
|
|
|
@ -4,6 +4,7 @@ HR-specific Form helpers
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
import datetime
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.contrib.localflavor.hr.hr_choices import (
|
from django.contrib.localflavor.hr.hr_choices import (
|
||||||
|
@ -91,10 +92,9 @@ class HRJMBGField(Field):
|
||||||
dd = int(matches.group('dd'))
|
dd = int(matches.group('dd'))
|
||||||
mm = int(matches.group('mm'))
|
mm = int(matches.group('mm'))
|
||||||
yyy = int(matches.group('yyy'))
|
yyy = int(matches.group('yyy'))
|
||||||
import datetime
|
|
||||||
try:
|
try:
|
||||||
datetime.date(yyy, mm, dd)
|
datetime.date(yyy, mm, dd)
|
||||||
except:
|
except ValueError:
|
||||||
raise ValidationError(self.error_messages['date'])
|
raise ValidationError(self.error_messages['date'])
|
||||||
|
|
||||||
# Validate checksum.
|
# Validate checksum.
|
||||||
|
|
|
@ -4,6 +4,8 @@ Romanian specific form helpers.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
from django.contrib.localflavor.ro.ro_counties import COUNTIES_CHOICES
|
from django.contrib.localflavor.ro.ro_counties import COUNTIES_CHOICES
|
||||||
from django.core.validators import EMPTY_VALUES
|
from django.core.validators import EMPTY_VALUES
|
||||||
from django.forms import ValidationError, Field, RegexField, Select
|
from django.forms import ValidationError, Field, RegexField, Select
|
||||||
|
@ -69,10 +71,9 @@ class ROCNPField(RegexField):
|
||||||
if value in EMPTY_VALUES:
|
if value in EMPTY_VALUES:
|
||||||
return ''
|
return ''
|
||||||
# check birthdate digits
|
# check birthdate digits
|
||||||
import datetime
|
|
||||||
try:
|
try:
|
||||||
datetime.date(int(value[1:3]), int(value[3:5]), int(value[5:7]))
|
datetime.date(int(value[1:3]), int(value[3:5]), int(value[5:7]))
|
||||||
except:
|
except ValueError:
|
||||||
raise ValidationError(self.error_messages['invalid'])
|
raise ValidationError(self.error_messages['invalid'])
|
||||||
# checksum
|
# checksum
|
||||||
key = '279146358279'
|
key = '279146358279'
|
||||||
|
@ -202,4 +203,3 @@ class ROPostalCodeField(RegexField):
|
||||||
def __init__(self, max_length=6, min_length=6, *args, **kwargs):
|
def __init__(self, max_length=6, min_length=6, *args, **kwargs):
|
||||||
super(ROPostalCodeField, self).__init__(r'^[0-9][0-8][0-9]{4}$',
|
super(ROPostalCodeField, self).__init__(r'^[0-9][0-8][0-9]{4}$',
|
||||||
max_length, min_length, *args, **kwargs)
|
max_length, min_length, *args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,9 @@ def markdown(value, arg=''):
|
||||||
they will be silently ignored.
|
they will be silently ignored.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import warnings
|
||||||
|
warnings.warn('The markdown filter has been deprecated',
|
||||||
|
category=DeprecationWarning)
|
||||||
try:
|
try:
|
||||||
import markdown
|
import markdown
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -72,6 +75,9 @@ def markdown(value, arg=''):
|
||||||
|
|
||||||
@register.filter(is_safe=True)
|
@register.filter(is_safe=True)
|
||||||
def restructuredtext(value):
|
def restructuredtext(value):
|
||||||
|
import warnings
|
||||||
|
warnings.warn('The restructuredtext filter has been deprecated',
|
||||||
|
category=DeprecationWarning)
|
||||||
try:
|
try:
|
||||||
from docutils.core import publish_parts
|
from docutils.core import publish_parts
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
# Quick tests for the markup templatetags (django.contrib.markup)
|
# Quick tests for the markup templatetags (django.contrib.markup)
|
||||||
import re
|
import re
|
||||||
|
import warnings
|
||||||
|
|
||||||
from django.template import Template, Context
|
from django.template import Template, Context
|
||||||
|
from django import test
|
||||||
from django.utils import unittest
|
from django.utils import unittest
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
|
|
||||||
|
@ -21,7 +23,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
docutils = None
|
docutils = None
|
||||||
|
|
||||||
class Templates(unittest.TestCase):
|
class Templates(test.TestCase):
|
||||||
|
|
||||||
textile_content = """Paragraph 1
|
textile_content = """Paragraph 1
|
||||||
|
|
||||||
|
@ -37,6 +39,13 @@ Paragraph 2 with a link_
|
||||||
|
|
||||||
.. _link: http://www.example.com/"""
|
.. _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')
|
@unittest.skipUnless(textile, 'textile not installed')
|
||||||
def test_textile(self):
|
def test_textile(self):
|
||||||
t = Template("{% load markup %}{{ textile_content|textile }}")
|
t = Template("{% load markup %}{{ textile_content|textile }}")
|
||||||
|
|
|
@ -46,10 +46,10 @@ class CookieStorage(BaseStorage):
|
||||||
Stores messages in a cookie.
|
Stores messages in a cookie.
|
||||||
"""
|
"""
|
||||||
cookie_name = 'messages'
|
cookie_name = 'messages'
|
||||||
# We should be able to store 4K in a cookie, but Internet Explorer
|
# uwsgi's default configuration enforces a maximum size of 4kb for all the
|
||||||
# imposes 4K as the *total* limit for a domain. To allow other
|
# HTTP headers. In order to leave some room for other cookies and headers,
|
||||||
# cookies, we go for 3/4 of 4K.
|
# restrict the session cookie to 1/2 of 4kb. See #18781.
|
||||||
max_cookie_size = 3072
|
max_cookie_size = 2048
|
||||||
not_finished = '__messagesnotfinished__'
|
not_finished = '__messagesnotfinished__'
|
||||||
|
|
||||||
def _get(self, *args, **kwargs):
|
def _get(self, *args, **kwargs):
|
||||||
|
|
|
@ -152,7 +152,7 @@ class BaseTest(TestCase):
|
||||||
cycle.
|
cycle.
|
||||||
"""
|
"""
|
||||||
data = {
|
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')
|
show_url = reverse('django.contrib.messages.tests.urls.show')
|
||||||
for level in ('debug', 'info', 'success', 'warning', 'error'):
|
for level in ('debug', 'info', 'success', 'warning', 'error'):
|
||||||
|
@ -170,7 +170,7 @@ class BaseTest(TestCase):
|
||||||
@override_settings(MESSAGE_LEVEL=constants.DEBUG)
|
@override_settings(MESSAGE_LEVEL=constants.DEBUG)
|
||||||
def test_with_template_response(self):
|
def test_with_template_response(self):
|
||||||
data = {
|
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')
|
show_url = reverse('django.contrib.messages.tests.urls.show_template_response')
|
||||||
for level in self.levels.keys():
|
for level in self.levels.keys():
|
||||||
|
@ -194,7 +194,7 @@ class BaseTest(TestCase):
|
||||||
before a GET.
|
before a GET.
|
||||||
"""
|
"""
|
||||||
data = {
|
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')
|
show_url = reverse('django.contrib.messages.tests.urls.show')
|
||||||
messages = []
|
messages = []
|
||||||
|
@ -226,7 +226,7 @@ class BaseTest(TestCase):
|
||||||
when one attempts to store a message.
|
when one attempts to store a message.
|
||||||
"""
|
"""
|
||||||
data = {
|
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')
|
show_url = reverse('django.contrib.messages.tests.urls.show')
|
||||||
for level in ('debug', 'info', 'success', 'warning', 'error'):
|
for level in ('debug', 'info', 'success', 'warning', 'error'):
|
||||||
|
@ -251,7 +251,7 @@ class BaseTest(TestCase):
|
||||||
raised if 'fail_silently' = True
|
raised if 'fail_silently' = True
|
||||||
"""
|
"""
|
||||||
data = {
|
data = {
|
||||||
'messages': ['Test message %d' % x for x in range(10)],
|
'messages': ['Test message %d' % x for x in range(5)],
|
||||||
'fail_silently': True,
|
'fail_silently': True,
|
||||||
}
|
}
|
||||||
show_url = reverse('django.contrib.messages.tests.urls.show')
|
show_url = reverse('django.contrib.messages.tests.urls.show')
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
import shutil
|
import shutil
|
||||||
import string
|
import string
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -302,11 +302,10 @@ class CacheDBSessionTests(SessionTestsMixin, TestCase):
|
||||||
self.assertTrue(self.session.exists(self.session.session_key))
|
self.assertTrue(self.session.exists(self.session.session_key))
|
||||||
|
|
||||||
def test_load_overlong_key(self):
|
def test_load_overlong_key(self):
|
||||||
with warnings.catch_warnings(record=True) as w:
|
# Some backends might issue a warning
|
||||||
warnings.simplefilter("always")
|
with warnings.catch_warnings():
|
||||||
self.session._session_key = (string.ascii_letters + string.digits) * 20
|
self.session._session_key = (string.ascii_letters + string.digits) * 20
|
||||||
self.assertEqual(self.session.load(), {})
|
self.assertEqual(self.session.load(), {})
|
||||||
self.assertEqual(len(w), 1)
|
|
||||||
|
|
||||||
|
|
||||||
@override_settings(USE_TZ=True)
|
@override_settings(USE_TZ=True)
|
||||||
|
@ -352,11 +351,10 @@ class CacheSessionTests(SessionTestsMixin, unittest.TestCase):
|
||||||
backend = CacheSession
|
backend = CacheSession
|
||||||
|
|
||||||
def test_load_overlong_key(self):
|
def test_load_overlong_key(self):
|
||||||
with warnings.catch_warnings(record=True) as w:
|
# Some backends might issue a warning
|
||||||
warnings.simplefilter("always")
|
with warnings.catch_warnings():
|
||||||
self.session._session_key = (string.ascii_letters + string.digits) * 20
|
self.session._session_key = (string.ascii_letters + string.digits) * 20
|
||||||
self.assertEqual(self.session.load(), {})
|
self.assertEqual(self.session.load(), {})
|
||||||
self.assertEqual(len(w), 1)
|
|
||||||
|
|
||||||
|
|
||||||
class SessionMiddlewareTests(unittest.TestCase):
|
class SessionMiddlewareTests(unittest.TestCase):
|
||||||
|
|
|
@ -141,7 +141,7 @@ class CacheClass(BaseMemcachedCache):
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
import memcache
|
import memcache
|
||||||
except:
|
except ImportError:
|
||||||
raise InvalidCacheBackendError(
|
raise InvalidCacheBackendError(
|
||||||
"Memcached cache backend requires either the 'memcache' or 'cmemcache' library"
|
"Memcached cache backend requires either the 'memcache' or 'cmemcache' library"
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,6 +5,7 @@ from optparse import OptionParser, NO_DEFAULT
|
||||||
import imp
|
import imp
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.management.base import BaseCommand, CommandError, handle_default_options
|
from django.core.management.base import BaseCommand, CommandError, handle_default_options
|
||||||
from django.core.management.color import color_style
|
from django.core.management.color import color_style
|
||||||
from django.utils.importlib import import_module
|
from django.utils.importlib import import_module
|
||||||
|
@ -105,7 +106,7 @@ def get_commands():
|
||||||
try:
|
try:
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
apps = settings.INSTALLED_APPS
|
apps = settings.INSTALLED_APPS
|
||||||
except (AttributeError, EnvironmentError, ImportError):
|
except (AttributeError, ImproperlyConfigured):
|
||||||
apps = []
|
apps = []
|
||||||
|
|
||||||
# Find and load the management module for each installed app.
|
# Find and load the management module for each installed app.
|
||||||
|
|
|
@ -1034,9 +1034,12 @@ class BaseDatabaseIntrospection(object):
|
||||||
|
|
||||||
def get_primary_key_column(self, cursor, table_name):
|
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):
|
def get_indexes(self, cursor, table_name):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import hashlib
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.utils import load_backend
|
from django.db.utils import load_backend
|
||||||
|
from django.utils.encoding import force_bytes
|
||||||
from django.utils.six.moves import input
|
from django.utils.six.moves import input
|
||||||
|
|
||||||
# The prefix to put on the default database name when creating
|
# 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
|
Generates a 32-bit digest of a set of arguments that can be used to
|
||||||
shorten identifying names.
|
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()):
|
def sql_create_model(self, model, style, known_models=set()):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -2,7 +2,6 @@ import re
|
||||||
from .base import FIELD_TYPE
|
from .base import FIELD_TYPE
|
||||||
|
|
||||||
from django.db.backends import BaseDatabaseIntrospection
|
from django.db.backends import BaseDatabaseIntrospection
|
||||||
from django.utils import six
|
|
||||||
|
|
||||||
|
|
||||||
foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
|
foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
|
||||||
|
@ -88,15 +87,6 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
|
||||||
key_columns.extend(cursor.fetchall())
|
key_columns.extend(cursor.fetchall())
|
||||||
return key_columns
|
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):
|
def get_indexes(self, cursor, table_name):
|
||||||
cursor.execute("SHOW INDEX FROM %s" % self.connection.ops.quote_name(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
|
# Do a two-pass search for indexes: on first pass check which indexes
|
||||||
|
|
|
@ -412,7 +412,4 @@ def _sqlite_format_dtdelta(dt, conn, days, secs, usecs):
|
||||||
return str(dt)
|
return str(dt)
|
||||||
|
|
||||||
def _sqlite_regexp(re_pattern, re_string):
|
def _sqlite_regexp(re_pattern, re_string):
|
||||||
try:
|
|
||||||
return bool(re.search(re_pattern, re_string))
|
return bool(re.search(re_pattern, re_string))
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
"""
|
||||||
|
Constants used across the ORM in general.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Separator used to split filter strings apart.
|
||||||
|
LOOKUP_SEP = '__'
|
||||||
|
|
|
@ -8,6 +8,7 @@ import sys
|
||||||
|
|
||||||
from django.core import exceptions
|
from django.core import exceptions
|
||||||
from django.db import connections, router, transaction, IntegrityError
|
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.fields import AutoField
|
||||||
from django.db.models.query_utils import (Q, select_related_descend,
|
from django.db.models.query_utils import (Q, select_related_descend,
|
||||||
deferred_class_factory, InvalidQuery)
|
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
|
Populates prefetched objects caches for a list of results
|
||||||
from a QuerySet
|
from a QuerySet
|
||||||
"""
|
"""
|
||||||
from django.db.models.sql.constants import LOOKUP_SEP
|
|
||||||
|
|
||||||
if len(result_cache) == 0:
|
if len(result_cache) == 0:
|
||||||
return # nothing to do
|
return # nothing to do
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,10 @@ from django.utils.six.moves import zip
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.backends.util import truncate_name
|
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.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.datastructures import EmptyResultSet
|
||||||
from django.db.models.sql.expressions import SQLEvaluator
|
from django.db.models.sql.expressions import SQLEvaluator
|
||||||
from django.db.models.sql.query import get_order_dir, Query
|
from django.db.models.sql.query import get_order_dir, Query
|
||||||
|
@ -811,7 +813,7 @@ class SQLCompiler(object):
|
||||||
raise EmptyResultSet
|
raise EmptyResultSet
|
||||||
except EmptyResultSet:
|
except EmptyResultSet:
|
||||||
if result_type == MULTI:
|
if result_type == MULTI:
|
||||||
return empty_iter()
|
return iter([])
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -1088,13 +1090,6 @@ class SQLDateCompiler(SQLCompiler):
|
||||||
yield date
|
yield date
|
||||||
|
|
||||||
|
|
||||||
def empty_iter():
|
|
||||||
"""
|
|
||||||
Returns an iterator containing no results.
|
|
||||||
"""
|
|
||||||
yield next(iter([]))
|
|
||||||
|
|
||||||
|
|
||||||
def order_modified_iter(cursor, trim, sentinel):
|
def order_modified_iter(cursor, trim, sentinel):
|
||||||
"""
|
"""
|
||||||
Yields blocks of rows from a cursor. We use this iterator in the special
|
Yields blocks of rows from a cursor. We use this iterator in the special
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
|
"""
|
||||||
|
Constants specific to the SQL storage portion of the ORM.
|
||||||
|
"""
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
import re
|
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([
|
QUERY_TERMS = set([
|
||||||
'exact', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte', 'in',
|
'exact', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte', 'in',
|
||||||
'startswith', 'istartswith', 'endswith', 'iendswith', 'range', 'year',
|
'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.
|
# Larger values are slightly faster at the expense of more storage space.
|
||||||
GET_ITERATOR_CHUNK_SIZE = 100
|
GET_ITERATOR_CHUNK_SIZE = 100
|
||||||
|
|
||||||
# Separator used to split filter strings apart.
|
|
||||||
LOOKUP_SEP = '__'
|
|
||||||
|
|
||||||
# Constants to make looking up tuple values clearer.
|
# Constants to make looking up tuple values clearer.
|
||||||
# Join lists (indexes into the tuples that are values in the alias_map
|
# Join lists (indexes into the tuples that are values in the alias_map
|
||||||
# dictionary in the Query class).
|
# dictionary in the Query class).
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from django.core.exceptions import FieldError
|
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.fields import FieldDoesNotExist
|
||||||
from django.db.models.sql.constants import LOOKUP_SEP
|
|
||||||
|
|
||||||
class SQLEvaluator(object):
|
class SQLEvaluator(object):
|
||||||
def __init__(self, expression, query, allow_joins=True):
|
def __init__(self, expression, query, allow_joins=True):
|
||||||
|
|
|
@ -15,11 +15,12 @@ from django.utils.tree import Node
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.db import connections, DEFAULT_DB_ALIAS
|
from django.db import connections, DEFAULT_DB_ALIAS
|
||||||
from django.db.models import signals
|
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.expressions import ExpressionNode
|
||||||
from django.db.models.fields import FieldDoesNotExist
|
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 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.datastructures import EmptyResultSet, Empty, MultiJoin
|
||||||
from django.db.models.sql.expressions import SQLEvaluator
|
from django.db.models.sql.expressions import SQLEvaluator
|
||||||
from django.db.models.sql.where import (WhereNode, Constraint, EverythingNode,
|
from django.db.models.sql.where import (WhereNode, Constraint, EverythingNode,
|
||||||
|
@ -28,6 +29,7 @@ from django.core.exceptions import FieldError
|
||||||
|
|
||||||
__all__ = ['Query', 'RawQuery']
|
__all__ = ['Query', 'RawQuery']
|
||||||
|
|
||||||
|
|
||||||
class RawQuery(object):
|
class RawQuery(object):
|
||||||
"""
|
"""
|
||||||
A single raw SQL query
|
A single raw SQL query
|
||||||
|
|
|
@ -3,6 +3,7 @@ Query subclasses which provide extra functionality beyond simple data retrieval.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.core.exceptions import FieldError
|
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.fields import DateField, FieldDoesNotExist
|
||||||
from django.db.models.sql.constants import *
|
from django.db.models.sql.constants import *
|
||||||
from django.db.models.sql.datastructures import Date
|
from django.db.models.sql.datastructures import Date
|
||||||
|
|
|
@ -507,11 +507,7 @@ class CheckboxInput(Widget):
|
||||||
|
|
||||||
def render(self, name, value, attrs=None):
|
def render(self, name, value, attrs=None):
|
||||||
final_attrs = self.build_attrs(attrs, type='checkbox', name=name)
|
final_attrs = self.build_attrs(attrs, type='checkbox', name=name)
|
||||||
try:
|
if self.check_test(value):
|
||||||
result = self.check_test(value)
|
|
||||||
except: # Silently catch exceptions
|
|
||||||
result = False
|
|
||||||
if result:
|
|
||||||
final_attrs['checked'] = 'checked'
|
final_attrs['checked'] = 'checked'
|
||||||
if not (value is True or value is False or value is None or value == ''):
|
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.
|
# Only add the 'value' attribute if a value is non-empty.
|
||||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import datetime
|
import datetime
|
||||||
|
from email.header import Header
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
@ -560,31 +561,44 @@ class HttpResponse(object):
|
||||||
else:
|
else:
|
||||||
__str__ = serialize
|
__str__ = serialize
|
||||||
|
|
||||||
def _convert_to_ascii(self, *values):
|
def _convert_to_charset(self, value, charset, mime_encode=False):
|
||||||
"""Converts all values to ascii strings."""
|
"""Converts headers key/value to ascii/latin1 native strings.
|
||||||
for value in values:
|
|
||||||
if not isinstance(value, six.string_types):
|
`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)
|
value = str(value)
|
||||||
try:
|
try:
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
# Ensure string only contains ASCII
|
if isinstance(value, str):
|
||||||
value.encode('us-ascii')
|
# Ensure string is valid in given charset
|
||||||
|
value.encode(charset)
|
||||||
|
else:
|
||||||
|
# Convert bytestring using given charset
|
||||||
|
value = value.decode(charset)
|
||||||
else:
|
else:
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
# Ensure string only contains ASCII
|
# Ensure string is valid in given charset
|
||||||
value.decode('us-ascii')
|
value.decode(charset)
|
||||||
else:
|
else:
|
||||||
# Convert unicode to an ASCII string
|
# Convert unicode string to given charset
|
||||||
value = value.encode('us-ascii')
|
value = value.encode(charset)
|
||||||
except UnicodeError as e:
|
except UnicodeError as e:
|
||||||
e.reason += ', HTTP response headers must be in US-ASCII format'
|
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
|
raise
|
||||||
if '\n' in value or '\r' in value:
|
if str('\n') in value or str('\r') in value:
|
||||||
raise BadHeaderError("Header values can't contain newlines (got %r)" % value)
|
raise BadHeaderError("Header values can't contain newlines (got %r)" % value)
|
||||||
yield value
|
return value
|
||||||
|
|
||||||
def __setitem__(self, header, 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)
|
self._headers[header.lower()] = (header, value)
|
||||||
|
|
||||||
def __delitem__(self, header):
|
def __delitem__(self, header):
|
||||||
|
|
|
@ -68,7 +68,6 @@ class MultiPartParser(object):
|
||||||
if not boundary or not cgi.valid_boundary(boundary):
|
if not boundary or not cgi.valid_boundary(boundary):
|
||||||
raise MultiPartParserError('Invalid boundary in multipart: %s' % boundary)
|
raise MultiPartParserError('Invalid boundary in multipart: %s' % boundary)
|
||||||
|
|
||||||
|
|
||||||
# Content-Length should contain the length of the body we are about
|
# Content-Length should contain the length of the body we are about
|
||||||
# to receive.
|
# to receive.
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -66,23 +66,7 @@ def redirect(to, *args, **kwargs):
|
||||||
else:
|
else:
|
||||||
redirect_class = HttpResponseRedirect
|
redirect_class = HttpResponseRedirect
|
||||||
|
|
||||||
# If it's a model, use get_absolute_url()
|
return redirect_class(resolve_url(to, *args, **kwargs))
|
||||||
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)
|
|
||||||
|
|
||||||
def _get_queryset(klass):
|
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)
|
raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
|
||||||
return obj_list
|
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
|
||||||
|
|
|
@ -360,7 +360,7 @@ class SimpleTestCase(ut2.TestCase):
|
||||||
args: Extra args.
|
args: Extra args.
|
||||||
kwargs: Extra kwargs.
|
kwargs: Extra kwargs.
|
||||||
"""
|
"""
|
||||||
return self.assertRaisesRegexp(expected_exception,
|
return six.assertRaisesRegex(self, expected_exception,
|
||||||
re.escape(expected_message), callable_obj, *args, **kwargs)
|
re.escape(expected_message), callable_obj, *args, **kwargs)
|
||||||
|
|
||||||
def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None,
|
def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import stat
|
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.encoding import force_text
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
|
@ -41,13 +41,16 @@ def safe_join(base, *paths):
|
||||||
paths = [force_text(p) for p in paths]
|
paths = [force_text(p) for p in paths]
|
||||||
final_path = abspathu(join(base, *paths))
|
final_path = abspathu(join(base, *paths))
|
||||||
base_path = abspathu(base)
|
base_path = abspathu(base)
|
||||||
base_path_len = len(base_path)
|
|
||||||
# Ensure final_path starts with base_path (using normcase to ensure we
|
# Ensure final_path starts with base_path (using normcase to ensure we
|
||||||
# don't false-negative on case insensitive operating systems like Windows)
|
# 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,
|
# further, one of the following conditions must be true:
|
||||||
# in which case final_path must be equal to base_path).
|
# a) The next character is the path separator (to prevent conditions like
|
||||||
if not normcase(final_path).startswith(normcase(base_path)) \
|
# safe_join("/dir", "/../d"))
|
||||||
or final_path[base_path_len:base_path_len+1] not in ('', sep):
|
# 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 '
|
raise ValueError('The joined path (%s) is located outside of the base '
|
||||||
'path component (%s)' % (final_path, base_path))
|
'path component (%s)' % (final_path, base_path))
|
||||||
return final_path
|
return final_path
|
||||||
|
|
|
@ -370,13 +370,20 @@ def with_metaclass(meta, base=object):
|
||||||
|
|
||||||
if PY3:
|
if PY3:
|
||||||
_iterlists = "lists"
|
_iterlists = "lists"
|
||||||
|
_assertRaisesRegex = "assertRaisesRegex"
|
||||||
else:
|
else:
|
||||||
_iterlists = "iterlists"
|
_iterlists = "iterlists"
|
||||||
|
_assertRaisesRegex = "assertRaisesRegexp"
|
||||||
|
|
||||||
|
|
||||||
def iterlists(d):
|
def iterlists(d):
|
||||||
"""Return an iterator over the values of a MultiValueDict."""
|
"""Return an iterator over the values of a MultiValueDict."""
|
||||||
return getattr(d, _iterlists)()
|
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("_dummy_thread", "dummy_thread"))
|
||||||
add_move(MovedModule("_thread", "thread"))
|
add_move(MovedModule("_thread", "thread"))
|
||||||
|
|
|
@ -372,6 +372,9 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View):
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def get_date_list_period(self):
|
def get_date_list_period(self):
|
||||||
|
"""
|
||||||
|
Get the aggregation period for the list of dates: 'year', 'month', or 'day'.
|
||||||
|
"""
|
||||||
return self.date_list_period
|
return self.date_list_period
|
||||||
|
|
||||||
def get_date_list(self, queryset, date_type=None):
|
def get_date_list(self, queryset, date_type=None):
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
The documentation in this tree is in plain text files and can be viewed using
|
The documentation in this tree is in plain text files and can be viewed using
|
||||||
any text file viewer.
|
any text file viewer.
|
||||||
|
|
||||||
Technically speaking, it uses ReST (reStructuredText) [1], and the Sphinx
|
It uses ReST (reStructuredText) [1], and the Sphinx documentation system [2].
|
||||||
documentation system [2]. This allows it to be built into other forms for
|
This allows it to be built into other forms for easier viewing and browsing.
|
||||||
easier viewing and browsing.
|
|
||||||
|
|
||||||
To create an HTML version of the docs:
|
To create an HTML version of the docs:
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,9 @@ How do I get started?
|
||||||
What are Django's prerequisites?
|
What are Django's prerequisites?
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
Django requires Python_, specifically Python 2.6.5 - 2.7.x. No other Python
|
Django requires Python, specifically Python 2.6.5 - 2.7.x. No other Python
|
||||||
libraries are required for basic Django usage.
|
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 --
|
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
|
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
|
Third-party applications for use with Django are, of course, free to set their
|
||||||
own version requirements.
|
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
|
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
|
(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
|
improvements and optimizations to the Python language since version 2.6.
|
||||||
will help ease the process of dropping support for older Python versions on
|
|
||||||
the road to Python 3.
|
Generally speaking, we don't recommend running Django on Python 3 yet; see
|
||||||
|
below for more.
|
||||||
|
|
||||||
What Python version can I use with Django?
|
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.2 2.4, 2.5, 2.6, 2.7
|
||||||
1.3 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.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?
|
Can I use Django with Python 3?
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
Not at the moment. Python 3.0 introduced a number of
|
Django 1.5 introduces experimental support for Python 3.2 and 3.3. However, we
|
||||||
backwards-incompatible changes to the Python language, and although
|
don't yet suggest that you use Django and Python 3 in production.
|
||||||
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).
|
|
||||||
|
|
||||||
In the meantime, Python 2.x releases will be supported and provided
|
Python 3 support should be considered a "preview". It's offered to bootstrap
|
||||||
with bug fixes and security updates by the Python development team, so
|
the transition of the Django ecosystem to Python 3, and to help you start
|
||||||
continuing to use a Python 2.x release during the transition should
|
porting your apps for future Python 3 compatibility. But we're not yet
|
||||||
not present any risk.
|
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)?
|
Will Django run under shared hosting (like TextDrive or Dreamhost)?
|
||||||
-------------------------------------------------------------------
|
-------------------------------------------------------------------
|
||||||
|
|
|
@ -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
|
Many of Django's model fields accept options that they don't do anything
|
||||||
with. For example, you can pass both
|
with. For example, you can pass both
|
||||||
:attr:`~django.db.models.Field.editable` and
|
: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
|
:class:`django.db.models.DateField` and it will simply ignore the
|
||||||
:attr:`~django.db.models.Field.editable` parameter
|
: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.
|
``editable=False``). No error is raised in this case.
|
||||||
|
|
||||||
This behavior simplifies the field classes, because they don't need to
|
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
|
You only need to override this method if you want to preprocess the value
|
||||||
somehow, just before saving. For example, Django's
|
somehow, just before saving. For example, Django's
|
||||||
:class:`~django.db.models.DateTimeField` uses this method to set the attribute
|
: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
|
correctly in the case of :attr:`~django.db.models.DateField.auto_now` or
|
||||||
:attr:`~django.db.models.Field.auto_now_add`.
|
:attr:`~django.db.models.DateField.auto_now_add`.
|
||||||
|
|
||||||
If you do override this method, you must return the value of the attribute at
|
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
|
the end. You should also update the model's attribute if you make any changes
|
||||||
|
|
|
@ -264,6 +264,9 @@ these changes.
|
||||||
in 1.4. The backward compatibility will be removed --
|
in 1.4. The backward compatibility will be removed --
|
||||||
``HttpRequest.raw_post_data`` will no longer work.
|
``HttpRequest.raw_post_data`` will no longer work.
|
||||||
|
|
||||||
|
* ``django.contrib.markup`` will be removed following an accelerated
|
||||||
|
deprecation.
|
||||||
|
|
||||||
1.7
|
1.7
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,9 @@ Install Python
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
Being a Python Web framework, Django requires Python. It works with any 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,
|
version from 2.6.5 to 2.7. It also features experimental support for versions
|
||||||
Django does not currently work with Python 3.0; see :doc:`the Django FAQ
|
3.2 and 3.3. All these versions of Python include a lightweight database
|
||||||
</faq/install>` for more information on supported Python versions and the 3.0
|
called SQLite_ so you won't need to set up a database just yet.
|
||||||
transition), 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/
|
.. _sqlite: http://sqlite.org/
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ the file ``mysite/news/models.py``::
|
||||||
return self.full_name
|
return self.full_name
|
||||||
|
|
||||||
class Article(models.Model):
|
class Article(models.Model):
|
||||||
pub_date = models.DateTimeField()
|
pub_date = models.DateField()
|
||||||
headline = models.CharField(max_length=200)
|
headline = models.CharField(max_length=200)
|
||||||
content = models.TextField()
|
content = models.TextField()
|
||||||
reporter = models.ForeignKey(Reporter)
|
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}
|
DoesNotExist: Reporter matching query does not exist. Lookup parameters were {'id': 2}
|
||||||
|
|
||||||
# Create an article.
|
# Create an article.
|
||||||
>>> from datetime import datetime
|
>>> from datetime import date
|
||||||
>>> a = Article(pub_date=datetime.now(), headline='Django is cool',
|
>>> a = Article(pub_date=date.today(), headline='Django is cool',
|
||||||
... content='Yeah.', reporter=r)
|
... content='Yeah.', reporter=r)
|
||||||
>>> a.save()
|
>>> a.save()
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ as registering your model in the admin site::
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
class Article(models.Model):
|
class Article(models.Model):
|
||||||
pub_date = models.DateTimeField()
|
pub_date = models.DateField()
|
||||||
headline = models.CharField(max_length=200)
|
headline = models.CharField(max_length=200)
|
||||||
content = models.TextField()
|
content = models.TextField()
|
||||||
reporter = models.ForeignKey(Reporter)
|
reporter = models.ForeignKey(Reporter)
|
||||||
|
|
|
@ -513,11 +513,12 @@ Here's what happens if a user goes to "/polls/34/" in this system:
|
||||||
further processing.
|
further processing.
|
||||||
|
|
||||||
Now that we've decoupled that, we need to decouple the ``polls.urls``
|
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
|
URLconf by removing the leading "polls/" from each line, removing the
|
||||||
lines registering the admin site. Your ``polls/urls.py`` file should now look like
|
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::
|
this::
|
||||||
|
|
||||||
from django.conf.urls import patterns, include, url
|
from django.conf.urls import patterns, url
|
||||||
|
|
||||||
urlpatterns = patterns('polls.views',
|
urlpatterns = patterns('polls.views',
|
||||||
url(r'^$', 'index'),
|
url(r'^$', 'index'),
|
||||||
|
|
|
@ -218,7 +218,7 @@ Read on for details.
|
||||||
First, open the ``polls/urls.py`` URLconf. It looks like this, according to the
|
First, open the ``polls/urls.py`` URLconf. It looks like this, according to the
|
||||||
tutorial so far::
|
tutorial so far::
|
||||||
|
|
||||||
from django.conf.urls import patterns, include, url
|
from django.conf.urls import patterns, url
|
||||||
|
|
||||||
urlpatterns = patterns('polls.views',
|
urlpatterns = patterns('polls.views',
|
||||||
url(r'^$', 'index'),
|
url(r'^$', 'index'),
|
||||||
|
@ -229,7 +229,7 @@ tutorial so far::
|
||||||
|
|
||||||
Change it like so::
|
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 django.views.generic import DetailView, ListView
|
||||||
from polls.models import Poll
|
from polls.models import Poll
|
||||||
|
|
||||||
|
|
|
@ -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
|
required for projects, in which case there are Mixins and Generic class-based
|
||||||
views.
|
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
|
View
|
||||||
----
|
----
|
||||||
|
|
||||||
|
@ -20,6 +25,7 @@ View
|
||||||
|
|
||||||
1. :meth:`dispatch()`
|
1. :meth:`dispatch()`
|
||||||
2. :meth:`http_method_not_allowed()`
|
2. :meth:`http_method_not_allowed()`
|
||||||
|
3. :meth:`options()`
|
||||||
|
|
||||||
**Example views.py**::
|
**Example views.py**::
|
||||||
|
|
||||||
|
@ -41,8 +47,20 @@ View
|
||||||
url(r'^mine/$', MyView.as_view(), name='my-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**
|
**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)
|
.. method:: dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
The ``view`` part of the view -- the method that accepts a ``request``
|
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()`,
|
delegated to :meth:`~View.get()`, a ``POST`` to :meth:`~View.post()`,
|
||||||
and so on.
|
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
|
The default implementation also sets ``request``, ``args`` and
|
||||||
``kwargs`` as instance variables, so any method on the view can know
|
``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.
|
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
|
If the view was called with a HTTP method it doesn't support, this
|
||||||
method is called instead.
|
method is called instead.
|
||||||
|
|
||||||
The default implementation returns ``HttpResponseNotAllowed`` with list
|
The default implementation returns ``HttpResponseNotAllowed`` with a
|
||||||
of allowed methods in plain text.
|
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
|
Handles responding to requests for the OPTIONS HTTP verb. Returns a
|
||||||
methods defined directly on the class are documented here, not methods
|
list of the allowed HTTP method names for the view.
|
||||||
defined on superclasses.
|
|
||||||
|
|
||||||
TemplateView
|
TemplateView
|
||||||
------------
|
------------
|
||||||
|
@ -81,6 +103,8 @@ TemplateView
|
||||||
|
|
||||||
**Ancestors (MRO)**
|
**Ancestors (MRO)**
|
||||||
|
|
||||||
|
This view inherits methods and attributes from the following views:
|
||||||
|
|
||||||
* :class:`django.views.generic.base.TemplateView`
|
* :class:`django.views.generic.base.TemplateView`
|
||||||
* :class:`django.views.generic.base.TemplateResponseMixin`
|
* :class:`django.views.generic.base.TemplateResponseMixin`
|
||||||
* :class:`django.views.generic.base.View`
|
* :class:`django.views.generic.base.View`
|
||||||
|
@ -116,28 +140,11 @@ TemplateView
|
||||||
url(r'^$', HomePageView.as_view(), name='home'),
|
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**
|
**Context**
|
||||||
|
|
||||||
* ``params``: The dictionary of keyword arguments captured from the URL
|
* ``params``: The dictionary of keyword arguments captured from the URL
|
||||||
pattern that served the view.
|
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
|
RedirectView
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
@ -156,6 +163,8 @@ RedirectView
|
||||||
|
|
||||||
**Ancestors (MRO)**
|
**Ancestors (MRO)**
|
||||||
|
|
||||||
|
This view inherits methods and attributes from the following view:
|
||||||
|
|
||||||
* :class:`django.views.generic.base.View`
|
* :class:`django.views.generic.base.View`
|
||||||
|
|
||||||
**Method Flowchart**
|
**Method Flowchart**
|
||||||
|
@ -194,7 +203,7 @@ RedirectView
|
||||||
url(r'^go-to-django/$', RedirectView.as_view(url='http://djangoproject.com'), name='go-to-django'),
|
url(r'^go-to-django/$', RedirectView.as_view(url='http://djangoproject.com'), name='go-to-django'),
|
||||||
)
|
)
|
||||||
|
|
||||||
**Methods and Attributes**
|
**Attributes**
|
||||||
|
|
||||||
.. attribute:: url
|
.. attribute:: url
|
||||||
|
|
||||||
|
@ -215,6 +224,8 @@ RedirectView
|
||||||
then the query string is discarded. By default, ``query_string`` is
|
then the query string is discarded. By default, ``query_string`` is
|
||||||
``False``.
|
``False``.
|
||||||
|
|
||||||
|
**Methods**
|
||||||
|
|
||||||
.. method:: get_redirect_url(**kwargs)
|
.. method:: get_redirect_url(**kwargs)
|
||||||
|
|
||||||
Constructs the target URL for redirection.
|
Constructs the target URL for redirection.
|
||||||
|
@ -225,9 +236,3 @@ RedirectView
|
||||||
:attr:`~RedirectView.query_string`. Subclasses may implement any
|
:attr:`~RedirectView.query_string`. Subclasses may implement any
|
||||||
behavior they wish, as long as the method returns a redirect-ready URL
|
behavior they wish, as long as the method returns a redirect-ready URL
|
||||||
string.
|
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.
|
|
||||||
|
|
|
@ -2,13 +2,15 @@
|
||||||
Generic date views
|
Generic date views
|
||||||
==================
|
==================
|
||||||
|
|
||||||
Date-based generic views (in the module :mod:`django.views.generic.dates`)
|
.. module:: django.views.generic.dates
|
||||||
are views for displaying drilldown pages for date-based data.
|
|
||||||
|
Date-based generic views, provided in :mod:`django.views.generic.dates`, are
|
||||||
|
views for displaying drilldown pages for date-based data.
|
||||||
|
|
||||||
ArchiveIndexView
|
ArchiveIndexView
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
.. class:: django.views.generic.dates.ArchiveIndexView
|
.. class:: ArchiveIndexView
|
||||||
|
|
||||||
A top-level index page showing the "latest" objects, by date. Objects with
|
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
|
a date in the *future* are not included unless you set ``allow_future`` to
|
||||||
|
@ -36,7 +38,7 @@ ArchiveIndexView
|
||||||
YearArchiveView
|
YearArchiveView
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
.. class:: django.views.generic.dates.YearArchiveView
|
.. class:: YearArchiveView
|
||||||
|
|
||||||
A yearly archive page showing all available months in a given year. Objects
|
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
|
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
|
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
|
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``.
|
``False``.
|
||||||
|
|
||||||
.. method:: get_make_object_list()
|
.. method:: get_make_object_list()
|
||||||
|
|
||||||
Determine if an object list will be returned as part of the context. If
|
Determine if an object list will be returned as part of the context.
|
||||||
``False``, the ``None`` queryset will be used as the object list.
|
Returns :attr:`~YearArchiveView.make_object_list` by default.
|
||||||
|
|
||||||
|
|
||||||
**Context**
|
**Context**
|
||||||
|
|
||||||
|
@ -80,16 +84,18 @@ YearArchiveView
|
||||||
:class:`datetime.datetime<python:datetime.datetime>` objects, in
|
:class:`datetime.datetime<python:datetime.datetime>` objects, in
|
||||||
ascending order.
|
ascending order.
|
||||||
|
|
||||||
* ``year``: A :class:`datetime.date<python:datetime.date>` object
|
* ``year``: A :class:`~datetime.date` object
|
||||||
representing the given year.
|
representing the given year.
|
||||||
|
|
||||||
* ``next_year``: A :class:`datetime.date<python:datetime.date>` object
|
* ``next_year``: A :class:`~datetime.date` object
|
||||||
representing the first day of the next year. If the next year is in the
|
representing the first day of the next year, according to
|
||||||
future, this will be ``None``.
|
:attr:`~BaseDateListView.allow_empty` and
|
||||||
|
:attr:`~DateMixin.allow_future`.
|
||||||
|
|
||||||
* ``previous_year``: A :class:`datetime.date<python:datetime.date>` object
|
* ``previous_year``: A :class:`~datetime.date` object
|
||||||
representing the first day of the previous year. Unlike ``next_year``,
|
representing the first day of the previous year, according to
|
||||||
this will never be ``None``.
|
:attr:`~BaseDateListView.allow_empty` and
|
||||||
|
:attr:`~DateMixin.allow_future`.
|
||||||
|
|
||||||
**Notes**
|
**Notes**
|
||||||
|
|
||||||
|
@ -98,7 +104,7 @@ YearArchiveView
|
||||||
MonthArchiveView
|
MonthArchiveView
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
.. class:: django.views.generic.dates.MonthArchiveView
|
.. class:: MonthArchiveView
|
||||||
|
|
||||||
A monthly archive page showing all objects in a given month. Objects with a
|
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
|
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
|
:class:`datetime.datetime<python:datetime.datetime>` objects, in
|
||||||
ascending order.
|
ascending order.
|
||||||
|
|
||||||
* ``month``: A :class:`datetime.date<python:datetime.date>` object
|
* ``month``: A :class:`~datetime.date` object
|
||||||
representing the given month.
|
representing the given month.
|
||||||
|
|
||||||
* ``next_month``: A :class:`datetime.date<python:datetime.date>` object
|
* ``next_month``: A :class:`~datetime.date` object
|
||||||
representing the first day of the next month. If the next month is in the
|
representing the first day of the next month, according to
|
||||||
future, this will be ``None``.
|
:attr:`~BaseDateListView.allow_empty` and
|
||||||
|
:attr:`~DateMixin.allow_future`.
|
||||||
|
|
||||||
* ``previous_month``: A :class:`datetime.date<python:datetime.date>` object
|
* ``previous_month``: A :class:`~datetime.date` object
|
||||||
representing the first day of the previous month. Unlike ``next_month``,
|
representing the first day of the previous month, according to
|
||||||
this will never be ``None``.
|
:attr:`~BaseDateListView.allow_empty` and
|
||||||
|
:attr:`~DateMixin.allow_future`.
|
||||||
|
|
||||||
**Notes**
|
**Notes**
|
||||||
|
|
||||||
|
@ -149,7 +157,7 @@ MonthArchiveView
|
||||||
WeekArchiveView
|
WeekArchiveView
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
.. class:: django.views.generic.dates.WeekArchiveView
|
.. class:: WeekArchiveView
|
||||||
|
|
||||||
A weekly archive page showing all objects in a given week. Objects with a
|
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
|
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
|
:class:`~django.views.generic.dates.BaseDateListView`), the template's
|
||||||
context will be:
|
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.
|
representing the first day of the given week.
|
||||||
|
|
||||||
* ``next_week``: A :class:`datetime.date<python:datetime.date>` object
|
* ``next_week``: A :class:`~datetime.date` object
|
||||||
representing the first day of the next week. If the next week is in the
|
representing the first day of the next week, according to
|
||||||
future, this will be ``None``.
|
:attr:`~BaseDateListView.allow_empty` and
|
||||||
|
:attr:`~DateMixin.allow_future`.
|
||||||
|
|
||||||
* ``previous_week``: A :class:`datetime.date<python:datetime.date>` object
|
* ``previous_week``: A :class:`~datetime.date` object
|
||||||
representing the first day of the previous week. Unlike ``next_week``,
|
representing the first day of the previous week, according to
|
||||||
this will never be ``None``.
|
:attr:`~BaseDateListView.allow_empty` and
|
||||||
|
:attr:`~DateMixin.allow_future`.
|
||||||
|
|
||||||
**Notes**
|
**Notes**
|
||||||
|
|
||||||
|
@ -193,7 +203,7 @@ WeekArchiveView
|
||||||
DayArchiveView
|
DayArchiveView
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
.. class:: django.views.generic.dates.DayArchiveView
|
.. class:: DayArchiveView
|
||||||
|
|
||||||
A day archive page showing all objects in a given day. Days in the future
|
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,
|
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
|
:class:`~django.views.generic.dates.BaseDateListView`), the template's
|
||||||
context will be:
|
context will be:
|
||||||
|
|
||||||
* ``day``: A :class:`datetime.date<python:datetime.date>` object
|
* ``day``: A :class:`~datetime.date` object
|
||||||
representing the given day.
|
representing the given day.
|
||||||
|
|
||||||
* ``next_day``: A :class:`datetime.date<python:datetime.date>` object
|
* ``next_day``: A :class:`~datetime.date` object
|
||||||
representing the next day. If the next day is in the future, this will be
|
representing the next day, according to
|
||||||
``None``.
|
:attr:`~BaseDateListView.allow_empty` and
|
||||||
|
:attr:`~DateMixin.allow_future`.
|
||||||
|
|
||||||
* ``previous_day``: A :class:`datetime.date<python:datetime.date>` object
|
* ``previous_day``: A :class:`~datetime.date` object
|
||||||
representing the previous day. Unlike ``next_day``, this will never be
|
representing the previous day, according to
|
||||||
``None``.
|
:attr:`~BaseDateListView.allow_empty` and
|
||||||
|
:attr:`~DateMixin.allow_future`.
|
||||||
|
|
||||||
* ``next_month``: A :class:`datetime.date<python:datetime.date>` object
|
* ``next_month``: A :class:`~datetime.date` object
|
||||||
representing the first day of the next month. If the next month is in the
|
representing the first day of the next month, according to
|
||||||
future, this will be ``None``.
|
:attr:`~BaseDateListView.allow_empty` and
|
||||||
|
:attr:`~DateMixin.allow_future`.
|
||||||
|
|
||||||
* ``previous_month``: A :class:`datetime.date<python:datetime.date>` object
|
* ``previous_month``: A :class:`~datetime.date` object
|
||||||
representing the first day of the previous month. Unlike ``next_month``,
|
representing the first day of the previous month, according to
|
||||||
this will never be ``None``.
|
:attr:`~BaseDateListView.allow_empty` and
|
||||||
|
:attr:`~DateMixin.allow_future`.
|
||||||
|
|
||||||
**Notes**
|
**Notes**
|
||||||
|
|
||||||
|
@ -246,7 +260,7 @@ DayArchiveView
|
||||||
TodayArchiveView
|
TodayArchiveView
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
.. class:: django.views.generic.dates.TodayArchiveView
|
.. class:: TodayArchiveView
|
||||||
|
|
||||||
A day archive page showing all objects for *today*. This is exactly the
|
A day archive page showing all objects for *today*. This is exactly the
|
||||||
same as :class:`django.views.generic.dates.DayArchiveView`, except today's
|
same as :class:`django.views.generic.dates.DayArchiveView`, except today's
|
||||||
|
@ -271,7 +285,7 @@ TodayArchiveView
|
||||||
DateDetailView
|
DateDetailView
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
.. class:: django.views.generic.dates.DateDetailView
|
.. class:: DateDetailView
|
||||||
|
|
||||||
A page representing an individual object. If the object has a date value in
|
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
|
the future, the view will throw a 404 error by default, unless you set
|
||||||
|
@ -293,6 +307,22 @@ DateDetailView
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
All of the generic views listed above have matching Base* views that only
|
All of the generic views listed above have matching ``Base`` views that
|
||||||
differ in that the they do not include the
|
only differ in that the they do not include the
|
||||||
:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`.
|
:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`:
|
||||||
|
|
||||||
|
.. class:: BaseArchiveIndexView
|
||||||
|
|
||||||
|
.. class:: BaseYearArchiveView
|
||||||
|
|
||||||
|
.. class:: BaseMonthArchiveView
|
||||||
|
|
||||||
|
.. class:: BaseWeekArchiveView
|
||||||
|
|
||||||
|
.. class:: BaseDayArchiveView
|
||||||
|
|
||||||
|
.. class:: BaseTodayArchiveView
|
||||||
|
|
||||||
|
.. class:: BaseDateDetailView
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@ DetailView
|
||||||
|
|
||||||
**Ancestors (MRO)**
|
**Ancestors (MRO)**
|
||||||
|
|
||||||
|
This view inherits methods and attributes from the following views:
|
||||||
|
|
||||||
* :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin`
|
* :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin`
|
||||||
* :class:`django.views.generic.base.TemplateResponseMixin`
|
* :class:`django.views.generic.base.TemplateResponseMixin`
|
||||||
* :class:`django.views.generic.detail.BaseDetailView`
|
* :class:`django.views.generic.detail.BaseDetailView`
|
||||||
|
@ -71,7 +73,9 @@ ListView
|
||||||
objects (usually, but not necessarily a queryset) that the view is
|
objects (usually, but not necessarily a queryset) that the view is
|
||||||
operating upon.
|
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.ListView`
|
||||||
* :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin`
|
* :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin`
|
||||||
|
@ -90,3 +94,54 @@ ListView
|
||||||
6. :meth:`get_context_data()`
|
6. :meth:`get_context_data()`
|
||||||
7. :meth:`get()`
|
7. :meth:`get()`
|
||||||
8. :meth:`render_to_response()`
|
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.
|
||||||
|
|
|
@ -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 thread-safe operation).
|
||||||
|
|
||||||
A class-based view is deployed into a URL pattern using the
|
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('',
|
urlpatterns = patterns('',
|
||||||
(r'^view/$', MyView.as_view(size=42)),
|
(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
|
is modified, the actions of one user visiting your view could have an
|
||||||
effect on subsequent users visiting the same view.
|
effect on subsequent users visiting the same view.
|
||||||
|
|
||||||
Any argument passed into :meth:`~View.as_view()` will be assigned onto the
|
Any argument passed into :meth:`~django.views.generic.base.View.as_view()` will
|
||||||
instance that is used to service a request. Using the previous example,
|
be assigned onto the instance that is used to service a request. Using the
|
||||||
this means that every request on ``MyView`` is able to use ``self.size``.
|
previous example, this means that every request on ``MyView`` is able to use
|
||||||
|
``self.size``.
|
||||||
|
|
||||||
Base vs Generic views
|
Base vs Generic views
|
||||||
---------------------
|
---------------------
|
||||||
|
|
|
@ -2,11 +2,12 @@
|
||||||
Date-based mixins
|
Date-based mixins
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
.. currentmodule:: django.views.generic.dates
|
||||||
|
|
||||||
YearMixin
|
YearMixin
|
||||||
---------
|
---------
|
||||||
|
|
||||||
.. class:: django.views.generic.dates.YearMixin
|
.. class:: YearMixin
|
||||||
|
|
||||||
A mixin that can be used to retrieve and provide parsing information for a
|
A mixin that can be used to retrieve and provide parsing information for a
|
||||||
year component of a date.
|
year component of a date.
|
||||||
|
@ -20,29 +21,45 @@ YearMixin
|
||||||
|
|
||||||
.. attribute:: year
|
.. 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.
|
``None``, which means the year will be determined using other means.
|
||||||
|
|
||||||
.. method:: get_year_format()
|
.. method:: get_year_format()
|
||||||
|
|
||||||
Returns the :func:`~time.strftime` format to use when parsing the year. Returns
|
Returns the :func:`~time.strftime` format to use when parsing the
|
||||||
:attr:`YearMixin.year_format` by default.
|
year. Returns :attr:`~YearMixin.year_format` by default.
|
||||||
|
|
||||||
.. method:: get_year()
|
.. method:: get_year()
|
||||||
|
|
||||||
Returns the year for which this view will display data. Tries the
|
Returns the year for which this view will display data, as a string.
|
||||||
following sources, in order:
|
Tries the following sources, in order:
|
||||||
|
|
||||||
* The value of the :attr:`YearMixin.year` attribute.
|
* 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.
|
* The value of the `year` GET query argument.
|
||||||
|
|
||||||
Raises a 404 if no valid year specification can be found.
|
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
|
MonthMixin
|
||||||
----------
|
----------
|
||||||
|
|
||||||
.. class:: django.views.generic.dates.MonthMixin
|
.. class:: MonthMixin
|
||||||
|
|
||||||
A mixin that can be used to retrieve and provide parsing information for a
|
A mixin that can be used to retrieve and provide parsing information for a
|
||||||
month component of a date.
|
month component of a date.
|
||||||
|
@ -51,26 +68,26 @@ MonthMixin
|
||||||
|
|
||||||
.. attribute:: month_format
|
.. attribute:: month_format
|
||||||
|
|
||||||
The :func:`~time.strftime` format to use when parsing the month. By default, this is
|
The :func:`~time.strftime` format to use when parsing the month. By
|
||||||
``'%b'``.
|
default, this is ``'%b'``.
|
||||||
|
|
||||||
.. attribute:: month
|
.. 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.
|
``None``, which means the month will be determined using other means.
|
||||||
|
|
||||||
.. method:: get_month_format()
|
.. method:: get_month_format()
|
||||||
|
|
||||||
Returns the :func:`~time.strftime` format to use when parsing the month. Returns
|
Returns the :func:`~time.strftime` format to use when parsing the
|
||||||
:attr:`MonthMixin.month_format` by default.
|
month. Returns :attr:`~MonthMixin.month_format` by default.
|
||||||
|
|
||||||
.. method:: get_month()
|
.. method:: get_month()
|
||||||
|
|
||||||
Returns the month for which this view will display data. Tries the
|
Returns the month for which this view will display data, as a string.
|
||||||
following sources, in order:
|
Tries the following sources, in order:
|
||||||
|
|
||||||
* The value of the :attr:`MonthMixin.month` attribute.
|
* 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.
|
* The value of the `month` GET query argument.
|
||||||
|
|
||||||
Raises a 404 if no valid month specification can be found.
|
Raises a 404 if no valid month specification can be found.
|
||||||
|
@ -78,20 +95,23 @@ MonthMixin
|
||||||
.. method:: get_next_month(date)
|
.. method:: get_next_month(date)
|
||||||
|
|
||||||
Returns a date object containing the first day of the month after the
|
Returns a date object containing the first day of the month after the
|
||||||
date provided. Returns ``None`` if mixed with a view that sets
|
date provided. This function can also return ``None`` or raise an
|
||||||
``allow_future = False``, and the next month is in the future. If
|
:class:`~django.http.Http404` exception, depending on the values of
|
||||||
``allow_empty = False``, returns the next month that contains data.
|
:attr:`~BaseDateListView.allow_empty` and
|
||||||
|
:attr:`~DateMixin.allow_future`.
|
||||||
|
|
||||||
.. method:: get_prev_month(date)
|
.. method:: get_prev_month(date)
|
||||||
|
|
||||||
Returns a date object containing the first day of the month before the
|
Returns a date object containing the first day of the month before the
|
||||||
date provided. If ``allow_empty = False``, returns the previous month
|
date provided. This function can also return ``None`` or raise an
|
||||||
that contained data.
|
:class:`~django.http.Http404` exception, depending on the values of
|
||||||
|
:attr:`~BaseDateListView.allow_empty` and
|
||||||
|
:attr:`~DateMixin.allow_future`.
|
||||||
|
|
||||||
DayMixin
|
DayMixin
|
||||||
--------
|
--------
|
||||||
|
|
||||||
.. class:: django.views.generic.dates.DayMixin
|
.. class:: DayMixin
|
||||||
|
|
||||||
A mixin that can be used to retrieve and provide parsing information for a
|
A mixin that can be used to retrieve and provide parsing information for a
|
||||||
day component of a date.
|
day component of a date.
|
||||||
|
@ -100,46 +120,50 @@ DayMixin
|
||||||
|
|
||||||
.. attribute:: day_format
|
.. attribute:: day_format
|
||||||
|
|
||||||
The :func:`~time.strftime` format to use when parsing the day. By default, this is
|
The :func:`~time.strftime` format to use when parsing the day. By
|
||||||
``'%d'``.
|
default, this is ``'%d'``.
|
||||||
|
|
||||||
.. attribute:: day
|
.. 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.
|
``None``, which means the day will be determined using other means.
|
||||||
|
|
||||||
.. method:: get_day_format()
|
.. method:: get_day_format()
|
||||||
|
|
||||||
Returns the :func:`~time.strftime` format to use when parsing the day. Returns
|
Returns the :func:`~time.strftime` format to use when parsing the day.
|
||||||
:attr:`DayMixin.day_format` by default.
|
Returns :attr:`~DayMixin.day_format` by default.
|
||||||
|
|
||||||
.. method:: get_day()
|
.. method:: get_day()
|
||||||
|
|
||||||
Returns the day for which this view will display data. Tries the
|
Returns the day for which this view will display data, as a string.
|
||||||
following sources, in order:
|
Tries the following sources, in order:
|
||||||
|
|
||||||
* The value of the :attr:`DayMixin.day` attribute.
|
* 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.
|
* The value of the `day` GET query argument.
|
||||||
|
|
||||||
Raises a 404 if no valid day specification can be found.
|
Raises a 404 if no valid day specification can be found.
|
||||||
|
|
||||||
.. method:: get_next_day(date)
|
.. method:: get_next_day(date)
|
||||||
|
|
||||||
Returns a date object containing the next day after the date provided.
|
Returns a date object containing the next valid day after the date
|
||||||
Returns ``None`` if mixed with a view that sets ``allow_future = False``,
|
provided. This function can also return ``None`` or raise an
|
||||||
and the next day is in the future. If ``allow_empty = False``, returns
|
:class:`~django.http.Http404` exception, depending on the values of
|
||||||
the next day that contains data.
|
:attr:`~BaseDateListView.allow_empty` and
|
||||||
|
:attr:`~DateMixin.allow_future`.
|
||||||
|
|
||||||
.. method:: get_prev_day(date)
|
.. method:: get_prev_day(date)
|
||||||
|
|
||||||
Returns a date object containing the previous day. If
|
Returns a date object containing the previous valid day. This function
|
||||||
``allow_empty = False``, returns the previous day that contained data.
|
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
|
WeekMixin
|
||||||
---------
|
---------
|
||||||
|
|
||||||
.. class:: django.views.generic.dates.WeekMixin
|
.. class:: WeekMixin
|
||||||
|
|
||||||
A mixin that can be used to retrieve and provide parsing information for a
|
A mixin that can be used to retrieve and provide parsing information for a
|
||||||
week component of a date.
|
week component of a date.
|
||||||
|
@ -148,23 +172,24 @@ WeekMixin
|
||||||
|
|
||||||
.. attribute:: week_format
|
.. attribute:: week_format
|
||||||
|
|
||||||
The :func:`~time.strftime` format to use when parsing the week. By default, this is
|
The :func:`~time.strftime` format to use when parsing the week. By
|
||||||
``'%U'``.
|
default, this is ``'%U'``, which means the week starts on Sunday. Set
|
||||||
|
it to ``'%W'`` if your week starts on Monday.
|
||||||
|
|
||||||
.. attribute:: week
|
.. 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.
|
``None``, which means the week will be determined using other means.
|
||||||
|
|
||||||
.. method:: get_week_format()
|
.. method:: get_week_format()
|
||||||
|
|
||||||
Returns the :func:`~time.strftime` format to use when parsing the week. Returns
|
Returns the :func:`~time.strftime` format to use when parsing the
|
||||||
:attr:`WeekMixin.week_format` by default.
|
week. Returns :attr:`~WeekMixin.week_format` by default.
|
||||||
|
|
||||||
.. method:: get_week()
|
.. method:: get_week()
|
||||||
|
|
||||||
Returns the week for which this view will display data. Tries the
|
Returns the week for which this view will display data, as a string.
|
||||||
following sources, in order:
|
Tries the following sources, in order:
|
||||||
|
|
||||||
* The value of the :attr:`WeekMixin.week` attribute.
|
* The value of the :attr:`WeekMixin.week` attribute.
|
||||||
* The value of the `week` argument captured in the URL pattern
|
* 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.
|
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
|
DateMixin
|
||||||
---------
|
---------
|
||||||
|
|
||||||
.. class:: django.views.generic.dates.DateMixin
|
.. class:: DateMixin
|
||||||
|
|
||||||
A mixin class providing common behavior for all date-based views.
|
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
|
The name of the ``DateField`` or ``DateTimeField`` in the
|
||||||
``QuerySet``'s model that the date-based archive should use to
|
``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
|
When :doc:`time zone support </topics/i18n/timezones>` is enabled and
|
||||||
``date_field`` is a ``DateTimeField``, dates are assumed to be in the
|
``date_field`` is a ``DateTimeField``, dates are assumed to be in the
|
||||||
|
@ -210,26 +250,26 @@ DateMixin
|
||||||
.. method:: get_date_field()
|
.. method:: get_date_field()
|
||||||
|
|
||||||
Returns the name of the field that contains the date data that this
|
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()
|
.. method:: get_allow_future()
|
||||||
|
|
||||||
Determine whether to include "future" objects on this page, where
|
Determine whether to include "future" objects on this page, where
|
||||||
"future" means objects in which the field specified in ``date_field``
|
"future" means objects in which the field specified in ``date_field``
|
||||||
is greater than the current date/time. Returns
|
is greater than the current date/time. Returns
|
||||||
:attr:`DateMixin.allow_future` by default.
|
:attr:`~DateMixin.allow_future` by default.
|
||||||
|
|
||||||
BaseDateListView
|
BaseDateListView
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
.. class:: django.views.generic.dates.BaseDateListView
|
.. class:: BaseDateListView
|
||||||
|
|
||||||
A base class that provides common behavior for all date-based views. There
|
A base class that provides common behavior for all date-based views. There
|
||||||
won't normally be a reason to instantiate
|
won't normally be a reason to instantiate
|
||||||
:class:`~django.views.generic.dates.BaseDateListView`; instantiate one of
|
:class:`~django.views.generic.dates.BaseDateListView`; instantiate one of
|
||||||
the subclasses instead.
|
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
|
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
|
``self.date_list`` will contain the list of dates for which data is
|
||||||
available.
|
available.
|
||||||
|
@ -245,10 +285,18 @@ BaseDateListView
|
||||||
|
|
||||||
A boolean specifying whether to display the page if no objects are
|
A boolean specifying whether to display the page if no objects are
|
||||||
available. If this is ``True`` and no objects are available, the view
|
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
|
will display an empty page instead of raising a 404.
|
||||||
is ``False``.
|
|
||||||
|
|
||||||
.. 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``,
|
Returns a 3-tuple containing (``date_list``, ``object_list``,
|
||||||
``extra_context``).
|
``extra_context``).
|
||||||
|
@ -265,10 +313,17 @@ BaseDateListView
|
||||||
``lookup``. Enforces any restrictions on the queryset, such as
|
``lookup``. Enforces any restrictions on the queryset, such as
|
||||||
``allow_empty`` and ``allow_future``.
|
``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
|
Returns the aggregation period for ``date_list``. Returns
|
||||||
``queryset`` contains entries. For example, ``get_date_list(qs,
|
:attr:`~BaseDateListView.date_list_period` by default.
|
||||||
'year')`` will return the list of years for which ``qs`` has entries.
|
|
||||||
See :meth:`~django.db.models.query.QuerySet.dates()` for the
|
.. method:: get_date_list(queryset, date_type=None)
|
||||||
ways that the ``date_type`` argument can be used.
|
|
||||||
|
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.
|
||||||
|
|
|
@ -86,7 +86,8 @@ MultipleObjectMixin
|
||||||
|
|
||||||
.. method:: get_queryset()
|
.. 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)
|
.. method:: paginate_queryset(queryset, page_size)
|
||||||
|
|
||||||
|
|
|
@ -9,16 +9,17 @@ ContextMixin
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
.. versionadded:: 1.5
|
||||||
|
|
||||||
**classpath**
|
|
||||||
|
|
||||||
``django.views.generic.base.ContextMixin``
|
|
||||||
|
|
||||||
**Methods**
|
**Methods**
|
||||||
|
|
||||||
.. method:: get_context_data(**kwargs)
|
.. method:: get_context_data(**kwargs)
|
||||||
|
|
||||||
Returns a dictionary representing the template context. The keyword
|
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
|
The template context of all class-based generic views include a
|
||||||
``view`` variable that points to the ``View`` instance.
|
``view`` variable that points to the ``View`` instance.
|
||||||
|
@ -42,7 +43,13 @@ TemplateResponseMixin
|
||||||
suitable context. The template to use is configurable and can be
|
suitable context. The template to use is configurable and can be
|
||||||
further customized by subclasses.
|
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
|
.. attribute:: response_class
|
||||||
|
|
||||||
|
@ -57,12 +64,14 @@ TemplateResponseMixin
|
||||||
instantiation, create a ``TemplateResponse`` subclass and assign it to
|
instantiation, create a ``TemplateResponse`` subclass and assign it to
|
||||||
``response_class``.
|
``response_class``.
|
||||||
|
|
||||||
|
**Methods**
|
||||||
|
|
||||||
.. method:: render_to_response(context, **response_kwargs)
|
.. method:: render_to_response(context, **response_kwargs)
|
||||||
|
|
||||||
Returns a ``self.response_class`` instance.
|
Returns a ``self.response_class`` instance.
|
||||||
|
|
||||||
If any keyword arguments are provided, they will be
|
If any keyword arguments are provided, they will be passed to the
|
||||||
passed to the constructor of the response class.
|
constructor of the response class.
|
||||||
|
|
||||||
Calls :meth:`~TemplateResponseMixin.get_template_names()` to obtain the
|
Calls :meth:`~TemplateResponseMixin.get_template_names()` to obtain the
|
||||||
list of template names that will be searched looking for an existent
|
list of template names that will be searched looking for an existent
|
||||||
|
|
|
@ -36,7 +36,7 @@ following model, which would represent entries in a Weblog::
|
||||||
class Entry(models.Model):
|
class Entry(models.Model):
|
||||||
title = models.CharField(maxlength=250)
|
title = models.CharField(maxlength=250)
|
||||||
body = models.TextField()
|
body = models.TextField()
|
||||||
pub_date = models.DateTimeField()
|
pub_date = models.DateField()
|
||||||
enable_comments = models.BooleanField()
|
enable_comments = models.BooleanField()
|
||||||
|
|
||||||
Now, suppose that we want the following steps to be applied whenever a
|
Now, suppose that we want the following steps to be applied whenever a
|
||||||
|
|
|
@ -80,7 +80,7 @@ geospatial libraries:
|
||||||
Program Description Required Supported Versions
|
Program Description Required Supported Versions
|
||||||
======================== ==================================== ================================ ==========================
|
======================== ==================================== ================================ ==========================
|
||||||
:ref:`GEOS <ref-geos>` Geometry Engine Open Source Yes 3.3, 3.2, 3.1, 3.0
|
: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:`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
|
: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
|
`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``)
|
geometries). Specifically, the C API library is called (e.g., ``libgeos_c.so``)
|
||||||
directly from Python using ctypes.
|
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::
|
archive::
|
||||||
|
|
||||||
$ wget http://download.osgeo.org/geos/geos-3.3.0.tar.bz2
|
$ wget http://download.osgeo.org/geos/geos-3.3.5.tar.bz2
|
||||||
$ tar xjf geos-3.3.0.tar.bz2
|
$ tar xjf geos-3.3.5.tar.bz2
|
||||||
|
|
||||||
Next, change into the directory where GEOS was unpacked, run the configure
|
Next, change into the directory where GEOS was unpacked, run the configure
|
||||||
script, compile, and install::
|
script, compile, and install::
|
||||||
|
|
||||||
$ cd geos-3.3.0
|
$ cd geos-3.3.5
|
||||||
$ ./configure
|
$ ./configure
|
||||||
$ make
|
$ make
|
||||||
$ sudo make install
|
$ sudo make install
|
||||||
|
@ -203,15 +203,15 @@ reference systems.
|
||||||
|
|
||||||
First, download the PROJ.4 source code and datum shifting files [#]_::
|
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-4.8.0.tar.gz
|
||||||
$ wget http://download.osgeo.org/proj/proj-datumgrid-1.5.zip
|
$ 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
|
Next, untar the source code archive, and extract the datum shifting files in the
|
||||||
``nad`` subdirectory. This must be done *prior* to configuration::
|
``nad`` subdirectory. This must be done *prior* to configuration::
|
||||||
|
|
||||||
$ tar xzf proj-4.7.0.tar.gz
|
$ tar xzf proj-4.8.0.tar.gz
|
||||||
$ cd proj-4.7.0/nad
|
$ cd proj-4.8.0/nad
|
||||||
$ unzip ../../proj-datumgrid-1.5.zip
|
$ tar xzf ../../proj-datumgrid-1.5.tar.gz
|
||||||
$ cd ..
|
$ cd ..
|
||||||
|
|
||||||
Finally, configure, make and install PROJ.4::
|
Finally, configure, make and install PROJ.4::
|
||||||
|
@ -239,9 +239,9 @@ installed prior to building PostGIS.
|
||||||
|
|
||||||
First download the source archive, and extract::
|
First download the source archive, and extract::
|
||||||
|
|
||||||
$ wget http://postgis.refractions.net/download/postgis-1.5.2.tar.gz
|
$ wget http://postgis.refractions.net/download/postgis-1.5.5.tar.gz
|
||||||
$ tar xzf postgis-1.5.2.tar.gz
|
$ tar xzf postgis-1.5.5.tar.gz
|
||||||
$ cd postgis-1.5.2
|
$ cd postgis-1.5.5
|
||||||
|
|
||||||
Next, configure, make and install PostGIS::
|
Next, configure, make and install PostGIS::
|
||||||
|
|
||||||
|
@ -959,15 +959,15 @@ Ubuntu & Debian GNU/Linux
|
||||||
Ubuntu
|
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
|
.. code-block:: bash
|
||||||
|
|
||||||
$ sudo apt-get install binutils gdal-bin libproj-dev postgresql-9.1-postgis \
|
$ sudo apt-get install binutils gdal-bin libproj-dev \
|
||||||
postgresql-server-dev-9.1 python-psycopg2
|
postgresql-9.1-postgis postgresql-server-dev-9.1 python-psycopg2
|
||||||
|
|
||||||
.. _ubuntu10:
|
.. _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.
|
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
|
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
|
.. code-block:: bash
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,9 @@ django.contrib.markup
|
||||||
.. module:: django.contrib.markup
|
.. module:: django.contrib.markup
|
||||||
:synopsis: A collection of template filters that implement common markup languages.
|
: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
|
Django provides template filters that implement the following markup
|
||||||
languages:
|
languages:
|
||||||
|
|
||||||
|
|
|
@ -5,14 +5,16 @@ The messages framework
|
||||||
.. module:: django.contrib.messages
|
.. module:: django.contrib.messages
|
||||||
:synopsis: Provides cookie- and session-based temporary message storage.
|
:synopsis: Provides cookie- and session-based temporary message storage.
|
||||||
|
|
||||||
Quite commonly in web applications, you may need to display a one-time
|
Quite commonly in web applications, you need to display a one-time
|
||||||
notification message (also know as "flash message") to the user after
|
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
|
processing a form or some other types of user input.
|
||||||
full support for cookie- and session-based messaging, for both anonymous and
|
|
||||||
authenticated users. The messages framework allows you to temporarily store
|
For this, Django provides full support for cookie- and session-based
|
||||||
messages in one request and retrieve them for display in a subsequent request
|
messaging, for both anonymous and authenticated users. The messages framework
|
||||||
(usually the next one). Every message is tagged with a specific ``level`` that
|
allows you to temporarily store messages in one request and retrieve them for
|
||||||
determines its priority (e.g., ``info``, ``warning``, or ``error``).
|
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
|
Enabling messages
|
||||||
=================
|
=================
|
||||||
|
@ -20,32 +22,27 @@ Enabling messages
|
||||||
Messages are implemented through a :doc:`middleware </ref/middleware>`
|
Messages are implemented through a :doc:`middleware </ref/middleware>`
|
||||||
class and corresponding :doc:`context processor </ref/templates/api>`.
|
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
|
* ``'django.contrib.messages'`` is in :setting:`INSTALLED_APPS`.
|
||||||
it contains ``'django.contrib.messages.middleware.MessageMiddleware'``.
|
|
||||||
|
|
||||||
If you are using a :ref:`storage backend <message-storage-backends>` that
|
* :setting:`MIDDLEWARE_CLASSES` contains
|
||||||
relies on :doc:`sessions </topics/http/sessions>` (the default),
|
``'django.contrib.sessions.middleware.SessionMiddleware'`` and
|
||||||
``'django.contrib.sessions.middleware.SessionMiddleware'`` must be
|
``'django.contrib.messages.middleware.MessageMiddleware'``.
|
||||||
enabled and appear before ``MessageMiddleware`` in your
|
|
||||||
|
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`.
|
:setting:`MIDDLEWARE_CLASSES`.
|
||||||
|
|
||||||
* Edit the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting and make sure
|
* :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains
|
||||||
it contains ``'django.contrib.messages.context_processors.messages'``.
|
``'django.contrib.messages.context_processors.messages'``.
|
||||||
|
|
||||||
* Add ``'django.contrib.messages'`` to your :setting:`INSTALLED_APPS`
|
If you don't want to use messages, you can remove
|
||||||
setting
|
``'django.contrib.messages'`` from your :setting:`INSTALLED_APPS`, the
|
||||||
|
``MessageMiddleware`` line from :setting:`MIDDLEWARE_CLASSES`, and the
|
||||||
The default ``settings.py`` created by ``django-admin.py startproject`` has
|
``messages`` context processor from :setting:`TEMPLATE_CONTEXT_PROCESSORS`.
|
||||||
``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`.
|
|
||||||
|
|
||||||
Configuring the message engine
|
Configuring the message engine
|
||||||
==============================
|
==============================
|
||||||
|
@ -56,34 +53,35 @@ Storage backends
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
The messages framework can use different backends to store temporary messages.
|
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'``
|
.. class:: django.contrib.messages.storage.cookie.CookieStorage
|
||||||
This class stores all messages inside of the request's session. It
|
|
||||||
requires Django's ``contrib.sessions`` application.
|
|
||||||
|
|
||||||
``'django.contrib.messages.storage.cookie.CookieStorage'``
|
|
||||||
This class stores the message data in a cookie (signed with a secret hash
|
This class stores the message data in a cookie (signed with a secret hash
|
||||||
to prevent manipulation) to persist notifications across requests. Old
|
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'``
|
.. class:: django.contrib.messages.storage.fallback.FallbackStorage
|
||||||
This is the default storage class.
|
|
||||||
|
|
||||||
This class first uses CookieStorage for all messages, falling back to using
|
This class first uses ``CookieStorage``, and falls back to using
|
||||||
SessionStorage for the messages that could not fit in a single cookie.
|
``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
|
This behavior avoids writing to the session whenever possible. It should
|
||||||
``contrib.sessions`` application.
|
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
|
To write your own storage class, subclass the ``BaseStorage`` class in
|
||||||
``django.contrib.messages.storage.base`` and implement the ``_get`` and
|
``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
|
messages by type so they can be filtered or displayed differently in views and
|
||||||
templates.
|
templates.
|
||||||
|
|
||||||
The built-in levels (which can be imported from ``django.contrib.messages``
|
The built-in levels, which can be imported from ``django.contrib.messages``
|
||||||
directly) are:
|
directly, are:
|
||||||
|
|
||||||
=========== ========
|
=========== ========
|
||||||
Constant Purpose
|
Constant Purpose
|
||||||
|
|
|
@ -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::
|
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'
|
'%Y-%m-%d', # '2006-10-25'
|
||||||
'%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
|
'%m/%d/%Y', # '10/25/2006'
|
||||||
'%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006'
|
'%m/%d/%y', # '10/25/06'
|
||||||
'%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
|
|
||||||
'%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
|
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``
|
``DateTimeField``
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
|
@ -70,9 +70,8 @@ overridden:
|
||||||
formfield-specific piece of validation and, possibly,
|
formfield-specific piece of validation and, possibly,
|
||||||
cleaning/normalizing the data.
|
cleaning/normalizing the data.
|
||||||
|
|
||||||
Just like the general field ``clean()`` method, above, this method
|
This method should return the cleaned value obtained from cleaned_data,
|
||||||
should return the cleaned data, regardless of whether it changed
|
regardless of whether it changed anything or not.
|
||||||
anything or not.
|
|
||||||
|
|
||||||
* The Form subclass's ``clean()`` method. This method can perform
|
* The Form subclass's ``clean()`` method. This method can perform
|
||||||
any validation that requires access to multiple fields from the form at
|
any validation that requires access to multiple fields from the form at
|
||||||
|
|
|
@ -310,6 +310,10 @@ commonly used groups of widgets:
|
||||||
A callable that takes the value of the CheckBoxInput and returns
|
A callable that takes the value of the CheckBoxInput and returns
|
||||||
``True`` if the checkbox should be checked for that value.
|
``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``
|
``Select``
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -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
|
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.
|
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``
|
``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
|
this with functions from the Python ``datetime`` module to limit choices of
|
||||||
objects by date. For example::
|
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
|
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`
|
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,
|
object for more :ref:`complex queries <complex-lookups-with-q>`. However,
|
||||||
|
|
|
@ -135,7 +135,7 @@ access to more than a single field::
|
||||||
raise ValidationError('Draft entries may not have a publication date.')
|
raise ValidationError('Draft entries may not have a publication date.')
|
||||||
# Set the pub_date for published items if it hasn't been set already.
|
# Set the pub_date for published items if it hasn't been set already.
|
||||||
if self.status == 'published' and self.pub_date is None:
|
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
|
Any :exc:`~django.core.exceptions.ValidationError` exceptions raised by
|
||||||
``Model.clean()`` will be stored in a special key error dictionary key,
|
``Model.clean()`` will be stored in a special key error dictionary key,
|
||||||
|
|
|
@ -83,9 +83,10 @@ Django quotes column and table names behind the scenes.
|
||||||
|
|
||||||
.. attribute:: Options.get_latest_by
|
.. attribute:: Options.get_latest_by
|
||||||
|
|
||||||
The name of a :class:`DateField` or :class:`DateTimeField` in the model.
|
The name of an orderable field in the model, typically a :class:`DateField`,
|
||||||
This specifies the default field to use in your model :class:`Manager`'s
|
:class:`DateTimeField`, or :class:`IntegerField`. This specifies the default
|
||||||
:class:`~QuerySet.latest` method.
|
field to use in your model :class:`Manager`'s :class:`~QuerySet.latest`
|
||||||
|
method.
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,9 @@ You can evaluate a ``QuerySet`` in the following ways:
|
||||||
for e in Entry.objects.all():
|
for e in Entry.objects.all():
|
||||||
print(e.headline)
|
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
|
* **Slicing.** As explained in :ref:`limiting-querysets`, a ``QuerySet`` can
|
||||||
be sliced, using Python's array-slicing syntax. Slicing an unevaluated
|
be sliced, using Python's array-slicing syntax. Slicing an unevaluated
|
||||||
``QuerySet`` usually returns another unevaluated ``QuerySet``, but Django
|
``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
|
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
|
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:
|
.. _pickling QuerySets:
|
||||||
|
|
||||||
|
@ -1523,9 +1526,40 @@ exists
|
||||||
|
|
||||||
Returns ``True`` if the :class:`.QuerySet` contains any results, and ``False``
|
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
|
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
|
possible, but it *does* execute nearly the same query as a normal
|
||||||
:meth:`.QuerySet.exists` is faster than ``bool(some_query_set)``, but not by
|
:class:`.QuerySet` query.
|
||||||
a large degree. If ``some_query_set`` has not yet been evaluated, but you know
|
|
||||||
|
: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
|
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
|
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
|
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,
|
You can use ``range`` anywhere you can use ``BETWEEN`` in SQL — for dates,
|
||||||
numbers and even characters.
|
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
|
.. fieldlookup:: year
|
||||||
|
|
||||||
year
|
year
|
||||||
|
@ -1958,7 +2003,7 @@ Example::
|
||||||
|
|
||||||
SQL equivalent::
|
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.)
|
(The exact SQL syntax varies for each database engine.)
|
||||||
|
|
||||||
|
|
|
@ -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`
|
This is used by the :func:`~django.contrib.auth.decorators.login_required`
|
||||||
decorator, for example.
|
decorator, for example.
|
||||||
|
|
||||||
.. _`note on LOGIN_REDIRECT_URL setting`:
|
.. versionchanged:: 1.5
|
||||||
|
|
||||||
.. note::
|
This setting now also accepts view function names and
|
||||||
You can use :func:`~django.core.urlresolvers.reverse_lazy` to reference
|
:ref:`named URL patterns <naming-url-patterns>` which can be used to reduce
|
||||||
URLs by their name instead of providing a hardcoded value. Assuming a
|
configuration duplication since you no longer have to define the URL in two
|
||||||
``urls.py`` with an URLpattern named ``home``::
|
places (``settings`` and URLconf).
|
||||||
|
For backward compatibility reasons the default remains unchanged.
|
||||||
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`.
|
|
||||||
|
|
||||||
.. setting:: LOGIN_URL
|
.. setting:: LOGIN_URL
|
||||||
|
|
||||||
|
@ -1343,8 +1331,13 @@ Default: ``'/accounts/login/'``
|
||||||
The URL where requests are redirected for login, especially when using the
|
The URL where requests are redirected for login, especially when using the
|
||||||
:func:`~django.contrib.auth.decorators.login_required` decorator.
|
:func:`~django.contrib.auth.decorators.login_required` decorator.
|
||||||
|
|
||||||
.. note::
|
.. versionchanged:: 1.5
|
||||||
See the `note on LOGIN_REDIRECT_URL setting`_
|
|
||||||
|
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
|
.. setting:: LOGOUT_URL
|
||||||
|
|
||||||
|
|
|
@ -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
|
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.
|
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
|
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.
|
The :class:`~django.utils.encoding.StrAndUnicode` mix-in has been deprecated.
|
||||||
Define a ``__str__`` method and apply the
|
Define a ``__str__`` method and apply the
|
||||||
:func:`~django.utils.encoding.python_2_unicode_compatible` decorator instead.
|
: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.
|
||||||
|
|
|
@ -951,6 +951,13 @@ The login_required decorator
|
||||||
|
|
||||||
(r'^accounts/login/$', 'django.contrib.auth.views.login'),
|
(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])
|
.. function:: views.login(request, [template_name, redirect_field_name, authentication_form])
|
||||||
|
|
||||||
**URL name:** ``login``
|
**URL name:** ``login``
|
||||||
|
|
|
@ -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
|
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>`.
|
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
|
Decorating class-based views
|
||||||
============================
|
============================
|
||||||
|
|
||||||
|
|
|
@ -42,8 +42,8 @@ Create a few Reporters::
|
||||||
|
|
||||||
Create an Article::
|
Create an Article::
|
||||||
|
|
||||||
>>> from datetime import datetime
|
>>> from datetime import date
|
||||||
>>> a = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter=r)
|
>>> a = Article(id=None, headline="This is a test", pub_date=date(2005, 7, 27), reporter=r)
|
||||||
>>> a.save()
|
>>> a.save()
|
||||||
|
|
||||||
>>> a.reporter.id
|
>>> a.reporter.id
|
||||||
|
@ -65,7 +65,7 @@ database, which always returns unicode strings)::
|
||||||
|
|
||||||
Create an Article via the Reporter object::
|
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
|
>>> new_article
|
||||||
<Article: John's second story>
|
<Article: John's second story>
|
||||||
>>> new_article.reporter
|
>>> 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::
|
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)
|
>>> r.article_set.add(new_article2)
|
||||||
>>> new_article2.reporter
|
>>> new_article2.reporter
|
||||||
<Reporter: John Smith>
|
<Reporter: John Smith>
|
||||||
|
|
|
@ -201,73 +201,129 @@ An example
|
||||||
write to propagate to the slaves). It also doesn't consider the
|
write to propagate to the slaves). It also doesn't consider the
|
||||||
interaction of transactions with the database utilization strategy.
|
interaction of transactions with the database utilization strategy.
|
||||||
|
|
||||||
So - what does this mean in practice? Say you want ``myapp`` to
|
So - what does this mean in practice? Let's consider another sample
|
||||||
exist on the ``other`` database, and you want all other models in a
|
configuration. This one will have several databases: one for the
|
||||||
master/slave relationship between the databases ``master``, ``slave1`` and
|
``auth`` application, and all other apps using a master/slave setup
|
||||||
``slave2``. To implement this, you would need 2 routers::
|
with two read slaves. Here are the settings specifying these
|
||||||
|
databases::
|
||||||
|
|
||||||
class MyAppRouter(object):
|
DATABASES = {
|
||||||
"""A router to control all database operations on models in
|
'auth_db': {
|
||||||
the myapp application"""
|
'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):
|
def db_for_read(self, model, **hints):
|
||||||
"Point all operations on myapp models to 'other'"
|
"""
|
||||||
if model._meta.app_label == 'myapp':
|
Attempts to read auth models go to auth_db.
|
||||||
return 'other'
|
"""
|
||||||
|
if model._meta.app_label == 'auth':
|
||||||
|
return 'auth_db'
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def db_for_write(self, model, **hints):
|
def db_for_write(self, model, **hints):
|
||||||
"Point all operations on myapp models to 'other'"
|
"""
|
||||||
if model._meta.app_label == 'myapp':
|
Attempts to write auth models go to auth_db.
|
||||||
return 'other'
|
"""
|
||||||
|
if model._meta.app_label == 'auth':
|
||||||
|
return 'auth_db'
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def allow_relation(self, obj1, obj2, **hints):
|
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':
|
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 True
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def allow_syncdb(self, db, model):
|
def allow_syncdb(self, db, model):
|
||||||
"Make sure the myapp app only appears on the 'other' db"
|
"""
|
||||||
if db == 'other':
|
Make sure the auth app only appears in the 'auth_db'
|
||||||
return model._meta.app_label == 'myapp'
|
database.
|
||||||
elif model._meta.app_label == 'myapp':
|
"""
|
||||||
|
if db == 'auth_db':
|
||||||
|
return model._meta.app_label == 'auth'
|
||||||
|
elif model._meta.app_label == 'auth':
|
||||||
return False
|
return False
|
||||||
return None
|
return None
|
||||||
|
|
||||||
class MasterSlaveRouter(object):
|
And we also want a router that sends all other apps to the
|
||||||
"""A router that sets up a simple master/slave configuration"""
|
master/slave configuration, and randomly chooses a slave to read
|
||||||
|
from::
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
class MasterSlaveRouter(object):
|
||||||
def db_for_read(self, model, **hints):
|
def db_for_read(self, model, **hints):
|
||||||
"Point all read operations to a random slave"
|
"""
|
||||||
|
Reads go to a randomly-chosen slave.
|
||||||
|
"""
|
||||||
return random.choice(['slave1', 'slave2'])
|
return random.choice(['slave1', 'slave2'])
|
||||||
|
|
||||||
def db_for_write(self, model, **hints):
|
def db_for_write(self, model, **hints):
|
||||||
"Point all write operations to the master"
|
"""
|
||||||
|
Writes always go to master.
|
||||||
|
"""
|
||||||
return 'master'
|
return 'master'
|
||||||
|
|
||||||
def allow_relation(self, obj1, obj2, **hints):
|
def allow_relation(self, obj1, obj2, **hints):
|
||||||
"Allow any relation between two objects in the db pool"
|
"""
|
||||||
|
Relations between objects are allowed if both objects are
|
||||||
|
in the master/slave pool.
|
||||||
|
"""
|
||||||
db_list = ('master', 'slave1', 'slave2')
|
db_list = ('master', 'slave1', 'slave2')
|
||||||
if obj1._state.db in db_list and obj2._state.db in db_list:
|
if obj1.state.db in db_list and obj2.state.db in db_list:
|
||||||
return True
|
return True
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def allow_syncdb(self, db, model):
|
def allow_syncdb(self, db, model):
|
||||||
"Explicitly put all models on all databases."
|
"""
|
||||||
|
All non-auth models end up in this pool.
|
||||||
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
Then, in your settings file, add the following (substituting ``path.to.`` with
|
Finally, in the settings file, we add the following (substituting
|
||||||
the actual python path to the module where you define the routers)::
|
``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
|
The order in which routers are processed is significant. Routers will
|
||||||
be queried in the order the are listed in the
|
be queried in the order the are listed in the
|
||||||
:setting:`DATABASE_ROUTERS` setting . In this example, the
|
:setting:`DATABASE_ROUTERS` setting . In this example, the
|
||||||
``MyAppRouter`` is processed before the ``MasterSlaveRouter``, and as a
|
``AuthRouter`` is processed before the ``MasterSlaveRouter``, and as a
|
||||||
result, decisions concerning the models in ``myapp`` are processed
|
result, decisions concerning the models in ``auth`` are processed
|
||||||
before any other decision is made. If the :setting:`DATABASE_ROUTERS`
|
before any other decision is made. If the :setting:`DATABASE_ROUTERS`
|
||||||
setting listed the two routers in the other order,
|
setting listed the two routers in the other order,
|
||||||
``MasterSlaveRouter.allow_syncdb()`` would be processed first. The
|
``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::
|
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 = User.objects.get(username='fred')
|
||||||
>>> fred.first_name = 'Frederick'
|
>>> fred.first_name = 'Frederick'
|
||||||
|
|
||||||
>>> # This save will also be directed to 'credentials'
|
>>> # This save will also be directed to 'auth_db'
|
||||||
>>> fred.save()
|
>>> fred.save()
|
||||||
|
|
||||||
>>> # These retrieval will be randomly allocated to a slave database
|
>>> # These retrieval will be randomly allocated to a slave database
|
||||||
|
|
|
@ -35,8 +35,8 @@ models, which comprise a Weblog application:
|
||||||
blog = models.ForeignKey(Blog)
|
blog = models.ForeignKey(Blog)
|
||||||
headline = models.CharField(max_length=255)
|
headline = models.CharField(max_length=255)
|
||||||
body_text = models.TextField()
|
body_text = models.TextField()
|
||||||
pub_date = models.DateTimeField()
|
pub_date = models.DateField()
|
||||||
mod_date = models.DateTimeField()
|
mod_date = models.DateField()
|
||||||
authors = models.ManyToManyField(Author)
|
authors = models.ManyToManyField(Author)
|
||||||
n_comments = models.IntegerField()
|
n_comments = models.IntegerField()
|
||||||
n_pingbacks = models.IntegerField()
|
n_pingbacks = models.IntegerField()
|
||||||
|
@ -233,7 +233,7 @@ refinements together. For example::
|
||||||
>>> Entry.objects.filter(
|
>>> Entry.objects.filter(
|
||||||
... headline__startswith='What'
|
... headline__startswith='What'
|
||||||
... ).exclude(
|
... ).exclude(
|
||||||
... pub_date__gte=datetime.now()
|
... pub_date__gte=datetime.date.today()
|
||||||
... ).filter(
|
... ).filter(
|
||||||
... pub_date__gte=datetime(2005, 1, 30)
|
... pub_date__gte=datetime(2005, 1, 30)
|
||||||
... )
|
... )
|
||||||
|
@ -258,8 +258,8 @@ stored, used and reused.
|
||||||
Example::
|
Example::
|
||||||
|
|
||||||
>> q1 = Entry.objects.filter(headline__startswith="What")
|
>> q1 = Entry.objects.filter(headline__startswith="What")
|
||||||
>> q2 = q1.exclude(pub_date__gte=datetime.now())
|
>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
|
||||||
>> q3 = q1.filter(pub_date__gte=datetime.now())
|
>> q3 = q1.filter(pub_date__gte=datetime.date.today())
|
||||||
|
|
||||||
These three ``QuerySets`` are separate. The first is a base
|
These three ``QuerySets`` are separate. The first is a base
|
||||||
:class:`~django.db.models.query.QuerySet` containing all entries that contain a
|
: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::
|
*evaluated*. Take a look at this example::
|
||||||
|
|
||||||
>>> q = Entry.objects.filter(headline__startswith="What")
|
>>> 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")
|
>>> q = q.exclude(body_text__icontains="food")
|
||||||
>>> print(q)
|
>>> 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
|
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
|
: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
|
``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
|
:meth:`~django.db.models.Model.save`), or honor the
|
||||||
:class:`~django.db.models.query.QuerySet` and make sure that the
|
:attr:`~django.db.models.DateField.auto_now` field option.
|
||||||
:meth:`~django.db.models.Model.save` method is called on each instance, you
|
If you want to save every item in a :class:`~django.db.models.query.QuerySet`
|
||||||
don't need any special function to handle that. Just loop over them and call
|
and make sure that the :meth:`~django.db.models.Model.save` method is called on
|
||||||
:meth:`~django.db.models.Model.save`::
|
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:
|
for item in my_queryset:
|
||||||
item.save()
|
item.save()
|
||||||
|
|
|
@ -311,18 +311,18 @@ model fields:
|
||||||
to exclude from the form.
|
to exclude from the form.
|
||||||
|
|
||||||
For example, if you want a form for the ``Author`` model (defined
|
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::
|
specify ``fields`` or ``exclude`` like this::
|
||||||
|
|
||||||
class PartialAuthorForm(ModelForm):
|
class PartialAuthorForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Author
|
model = Author
|
||||||
fields = ('name', 'title')
|
fields = ('name', 'birth_date')
|
||||||
|
|
||||||
class PartialAuthorForm(ModelForm):
|
class PartialAuthorForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Author
|
model = Author
|
||||||
exclude = ('birth_date',)
|
exclude = ('title',)
|
||||||
|
|
||||||
Since the Author model has only 3 fields, 'name', 'title', and
|
Since the Author model has only 3 fields, 'name', 'title', and
|
||||||
'birth_date', the forms above will contain exactly the same fields.
|
'birth_date', the forms above will contain exactly the same fields.
|
||||||
|
|
|
@ -20,18 +20,18 @@ Overview
|
||||||
========
|
========
|
||||||
|
|
||||||
To design URLs for an app, you create a Python module informally called a
|
To design URLs for an app, you create a Python module informally called a
|
||||||
**URLconf** (URL configuration). This module is pure Python code and
|
**URLconf** (URL configuration). This module is pure Python code and is a
|
||||||
is a simple mapping between URL patterns (as simple regular expressions) to
|
simple mapping between URL patterns (simple regular expressions) to Python
|
||||||
Python callback functions (your views).
|
functions (your views).
|
||||||
|
|
||||||
This mapping can be as short or as long as needed. It can reference other
|
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
|
mappings. And, because it's pure Python code, it can be constructed
|
||||||
dynamically.
|
dynamically.
|
||||||
|
|
||||||
.. versionadded:: 1.4
|
.. versionadded:: 1.4
|
||||||
Django also allows to translate URLs according to the active language.
|
Django also provides a way to translate URLs according to the active
|
||||||
This process is described in the
|
language. See the :ref:`internationalization documentation
|
||||||
:ref:`internationalization docs <url-internationalization>`.
|
<url-internationalization>` for more information.
|
||||||
|
|
||||||
.. _how-django-processes-a-request:
|
.. _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
|
Here's the algorithm the URLconf parser follows, with respect to named groups
|
||||||
vs. non-named groups in a regular expression:
|
vs. non-named groups in a regular expression:
|
||||||
|
|
||||||
If there are any named arguments, it will use those, ignoring non-named arguments.
|
1. If there are any named arguments, it will use those, ignoring non-named
|
||||||
Otherwise, it will pass all non-named arguments as positional arguments.
|
arguments.
|
||||||
|
|
||||||
In both cases, it will pass any extra keyword arguments as keyword arguments.
|
2. Otherwise, it will pass all non-named arguments as positional arguments.
|
||||||
See "Passing extra options to view functions" below.
|
|
||||||
|
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
|
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
|
methods -- ``POST``, ``GET``, ``HEAD``, etc. -- will be routed to the same
|
||||||
function for the same URL.
|
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
|
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::
|
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
|
The ``optional_dictionary`` and ``optional_name`` parameters are described in
|
||||||
`Passing extra options to view functions`_ below.)
|
`Passing extra options to view functions`_ below.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Because `patterns()` is a function call, it accepts a maximum of 255
|
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
|
See the documentation about :ref:`the 500 (HTTP Internal Server Error) view
|
||||||
<http_internal_server_error_view>` for more information.
|
<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
|
The view prefix
|
||||||
===============
|
===============
|
||||||
|
|
||||||
|
|
|
@ -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,
|
that sets a user's language preference and redirects to a given URL or, by default,
|
||||||
back to the previous page.
|
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::
|
Activate this view by adding the following line to your URLconf::
|
||||||
|
|
||||||
(r'^i18n/', include('django.conf.urls.i18n')),
|
(r'^i18n/', include('django.conf.urls.i18n')),
|
||||||
|
|
|
@ -9,10 +9,8 @@ Install Python
|
||||||
|
|
||||||
Being a Python Web framework, Django requires 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
|
It works with any Python version from 2.6.5 to 2.7. It also features
|
||||||
incompatibilities in Python 3.0, Django does not currently work with
|
experimental support for versions 3.2 and 3.3.
|
||||||
Python 3.0; see :doc:`the Django FAQ </faq/install>` for more
|
|
||||||
information on supported Python versions and the 3.0 transition).
|
|
||||||
|
|
||||||
Get Python at http://www.python.org. If you're running Linux or Mac OS X, you
|
Get Python at http://www.python.org. If you're running Linux or Mac OS X, you
|
||||||
probably already have it installed.
|
probably already have it installed.
|
||||||
|
|
|
@ -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
|
to stay compatible with Python 2. But authors of pluggable applications are
|
||||||
encouraged to use the same porting strategy as Django itself.
|
encouraged to use the same porting strategy as Django itself.
|
||||||
|
|
||||||
Writing compatible code is much easier if you target Python ≥ 2.6. You will
|
Writing compatible code is much easier if you target Python ≥ 2.6. Django 1.5
|
||||||
most likely take advantage of the compatibility functions introduced in Django
|
introduces compatibility tools such as :mod:`django.utils.six`. For
|
||||||
1.5, like :mod:`django.utils.six`, so your application will also require
|
convenience, forwards-compatible aliases were introduced in Django 1.4.2. If
|
||||||
Django ≥ 1.5.
|
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
|
Obviously, writing compatible source code adds some overhead, and that can
|
||||||
cause frustration. Django's developers have found that attempting to write
|
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
|
For backwards compatibility, the old names still work on Python 2. Under
|
||||||
Python 3, ``smart_str`` is an alias for ``smart_text``.
|
Python 3, ``smart_str`` is an alias for ``smart_text``.
|
||||||
|
|
||||||
|
For forwards compatibility, the new names work as of Django 1.4.2.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
:mod:`django.utils.encoding` was deeply refactored in Django 1.5 to
|
: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``
|
Python 3, ``EscapeString`` and ``SafeString`` are aliases for ``EscapeText``
|
||||||
and ``SafeText`` respectively.
|
and ``SafeText`` respectively.
|
||||||
|
|
||||||
|
For forwards compatibility, the new names work as of Django 1.4.2.
|
||||||
|
|
||||||
:meth:`__str__` and :meth:`__unicode__` methods
|
: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.
|
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
|
Finally, note that :meth:`__repr__` must return a :class:`str` on all versions
|
||||||
of Python.
|
of Python.
|
||||||
|
|
||||||
|
@ -317,7 +324,8 @@ Writing compatible code with six
|
||||||
six_ is the canonical compatibility library for supporting Python 2 and 3 in
|
six_ is the canonical compatibility library for supporting Python 2 and 3 in
|
||||||
a single codebase. Read its documentation!
|
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.
|
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
|
2 and :meth:`~django.utils.datastructures.MultiValueDict.lists()` on
|
||||||
Python 3.
|
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
|
In addition to six' defaults moves, Django's version provides ``thread`` as
|
||||||
``_thread`` and ``dummy_thread`` as ``_dummy_thread``.
|
``_thread`` and ``dummy_thread`` as ``_dummy_thread``.
|
||||||
|
|
|
@ -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
|
form. The malicious user would have to know the nonce, which is user specific
|
||||||
(using a cookie).
|
(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
|
Be very careful with marking views with the ``csrf_exempt`` decorator unless
|
||||||
it is absolutely necessary.
|
it is absolutely necessary.
|
||||||
|
|
||||||
|
|
||||||
SQL injection protection
|
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
|
its pages wrapped in a frame by third party sites, or only needs to allow that
|
||||||
for a small section of the site.
|
for a small section of the site.
|
||||||
|
|
||||||
|
.. _security-recommendation-ssl:
|
||||||
|
|
||||||
SSL/HTTPS
|
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
|
any POST data being accepted over HTTP (which will be fine if you are
|
||||||
redirecting all HTTP traffic to HTTPS).
|
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
|
Host headers and virtual hosting
|
||||||
================================
|
================================
|
||||||
|
@ -167,6 +185,8 @@ recommend you ensure your Web server is configured such that:
|
||||||
Additionally, as of 1.3.1, Django requires you to explicitly enable support for
|
Additionally, as of 1.3.1, Django requires you to explicitly enable support for
|
||||||
the ``X-Forwarded-Host`` header if your configuration requires it.
|
the ``X-Forwarded-Host`` header if your configuration requires it.
|
||||||
|
|
||||||
|
.. _additional-security-topics:
|
||||||
|
|
||||||
Additional security topics
|
Additional security topics
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ from datetime import datetime
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db.models.fields import Field, FieldDoesNotExist
|
from django.db.models.fields import Field, FieldDoesNotExist
|
||||||
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
|
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 django.utils.translation import ugettext_lazy
|
||||||
|
|
||||||
from .models import Article
|
from .models import Article
|
||||||
|
@ -82,7 +82,7 @@ class ModelTest(TestCase):
|
||||||
|
|
||||||
# Django raises an Article.DoesNotExist exception for get() if the
|
# Django raises an Article.DoesNotExist exception for get() if the
|
||||||
# parameters don't match any object.
|
# parameters don't match any object.
|
||||||
self.assertRaisesRegexp(
|
six.assertRaisesRegex(self,
|
||||||
ObjectDoesNotExist,
|
ObjectDoesNotExist,
|
||||||
"Article matching query does not exist. Lookup parameters were "
|
"Article matching query does not exist. Lookup parameters were "
|
||||||
"{'id__exact': 2000}",
|
"{'id__exact': 2000}",
|
||||||
|
@ -91,14 +91,14 @@ class ModelTest(TestCase):
|
||||||
)
|
)
|
||||||
# To avoid dict-ordering related errors check only one lookup
|
# To avoid dict-ordering related errors check only one lookup
|
||||||
# in single assert.
|
# in single assert.
|
||||||
self.assertRaisesRegexp(
|
six.assertRaisesRegex(self,
|
||||||
ObjectDoesNotExist,
|
ObjectDoesNotExist,
|
||||||
".*'pub_date__year': 2005.*",
|
".*'pub_date__year': 2005.*",
|
||||||
Article.objects.get,
|
Article.objects.get,
|
||||||
pub_date__year=2005,
|
pub_date__year=2005,
|
||||||
pub_date__month=8,
|
pub_date__month=8,
|
||||||
)
|
)
|
||||||
self.assertRaisesRegexp(
|
six.assertRaisesRegex(self,
|
||||||
ObjectDoesNotExist,
|
ObjectDoesNotExist,
|
||||||
".*'pub_date__month': 8.*",
|
".*'pub_date__month': 8.*",
|
||||||
Article.objects.get,
|
Article.objects.get,
|
||||||
|
@ -106,7 +106,7 @@ class ModelTest(TestCase):
|
||||||
pub_date__month=8,
|
pub_date__month=8,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertRaisesRegexp(
|
six.assertRaisesRegex(self,
|
||||||
ObjectDoesNotExist,
|
ObjectDoesNotExist,
|
||||||
"Article matching query does not exist. Lookup parameters were "
|
"Article matching query does not exist. Lookup parameters were "
|
||||||
"{'pub_date__week_day': 6}",
|
"{'pub_date__week_day': 6}",
|
||||||
|
@ -168,7 +168,7 @@ class ModelTest(TestCase):
|
||||||
self.assertEqual(a4.headline, 'Fourth article')
|
self.assertEqual(a4.headline, 'Fourth article')
|
||||||
|
|
||||||
# Don't use invalid keyword arguments.
|
# Don't use invalid keyword arguments.
|
||||||
self.assertRaisesRegexp(
|
six.assertRaisesRegex(self,
|
||||||
TypeError,
|
TypeError,
|
||||||
"'foo' is an invalid keyword argument for this function",
|
"'foo' is an invalid keyword argument for this function",
|
||||||
Article,
|
Article,
|
||||||
|
@ -259,13 +259,13 @@ class ModelTest(TestCase):
|
||||||
"datetime.datetime(2005, 7, 28, 0, 0)"])
|
"datetime.datetime(2005, 7, 28, 0, 0)"])
|
||||||
|
|
||||||
# dates() requires valid arguments.
|
# dates() requires valid arguments.
|
||||||
self.assertRaisesRegexp(
|
six.assertRaisesRegex(self,
|
||||||
TypeError,
|
TypeError,
|
||||||
"dates\(\) takes at least 3 arguments \(1 given\)",
|
"dates\(\) takes at least 3 arguments \(1 given\)",
|
||||||
Article.objects.dates,
|
Article.objects.dates,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertRaisesRegexp(
|
six.assertRaisesRegex(self,
|
||||||
FieldDoesNotExist,
|
FieldDoesNotExist,
|
||||||
"Article has no field named 'invalid_field'",
|
"Article has no field named 'invalid_field'",
|
||||||
Article.objects.dates,
|
Article.objects.dates,
|
||||||
|
@ -273,7 +273,7 @@ class ModelTest(TestCase):
|
||||||
"year",
|
"year",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertRaisesRegexp(
|
six.assertRaisesRegex(self,
|
||||||
AssertionError,
|
AssertionError,
|
||||||
"'kind' must be one of 'year', 'month' or 'day'.",
|
"'kind' must be one of 'year', 'month' or 'day'.",
|
||||||
Article.objects.dates,
|
Article.objects.dates,
|
||||||
|
@ -281,7 +281,7 @@ class ModelTest(TestCase):
|
||||||
"bad_kind",
|
"bad_kind",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertRaisesRegexp(
|
six.assertRaisesRegex(self,
|
||||||
AssertionError,
|
AssertionError,
|
||||||
"'order' must be either 'ASC' or 'DESC'.",
|
"'order' must be either 'ASC' or 'DESC'.",
|
||||||
Article.objects.dates,
|
Article.objects.dates,
|
||||||
|
@ -323,7 +323,7 @@ class ModelTest(TestCase):
|
||||||
"<Article: Third article>"])
|
"<Article: Third article>"])
|
||||||
|
|
||||||
# Slicing works with longs (Python 2 only -- Python 3 doesn't have longs).
|
# 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.assertEqual(Article.objects.all()[long(0)], a)
|
||||||
self.assertQuerysetEqual(Article.objects.all()[long(1):long(3)],
|
self.assertQuerysetEqual(Article.objects.all()[long(1):long(3)],
|
||||||
["<Article: Second article>", "<Article: Third article>"])
|
["<Article: Second article>", "<Article: Third article>"])
|
||||||
|
@ -369,14 +369,14 @@ class ModelTest(TestCase):
|
||||||
"<Article: Updated article 8>"])
|
"<Article: Updated article 8>"])
|
||||||
|
|
||||||
# Also, once you have sliced you can't filter, re-order or combine
|
# Also, once you have sliced you can't filter, re-order or combine
|
||||||
self.assertRaisesRegexp(
|
six.assertRaisesRegex(self,
|
||||||
AssertionError,
|
AssertionError,
|
||||||
"Cannot filter a query once a slice has been taken.",
|
"Cannot filter a query once a slice has been taken.",
|
||||||
Article.objects.all()[0:5].filter,
|
Article.objects.all()[0:5].filter,
|
||||||
id=a.id,
|
id=a.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertRaisesRegexp(
|
six.assertRaisesRegex(self,
|
||||||
AssertionError,
|
AssertionError,
|
||||||
"Cannot reorder a query once a slice has been taken.",
|
"Cannot reorder a query once a slice has been taken.",
|
||||||
Article.objects.all()[0:5].order_by,
|
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.
|
# An Article instance doesn't have access to the "objects" attribute.
|
||||||
# That's only available on the class.
|
# That's only available on the class.
|
||||||
self.assertRaisesRegexp(
|
six.assertRaisesRegex(self,
|
||||||
AttributeError,
|
AttributeError,
|
||||||
"Manager isn't accessible via Article instances",
|
"Manager isn't accessible via Article instances",
|
||||||
getattr,
|
getattr,
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db.models.loading import get_app
|
from django.db.models.loading import get_app
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
from django.utils import six
|
||||||
|
|
||||||
from .models import Empty
|
from .models import Empty
|
||||||
|
|
||||||
|
@ -14,12 +14,13 @@ class EmptyModelTests(TestCase):
|
||||||
m = Empty()
|
m = Empty()
|
||||||
self.assertEqual(m.id, None)
|
self.assertEqual(m.id, None)
|
||||||
m.save()
|
m.save()
|
||||||
m2 = Empty.objects.create()
|
Empty.objects.create()
|
||||||
self.assertEqual(len(Empty.objects.all()), 2)
|
self.assertEqual(len(Empty.objects.all()), 2)
|
||||||
self.assertTrue(m.id is not None)
|
self.assertTrue(m.id is not None)
|
||||||
existing = Empty(m.id)
|
existing = Empty(m.id)
|
||||||
existing.save()
|
existing.save()
|
||||||
|
|
||||||
|
|
||||||
class NoModelTests(TestCase):
|
class NoModelTests(TestCase):
|
||||||
"""
|
"""
|
||||||
Test for #7198 to ensure that the proper error message is raised
|
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",))
|
@override_settings(INSTALLED_APPS=("modeltests.empty.no_models",))
|
||||||
def test_no_models(self):
|
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.'):
|
'App with label no_models is missing a models.py module.'):
|
||||||
get_app('no_models')
|
get_app('no_models')
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django.contrib.sites.models import Site
|
||||||
from django.core import management
|
from django.core import management
|
||||||
from django.db import connection, IntegrityError
|
from django.db import connection, IntegrityError
|
||||||
from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature
|
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
|
from .models import Article, Book, Spy, Tag, Visa
|
||||||
|
|
||||||
|
@ -21,11 +21,12 @@ class TestCaseFixtureLoadingTests(TestCase):
|
||||||
'<Article: Poker has no place on ESPN>',
|
'<Article: Poker has no place on ESPN>',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
class FixtureLoadingTests(TestCase):
|
class FixtureLoadingTests(TestCase):
|
||||||
|
|
||||||
def _dumpdata_assert(self, args, output, format='json', natural_keys=False,
|
def _dumpdata_assert(self, args, output, format='json', natural_keys=False,
|
||||||
use_base_manager=False, exclude_list=[]):
|
use_base_manager=False, exclude_list=[]):
|
||||||
new_io = StringIO()
|
new_io = six.StringIO()
|
||||||
management.call_command('dumpdata', *args, **{'format': format,
|
management.call_command('dumpdata', *args, **{'format': format,
|
||||||
'stdout': new_io,
|
'stdout': new_io,
|
||||||
'stderr': new_io,
|
'stderr': new_io,
|
||||||
|
@ -42,8 +43,6 @@ class FixtureLoadingTests(TestCase):
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_loading_and_dumping(self):
|
def test_loading_and_dumping(self):
|
||||||
new_io = StringIO()
|
|
||||||
|
|
||||||
Site.objects.all().delete()
|
Site.objects.all().delete()
|
||||||
# Load fixture 1. Single JSON file, with two objects.
|
# Load fixture 1. Single JSON file, with two objects.
|
||||||
management.call_command('loaddata', 'fixture1.json', verbosity=0, commit=False)
|
management.call_command('loaddata', 'fixture1.json', verbosity=0, commit=False)
|
||||||
|
@ -184,12 +183,12 @@ class FixtureLoadingTests(TestCase):
|
||||||
exclude_list=['fixtures.Article', 'fixtures.Book', 'sites'])
|
exclude_list=['fixtures.Article', 'fixtures.Book', 'sites'])
|
||||||
|
|
||||||
# Excluding a bogus app should throw an error
|
# 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"):
|
"Unknown app in excludes: foo_app"):
|
||||||
self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['foo_app'])
|
self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['foo_app'])
|
||||||
|
|
||||||
# Excluding a bogus model should throw an error
|
# 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"):
|
"Unknown model in excludes: fixtures.FooModel"):
|
||||||
self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['fixtures.FooModel'])
|
self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['fixtures.FooModel'])
|
||||||
|
|
||||||
|
@ -227,7 +226,7 @@ class FixtureLoadingTests(TestCase):
|
||||||
|
|
||||||
def test_ambiguous_compressed_fixture(self):
|
def test_ambiguous_compressed_fixture(self):
|
||||||
# The name "fixture5" is ambigous, so loading it will raise an error
|
# 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'"):
|
"Multiple fixtures named 'fixture5'"):
|
||||||
management.call_command('loaddata', 'fixture5', verbosity=0, commit=False)
|
management.call_command('loaddata', 'fixture5', verbosity=0, commit=False)
|
||||||
|
|
||||||
|
@ -251,7 +250,7 @@ class FixtureLoadingTests(TestCase):
|
||||||
# is closed at the end of each test.
|
# is closed at the end of each test.
|
||||||
if connection.vendor == 'mysql':
|
if connection.vendor == 'mysql':
|
||||||
connection.cursor().execute("SET sql_mode = 'TRADITIONAL'")
|
connection.cursor().execute("SET sql_mode = 'TRADITIONAL'")
|
||||||
with self.assertRaisesRegexp(IntegrityError,
|
with six.assertRaisesRegex(self, IntegrityError,
|
||||||
"Could not load fixtures.Article\(pk=1\): .*$"):
|
"Could not load fixtures.Article\(pk=1\): .*$"):
|
||||||
management.call_command('loaddata', 'invalid.json', verbosity=0, commit=False)
|
management.call_command('loaddata', 'invalid.json', verbosity=0, commit=False)
|
||||||
|
|
||||||
|
@ -290,9 +289,10 @@ class FixtureLoadingTests(TestCase):
|
||||||
self._dumpdata_assert(['fixtures'], """<?xml version="1.0" encoding="utf-8"?>
|
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)
|
<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):
|
class FixtureTransactionTests(TransactionTestCase):
|
||||||
def _dumpdata_assert(self, args, output, format='json'):
|
def _dumpdata_assert(self, args, output, format='json'):
|
||||||
new_io = StringIO()
|
new_io = six.StringIO()
|
||||||
management.call_command('dumpdata', *args, **{'format': format, 'stdout': new_io})
|
management.call_command('dumpdata', *args, **{'format': format, 'stdout': new_io})
|
||||||
command_output = new_io.getvalue().strip()
|
command_output = new_io.getvalue().strip()
|
||||||
self.assertEqual(command_output, output)
|
self.assertEqual(command_output, output)
|
||||||
|
@ -308,7 +308,7 @@ class FixtureTransactionTests(TransactionTestCase):
|
||||||
|
|
||||||
# Try to load fixture 2 using format discovery; this will fail
|
# Try to load fixture 2 using format discovery; this will fail
|
||||||
# because there are two fixture2's in the fixtures directory
|
# 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'"):
|
"Multiple fixtures named 'fixture2'"):
|
||||||
management.call_command('loaddata', 'fixture2', verbosity=0)
|
management.call_command('loaddata', 'fixture2', verbosity=0)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.utils import six
|
||||||
|
|
||||||
from .models import Article, Publication
|
from .models import Article, Publication
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ class ManyToManyTests(TestCase):
|
||||||
])
|
])
|
||||||
|
|
||||||
# Adding an object of the wrong type raises TypeError
|
# 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)
|
a6.publications.add(a5)
|
||||||
# Add a Publication directly via publications.add by using keyword arguments.
|
# Add a Publication directly via publications.add by using keyword arguments.
|
||||||
p4 = a6.publications.create(title='Highlights for Adults')
|
p4 = a6.publications.create(title='Highlights for Adults')
|
||||||
|
|
|
@ -70,7 +70,7 @@ class ManyToOneTests(TestCase):
|
||||||
self.assertQuerysetEqual(self.r2.article_set.all(), ["<Article: Paul's story>"])
|
self.assertQuerysetEqual(self.r2.article_set.all(), ["<Article: Paul's story>"])
|
||||||
|
|
||||||
# Adding an object of the wrong type raises TypeError.
|
# 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.r.article_set.add(self.r2)
|
||||||
self.assertQuerysetEqual(self.r.article_set.all(),
|
self.assertQuerysetEqual(self.r.article_set.all(),
|
||||||
[
|
[
|
||||||
|
|
|
@ -2,7 +2,7 @@ from __future__ import absolute_import
|
||||||
|
|
||||||
from datetime import date
|
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 django.test import TestCase
|
||||||
|
|
||||||
from .models import Author, Book, Coffee, Reviewer, FriendlyAuthor
|
from .models import Author, Book, Coffee, Reviewer, FriendlyAuthor
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
from django.test import TestCase
|
|
||||||
from django.db.models.signals import pre_save, post_save
|
from django.db.models.signals import pre_save, post_save
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
from .models import Person, Employee, ProxyEmployee, Profile, Account
|
from .models import Person, Employee, ProxyEmployee, Profile, Account
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils import six
|
||||||
from django.utils.unittest import TestCase
|
from django.utils.unittest import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ class ValidationMessagesTest(TestCase):
|
||||||
self._test_validation_messages(f, 'fõo',
|
self._test_validation_messages(f, 'fõo',
|
||||||
["'fõo' value must be an integer."])
|
["'fõo' value must be an integer."])
|
||||||
# primary_key must be True. Refs #12467.
|
# primary_key must be True. Refs #12467.
|
||||||
with self.assertRaisesRegexp(AssertionError,
|
with six.assertRaisesRegex(self, AssertionError,
|
||||||
"AutoFields must have primary_key=True."):
|
"AutoFields must have primary_key=True."):
|
||||||
models.AutoField(primary_key=False)
|
models.AutoField(primary_key=False)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ from django.contrib.auth.admin import UserAdmin
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.test import TestCase, RequestFactory
|
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 django.utils.encoding import force_text
|
||||||
|
|
||||||
from .models import Book, Department, Employee
|
from .models import Book, Department, Employee
|
||||||
|
@ -18,6 +18,7 @@ from .models import Book, Department, Employee
|
||||||
def select_by(dictlist, key, value):
|
def select_by(dictlist, key, value):
|
||||||
return [x for x in dictlist if x[key] == value][0]
|
return [x for x in dictlist if x[key] == value][0]
|
||||||
|
|
||||||
|
|
||||||
class DecadeListFilter(SimpleListFilter):
|
class DecadeListFilter(SimpleListFilter):
|
||||||
|
|
||||||
def lookups(self, request, model_admin):
|
def lookups(self, request, model_admin):
|
||||||
|
@ -520,7 +521,7 @@ class ListFiltersTests(TestCase):
|
||||||
"""
|
"""
|
||||||
modeladmin = DecadeFilterBookAdminWithoutTitle(Book, site)
|
modeladmin = DecadeFilterBookAdminWithoutTitle(Book, site)
|
||||||
request = self.request_factory.get('/', {})
|
request = self.request_factory.get('/', {})
|
||||||
self.assertRaisesRegexp(ImproperlyConfigured,
|
six.assertRaisesRegex(self, ImproperlyConfigured,
|
||||||
"The list filter 'DecadeListFilterWithoutTitle' does not specify a 'title'.",
|
"The list filter 'DecadeListFilterWithoutTitle' does not specify a 'title'.",
|
||||||
self.get_changelist, request, Book, modeladmin)
|
self.get_changelist, request, Book, modeladmin)
|
||||||
|
|
||||||
|
@ -530,7 +531,7 @@ class ListFiltersTests(TestCase):
|
||||||
"""
|
"""
|
||||||
modeladmin = DecadeFilterBookAdminWithoutParameter(Book, site)
|
modeladmin = DecadeFilterBookAdminWithoutParameter(Book, site)
|
||||||
request = self.request_factory.get('/', {})
|
request = self.request_factory.get('/', {})
|
||||||
self.assertRaisesRegexp(ImproperlyConfigured,
|
six.assertRaisesRegex(self, ImproperlyConfigured,
|
||||||
"The list filter 'DecadeListFilterWithoutParameter' does not specify a 'parameter_name'.",
|
"The list filter 'DecadeListFilterWithoutParameter' does not specify a 'parameter_name'.",
|
||||||
self.get_changelist, request, Book, modeladmin)
|
self.get_changelist, request, Book, modeladmin)
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,11 @@ from django.test import TestCase
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
# local test models
|
# local test models
|
||||||
from .admin import InnerInline
|
from .admin import InnerInline, TitleInline, site
|
||||||
from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person,
|
from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person,
|
||||||
OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile,
|
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',))
|
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
||||||
|
@ -408,6 +409,47 @@ class SeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
|
||||||
fixtures = ['admin-views-users.xml']
|
fixtures = ['admin-views-users.xml']
|
||||||
urls = "regressiontests.admin_inlines.urls"
|
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):
|
def test_add_inlines(self):
|
||||||
"""
|
"""
|
||||||
Ensure that the "Add another XXX" link correctly adds items to the
|
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(
|
self.assertEqual(len(self.selenium.find_elements_by_css_selector(
|
||||||
'form#profilecollection_form tr.dynamic-profile_set#profile_set-2')), 1)
|
'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):
|
class SeleniumChromeTests(SeleniumFirefoxTests):
|
||||||
webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver'
|
webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver'
|
||||||
|
|
|
@ -181,11 +181,11 @@ class DjangoAdminNoSettings(AdminScriptTestCase):
|
||||||
"A series of tests for django-admin.py when there is no settings.py file."
|
"A series of tests for django-admin.py when there is no settings.py file."
|
||||||
|
|
||||||
def test_builtin_command(self):
|
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']
|
args = ['sqlall', 'admin_scripts']
|
||||||
out, err = self.run_django_admin(args)
|
out, err = self.run_django_admin(args)
|
||||||
self.assertNoOutput(out)
|
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):
|
def test_builtin_with_bad_settings(self):
|
||||||
"no settings: django-admin builtin commands fail if settings file (from argument) doesn't exist"
|
"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')
|
self.remove_settings('settings.py')
|
||||||
|
|
||||||
def test_builtin_command(self):
|
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']
|
args = ['sqlall', 'admin_scripts']
|
||||||
out, err = self.run_django_admin(args)
|
out, err = self.run_django_admin(args)
|
||||||
self.assertNoOutput(out)
|
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):
|
def test_builtin_with_settings(self):
|
||||||
"default: django-admin builtin commands succeed if settings are provided as argument"
|
"default: django-admin builtin commands succeed if settings are provided as argument"
|
||||||
|
@ -279,11 +279,11 @@ class DjangoAdminFullPathDefaultSettings(AdminScriptTestCase):
|
||||||
self.remove_settings('settings.py')
|
self.remove_settings('settings.py')
|
||||||
|
|
||||||
def test_builtin_command(self):
|
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']
|
args = ['sqlall', 'admin_scripts']
|
||||||
out, err = self.run_django_admin(args)
|
out, err = self.run_django_admin(args)
|
||||||
self.assertNoOutput(out)
|
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):
|
def test_builtin_with_settings(self):
|
||||||
"fulldefault: django-admin builtin commands succeed if a settings file is provided"
|
"fulldefault: django-admin builtin commands succeed if a settings file is provided"
|
||||||
|
@ -345,11 +345,11 @@ class DjangoAdminMinimalSettings(AdminScriptTestCase):
|
||||||
self.remove_settings('settings.py')
|
self.remove_settings('settings.py')
|
||||||
|
|
||||||
def test_builtin_command(self):
|
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']
|
args = ['sqlall', 'admin_scripts']
|
||||||
out, err = self.run_django_admin(args)
|
out, err = self.run_django_admin(args)
|
||||||
self.assertNoOutput(out)
|
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):
|
def test_builtin_with_settings(self):
|
||||||
"minimal: django-admin builtin commands fail if settings are provided as argument"
|
"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')
|
self.remove_settings('alternate_settings.py')
|
||||||
|
|
||||||
def test_builtin_command(self):
|
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']
|
args = ['sqlall', 'admin_scripts']
|
||||||
out, err = self.run_django_admin(args)
|
out, err = self.run_django_admin(args)
|
||||||
self.assertNoOutput(out)
|
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):
|
def test_builtin_with_settings(self):
|
||||||
"alternate: django-admin builtin commands succeed if settings are provided as argument"
|
"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')
|
self.remove_settings('alternate_settings.py')
|
||||||
|
|
||||||
def test_builtin_command(self):
|
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']
|
args = ['sqlall', 'admin_scripts']
|
||||||
out, err = self.run_django_admin(args)
|
out, err = self.run_django_admin(args)
|
||||||
self.assertNoOutput(out)
|
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):
|
def test_builtin_with_settings(self):
|
||||||
"alternate: django-admin builtin commands succeed if settings are provided as argument"
|
"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')))
|
self.assertTrue(os.path.exists(os.path.join(app_path, 'api.py')))
|
||||||
|
|
||||||
def test_builtin_command(self):
|
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']
|
args = ['sqlall', 'admin_scripts']
|
||||||
out, err = self.run_django_admin(args)
|
out, err = self.run_django_admin(args)
|
||||||
self.assertNoOutput(out)
|
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):
|
def test_builtin_with_bad_settings(self):
|
||||||
"directory: django-admin builtin commands fail if settings file (from argument) doesn't exist"
|
"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."
|
"A series of tests for manage.py when there is no settings.py file."
|
||||||
|
|
||||||
def test_builtin_command(self):
|
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']
|
args = ['sqlall', 'admin_scripts']
|
||||||
out, err = self.run_manage(args)
|
out, err = self.run_manage(args)
|
||||||
self.assertNoOutput(out)
|
self.assertNoOutput(out)
|
||||||
|
@ -786,7 +786,7 @@ class ManageMinimalSettings(AdminScriptTestCase):
|
||||||
self.remove_settings('settings.py')
|
self.remove_settings('settings.py')
|
||||||
|
|
||||||
def test_builtin_command(self):
|
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']
|
args = ['sqlall', 'admin_scripts']
|
||||||
out, err = self.run_manage(args)
|
out, err = self.run_manage(args)
|
||||||
self.assertNoOutput(out)
|
self.assertNoOutput(out)
|
||||||
|
@ -852,7 +852,7 @@ class ManageAlternateSettings(AdminScriptTestCase):
|
||||||
self.remove_settings('alternate_settings.py')
|
self.remove_settings('alternate_settings.py')
|
||||||
|
|
||||||
def test_builtin_command(self):
|
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']
|
args = ['sqlall', 'admin_scripts']
|
||||||
out, err = self.run_manage(args)
|
out, err = self.run_manage(args)
|
||||||
self.assertNoOutput(out)
|
self.assertNoOutput(out)
|
||||||
|
@ -895,7 +895,7 @@ class ManageAlternateSettings(AdminScriptTestCase):
|
||||||
args = ['noargs_command']
|
args = ['noargs_command']
|
||||||
out, err = self.run_manage(args)
|
out, err = self.run_manage(args)
|
||||||
self.assertNoOutput(out)
|
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):
|
def test_custom_command_with_settings(self):
|
||||||
"alternate: manage.py can execute user commands if settings are provided as argument"
|
"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')
|
self.remove_settings('alternate_settings.py')
|
||||||
|
|
||||||
def test_builtin_command(self):
|
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']
|
args = ['sqlall', 'admin_scripts']
|
||||||
out, err = self.run_manage(args)
|
out, err = self.run_manage(args)
|
||||||
self.assertNoOutput(out)
|
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.assertOutput(err, "Destination directory '%s' does not exist, please create it first." % testproject_dir)
|
||||||
self.assertFalse(os.path.exists(testproject_dir))
|
self.assertFalse(os.path.exists(testproject_dir))
|
||||||
|
|
||||||
|
|
||||||
def test_custom_project_template_with_non_ascii_templates(self):
|
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"
|
"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')
|
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.assertNoOutput(err)
|
||||||
self.assertTrue(os.path.isdir(testproject_dir))
|
self.assertTrue(os.path.isdir(testproject_dir))
|
||||||
path = os.path.join(testproject_dir, 'ticket-18091-non-ascii-template.txt')
|
path = os.path.join(testproject_dir, 'ticket-18091-non-ascii-template.txt')
|
||||||
self.assertEqual(codecs.open(path, 'r', 'utf-8').read(),
|
with codecs.open(path, 'r', 'utf-8') as f:
|
||||||
|
self.assertEqual(f.read(),
|
||||||
'Some non-ASCII text for testing ticket #18091:\nüäö €\n')
|
'Some non-ASCII text for testing ticket #18091:\nüäö €\n')
|
||||||
|
|
|
@ -27,11 +27,14 @@ from .models import (Article, Chapter, Account, Media, Child, Parent, Picture,
|
||||||
Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug,
|
Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug,
|
||||||
AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod,
|
AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod,
|
||||||
AdminOrderedCallable, Report, Color2, UnorderedObject, MainPrepopulated,
|
AdminOrderedCallable, Report, Color2, UnorderedObject, MainPrepopulated,
|
||||||
RelatedPrepopulated, UndeletableObject)
|
RelatedPrepopulated, UndeletableObject, Simple)
|
||||||
|
|
||||||
|
|
||||||
def callable_year(dt_value):
|
def callable_year(dt_value):
|
||||||
|
try:
|
||||||
return dt_value.year
|
return dt_value.year
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
callable_year.admin_order_field = 'date'
|
callable_year.admin_order_field = 'date'
|
||||||
|
|
||||||
|
|
||||||
|
@ -575,6 +578,14 @@ class UndeletableObjectAdmin(admin.ModelAdmin):
|
||||||
return super(UndeletableObjectAdmin, self).change_view(*args, **kwargs)
|
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 = admin.AdminSite(name="admin")
|
||||||
site.register(Article, ArticleAdmin)
|
site.register(Article, ArticleAdmin)
|
||||||
site.register(CustomArticle, CustomArticleAdmin)
|
site.register(CustomArticle, CustomArticleAdmin)
|
||||||
|
@ -648,6 +659,7 @@ site.register(AdminOrderedModelMethod, AdminOrderedModelMethodAdmin)
|
||||||
site.register(AdminOrderedAdminMethod, AdminOrderedAdminMethodAdmin)
|
site.register(AdminOrderedAdminMethod, AdminOrderedAdminMethodAdmin)
|
||||||
site.register(AdminOrderedCallable, AdminOrderedCallableAdmin)
|
site.register(AdminOrderedCallable, AdminOrderedCallableAdmin)
|
||||||
site.register(Color2, CustomTemplateFilterColorAdmin)
|
site.register(Color2, CustomTemplateFilterColorAdmin)
|
||||||
|
site.register(Simple, AttributeErrorRaisingAdmin)
|
||||||
|
|
||||||
# Register core models we need in our tests
|
# Register core models we need in our tests
|
||||||
from django.contrib.auth.models import User, Group
|
from django.contrib.auth.models import User, Group
|
||||||
|
|
|
@ -49,3 +49,4 @@ site.register(models.Fabric, base_admin.FabricAdmin)
|
||||||
site.register(models.ChapterXtra1, base_admin.ChapterXtra1Admin)
|
site.register(models.ChapterXtra1, base_admin.ChapterXtra1Admin)
|
||||||
site.register(User, UserLimitedAdmin)
|
site.register(User, UserLimitedAdmin)
|
||||||
site.register(models.UndeletableObject, base_admin.UndeletableObjectAdmin)
|
site.register(models.UndeletableObject, base_admin.UndeletableObjectAdmin)
|
||||||
|
site.register(models.Simple, base_admin.AttributeErrorRaisingAdmin)
|
||||||
|
|
|
@ -649,3 +649,9 @@ class UndeletableObject(models.Model):
|
||||||
Refs #10057.
|
Refs #10057.
|
||||||
"""
|
"""
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
|
|
||||||
|
|
||||||
|
class Simple(models.Model):
|
||||||
|
"""
|
||||||
|
Simple model with nothing on it for use in testing
|
||||||
|
"""
|
||||||
|
|
|
@ -46,7 +46,7 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount,
|
||||||
OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField,
|
OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField,
|
||||||
AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable,
|
AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable,
|
||||||
Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject,
|
Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject,
|
||||||
UndeletableObject)
|
Simple, UndeletableObject)
|
||||||
|
|
||||||
|
|
||||||
ERROR_MESSAGE = "Please enter the correct username and password \
|
ERROR_MESSAGE = "Please enter the correct username and password \
|
||||||
|
@ -579,6 +579,19 @@ class AdminViewBasicTest(TestCase):
|
||||||
(self.urlbit, instance.pk))
|
(self.urlbit, instance.pk))
|
||||||
self.assertNotContains(response, 'deletelink')
|
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',))
|
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
||||||
class AdminViewFormUrlTest(TestCase):
|
class AdminViewFormUrlTest(TestCase):
|
||||||
|
|
|
@ -655,7 +655,7 @@ class ThreadTests(TestCase):
|
||||||
|
|
||||||
class BackendLoadingTests(TestCase):
|
class BackendLoadingTests(TestCase):
|
||||||
def test_old_style_backends_raise_useful_exception(self):
|
def test_old_style_backends_raise_useful_exception(self):
|
||||||
self.assertRaisesRegexp(ImproperlyConfigured,
|
six.assertRaisesRegex(self, ImproperlyConfigured,
|
||||||
"Try using django.db.backends.sqlite3 instead",
|
"Try using django.db.backends.sqlite3 instead",
|
||||||
load_backend, 'sqlite3')
|
load_backend, 'sqlite3')
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import warnings
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import management
|
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,
|
from django.core.cache.backends.base import (CacheKeyWarning,
|
||||||
InvalidCacheBackendError)
|
InvalidCacheBackendError)
|
||||||
from django.db import router
|
from django.db import router
|
||||||
|
@ -25,8 +25,7 @@ from django.middleware.cache import (FetchFromCacheMiddleware,
|
||||||
from django.template import Template
|
from django.template import Template
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.test import TestCase, TransactionTestCase, RequestFactory
|
from django.test import TestCase, TransactionTestCase, RequestFactory
|
||||||
from django.test.utils import (get_warnings_state, restore_warnings_state,
|
from django.test.utils import override_settings, six
|
||||||
override_settings)
|
|
||||||
from django.utils import timezone, translation, unittest
|
from django.utils import timezone, translation, unittest
|
||||||
from django.utils.cache import (patch_vary_headers, get_cache_key,
|
from django.utils.cache import (patch_vary_headers, get_cache_key,
|
||||||
learn_cache_key, patch_cache_control, patch_response_headers)
|
learn_cache_key, patch_cache_control, patch_response_headers)
|
||||||
|
@ -821,7 +820,7 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase):
|
||||||
self.perform_cull_test(50, 18)
|
self.perform_cull_test(50, 18)
|
||||||
|
|
||||||
def test_second_call_doesnt_crash(self):
|
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"):
|
"Cache table 'test cache table' could not be created"):
|
||||||
management.call_command(
|
management.call_command(
|
||||||
'createcachetable',
|
'createcachetable',
|
||||||
|
|
|
@ -17,7 +17,7 @@ CT = ContentType.objects.get_for_model
|
||||||
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',))
|
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',))
|
||||||
class CommentTestCase(TestCase):
|
class CommentTestCase(TestCase):
|
||||||
fixtures = ["comment_tests"]
|
fixtures = ["comment_tests"]
|
||||||
urls = 'django.contrib.comments.urls'
|
urls = 'regressiontests.comment_tests.urls_default'
|
||||||
|
|
||||||
def createSomeComments(self):
|
def createSomeComments(self):
|
||||||
# Two anonymous comments on two different objects
|
# Two anonymous comments on two different objects
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
from django.conf.urls.defaults import *
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
(r'^', include('django.contrib.comments.urls')),
|
||||||
|
|
||||||
|
# Provide the auth system login and logout views
|
||||||
|
(r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}),
|
||||||
|
(r'^accounts/logout/$', 'django.contrib.auth.views.logout'),
|
||||||
|
)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue