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>
|
||||
Nowell Strite <http://nowell.strite.org/>
|
||||
Thomas Stromberg <tstromberg@google.com>
|
||||
Travis Swicegood <travis@domain51.com>
|
||||
Pascal Varet
|
||||
SuperJared
|
||||
Radek Švarz <http://www.svarz.cz/translate/>
|
||||
|
|
|
@ -7,7 +7,6 @@ a list of all possible variables.
|
|||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import time # Needed for Windows
|
||||
import warnings
|
||||
|
||||
|
@ -26,7 +25,7 @@ class LazySettings(LazyObject):
|
|||
The user can manually configure settings prior to using them. Otherwise,
|
||||
Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE.
|
||||
"""
|
||||
def _setup(self):
|
||||
def _setup(self, name):
|
||||
"""
|
||||
Load the settings module pointed to by the environment variable. This
|
||||
is used the first time we need any settings at all, if the user has not
|
||||
|
@ -37,12 +36,21 @@ class LazySettings(LazyObject):
|
|||
if not settings_module: # If it's set but is an empty string.
|
||||
raise KeyError
|
||||
except KeyError:
|
||||
# NOTE: This is arguably an EnvironmentError, but that causes
|
||||
# problems with Python's interactive help.
|
||||
raise ImportError("Settings cannot be imported, because environment variable %s is undefined." % ENVIRONMENT_VARIABLE)
|
||||
raise ImproperlyConfigured(
|
||||
"Requested setting %s, but settings are not configured. "
|
||||
"You must either define the environment variable %s "
|
||||
"or call settings.configure() before accessing settings."
|
||||
% (name, ENVIRONMENT_VARIABLE))
|
||||
|
||||
self._wrapped = Settings(settings_module)
|
||||
|
||||
|
||||
def __getattr__(self, name):
|
||||
if self._wrapped is empty:
|
||||
self._setup(name)
|
||||
return getattr(self._wrapped, name)
|
||||
|
||||
|
||||
def configure(self, default_settings=global_settings, **options):
|
||||
"""
|
||||
Called to manually configure the settings. The 'default_settings'
|
||||
|
|
|
@ -14,9 +14,10 @@ from django.core.exceptions import PermissionDenied, ValidationError
|
|||
from django.core.paginator import Paginator
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models, transaction, router
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.related import RelatedObject
|
||||
from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
|
||||
from django.db.models.sql.constants import LOOKUP_SEP, QUERY_TERMS
|
||||
from django.db.models.sql.constants import QUERY_TERMS
|
||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template.response import SimpleTemplateResponse, TemplateResponse
|
||||
|
@ -1456,8 +1457,10 @@ class InlineModelAdmin(BaseModelAdmin):
|
|||
return request.user.has_perm(
|
||||
self.opts.app_label + '.' + self.opts.get_delete_permission())
|
||||
|
||||
|
||||
class StackedInline(InlineModelAdmin):
|
||||
template = 'admin/edit_inline/stacked.html'
|
||||
|
||||
|
||||
class TabularInline(InlineModelAdmin):
|
||||
template = 'admin/edit_inline/tabular.html'
|
||||
|
|
|
@ -9,128 +9,264 @@
|
|||
* All rights reserved.
|
||||
*
|
||||
* Spiced up with Code from Zain Memon's GSoC project 2009
|
||||
* and modified for Django by Jannis Leidel
|
||||
* and modified for Django by Jannis Leidel, Travis Swicegood and Julien Phalip.
|
||||
*
|
||||
* Licensed under the New BSD License
|
||||
* See: http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
(function($) {
|
||||
$.fn.formset = function(opts) {
|
||||
var options = $.extend({}, $.fn.formset.defaults, opts);
|
||||
var updateElementIndex = function(el, prefix, ndx) {
|
||||
var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))");
|
||||
var replacement = prefix + "-" + ndx;
|
||||
if ($(el).attr("for")) {
|
||||
$(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
|
||||
}
|
||||
if (el.id) {
|
||||
el.id = el.id.replace(id_regex, replacement);
|
||||
}
|
||||
if (el.name) {
|
||||
el.name = el.name.replace(id_regex, replacement);
|
||||
}
|
||||
};
|
||||
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off");
|
||||
var nextIndex = parseInt(totalForms.val(), 10);
|
||||
var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").attr("autocomplete", "off");
|
||||
// only show the add button if we are allowed to add more items,
|
||||
$.fn.formset = function(opts) {
|
||||
var options = $.extend({}, $.fn.formset.defaults, opts);
|
||||
var $this = $(this);
|
||||
var $parent = $this.parent();
|
||||
var updateElementIndex = function(el, prefix, ndx) {
|
||||
var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))");
|
||||
var replacement = prefix + "-" + ndx;
|
||||
if ($(el).attr("for")) {
|
||||
$(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
|
||||
}
|
||||
if (el.id) {
|
||||
el.id = el.id.replace(id_regex, replacement);
|
||||
}
|
||||
if (el.name) {
|
||||
el.name = el.name.replace(id_regex, replacement);
|
||||
}
|
||||
};
|
||||
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off");
|
||||
var nextIndex = parseInt(totalForms.val(), 10);
|
||||
var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").attr("autocomplete", "off");
|
||||
// only show the add button if we are allowed to add more items,
|
||||
// note that max_num = None translates to a blank string.
|
||||
var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0;
|
||||
$(this).each(function(i) {
|
||||
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
|
||||
});
|
||||
if ($(this).length && showAddButton) {
|
||||
var addButton;
|
||||
if ($(this).attr("tagName") == "TR") {
|
||||
// If forms are laid out as table rows, insert the
|
||||
// "add" button in a new table row:
|
||||
var numCols = this.eq(-1).children().length;
|
||||
$(this).parent().append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a href="javascript:void(0)">' + options.addText + "</a></tr>");
|
||||
addButton = $(this).parent().find("tr:last a");
|
||||
} else {
|
||||
// Otherwise, insert it immediately after the last form:
|
||||
$(this).filter(":last").after('<div class="' + options.addCssClass + '"><a href="javascript:void(0)">' + options.addText + "</a></div>");
|
||||
addButton = $(this).filter(":last").next().find("a");
|
||||
}
|
||||
addButton.click(function(e) {
|
||||
e.preventDefault();
|
||||
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS");
|
||||
var template = $("#" + options.prefix + "-empty");
|
||||
var row = template.clone(true);
|
||||
row.removeClass(options.emptyCssClass)
|
||||
.addClass(options.formCssClass)
|
||||
.attr("id", options.prefix + "-" + nextIndex);
|
||||
if (row.is("tr")) {
|
||||
// If the forms are laid out in table rows, insert
|
||||
// the remove button into the last table cell:
|
||||
row.children(":last").append('<div><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></div>");
|
||||
} else if (row.is("ul") || row.is("ol")) {
|
||||
// If they're laid out as an ordered/unordered list,
|
||||
// insert an <li> after the last list item:
|
||||
row.append('<li><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></li>");
|
||||
} else {
|
||||
// Otherwise, just insert the remove button as the
|
||||
// last child element of the form's container:
|
||||
row.children(":first").append('<span><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText + "</a></span>");
|
||||
}
|
||||
row.find("*").each(function() {
|
||||
updateElementIndex(this, options.prefix, totalForms.val());
|
||||
});
|
||||
// Insert the new form when it has been fully edited
|
||||
row.insertBefore($(template));
|
||||
// Update number of total forms
|
||||
$(totalForms).val(parseInt(totalForms.val(), 10) + 1);
|
||||
nextIndex += 1;
|
||||
// Hide add button in case we've hit the max, except we want to add infinitely
|
||||
if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) {
|
||||
addButton.parent().hide();
|
||||
}
|
||||
// The delete button of each row triggers a bunch of other things
|
||||
row.find("a." + options.deleteCssClass).click(function(e) {
|
||||
e.preventDefault();
|
||||
// Remove the parent form containing this button:
|
||||
var row = $(this).parents("." + options.formCssClass);
|
||||
row.remove();
|
||||
nextIndex -= 1;
|
||||
// If a post-delete callback was provided, call it with the deleted form:
|
||||
if (options.removed) {
|
||||
options.removed(row);
|
||||
}
|
||||
// Update the TOTAL_FORMS form count.
|
||||
var forms = $("." + options.formCssClass);
|
||||
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
|
||||
// Show add button again once we drop below max
|
||||
if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) {
|
||||
addButton.parent().show();
|
||||
}
|
||||
// Also, update names and ids for all remaining form controls
|
||||
// so they remain in sequence:
|
||||
for (var i=0, formCount=forms.length; i<formCount; i++)
|
||||
{
|
||||
updateElementIndex($(forms).get(i), options.prefix, i);
|
||||
$(forms.get(i)).find("*").each(function() {
|
||||
updateElementIndex(this, options.prefix, i);
|
||||
});
|
||||
}
|
||||
});
|
||||
// If a post-add callback was supplied, call it with the added form:
|
||||
if (options.added) {
|
||||
options.added(row);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
};
|
||||
/* Setup plugin defaults */
|
||||
$.fn.formset.defaults = {
|
||||
prefix: "form", // The form prefix for your django formset
|
||||
addText: "add another", // Text for the add link
|
||||
deleteText: "remove", // Text for the delete link
|
||||
addCssClass: "add-row", // CSS class applied to the add link
|
||||
deleteCssClass: "delete-row", // CSS class applied to the delete link
|
||||
emptyCssClass: "empty-row", // CSS class applied to the empty row
|
||||
formCssClass: "dynamic-form", // CSS class applied to each form in a formset
|
||||
added: null, // Function called each time a new form is added
|
||||
removed: null // Function called each time a form is deleted
|
||||
};
|
||||
var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0;
|
||||
$this.each(function(i) {
|
||||
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
|
||||
});
|
||||
if ($this.length && showAddButton) {
|
||||
var addButton;
|
||||
if ($this.attr("tagName") == "TR") {
|
||||
// If forms are laid out as table rows, insert the
|
||||
// "add" button in a new table row:
|
||||
var numCols = this.eq(-1).children().length;
|
||||
$parent.append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a href="javascript:void(0)">' + options.addText + "</a></tr>");
|
||||
addButton = $parent.find("tr:last a");
|
||||
} else {
|
||||
// Otherwise, insert it immediately after the last form:
|
||||
$this.filter(":last").after('<div class="' + options.addCssClass + '"><a href="javascript:void(0)">' + options.addText + "</a></div>");
|
||||
addButton = $this.filter(":last").next().find("a");
|
||||
}
|
||||
addButton.click(function(e) {
|
||||
e.preventDefault();
|
||||
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS");
|
||||
var template = $("#" + options.prefix + "-empty");
|
||||
var row = template.clone(true);
|
||||
row.removeClass(options.emptyCssClass)
|
||||
.addClass(options.formCssClass)
|
||||
.attr("id", options.prefix + "-" + nextIndex);
|
||||
if (row.is("tr")) {
|
||||
// If the forms are laid out in table rows, insert
|
||||
// the remove button into the last table cell:
|
||||
row.children(":last").append('<div><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></div>");
|
||||
} else if (row.is("ul") || row.is("ol")) {
|
||||
// If they're laid out as an ordered/unordered list,
|
||||
// insert an <li> after the last list item:
|
||||
row.append('<li><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></li>");
|
||||
} else {
|
||||
// Otherwise, just insert the remove button as the
|
||||
// last child element of the form's container:
|
||||
row.children(":first").append('<span><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText + "</a></span>");
|
||||
}
|
||||
row.find("*").each(function() {
|
||||
updateElementIndex(this, options.prefix, totalForms.val());
|
||||
});
|
||||
// Insert the new form when it has been fully edited
|
||||
row.insertBefore($(template));
|
||||
// Update number of total forms
|
||||
$(totalForms).val(parseInt(totalForms.val(), 10) + 1);
|
||||
nextIndex += 1;
|
||||
// Hide add button in case we've hit the max, except we want to add infinitely
|
||||
if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) {
|
||||
addButton.parent().hide();
|
||||
}
|
||||
// The delete button of each row triggers a bunch of other things
|
||||
row.find("a." + options.deleteCssClass).click(function(e) {
|
||||
e.preventDefault();
|
||||
// Remove the parent form containing this button:
|
||||
var row = $(this).parents("." + options.formCssClass);
|
||||
row.remove();
|
||||
nextIndex -= 1;
|
||||
// If a post-delete callback was provided, call it with the deleted form:
|
||||
if (options.removed) {
|
||||
options.removed(row);
|
||||
}
|
||||
// Update the TOTAL_FORMS form count.
|
||||
var forms = $("." + options.formCssClass);
|
||||
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
|
||||
// Show add button again once we drop below max
|
||||
if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) {
|
||||
addButton.parent().show();
|
||||
}
|
||||
// Also, update names and ids for all remaining form controls
|
||||
// so they remain in sequence:
|
||||
for (var i=0, formCount=forms.length; i<formCount; i++)
|
||||
{
|
||||
updateElementIndex($(forms).get(i), options.prefix, i);
|
||||
$(forms.get(i)).find("*").each(function() {
|
||||
updateElementIndex(this, options.prefix, i);
|
||||
});
|
||||
}
|
||||
});
|
||||
// If a post-add callback was supplied, call it with the added form:
|
||||
if (options.added) {
|
||||
options.added(row);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/* Setup plugin defaults */
|
||||
$.fn.formset.defaults = {
|
||||
prefix: "form", // The form prefix for your django formset
|
||||
addText: "add another", // Text for the add link
|
||||
deleteText: "remove", // Text for the delete link
|
||||
addCssClass: "add-row", // CSS class applied to the add link
|
||||
deleteCssClass: "delete-row", // CSS class applied to the delete link
|
||||
emptyCssClass: "empty-row", // CSS class applied to the empty row
|
||||
formCssClass: "dynamic-form", // CSS class applied to each form in a formset
|
||||
added: null, // Function called each time a new form is added
|
||||
removed: null // Function called each time a form is deleted
|
||||
};
|
||||
|
||||
|
||||
// Tabular inlines ---------------------------------------------------------
|
||||
$.fn.tabularFormset = function(options) {
|
||||
var $rows = $(this);
|
||||
var alternatingRows = function(row) {
|
||||
$($rows.selector).not(".add-row").removeClass("row1 row2")
|
||||
.filter(":even").addClass("row1").end()
|
||||
.filter(":odd").addClass("row2");
|
||||
};
|
||||
|
||||
var reinitDateTimeShortCuts = function() {
|
||||
// Reinitialize the calendar and clock widgets by force
|
||||
if (typeof DateTimeShortcuts != "undefined") {
|
||||
$(".datetimeshortcuts").remove();
|
||||
DateTimeShortcuts.init();
|
||||
}
|
||||
};
|
||||
|
||||
var updateSelectFilter = function() {
|
||||
// If any SelectFilter widgets are a part of the new form,
|
||||
// instantiate a new SelectFilter instance for it.
|
||||
if (typeof SelectFilter != 'undefined'){
|
||||
$('.selectfilter').each(function(index, value){
|
||||
var namearr = value.name.split('-');
|
||||
SelectFilter.init(value.id, namearr[namearr.length-1], false, options.adminStaticPrefix );
|
||||
});
|
||||
$('.selectfilterstacked').each(function(index, value){
|
||||
var namearr = value.name.split('-');
|
||||
SelectFilter.init(value.id, namearr[namearr.length-1], true, options.adminStaticPrefix );
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var initPrepopulatedFields = function(row) {
|
||||
row.find('.prepopulated_field').each(function() {
|
||||
var field = $(this),
|
||||
input = field.find('input, select, textarea'),
|
||||
dependency_list = input.data('dependency_list') || [],
|
||||
dependencies = [];
|
||||
$.each(dependency_list, function(i, field_name) {
|
||||
dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id'));
|
||||
});
|
||||
if (dependencies.length) {
|
||||
input.prepopulate(dependencies, input.attr('maxlength'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$rows.formset({
|
||||
prefix: options.prefix,
|
||||
addText: options.addText,
|
||||
formCssClass: "dynamic-" + options.prefix,
|
||||
deleteCssClass: "inline-deletelink",
|
||||
deleteText: options.deleteText,
|
||||
emptyCssClass: "empty-form",
|
||||
removed: alternatingRows,
|
||||
added: function(row) {
|
||||
initPrepopulatedFields(row);
|
||||
reinitDateTimeShortCuts();
|
||||
updateSelectFilter();
|
||||
alternatingRows(row);
|
||||
}
|
||||
});
|
||||
|
||||
return $rows;
|
||||
};
|
||||
|
||||
// Stacked inlines ---------------------------------------------------------
|
||||
$.fn.stackedFormset = function(options) {
|
||||
var $rows = $(this);
|
||||
var updateInlineLabel = function(row) {
|
||||
$($rows.selector).find(".inline_label").each(function(i) {
|
||||
var count = i + 1;
|
||||
$(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
|
||||
});
|
||||
};
|
||||
|
||||
var reinitDateTimeShortCuts = function() {
|
||||
// Reinitialize the calendar and clock widgets by force, yuck.
|
||||
if (typeof DateTimeShortcuts != "undefined") {
|
||||
$(".datetimeshortcuts").remove();
|
||||
DateTimeShortcuts.init();
|
||||
}
|
||||
};
|
||||
|
||||
var updateSelectFilter = function() {
|
||||
// If any SelectFilter widgets were added, instantiate a new instance.
|
||||
if (typeof SelectFilter != "undefined"){
|
||||
$(".selectfilter").each(function(index, value){
|
||||
var namearr = value.name.split('-');
|
||||
SelectFilter.init(value.id, namearr[namearr.length-1], false, options.adminStaticPrefix);
|
||||
});
|
||||
$(".selectfilterstacked").each(function(index, value){
|
||||
var namearr = value.name.split('-');
|
||||
SelectFilter.init(value.id, namearr[namearr.length-1], true, options.adminStaticPrefix);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var initPrepopulatedFields = function(row) {
|
||||
row.find('.prepopulated_field').each(function() {
|
||||
var field = $(this),
|
||||
input = field.find('input, select, textarea'),
|
||||
dependency_list = input.data('dependency_list') || [],
|
||||
dependencies = [];
|
||||
$.each(dependency_list, function(i, field_name) {
|
||||
dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id'));
|
||||
});
|
||||
if (dependencies.length) {
|
||||
input.prepopulate(dependencies, input.attr('maxlength'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$rows.formset({
|
||||
prefix: options.prefix,
|
||||
addText: options.addText,
|
||||
formCssClass: "dynamic-" + options.prefix,
|
||||
deleteCssClass: "inline-deletelink",
|
||||
deleteText: options.deleteText,
|
||||
emptyCssClass: "empty-form",
|
||||
removed: updateInlineLabel,
|
||||
added: (function(row) {
|
||||
initPrepopulatedFields(row);
|
||||
reinitDateTimeShortCuts();
|
||||
updateSelectFilter();
|
||||
updateInlineLabel(row);
|
||||
})
|
||||
});
|
||||
|
||||
return $rows;
|
||||
};
|
||||
})(django.jQuery);
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
(function(b){b.fn.formset=function(g){var a=b.extend({},b.fn.formset.defaults,g),k=function(c,f,e){var d=RegExp("("+f+"-(\\d+|__prefix__))");f=f+"-"+e;b(c).attr("for")&&b(c).attr("for",b(c).attr("for").replace(d,f));if(c.id)c.id=c.id.replace(d,f);if(c.name)c.name=c.name.replace(d,f)};g=b("#id_"+a.prefix+"-TOTAL_FORMS").attr("autocomplete","off");var l=parseInt(g.val(),10),h=b("#id_"+a.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off");g=h.val()===""||h.val()-g.val()>0;b(this).each(function(){b(this).not("."+
|
||||
a.emptyCssClass).addClass(a.formCssClass)});if(b(this).length&&g){var j;if(b(this).attr("tagName")=="TR"){g=this.eq(-1).children().length;b(this).parent().append('<tr class="'+a.addCssClass+'"><td colspan="'+g+'"><a href="javascript:void(0)">'+a.addText+"</a></tr>");j=b(this).parent().find("tr:last a")}else{b(this).filter(":last").after('<div class="'+a.addCssClass+'"><a href="javascript:void(0)">'+a.addText+"</a></div>");j=b(this).filter(":last").next().find("a")}j.click(function(c){c.preventDefault();
|
||||
var f=b("#id_"+a.prefix+"-TOTAL_FORMS");c=b("#"+a.prefix+"-empty");var e=c.clone(true);e.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+l);if(e.is("tr"))e.children(":last").append('<div><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></div>");else e.is("ul")||e.is("ol")?e.append('<li><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></li>"):e.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+
|
||||
a.deleteText+"</a></span>");e.find("*").each(function(){k(this,a.prefix,f.val())});e.insertBefore(b(c));b(f).val(parseInt(f.val(),10)+1);l+=1;h.val()!==""&&h.val()-f.val()<=0&&j.parent().hide();e.find("a."+a.deleteCssClass).click(function(d){d.preventDefault();d=b(this).parents("."+a.formCssClass);d.remove();l-=1;a.removed&&a.removed(d);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);if(h.val()===""||h.val()-d.length>0)j.parent().show();for(var i=0,m=d.length;i<m;i++){k(b(d).get(i),
|
||||
a.prefix,i);b(d.get(i)).find("*").each(function(){k(this,a.prefix,i)})}});a.added&&a.added(e)})}return this};b.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null}})(django.jQuery);
|
||||
(function(b){b.fn.formset=function(d){var a=b.extend({},b.fn.formset.defaults,d),c=b(this),d=c.parent(),i=function(a,e,g){var d=RegExp("("+e+"-(\\d+|__prefix__))"),e=e+"-"+g;b(a).attr("for")&&b(a).attr("for",b(a).attr("for").replace(d,e));a.id&&(a.id=a.id.replace(d,e));a.name&&(a.name=a.name.replace(d,e))},f=b("#id_"+a.prefix+"-TOTAL_FORMS").attr("autocomplete","off"),g=parseInt(f.val(),10),e=b("#id_"+a.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off"),f=""===e.val()||0<e.val()-f.val();c.each(function(){b(this).not("."+
|
||||
a.emptyCssClass).addClass(a.formCssClass)});if(c.length&&f){var h;"TR"==c.attr("tagName")?(c=this.eq(-1).children().length,d.append('<tr class="'+a.addCssClass+'"><td colspan="'+c+'"><a href="javascript:void(0)">'+a.addText+"</a></tr>"),h=d.find("tr:last a")):(c.filter(":last").after('<div class="'+a.addCssClass+'"><a href="javascript:void(0)">'+a.addText+"</a></div>"),h=c.filter(":last").next().find("a"));h.click(function(d){d.preventDefault();var f=b("#id_"+a.prefix+"-TOTAL_FORMS"),d=b("#"+a.prefix+
|
||||
"-empty"),c=d.clone(true);c.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+g);c.is("tr")?c.children(":last").append('<div><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></div>"):c.is("ul")||c.is("ol")?c.append('<li><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></li>"):c.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></span>");c.find("*").each(function(){i(this,
|
||||
a.prefix,f.val())});c.insertBefore(b(d));b(f).val(parseInt(f.val(),10)+1);g=g+1;e.val()!==""&&e.val()-f.val()<=0&&h.parent().hide();c.find("a."+a.deleteCssClass).click(function(d){d.preventDefault();d=b(this).parents("."+a.formCssClass);d.remove();g=g-1;a.removed&&a.removed(d);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);(e.val()===""||e.val()-d.length>0)&&h.parent().show();for(var c=0,f=d.length;c<f;c++){i(b(d).get(c),a.prefix,c);b(d.get(c)).find("*").each(function(){i(this,
|
||||
a.prefix,c)})}});a.added&&a.added(c)})}return this};b.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null};b.fn.tabularFormset=function(d){var a=b(this),c=function(){b(a.selector).not(".add-row").removeClass("row1 row2").filter(":even").addClass("row1").end().filter(":odd").addClass("row2")};a.formset({prefix:d.prefix,addText:d.addText,formCssClass:"dynamic-"+
|
||||
d.prefix,deleteCssClass:"inline-deletelink",deleteText:d.deleteText,emptyCssClass:"empty-form",removed:c,added:function(a){a.find(".prepopulated_field").each(function(){var d=b(this).find("input, select, textarea"),c=d.data("dependency_list")||[],e=[];b.each(c,function(d,b){e.push("#"+a.find(".field-"+b).find("input, select, textarea").attr("id"))});e.length&&d.prepopulate(e,d.attr("maxlength"))});"undefined"!=typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),DateTimeShortcuts.init());"undefined"!=
|
||||
typeof SelectFilter&&(b(".selectfilter").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],false,d.adminStaticPrefix)}),b(".selectfilterstacked").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],true,d.adminStaticPrefix)}));c(a)}});return a};b.fn.stackedFormset=function(d){var a=b(this),c=function(){b(a.selector).find(".inline_label").each(function(a){a+=1;b(this).html(b(this).html().replace(/(#\d+)/g,"#"+a))})};a.formset({prefix:d.prefix,
|
||||
addText:d.addText,formCssClass:"dynamic-"+d.prefix,deleteCssClass:"inline-deletelink",deleteText:d.deleteText,emptyCssClass:"empty-form",removed:c,added:function(a){a.find(".prepopulated_field").each(function(){var d=b(this).find("input, select, textarea"),c=d.data("dependency_list")||[],e=[];b.each(c,function(d,b){e.push("#"+a.find(".form-row .field-"+b).find("input, select, textarea").attr("id"))});e.length&&d.prepopulate(e,d.attr("maxlength"))});"undefined"!=typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),
|
||||
DateTimeShortcuts.init());"undefined"!=typeof SelectFilter&&(b(".selectfilter").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],false,d.adminStaticPrefix)}),b(".selectfilterstacked").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],true,d.adminStaticPrefix)}));c(a)}});return a}})(django.jQuery);
|
||||
|
|
|
@ -20,63 +20,11 @@
|
|||
|
||||
<script type="text/javascript">
|
||||
(function($) {
|
||||
$(document).ready(function() {
|
||||
var rows = "#{{ inline_admin_formset.formset.prefix }}-group .inline-related";
|
||||
var updateInlineLabel = function(row) {
|
||||
$(rows).find(".inline_label").each(function(i) {
|
||||
var count = i + 1;
|
||||
$(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
|
||||
});
|
||||
};
|
||||
var reinitDateTimeShortCuts = function() {
|
||||
// Reinitialize the calendar and clock widgets by force, yuck.
|
||||
if (typeof DateTimeShortcuts != "undefined") {
|
||||
$(".datetimeshortcuts").remove();
|
||||
DateTimeShortcuts.init();
|
||||
}
|
||||
};
|
||||
var updateSelectFilter = function() {
|
||||
// If any SelectFilter widgets were added, instantiate a new instance.
|
||||
if (typeof SelectFilter != "undefined"){
|
||||
$(".selectfilter").each(function(index, value){
|
||||
var namearr = value.name.split('-');
|
||||
SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% static "admin/" %}");
|
||||
});
|
||||
$(".selectfilterstacked").each(function(index, value){
|
||||
var namearr = value.name.split('-');
|
||||
SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% static "admin/" %}");
|
||||
});
|
||||
}
|
||||
};
|
||||
var initPrepopulatedFields = function(row) {
|
||||
row.find('.prepopulated_field').each(function() {
|
||||
var field = $(this);
|
||||
var input = field.find('input, select, textarea');
|
||||
var dependency_list = input.data('dependency_list') || [];
|
||||
var dependencies = [];
|
||||
$.each(dependency_list, function(i, field_name) {
|
||||
dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id'));
|
||||
});
|
||||
if (dependencies.length) {
|
||||
input.prepopulate(dependencies, input.attr('maxlength'));
|
||||
}
|
||||
});
|
||||
};
|
||||
$(rows).formset({
|
||||
prefix: "{{ inline_admin_formset.formset.prefix }}",
|
||||
addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}",
|
||||
formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
|
||||
deleteCssClass: "inline-deletelink",
|
||||
deleteText: "{% trans "Remove" %}",
|
||||
emptyCssClass: "empty-form",
|
||||
removed: updateInlineLabel,
|
||||
added: (function(row) {
|
||||
initPrepopulatedFields(row);
|
||||
reinitDateTimeShortCuts();
|
||||
updateSelectFilter();
|
||||
updateInlineLabel(row);
|
||||
})
|
||||
});
|
||||
});
|
||||
$("#{{ inline_admin_formset.formset.prefix }}-group .inline-related").stackedFormset({
|
||||
prefix: '{{ inline_admin_formset.formset.prefix }}',
|
||||
adminStaticPrefix: '{% static "admin/" %}',
|
||||
deleteText: "{% trans "Remove" %}",
|
||||
addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}"
|
||||
});
|
||||
})(django.jQuery);
|
||||
</script>
|
||||
|
|
|
@ -67,64 +67,13 @@
|
|||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
(function($) {
|
||||
$(document).ready(function($) {
|
||||
var rows = "#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr";
|
||||
var alternatingRows = function(row) {
|
||||
$(rows).not(".add-row").removeClass("row1 row2")
|
||||
.filter(":even").addClass("row1").end()
|
||||
.filter(rows + ":odd").addClass("row2");
|
||||
}
|
||||
var reinitDateTimeShortCuts = function() {
|
||||
// Reinitialize the calendar and clock widgets by force
|
||||
if (typeof DateTimeShortcuts != "undefined") {
|
||||
$(".datetimeshortcuts").remove();
|
||||
DateTimeShortcuts.init();
|
||||
}
|
||||
}
|
||||
var updateSelectFilter = function() {
|
||||
// If any SelectFilter widgets are a part of the new form,
|
||||
// instantiate a new SelectFilter instance for it.
|
||||
if (typeof SelectFilter != "undefined"){
|
||||
$(".selectfilter").each(function(index, value){
|
||||
var namearr = value.name.split('-');
|
||||
SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% static "admin/" %}");
|
||||
});
|
||||
$(".selectfilterstacked").each(function(index, value){
|
||||
var namearr = value.name.split('-');
|
||||
SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% static "admin/" %}");
|
||||
});
|
||||
}
|
||||
}
|
||||
var initPrepopulatedFields = function(row) {
|
||||
row.find('.prepopulated_field').each(function() {
|
||||
var field = $(this);
|
||||
var input = field.find('input, select, textarea');
|
||||
var dependency_list = input.data('dependency_list') || [];
|
||||
var dependencies = [];
|
||||
$.each(dependency_list, function(i, field_name) {
|
||||
dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id'));
|
||||
});
|
||||
if (dependencies.length) {
|
||||
input.prepopulate(dependencies, input.attr('maxlength'));
|
||||
}
|
||||
});
|
||||
}
|
||||
$(rows).formset({
|
||||
prefix: "{{ inline_admin_formset.formset.prefix }}",
|
||||
addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}",
|
||||
formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
|
||||
deleteCssClass: "inline-deletelink",
|
||||
deleteText: "{% trans "Remove" %}",
|
||||
emptyCssClass: "empty-form",
|
||||
removed: alternatingRows,
|
||||
added: (function(row) {
|
||||
initPrepopulatedFields(row);
|
||||
reinitDateTimeShortCuts();
|
||||
updateSelectFilter();
|
||||
alternatingRows(row);
|
||||
})
|
||||
});
|
||||
});
|
||||
$("#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr").tabularFormset({
|
||||
prefix: "{{ inline_admin_formset.formset.prefix }}",
|
||||
adminStaticPrefix: '{% static "admin/" %}',
|
||||
addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
|
||||
deleteText: "{% trans 'Remove' %}"
|
||||
});
|
||||
})(django.jQuery);
|
||||
</script>
|
||||
|
|
|
@ -182,7 +182,7 @@ def items_for_result(cl, result, form):
|
|||
row_class = ''
|
||||
try:
|
||||
f, attr, value = lookup_field(field_name, result, cl.model_admin)
|
||||
except (AttributeError, ObjectDoesNotExist):
|
||||
except ObjectDoesNotExist:
|
||||
result_repr = EMPTY_CHANGELIST_VALUE
|
||||
else:
|
||||
if f is None:
|
||||
|
|
|
@ -4,7 +4,7 @@ import datetime
|
|||
import decimal
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.sql.constants import LOOKUP_SEP
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.deletion import Collector
|
||||
from django.db.models.related import RelatedObject
|
||||
from django.forms.forms import pretty_name
|
||||
|
|
|
@ -8,6 +8,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME
|
|||
from django.core.exceptions import PermissionDenied
|
||||
from django.utils.decorators import available_attrs
|
||||
from django.utils.encoding import force_str
|
||||
from django.shortcuts import resolve_url
|
||||
|
||||
|
||||
def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
|
||||
|
@ -23,11 +24,10 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE
|
|||
if test_func(request.user):
|
||||
return view_func(request, *args, **kwargs)
|
||||
path = request.build_absolute_uri()
|
||||
# urlparse chokes on lazy objects in Python 3
|
||||
login_url_as_str = force_str(login_url or settings.LOGIN_URL)
|
||||
resolved_login_url = resolve_url(login_url or settings.LOGIN_URL)
|
||||
# If the login url is the same scheme and net location then just
|
||||
# use the path as the "next" url.
|
||||
login_scheme, login_netloc = urlparse(login_url_as_str)[:2]
|
||||
login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
|
||||
current_scheme, current_netloc = urlparse(path)[:2]
|
||||
if ((not login_scheme or login_scheme == current_scheme) and
|
||||
(not login_netloc or login_netloc == current_netloc)):
|
||||
|
|
|
@ -28,7 +28,7 @@ class LoginRequiredTestCase(AuthViewsTestCase):
|
|||
pass
|
||||
login_required(normal_view)
|
||||
|
||||
def testLoginRequired(self, view_url='/login_required/', login_url=settings.LOGIN_URL):
|
||||
def testLoginRequired(self, view_url='/login_required/', login_url='/login/'):
|
||||
"""
|
||||
Check that login_required works on a simple view wrapped in a
|
||||
login_required decorator.
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.models import (Group, User, SiteProfileNotAvailable,
|
||||
UserManager)
|
||||
from django.test import TestCase, skipIfCustomUser
|
||||
from django.test.utils import override_settings
|
||||
from django.contrib.auth.models import (Group, User,
|
||||
SiteProfileNotAvailable, UserManager)
|
||||
from django.utils import six
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
|
@ -14,19 +15,19 @@ class ProfileTestCase(TestCase):
|
|||
|
||||
# calling get_profile without AUTH_PROFILE_MODULE set
|
||||
del settings.AUTH_PROFILE_MODULE
|
||||
with self.assertRaisesRegexp(SiteProfileNotAvailable,
|
||||
with six.assertRaisesRegex(self, SiteProfileNotAvailable,
|
||||
"You need to set AUTH_PROFILE_MODULE in your project"):
|
||||
user.get_profile()
|
||||
|
||||
# Bad syntax in AUTH_PROFILE_MODULE:
|
||||
settings.AUTH_PROFILE_MODULE = 'foobar'
|
||||
with self.assertRaisesRegexp(SiteProfileNotAvailable,
|
||||
with six.assertRaisesRegex(self, SiteProfileNotAvailable,
|
||||
"app_label and model_name should be separated by a dot"):
|
||||
user.get_profile()
|
||||
|
||||
# module that doesn't exist
|
||||
settings.AUTH_PROFILE_MODULE = 'foo.bar'
|
||||
with self.assertRaisesRegexp(SiteProfileNotAvailable,
|
||||
with six.assertRaisesRegex(self, SiteProfileNotAvailable,
|
||||
"Unable to load the profile model"):
|
||||
user.get_profile()
|
||||
|
||||
|
|
|
@ -7,9 +7,9 @@ from django.conf import settings
|
|||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponseRedirect, QueryDict
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.http import base36_to_int
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.shortcuts import resolve_url
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
|
@ -38,16 +38,16 @@ def login(request, template_name='registration/login.html',
|
|||
if request.method == "POST":
|
||||
form = authentication_form(data=request.POST)
|
||||
if form.is_valid():
|
||||
netloc = urlparse(redirect_to)[1]
|
||||
|
||||
# Use default setting if redirect_to is empty
|
||||
if not redirect_to:
|
||||
redirect_to = settings.LOGIN_REDIRECT_URL
|
||||
redirect_to = resolve_url(redirect_to)
|
||||
|
||||
netloc = urlparse(redirect_to)[1]
|
||||
# Heavier security check -- don't allow redirection to a different
|
||||
# host.
|
||||
elif netloc and netloc != request.get_host():
|
||||
redirect_to = settings.LOGIN_REDIRECT_URL
|
||||
if netloc and netloc != request.get_host():
|
||||
redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
|
||||
|
||||
# Okay, security checks complete. Log the user in.
|
||||
auth_login(request, form.get_user())
|
||||
|
@ -112,6 +112,7 @@ def logout_then_login(request, login_url=None, current_app=None, extra_context=N
|
|||
"""
|
||||
if not login_url:
|
||||
login_url = settings.LOGIN_URL
|
||||
login_url = resolve_url(login_url)
|
||||
return logout(request, login_url, current_app=current_app, extra_context=extra_context)
|
||||
|
||||
|
||||
|
@ -120,10 +121,9 @@ def redirect_to_login(next, login_url=None,
|
|||
"""
|
||||
Redirects the user to the login page, passing the given 'next' page
|
||||
"""
|
||||
# urlparse chokes on lazy objects in Python 3
|
||||
login_url_as_str = force_str(login_url or settings.LOGIN_URL)
|
||||
resolved_url = resolve_url(login_url or settings.LOGIN_URL)
|
||||
|
||||
login_url_parts = list(urlparse(login_url_as_str))
|
||||
login_url_parts = list(urlparse(resolved_url))
|
||||
if redirect_field_name:
|
||||
querystring = QueryDict(login_url_parts[4], mutable=True)
|
||||
querystring[redirect_field_name] = next
|
||||
|
@ -236,7 +236,7 @@ def password_reset_complete(request,
|
|||
template_name='registration/password_reset_complete.html',
|
||||
current_app=None, extra_context=None):
|
||||
context = {
|
||||
'login_url': settings.LOGIN_URL
|
||||
'login_url': resolve_url(settings.LOGIN_URL)
|
||||
}
|
||||
if extra_context is not None:
|
||||
context.update(extra_context)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
from django.db.models.sql.constants import LOOKUP_SEP
|
||||
from django.db.models.sql.expressions import SQLEvaluator
|
||||
from django.db.models.sql.where import Constraint, WhereNode
|
||||
from django.contrib.gis.db.models.fields import GeometryField
|
||||
|
|
|
@ -4,6 +4,7 @@ HR-specific Form helpers
|
|||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from django.contrib.localflavor.hr.hr_choices import (
|
||||
|
@ -91,17 +92,16 @@ class HRJMBGField(Field):
|
|||
dd = int(matches.group('dd'))
|
||||
mm = int(matches.group('mm'))
|
||||
yyy = int(matches.group('yyy'))
|
||||
import datetime
|
||||
try:
|
||||
datetime.date(yyy,mm,dd)
|
||||
except:
|
||||
datetime.date(yyy, mm, dd)
|
||||
except ValueError:
|
||||
raise ValidationError(self.error_messages['date'])
|
||||
|
||||
# Validate checksum.
|
||||
k = matches.group('k')
|
||||
checksum = 0
|
||||
for i,j in zip(range(7,1,-1),range(6)):
|
||||
checksum+=i*(int(value[j])+int(value[13-i]))
|
||||
for i, j in zip(range(7, 1, -1), range(6)):
|
||||
checksum += i * (int(value[j]) + int(value[13 - i]))
|
||||
m = 11 - checksum % 11
|
||||
if m == 10:
|
||||
raise ValidationError(self.error_messages['invalid'])
|
||||
|
|
|
@ -4,6 +4,8 @@ Romanian specific form helpers.
|
|||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import datetime
|
||||
|
||||
from django.contrib.localflavor.ro.ro_counties import COUNTIES_CHOICES
|
||||
from django.core.validators import EMPTY_VALUES
|
||||
from django.forms import ValidationError, Field, RegexField, Select
|
||||
|
@ -69,10 +71,9 @@ class ROCNPField(RegexField):
|
|||
if value in EMPTY_VALUES:
|
||||
return ''
|
||||
# check birthdate digits
|
||||
import datetime
|
||||
try:
|
||||
datetime.date(int(value[1:3]),int(value[3:5]),int(value[5:7]))
|
||||
except:
|
||||
datetime.date(int(value[1:3]), int(value[3:5]), int(value[5:7]))
|
||||
except ValueError:
|
||||
raise ValidationError(self.error_messages['invalid'])
|
||||
# checksum
|
||||
key = '279146358279'
|
||||
|
@ -118,7 +119,7 @@ class ROCountyField(Field):
|
|||
# search for county name
|
||||
normalized_CC = []
|
||||
for entry in COUNTIES_CHOICES:
|
||||
normalized_CC.append((entry[0],entry[1].upper()))
|
||||
normalized_CC.append((entry[0], entry[1].upper()))
|
||||
for entry in normalized_CC:
|
||||
if entry[1] == value:
|
||||
return entry[0]
|
||||
|
@ -153,8 +154,8 @@ class ROIBANField(RegexField):
|
|||
value = super(ROIBANField, self).clean(value)
|
||||
if value in EMPTY_VALUES:
|
||||
return ''
|
||||
value = value.replace('-','')
|
||||
value = value.replace(' ','')
|
||||
value = value.replace('-', '')
|
||||
value = value.replace(' ', '')
|
||||
value = value.upper()
|
||||
if value[0:2] != 'RO':
|
||||
raise ValidationError(self.error_messages['invalid'])
|
||||
|
@ -185,10 +186,10 @@ class ROPhoneNumberField(RegexField):
|
|||
value = super(ROPhoneNumberField, self).clean(value)
|
||||
if value in EMPTY_VALUES:
|
||||
return ''
|
||||
value = value.replace('-','')
|
||||
value = value.replace('(','')
|
||||
value = value.replace(')','')
|
||||
value = value.replace(' ','')
|
||||
value = value.replace('-', '')
|
||||
value = value.replace('(', '')
|
||||
value = value.replace(')', '')
|
||||
value = value.replace(' ', '')
|
||||
if len(value) != 10:
|
||||
raise ValidationError(self.error_messages['invalid'])
|
||||
return value
|
||||
|
@ -202,4 +203,3 @@ class ROPostalCodeField(RegexField):
|
|||
def __init__(self, max_length=6, min_length=6, *args, **kwargs):
|
||||
super(ROPostalCodeField, self).__init__(r'^[0-9][0-8][0-9]{4}$',
|
||||
max_length, min_length, *args, **kwargs)
|
||||
|
||||
|
|
|
@ -47,6 +47,9 @@ def markdown(value, arg=''):
|
|||
they will be silently ignored.
|
||||
|
||||
"""
|
||||
import warnings
|
||||
warnings.warn('The markdown filter has been deprecated',
|
||||
category=DeprecationWarning)
|
||||
try:
|
||||
import markdown
|
||||
except ImportError:
|
||||
|
@ -72,6 +75,9 @@ def markdown(value, arg=''):
|
|||
|
||||
@register.filter(is_safe=True)
|
||||
def restructuredtext(value):
|
||||
import warnings
|
||||
warnings.warn('The restructuredtext filter has been deprecated',
|
||||
category=DeprecationWarning)
|
||||
try:
|
||||
from docutils.core import publish_parts
|
||||
except ImportError:
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# Quick tests for the markup templatetags (django.contrib.markup)
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from django.template import Template, Context
|
||||
from django import test
|
||||
from django.utils import unittest
|
||||
from django.utils.html import escape
|
||||
|
||||
|
@ -21,7 +23,7 @@ try:
|
|||
except ImportError:
|
||||
docutils = None
|
||||
|
||||
class Templates(unittest.TestCase):
|
||||
class Templates(test.TestCase):
|
||||
|
||||
textile_content = """Paragraph 1
|
||||
|
||||
|
@ -37,6 +39,13 @@ Paragraph 2 with a link_
|
|||
|
||||
.. _link: http://www.example.com/"""
|
||||
|
||||
def setUp(self):
|
||||
self.save_warnings_state()
|
||||
warnings.filterwarnings('ignore', category=DeprecationWarning, module='django.contrib.markup')
|
||||
|
||||
def tearDown(self):
|
||||
self.restore_warnings_state()
|
||||
|
||||
@unittest.skipUnless(textile, 'textile not installed')
|
||||
def test_textile(self):
|
||||
t = Template("{% load markup %}{{ textile_content|textile }}")
|
||||
|
|
|
@ -46,10 +46,10 @@ class CookieStorage(BaseStorage):
|
|||
Stores messages in a cookie.
|
||||
"""
|
||||
cookie_name = 'messages'
|
||||
# We should be able to store 4K in a cookie, but Internet Explorer
|
||||
# imposes 4K as the *total* limit for a domain. To allow other
|
||||
# cookies, we go for 3/4 of 4K.
|
||||
max_cookie_size = 3072
|
||||
# uwsgi's default configuration enforces a maximum size of 4kb for all the
|
||||
# HTTP headers. In order to leave some room for other cookies and headers,
|
||||
# restrict the session cookie to 1/2 of 4kb. See #18781.
|
||||
max_cookie_size = 2048
|
||||
not_finished = '__messagesnotfinished__'
|
||||
|
||||
def _get(self, *args, **kwargs):
|
||||
|
|
|
@ -152,7 +152,7 @@ class BaseTest(TestCase):
|
|||
cycle.
|
||||
"""
|
||||
data = {
|
||||
'messages': ['Test message %d' % x for x in range(10)],
|
||||
'messages': ['Test message %d' % x for x in range(5)],
|
||||
}
|
||||
show_url = reverse('django.contrib.messages.tests.urls.show')
|
||||
for level in ('debug', 'info', 'success', 'warning', 'error'):
|
||||
|
@ -170,7 +170,7 @@ class BaseTest(TestCase):
|
|||
@override_settings(MESSAGE_LEVEL=constants.DEBUG)
|
||||
def test_with_template_response(self):
|
||||
data = {
|
||||
'messages': ['Test message %d' % x for x in range(10)],
|
||||
'messages': ['Test message %d' % x for x in range(5)],
|
||||
}
|
||||
show_url = reverse('django.contrib.messages.tests.urls.show_template_response')
|
||||
for level in self.levels.keys():
|
||||
|
@ -194,7 +194,7 @@ class BaseTest(TestCase):
|
|||
before a GET.
|
||||
"""
|
||||
data = {
|
||||
'messages': ['Test message %d' % x for x in range(10)],
|
||||
'messages': ['Test message %d' % x for x in range(5)],
|
||||
}
|
||||
show_url = reverse('django.contrib.messages.tests.urls.show')
|
||||
messages = []
|
||||
|
@ -226,7 +226,7 @@ class BaseTest(TestCase):
|
|||
when one attempts to store a message.
|
||||
"""
|
||||
data = {
|
||||
'messages': ['Test message %d' % x for x in range(10)],
|
||||
'messages': ['Test message %d' % x for x in range(5)],
|
||||
}
|
||||
show_url = reverse('django.contrib.messages.tests.urls.show')
|
||||
for level in ('debug', 'info', 'success', 'warning', 'error'):
|
||||
|
@ -251,7 +251,7 @@ class BaseTest(TestCase):
|
|||
raised if 'fail_silently' = True
|
||||
"""
|
||||
data = {
|
||||
'messages': ['Test message %d' % x for x in range(10)],
|
||||
'messages': ['Test message %d' % x for x in range(5)],
|
||||
'fail_silently': True,
|
||||
}
|
||||
show_url = reverse('django.contrib.messages.tests.urls.show')
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
import shutil
|
||||
import string
|
||||
import tempfile
|
||||
|
@ -302,11 +302,10 @@ class CacheDBSessionTests(SessionTestsMixin, TestCase):
|
|||
self.assertTrue(self.session.exists(self.session.session_key))
|
||||
|
||||
def test_load_overlong_key(self):
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter("always")
|
||||
# Some backends might issue a warning
|
||||
with warnings.catch_warnings():
|
||||
self.session._session_key = (string.ascii_letters + string.digits) * 20
|
||||
self.assertEqual(self.session.load(), {})
|
||||
self.assertEqual(len(w), 1)
|
||||
|
||||
|
||||
@override_settings(USE_TZ=True)
|
||||
|
@ -352,11 +351,10 @@ class CacheSessionTests(SessionTestsMixin, unittest.TestCase):
|
|||
backend = CacheSession
|
||||
|
||||
def test_load_overlong_key(self):
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter("always")
|
||||
# Some backends might issue a warning
|
||||
with warnings.catch_warnings():
|
||||
self.session._session_key = (string.ascii_letters + string.digits) * 20
|
||||
self.assertEqual(self.session.load(), {})
|
||||
self.assertEqual(len(w), 1)
|
||||
|
||||
|
||||
class SessionMiddlewareTests(unittest.TestCase):
|
||||
|
|
|
@ -141,7 +141,7 @@ class CacheClass(BaseMemcachedCache):
|
|||
)
|
||||
try:
|
||||
import memcache
|
||||
except:
|
||||
except ImportError:
|
||||
raise InvalidCacheBackendError(
|
||||
"Memcached cache backend requires either the 'memcache' or 'cmemcache' library"
|
||||
)
|
||||
|
|
|
@ -21,7 +21,7 @@ class EmailBackend(BaseEmailBackend):
|
|||
stream_created = self.open()
|
||||
for message in email_messages:
|
||||
self.stream.write('%s\n' % message.message().as_string())
|
||||
self.stream.write('-'*79)
|
||||
self.stream.write('-' * 79)
|
||||
self.stream.write('\n')
|
||||
self.stream.flush() # flush after each message
|
||||
if stream_created:
|
||||
|
|
|
@ -5,6 +5,7 @@ from optparse import OptionParser, NO_DEFAULT
|
|||
import imp
|
||||
import warnings
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.management.base import BaseCommand, CommandError, handle_default_options
|
||||
from django.core.management.color import color_style
|
||||
from django.utils.importlib import import_module
|
||||
|
@ -105,7 +106,7 @@ def get_commands():
|
|||
try:
|
||||
from django.conf import settings
|
||||
apps = settings.INSTALLED_APPS
|
||||
except (AttributeError, EnvironmentError, ImportError):
|
||||
except (AttributeError, ImproperlyConfigured):
|
||||
apps = []
|
||||
|
||||
# Find and load the management module for each installed app.
|
||||
|
|
|
@ -1034,9 +1034,12 @@ class BaseDatabaseIntrospection(object):
|
|||
|
||||
def get_primary_key_column(self, cursor, table_name):
|
||||
"""
|
||||
Backends can override this to return the column name of the primary key for the given table.
|
||||
Returns the name of the primary key column for the given table.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
for column in six.iteritems(self.get_indexes(cursor, table_name)):
|
||||
if column[1]['primary_key']:
|
||||
return column[0]
|
||||
return None
|
||||
|
||||
def get_indexes(self, cursor, table_name):
|
||||
"""
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import hashlib
|
||||
import sys
|
||||
import time
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.utils import load_backend
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.utils.six.moves import input
|
||||
|
||||
# The prefix to put on the default database name when creating
|
||||
|
@ -27,7 +29,10 @@ class BaseDatabaseCreation(object):
|
|||
Generates a 32-bit digest of a set of arguments that can be used to
|
||||
shorten identifying names.
|
||||
"""
|
||||
return '%x' % (abs(hash(args)) % 4294967296) # 2**32
|
||||
h = hashlib.md5()
|
||||
for arg in args:
|
||||
h.update(force_bytes(arg))
|
||||
return h.hexdigest()[:8]
|
||||
|
||||
def sql_create_model(self, model, style, known_models=set()):
|
||||
"""
|
||||
|
|
|
@ -2,7 +2,6 @@ import re
|
|||
from .base import FIELD_TYPE
|
||||
|
||||
from django.db.backends import BaseDatabaseIntrospection
|
||||
from django.utils import six
|
||||
|
||||
|
||||
foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
|
||||
|
@ -88,15 +87,6 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
|
|||
key_columns.extend(cursor.fetchall())
|
||||
return key_columns
|
||||
|
||||
def get_primary_key_column(self, cursor, table_name):
|
||||
"""
|
||||
Returns the name of the primary key column for the given table
|
||||
"""
|
||||
for column in six.iteritems(self.get_indexes(cursor, table_name)):
|
||||
if column[1]['primary_key']:
|
||||
return column[0]
|
||||
return None
|
||||
|
||||
def get_indexes(self, cursor, table_name):
|
||||
cursor.execute("SHOW INDEX FROM %s" % self.connection.ops.quote_name(table_name))
|
||||
# Do a two-pass search for indexes: on first pass check which indexes
|
||||
|
|
|
@ -412,7 +412,4 @@ def _sqlite_format_dtdelta(dt, conn, days, secs, usecs):
|
|||
return str(dt)
|
||||
|
||||
def _sqlite_regexp(re_pattern, re_string):
|
||||
try:
|
||||
return bool(re.search(re_pattern, re_string))
|
||||
except:
|
||||
return False
|
||||
return bool(re.search(re_pattern, re_string))
|
||||
|
|
|
@ -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.db import connections, router, transaction, IntegrityError
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.fields import AutoField
|
||||
from django.db.models.query_utils import (Q, select_related_descend,
|
||||
deferred_class_factory, InvalidQuery)
|
||||
|
@ -1613,8 +1614,6 @@ def prefetch_related_objects(result_cache, related_lookups):
|
|||
Populates prefetched objects caches for a list of results
|
||||
from a QuerySet
|
||||
"""
|
||||
from django.db.models.sql.constants import LOOKUP_SEP
|
||||
|
||||
if len(result_cache) == 0:
|
||||
return # nothing to do
|
||||
|
||||
|
|
|
@ -3,8 +3,10 @@ from django.utils.six.moves import zip
|
|||
from django.core.exceptions import FieldError
|
||||
from django.db import transaction
|
||||
from django.db.backends.util import truncate_name
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.query_utils import select_related_descend
|
||||
from django.db.models.sql.constants import *
|
||||
from django.db.models.sql.constants import (SINGLE, MULTI, ORDER_DIR,
|
||||
GET_ITERATOR_CHUNK_SIZE)
|
||||
from django.db.models.sql.datastructures import EmptyResultSet
|
||||
from django.db.models.sql.expressions import SQLEvaluator
|
||||
from django.db.models.sql.query import get_order_dir, Query
|
||||
|
@ -811,7 +813,7 @@ class SQLCompiler(object):
|
|||
raise EmptyResultSet
|
||||
except EmptyResultSet:
|
||||
if result_type == MULTI:
|
||||
return empty_iter()
|
||||
return iter([])
|
||||
else:
|
||||
return
|
||||
|
||||
|
@ -1088,13 +1090,6 @@ class SQLDateCompiler(SQLCompiler):
|
|||
yield date
|
||||
|
||||
|
||||
def empty_iter():
|
||||
"""
|
||||
Returns an iterator containing no results.
|
||||
"""
|
||||
yield next(iter([]))
|
||||
|
||||
|
||||
def order_modified_iter(cursor, trim, sentinel):
|
||||
"""
|
||||
Yields blocks of rows from a cursor. We use this iterator in the special
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
"""
|
||||
Constants specific to the SQL storage portion of the ORM.
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
import re
|
||||
|
||||
# Valid query types (a set is used for speedy lookups).
|
||||
# Valid query types (a set is used for speedy lookups). These are (currently)
|
||||
# considered SQL-specific; other storage systems may choose to use different
|
||||
# lookup types.
|
||||
QUERY_TERMS = set([
|
||||
'exact', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte', 'in',
|
||||
'startswith', 'istartswith', 'endswith', 'iendswith', 'range', 'year',
|
||||
|
@ -12,9 +18,6 @@ QUERY_TERMS = set([
|
|||
# Larger values are slightly faster at the expense of more storage space.
|
||||
GET_ITERATOR_CHUNK_SIZE = 100
|
||||
|
||||
# Separator used to split filter strings apart.
|
||||
LOOKUP_SEP = '__'
|
||||
|
||||
# Constants to make looking up tuple values clearer.
|
||||
# Join lists (indexes into the tuples that are values in the alias_map
|
||||
# dictionary in the Query class).
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.core.exceptions import FieldError
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
from django.db.models.sql.constants import LOOKUP_SEP
|
||||
|
||||
class SQLEvaluator(object):
|
||||
def __init__(self, expression, query, allow_joins=True):
|
||||
|
|
|
@ -15,11 +15,12 @@ from django.utils.tree import Node
|
|||
from django.utils import six
|
||||
from django.db import connections, DEFAULT_DB_ALIAS
|
||||
from django.db.models import signals
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.expressions import ExpressionNode
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
from django.db.models.query_utils import InvalidQuery
|
||||
from django.db.models.sql import aggregates as base_aggregates_module
|
||||
from django.db.models.sql.constants import *
|
||||
from django.db.models.sql.constants import (QUERY_TERMS, ORDER_DIR, SINGLE,
|
||||
ORDER_PATTERN, JoinInfo)
|
||||
from django.db.models.sql.datastructures import EmptyResultSet, Empty, MultiJoin
|
||||
from django.db.models.sql.expressions import SQLEvaluator
|
||||
from django.db.models.sql.where import (WhereNode, Constraint, EverythingNode,
|
||||
|
@ -28,6 +29,7 @@ from django.core.exceptions import FieldError
|
|||
|
||||
__all__ = ['Query', 'RawQuery']
|
||||
|
||||
|
||||
class RawQuery(object):
|
||||
"""
|
||||
A single raw SQL query
|
||||
|
|
|
@ -3,6 +3,7 @@ Query subclasses which provide extra functionality beyond simple data retrieval.
|
|||
"""
|
||||
|
||||
from django.core.exceptions import FieldError
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.fields import DateField, FieldDoesNotExist
|
||||
from django.db.models.sql.constants import *
|
||||
from django.db.models.sql.datastructures import Date
|
||||
|
|
|
@ -507,11 +507,7 @@ class CheckboxInput(Widget):
|
|||
|
||||
def render(self, name, value, attrs=None):
|
||||
final_attrs = self.build_attrs(attrs, type='checkbox', name=name)
|
||||
try:
|
||||
result = self.check_test(value)
|
||||
except: # Silently catch exceptions
|
||||
result = False
|
||||
if result:
|
||||
if self.check_test(value):
|
||||
final_attrs['checked'] = 'checked'
|
||||
if not (value is True or value is False or value is None or value == ''):
|
||||
# Only add the 'value' attribute if a value is non-empty.
|
||||
|
@ -525,7 +521,7 @@ class CheckboxInput(Widget):
|
|||
return False
|
||||
value = data.get(name)
|
||||
# Translate true and false strings to boolean values.
|
||||
values = {'true': True, 'false': False}
|
||||
values = {'true': True, 'false': False}
|
||||
if isinstance(value, six.string_types):
|
||||
value = values.get(value.lower(), value)
|
||||
return value
|
||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals
|
|||
|
||||
import copy
|
||||
import datetime
|
||||
from email.header import Header
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
@ -560,31 +561,44 @@ class HttpResponse(object):
|
|||
else:
|
||||
__str__ = serialize
|
||||
|
||||
def _convert_to_ascii(self, *values):
|
||||
"""Converts all values to ascii strings."""
|
||||
for value in values:
|
||||
if not isinstance(value, six.string_types):
|
||||
value = str(value)
|
||||
try:
|
||||
if six.PY3:
|
||||
# Ensure string only contains ASCII
|
||||
value.encode('us-ascii')
|
||||
def _convert_to_charset(self, value, charset, mime_encode=False):
|
||||
"""Converts headers key/value to ascii/latin1 native strings.
|
||||
|
||||
`charset` must be 'ascii' or 'latin-1'. If `mime_encode` is True and
|
||||
`value` value can't be represented in the given charset, MIME-encoding
|
||||
is applied.
|
||||
"""
|
||||
if not isinstance(value, (bytes, six.text_type)):
|
||||
value = str(value)
|
||||
try:
|
||||
if six.PY3:
|
||||
if isinstance(value, str):
|
||||
# Ensure string is valid in given charset
|
||||
value.encode(charset)
|
||||
else:
|
||||
if isinstance(value, str):
|
||||
# Ensure string only contains ASCII
|
||||
value.decode('us-ascii')
|
||||
else:
|
||||
# Convert unicode to an ASCII string
|
||||
value = value.encode('us-ascii')
|
||||
except UnicodeError as e:
|
||||
e.reason += ', HTTP response headers must be in US-ASCII format'
|
||||
# Convert bytestring using given charset
|
||||
value = value.decode(charset)
|
||||
else:
|
||||
if isinstance(value, str):
|
||||
# Ensure string is valid in given charset
|
||||
value.decode(charset)
|
||||
else:
|
||||
# Convert unicode string to given charset
|
||||
value = value.encode(charset)
|
||||
except UnicodeError as e:
|
||||
if mime_encode:
|
||||
# Wrapping in str() is a workaround for #12422 under Python 2.
|
||||
value = str(Header(value, 'utf-8').encode())
|
||||
else:
|
||||
e.reason += ', HTTP response headers must be in %s format' % charset
|
||||
raise
|
||||
if '\n' in value or '\r' in value:
|
||||
raise BadHeaderError("Header values can't contain newlines (got %r)" % value)
|
||||
yield value
|
||||
if str('\n') in value or str('\r') in value:
|
||||
raise BadHeaderError("Header values can't contain newlines (got %r)" % value)
|
||||
return value
|
||||
|
||||
def __setitem__(self, header, value):
|
||||
header, value = self._convert_to_ascii(header, value)
|
||||
header = self._convert_to_charset(header, 'ascii')
|
||||
value = self._convert_to_charset(value, 'latin1', mime_encode=True)
|
||||
self._headers[header.lower()] = (header, value)
|
||||
|
||||
def __delitem__(self, header):
|
||||
|
|
|
@ -68,11 +68,10 @@ class MultiPartParser(object):
|
|||
if not boundary or not cgi.valid_boundary(boundary):
|
||||
raise MultiPartParserError('Invalid boundary in multipart: %s' % boundary)
|
||||
|
||||
|
||||
# Content-Length should contain the length of the body we are about
|
||||
# to receive.
|
||||
try:
|
||||
content_length = int(META.get('HTTP_CONTENT_LENGTH', META.get('CONTENT_LENGTH',0)))
|
||||
content_length = int(META.get('HTTP_CONTENT_LENGTH', META.get('CONTENT_LENGTH', 0)))
|
||||
except (ValueError, TypeError):
|
||||
content_length = 0
|
||||
|
||||
|
@ -178,7 +177,7 @@ class MultiPartParser(object):
|
|||
|
||||
content_type = meta_data.get('content-type', ('',))[0].strip()
|
||||
try:
|
||||
charset = meta_data.get('content-type', (0,{}))[1].get('charset', None)
|
||||
charset = meta_data.get('content-type', (0, {}))[1].get('charset', None)
|
||||
except:
|
||||
charset = None
|
||||
|
||||
|
|
|
@ -66,23 +66,7 @@ def redirect(to, *args, **kwargs):
|
|||
else:
|
||||
redirect_class = HttpResponseRedirect
|
||||
|
||||
# If it's a model, use get_absolute_url()
|
||||
if hasattr(to, 'get_absolute_url'):
|
||||
return redirect_class(to.get_absolute_url())
|
||||
|
||||
# Next try a reverse URL resolution.
|
||||
try:
|
||||
return redirect_class(urlresolvers.reverse(to, args=args, kwargs=kwargs))
|
||||
except urlresolvers.NoReverseMatch:
|
||||
# If this is a callable, re-raise.
|
||||
if callable(to):
|
||||
raise
|
||||
# If this doesn't "feel" like a URL, re-raise.
|
||||
if '/' not in to and '.' not in to:
|
||||
raise
|
||||
|
||||
# Finally, fall back and assume it's a URL
|
||||
return redirect_class(to)
|
||||
return redirect_class(resolve_url(to, *args, **kwargs))
|
||||
|
||||
def _get_queryset(klass):
|
||||
"""
|
||||
|
@ -128,3 +112,34 @@ def get_list_or_404(klass, *args, **kwargs):
|
|||
raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
|
||||
return obj_list
|
||||
|
||||
def resolve_url(to, *args, **kwargs):
|
||||
"""
|
||||
Return a URL appropriate for the arguments passed.
|
||||
|
||||
The arguments could be:
|
||||
|
||||
* A model: the model's `get_absolute_url()` function will be called.
|
||||
|
||||
* A view name, possibly with arguments: `urlresolvers.reverse()` will
|
||||
be used to reverse-resolve the name.
|
||||
|
||||
* A URL, which will be returned as-is.
|
||||
|
||||
"""
|
||||
# If it's a model, use get_absolute_url()
|
||||
if hasattr(to, 'get_absolute_url'):
|
||||
return to.get_absolute_url()
|
||||
|
||||
# Next try a reverse URL resolution.
|
||||
try:
|
||||
return urlresolvers.reverse(to, args=args, kwargs=kwargs)
|
||||
except urlresolvers.NoReverseMatch:
|
||||
# If this is a callable, re-raise.
|
||||
if callable(to):
|
||||
raise
|
||||
# If this doesn't "feel" like a URL, re-raise.
|
||||
if '/' not in to and '.' not in to:
|
||||
raise
|
||||
|
||||
# Finally, fall back and assume it's a URL
|
||||
return to
|
||||
|
|
|
@ -360,7 +360,7 @@ class SimpleTestCase(ut2.TestCase):
|
|||
args: Extra args.
|
||||
kwargs: Extra kwargs.
|
||||
"""
|
||||
return self.assertRaisesRegexp(expected_exception,
|
||||
return six.assertRaisesRegex(self, expected_exception,
|
||||
re.escape(expected_message), callable_obj, *args, **kwargs)
|
||||
|
||||
def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import os
|
||||
import stat
|
||||
from os.path import join, normcase, normpath, abspath, isabs, sep
|
||||
from os.path import join, normcase, normpath, abspath, isabs, sep, dirname
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils import six
|
||||
|
||||
|
@ -41,13 +41,16 @@ def safe_join(base, *paths):
|
|||
paths = [force_text(p) for p in paths]
|
||||
final_path = abspathu(join(base, *paths))
|
||||
base_path = abspathu(base)
|
||||
base_path_len = len(base_path)
|
||||
# Ensure final_path starts with base_path (using normcase to ensure we
|
||||
# don't false-negative on case insensitive operating systems like Windows)
|
||||
# and that the next character after the final path is os.sep (or nothing,
|
||||
# in which case final_path must be equal to base_path).
|
||||
if not normcase(final_path).startswith(normcase(base_path)) \
|
||||
or final_path[base_path_len:base_path_len+1] not in ('', sep):
|
||||
# don't false-negative on case insensitive operating systems like Windows),
|
||||
# further, one of the following conditions must be true:
|
||||
# a) The next character is the path separator (to prevent conditions like
|
||||
# safe_join("/dir", "/../d"))
|
||||
# b) The final path must be the same as the base path.
|
||||
# c) The base path must be the most root path (meaning either "/" or "C:\\")
|
||||
if (not normcase(final_path).startswith(normcase(base_path + sep)) and
|
||||
normcase(final_path) != normcase(base_path) and
|
||||
dirname(normcase(base_path)) != normcase(base_path)):
|
||||
raise ValueError('The joined path (%s) is located outside of the base '
|
||||
'path component (%s)' % (final_path, base_path))
|
||||
return final_path
|
||||
|
|
|
@ -370,13 +370,20 @@ def with_metaclass(meta, base=object):
|
|||
|
||||
if PY3:
|
||||
_iterlists = "lists"
|
||||
_assertRaisesRegex = "assertRaisesRegex"
|
||||
else:
|
||||
_iterlists = "iterlists"
|
||||
_assertRaisesRegex = "assertRaisesRegexp"
|
||||
|
||||
|
||||
def iterlists(d):
|
||||
"""Return an iterator over the values of a MultiValueDict."""
|
||||
return getattr(d, _iterlists)()
|
||||
|
||||
|
||||
def assertRaisesRegex(self, *args, **kwargs):
|
||||
return getattr(self, _assertRaisesRegex)(*args, **kwargs)
|
||||
|
||||
|
||||
add_move(MovedModule("_dummy_thread", "dummy_thread"))
|
||||
add_move(MovedModule("_thread", "thread"))
|
||||
|
|
|
@ -372,6 +372,9 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View):
|
|||
return qs
|
||||
|
||||
def get_date_list_period(self):
|
||||
"""
|
||||
Get the aggregation period for the list of dates: 'year', 'month', or 'day'.
|
||||
"""
|
||||
return self.date_list_period
|
||||
|
||||
def get_date_list(self, queryset, date_type=None):
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
The documentation in this tree is in plain text files and can be viewed using
|
||||
any text file viewer.
|
||||
|
||||
Technically speaking, it uses ReST (reStructuredText) [1], and the Sphinx
|
||||
documentation system [2]. This allows it to be built into other forms for
|
||||
easier viewing and browsing.
|
||||
It uses ReST (reStructuredText) [1], and the Sphinx documentation system [2].
|
||||
This allows it to be built into other forms for easier viewing and browsing.
|
||||
|
||||
To create an HTML version of the docs:
|
||||
|
||||
|
|
|
@ -16,8 +16,9 @@ How do I get started?
|
|||
What are Django's prerequisites?
|
||||
--------------------------------
|
||||
|
||||
Django requires Python_, specifically Python 2.6.5 - 2.7.x. No other Python
|
||||
libraries are required for basic Django usage.
|
||||
Django requires Python, specifically Python 2.6.5 - 2.7.x. No other Python
|
||||
libraries are required for basic Django usage. Django 1.5 also has
|
||||
experimental support for Python 3.2 and above.
|
||||
|
||||
For a development environment -- if you just want to experiment with Django --
|
||||
you don't need to have a separate Web server installed; Django comes with its
|
||||
|
@ -50,15 +51,12 @@ aren't available under older versions of Python.
|
|||
Third-party applications for use with Django are, of course, free to set their
|
||||
own version requirements.
|
||||
|
||||
Over the next year or two Django will begin dropping support for older Python
|
||||
versions as part of a migration which will end with Django running on Python 3
|
||||
(see below for details).
|
||||
|
||||
All else being equal, we recommend that you use the latest 2.x release
|
||||
(currently Python 2.7). This will let you take advantage of the numerous
|
||||
improvements and optimizations to the Python language since version 2.6, and
|
||||
will help ease the process of dropping support for older Python versions on
|
||||
the road to Python 3.
|
||||
improvements and optimizations to the Python language since version 2.6.
|
||||
|
||||
Generally speaking, we don't recommend running Django on Python 3 yet; see
|
||||
below for more.
|
||||
|
||||
What Python version can I use with Django?
|
||||
------------------------------------------
|
||||
|
@ -71,25 +69,21 @@ Django version Python versions
|
|||
1.2 2.4, 2.5, 2.6, 2.7
|
||||
1.3 2.4, 2.5, 2.6, 2.7
|
||||
**1.4** **2.5, 2.6, 2.7**
|
||||
*1.5 (future)* *2.6, 2.7, 3.x (experimental)*
|
||||
*1.5 (future)* *2.6, 2.7* and *3.2, 3.3 (experimental)*
|
||||
============== ===============
|
||||
|
||||
Can I use Django with Python 3?
|
||||
-------------------------------
|
||||
|
||||
Not at the moment. Python 3.0 introduced a number of
|
||||
backwards-incompatible changes to the Python language, and although
|
||||
these changes are generally a good thing for Python's future, it will
|
||||
be a while before most Python software catches up and is able to run
|
||||
on Python 3.0. For larger Python-based software like Django, the
|
||||
transition is expected to take at least a year or two (since it
|
||||
involves dropping support for older Python releases and so must be
|
||||
done gradually).
|
||||
Django 1.5 introduces experimental support for Python 3.2 and 3.3. However, we
|
||||
don't yet suggest that you use Django and Python 3 in production.
|
||||
|
||||
In the meantime, Python 2.x releases will be supported and provided
|
||||
with bug fixes and security updates by the Python development team, so
|
||||
continuing to use a Python 2.x release during the transition should
|
||||
not present any risk.
|
||||
Python 3 support should be considered a "preview". It's offered to bootstrap
|
||||
the transition of the Django ecosystem to Python 3, and to help you start
|
||||
porting your apps for future Python 3 compatibility. But we're not yet
|
||||
confident enough to promise stability in production.
|
||||
|
||||
Our current plan is to make Django 1.6 suitable for general use with Python 3.
|
||||
|
||||
Will Django run under shared hosting (like TextDrive or Dreamhost)?
|
||||
-------------------------------------------------------------------
|
||||
|
|
|
@ -11,7 +11,7 @@ Then, just do this::
|
|||
|
||||
>>> from django.db import connection
|
||||
>>> connection.queries
|
||||
[{'sql': 'SELECT polls_polls.id,polls_polls.question,polls_polls.pub_date FROM polls_polls',
|
||||
[{'sql': 'SELECT polls_polls.id, polls_polls.question, polls_polls.pub_date FROM polls_polls',
|
||||
'time': '0.002'}]
|
||||
|
||||
``connection.queries`` is only available if :setting:`DEBUG` is ``True``.
|
||||
|
|
|
@ -181,10 +181,10 @@ card values plus their suits; 104 characters in total.
|
|||
Many of Django's model fields accept options that they don't do anything
|
||||
with. For example, you can pass both
|
||||
:attr:`~django.db.models.Field.editable` and
|
||||
:attr:`~django.db.models.Field.auto_now` to a
|
||||
:attr:`~django.db.models.DateField.auto_now` to a
|
||||
:class:`django.db.models.DateField` and it will simply ignore the
|
||||
:attr:`~django.db.models.Field.editable` parameter
|
||||
(:attr:`~django.db.models.Field.auto_now` being set implies
|
||||
(:attr:`~django.db.models.DateField.auto_now` being set implies
|
||||
``editable=False``). No error is raised in this case.
|
||||
|
||||
This behavior simplifies the field classes, because they don't need to
|
||||
|
@ -516,8 +516,8 @@ for the first time, the ``add`` parameter will be ``True``, otherwise it will be
|
|||
You only need to override this method if you want to preprocess the value
|
||||
somehow, just before saving. For example, Django's
|
||||
:class:`~django.db.models.DateTimeField` uses this method to set the attribute
|
||||
correctly in the case of :attr:`~django.db.models.Field.auto_now` or
|
||||
:attr:`~django.db.models.Field.auto_now_add`.
|
||||
correctly in the case of :attr:`~django.db.models.DateField.auto_now` or
|
||||
:attr:`~django.db.models.DateField.auto_now_add`.
|
||||
|
||||
If you do override this method, you must return the value of the attribute at
|
||||
the end. You should also update the model's attribute if you make any changes
|
||||
|
|
|
@ -264,6 +264,9 @@ these changes.
|
|||
in 1.4. The backward compatibility will be removed --
|
||||
``HttpRequest.raw_post_data`` will no longer work.
|
||||
|
||||
* ``django.contrib.markup`` will be removed following an accelerated
|
||||
deprecation.
|
||||
|
||||
1.7
|
||||
---
|
||||
|
||||
|
|
|
@ -10,11 +10,9 @@ Install Python
|
|||
--------------
|
||||
|
||||
Being a Python Web framework, Django requires Python. It works with any Python
|
||||
version from 2.6.5 to 2.7 (due to backwards incompatibilities in Python 3.0,
|
||||
Django does not currently work with Python 3.0; see :doc:`the Django FAQ
|
||||
</faq/install>` for more information on supported Python versions and the 3.0
|
||||
transition), these versions of Python include a lightweight database called
|
||||
SQLite_ so you won't need to set up a database just yet.
|
||||
version from 2.6.5 to 2.7. It also features experimental support for versions
|
||||
3.2 and 3.3. All these versions of Python include a lightweight database
|
||||
called SQLite_ so you won't need to set up a database just yet.
|
||||
|
||||
.. _sqlite: http://sqlite.org/
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ the file ``mysite/news/models.py``::
|
|||
return self.full_name
|
||||
|
||||
class Article(models.Model):
|
||||
pub_date = models.DateTimeField()
|
||||
pub_date = models.DateField()
|
||||
headline = models.CharField(max_length=200)
|
||||
content = models.TextField()
|
||||
reporter = models.ForeignKey(Reporter)
|
||||
|
@ -96,8 +96,8 @@ access your data. The API is created on the fly, no code generation necessary::
|
|||
DoesNotExist: Reporter matching query does not exist. Lookup parameters were {'id': 2}
|
||||
|
||||
# Create an article.
|
||||
>>> from datetime import datetime
|
||||
>>> a = Article(pub_date=datetime.now(), headline='Django is cool',
|
||||
>>> from datetime import date
|
||||
>>> a = Article(pub_date=date.today(), headline='Django is cool',
|
||||
... content='Yeah.', reporter=r)
|
||||
>>> a.save()
|
||||
|
||||
|
@ -140,7 +140,7 @@ as registering your model in the admin site::
|
|||
from django.db import models
|
||||
|
||||
class Article(models.Model):
|
||||
pub_date = models.DateTimeField()
|
||||
pub_date = models.DateField()
|
||||
headline = models.CharField(max_length=200)
|
||||
content = models.TextField()
|
||||
reporter = models.ForeignKey(Reporter)
|
||||
|
|
|
@ -513,11 +513,12 @@ Here's what happens if a user goes to "/polls/34/" in this system:
|
|||
further processing.
|
||||
|
||||
Now that we've decoupled that, we need to decouple the ``polls.urls``
|
||||
URLconf by removing the leading "polls/" from each line, and removing the
|
||||
lines registering the admin site. Your ``polls/urls.py`` file should now look like
|
||||
URLconf by removing the leading "polls/" from each line, removing the
|
||||
lines registering the admin site, and removing the ``include`` import which
|
||||
is no longer used. Your ``polls/urls.py`` file should now look like
|
||||
this::
|
||||
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns('polls.views',
|
||||
url(r'^$', 'index'),
|
||||
|
|
|
@ -218,7 +218,7 @@ Read on for details.
|
|||
First, open the ``polls/urls.py`` URLconf. It looks like this, according to the
|
||||
tutorial so far::
|
||||
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns('polls.views',
|
||||
url(r'^$', 'index'),
|
||||
|
@ -229,7 +229,7 @@ tutorial so far::
|
|||
|
||||
Change it like so::
|
||||
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.conf.urls import patterns, url
|
||||
from django.views.generic import DetailView, ListView
|
||||
from polls.models import Poll
|
||||
|
||||
|
|
|
@ -8,6 +8,11 @@ themselves or inherited from. They may not provide all the capabilities
|
|||
required for projects, in which case there are Mixins and Generic class-based
|
||||
views.
|
||||
|
||||
Many of Django's built-in class-based views inherit from other class-based
|
||||
views or various mixins. Because this inheritence chain is very important, the
|
||||
ancestor classes are documented under the section title of **Ancestors (MRO)**.
|
||||
MRO is an acronym for Method Resolution Order.
|
||||
|
||||
View
|
||||
----
|
||||
|
||||
|
@ -20,6 +25,7 @@ View
|
|||
|
||||
1. :meth:`dispatch()`
|
||||
2. :meth:`http_method_not_allowed()`
|
||||
3. :meth:`options()`
|
||||
|
||||
**Example views.py**::
|
||||
|
||||
|
@ -41,8 +47,20 @@ View
|
|||
url(r'^mine/$', MyView.as_view(), name='my-view'),
|
||||
)
|
||||
|
||||
**Attributes**
|
||||
|
||||
.. attribute:: http_method_names = ['get', 'post', 'put', 'delete', 'head', 'options', 'trace']
|
||||
|
||||
The default list of HTTP method names that this view will accept.
|
||||
|
||||
**Methods**
|
||||
|
||||
.. classmethod:: as_view(**initkwargs)
|
||||
|
||||
Returns a callable view that takes a request and returns a response::
|
||||
|
||||
response = MyView.as_view()(request)
|
||||
|
||||
.. method:: dispatch(request, *args, **kwargs)
|
||||
|
||||
The ``view`` part of the view -- the method that accepts a ``request``
|
||||
|
@ -53,6 +71,11 @@ View
|
|||
delegated to :meth:`~View.get()`, a ``POST`` to :meth:`~View.post()`,
|
||||
and so on.
|
||||
|
||||
By default, a ``HEAD`` request will be delegated to :meth:`~View.get()`.
|
||||
If you need to handle ``HEAD`` requests in a different way than ``GET``,
|
||||
you can override the :meth:`~View.head()` method. See
|
||||
:ref:`supporting-other-http-methods` for an example.
|
||||
|
||||
The default implementation also sets ``request``, ``args`` and
|
||||
``kwargs`` as instance variables, so any method on the view can know
|
||||
the full details of the request that was made to invoke the view.
|
||||
|
@ -62,14 +85,13 @@ View
|
|||
If the view was called with a HTTP method it doesn't support, this
|
||||
method is called instead.
|
||||
|
||||
The default implementation returns ``HttpResponseNotAllowed`` with list
|
||||
of allowed methods in plain text.
|
||||
The default implementation returns ``HttpResponseNotAllowed`` with a
|
||||
list of allowed methods in plain text.
|
||||
|
||||
.. note::
|
||||
.. method:: options(request, *args, **kwargs)
|
||||
|
||||
Documentation on class-based views is a work in progress. As yet, only the
|
||||
methods defined directly on the class are documented here, not methods
|
||||
defined on superclasses.
|
||||
Handles responding to requests for the OPTIONS HTTP verb. Returns a
|
||||
list of the allowed HTTP method names for the view.
|
||||
|
||||
TemplateView
|
||||
------------
|
||||
|
@ -81,6 +103,8 @@ TemplateView
|
|||
|
||||
**Ancestors (MRO)**
|
||||
|
||||
This view inherits methods and attributes from the following views:
|
||||
|
||||
* :class:`django.views.generic.base.TemplateView`
|
||||
* :class:`django.views.generic.base.TemplateResponseMixin`
|
||||
* :class:`django.views.generic.base.View`
|
||||
|
@ -116,28 +140,11 @@ TemplateView
|
|||
url(r'^$', HomePageView.as_view(), name='home'),
|
||||
)
|
||||
|
||||
**Methods and Attributes**
|
||||
|
||||
.. attribute:: template_name
|
||||
|
||||
The full name of a template to use.
|
||||
|
||||
.. method:: get_context_data(**kwargs)
|
||||
|
||||
Return a context data dictionary consisting of the contents of
|
||||
``kwargs`` stored in the context variable ``params``.
|
||||
|
||||
**Context**
|
||||
|
||||
* ``params``: The dictionary of keyword arguments captured from the URL
|
||||
pattern that served the view.
|
||||
|
||||
.. note::
|
||||
|
||||
Documentation on class-based views is a work in progress. As yet, only the
|
||||
methods defined directly on the class are documented here, not methods
|
||||
defined on superclasses.
|
||||
|
||||
RedirectView
|
||||
------------
|
||||
|
||||
|
@ -156,6 +163,8 @@ RedirectView
|
|||
|
||||
**Ancestors (MRO)**
|
||||
|
||||
This view inherits methods and attributes from the following view:
|
||||
|
||||
* :class:`django.views.generic.base.View`
|
||||
|
||||
**Method Flowchart**
|
||||
|
@ -194,7 +203,7 @@ RedirectView
|
|||
url(r'^go-to-django/$', RedirectView.as_view(url='http://djangoproject.com'), name='go-to-django'),
|
||||
)
|
||||
|
||||
**Methods and Attributes**
|
||||
**Attributes**
|
||||
|
||||
.. attribute:: url
|
||||
|
||||
|
@ -215,6 +224,8 @@ RedirectView
|
|||
then the query string is discarded. By default, ``query_string`` is
|
||||
``False``.
|
||||
|
||||
**Methods**
|
||||
|
||||
.. method:: get_redirect_url(**kwargs)
|
||||
|
||||
Constructs the target URL for redirection.
|
||||
|
@ -225,9 +236,3 @@ RedirectView
|
|||
:attr:`~RedirectView.query_string`. Subclasses may implement any
|
||||
behavior they wish, as long as the method returns a redirect-ready URL
|
||||
string.
|
||||
|
||||
.. note::
|
||||
|
||||
Documentation on class-based views is a work in progress. As yet, only the
|
||||
methods defined directly on the class are documented here, not methods
|
||||
defined on superclasses.
|
||||
|
|
|
@ -2,13 +2,15 @@
|
|||
Generic date views
|
||||
==================
|
||||
|
||||
Date-based generic views (in the module :mod:`django.views.generic.dates`)
|
||||
are views for displaying drilldown pages for date-based data.
|
||||
.. module:: django.views.generic.dates
|
||||
|
||||
Date-based generic views, provided in :mod:`django.views.generic.dates`, are
|
||||
views for displaying drilldown pages for date-based data.
|
||||
|
||||
ArchiveIndexView
|
||||
----------------
|
||||
|
||||
.. class:: django.views.generic.dates.ArchiveIndexView
|
||||
.. class:: ArchiveIndexView
|
||||
|
||||
A top-level index page showing the "latest" objects, by date. Objects with
|
||||
a date in the *future* are not included unless you set ``allow_future`` to
|
||||
|
@ -36,7 +38,7 @@ ArchiveIndexView
|
|||
YearArchiveView
|
||||
---------------
|
||||
|
||||
.. class:: django.views.generic.dates.YearArchiveView
|
||||
.. class:: YearArchiveView
|
||||
|
||||
A yearly archive page showing all available months in a given year. Objects
|
||||
with a date in the *future* are not displayed unless you set
|
||||
|
@ -58,13 +60,15 @@ YearArchiveView
|
|||
|
||||
A boolean specifying whether to retrieve the full list of objects for
|
||||
this year and pass those to the template. If ``True``, the list of
|
||||
objects will be made available to the context. By default, this is
|
||||
objects will be made available to the context. If ``False``, the
|
||||
``None`` queryset will be used as the object list. By default, this is
|
||||
``False``.
|
||||
|
||||
.. method:: get_make_object_list()
|
||||
|
||||
Determine if an object list will be returned as part of the context. If
|
||||
``False``, the ``None`` queryset will be used as the object list.
|
||||
Determine if an object list will be returned as part of the context.
|
||||
Returns :attr:`~YearArchiveView.make_object_list` by default.
|
||||
|
||||
|
||||
**Context**
|
||||
|
||||
|
@ -80,16 +84,18 @@ YearArchiveView
|
|||
:class:`datetime.datetime<python:datetime.datetime>` objects, in
|
||||
ascending order.
|
||||
|
||||
* ``year``: A :class:`datetime.date<python:datetime.date>` object
|
||||
* ``year``: A :class:`~datetime.date` object
|
||||
representing the given year.
|
||||
|
||||
* ``next_year``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the first day of the next year. If the next year is in the
|
||||
future, this will be ``None``.
|
||||
* ``next_year``: A :class:`~datetime.date` object
|
||||
representing the first day of the next year, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
* ``previous_year``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the first day of the previous year. Unlike ``next_year``,
|
||||
this will never be ``None``.
|
||||
* ``previous_year``: A :class:`~datetime.date` object
|
||||
representing the first day of the previous year, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
**Notes**
|
||||
|
||||
|
@ -98,7 +104,7 @@ YearArchiveView
|
|||
MonthArchiveView
|
||||
----------------
|
||||
|
||||
.. class:: django.views.generic.dates.MonthArchiveView
|
||||
.. class:: MonthArchiveView
|
||||
|
||||
A monthly archive page showing all objects in a given month. Objects with a
|
||||
date in the *future* are not displayed unless you set ``allow_future`` to
|
||||
|
@ -131,16 +137,18 @@ MonthArchiveView
|
|||
:class:`datetime.datetime<python:datetime.datetime>` objects, in
|
||||
ascending order.
|
||||
|
||||
* ``month``: A :class:`datetime.date<python:datetime.date>` object
|
||||
* ``month``: A :class:`~datetime.date` object
|
||||
representing the given month.
|
||||
|
||||
* ``next_month``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the first day of the next month. If the next month is in the
|
||||
future, this will be ``None``.
|
||||
* ``next_month``: A :class:`~datetime.date` object
|
||||
representing the first day of the next month, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
* ``previous_month``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the first day of the previous month. Unlike ``next_month``,
|
||||
this will never be ``None``.
|
||||
* ``previous_month``: A :class:`~datetime.date` object
|
||||
representing the first day of the previous month, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
**Notes**
|
||||
|
||||
|
@ -149,7 +157,7 @@ MonthArchiveView
|
|||
WeekArchiveView
|
||||
---------------
|
||||
|
||||
.. class:: django.views.generic.dates.WeekArchiveView
|
||||
.. class:: WeekArchiveView
|
||||
|
||||
A weekly archive page showing all objects in a given week. Objects with a
|
||||
date in the *future* are not displayed unless you set ``allow_future`` to
|
||||
|
@ -175,16 +183,18 @@ WeekArchiveView
|
|||
:class:`~django.views.generic.dates.BaseDateListView`), the template's
|
||||
context will be:
|
||||
|
||||
* ``week``: A :class:`datetime.date<python:datetime.date>` object
|
||||
* ``week``: A :class:`~datetime.date` object
|
||||
representing the first day of the given week.
|
||||
|
||||
* ``next_week``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the first day of the next week. If the next week is in the
|
||||
future, this will be ``None``.
|
||||
* ``next_week``: A :class:`~datetime.date` object
|
||||
representing the first day of the next week, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
* ``previous_week``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the first day of the previous week. Unlike ``next_week``,
|
||||
this will never be ``None``.
|
||||
* ``previous_week``: A :class:`~datetime.date` object
|
||||
representing the first day of the previous week, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
**Notes**
|
||||
|
||||
|
@ -193,7 +203,7 @@ WeekArchiveView
|
|||
DayArchiveView
|
||||
--------------
|
||||
|
||||
.. class:: django.views.generic.dates.DayArchiveView
|
||||
.. class:: DayArchiveView
|
||||
|
||||
A day archive page showing all objects in a given day. Days in the future
|
||||
throw a 404 error, regardless of whether any objects exist for future days,
|
||||
|
@ -220,24 +230,28 @@ DayArchiveView
|
|||
:class:`~django.views.generic.dates.BaseDateListView`), the template's
|
||||
context will be:
|
||||
|
||||
* ``day``: A :class:`datetime.date<python:datetime.date>` object
|
||||
* ``day``: A :class:`~datetime.date` object
|
||||
representing the given day.
|
||||
|
||||
* ``next_day``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the next day. If the next day is in the future, this will be
|
||||
``None``.
|
||||
* ``next_day``: A :class:`~datetime.date` object
|
||||
representing the next day, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
* ``previous_day``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the previous day. Unlike ``next_day``, this will never be
|
||||
``None``.
|
||||
* ``previous_day``: A :class:`~datetime.date` object
|
||||
representing the previous day, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
* ``next_month``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the first day of the next month. If the next month is in the
|
||||
future, this will be ``None``.
|
||||
* ``next_month``: A :class:`~datetime.date` object
|
||||
representing the first day of the next month, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
* ``previous_month``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the first day of the previous month. Unlike ``next_month``,
|
||||
this will never be ``None``.
|
||||
* ``previous_month``: A :class:`~datetime.date` object
|
||||
representing the first day of the previous month, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
**Notes**
|
||||
|
||||
|
@ -246,7 +260,7 @@ DayArchiveView
|
|||
TodayArchiveView
|
||||
----------------
|
||||
|
||||
.. class:: django.views.generic.dates.TodayArchiveView
|
||||
.. class:: TodayArchiveView
|
||||
|
||||
A day archive page showing all objects for *today*. This is exactly the
|
||||
same as :class:`django.views.generic.dates.DayArchiveView`, except today's
|
||||
|
@ -271,7 +285,7 @@ TodayArchiveView
|
|||
DateDetailView
|
||||
--------------
|
||||
|
||||
.. class:: django.views.generic.dates.DateDetailView
|
||||
.. class:: DateDetailView
|
||||
|
||||
A page representing an individual object. If the object has a date value in
|
||||
the future, the view will throw a 404 error by default, unless you set
|
||||
|
@ -293,6 +307,22 @@ DateDetailView
|
|||
|
||||
.. note::
|
||||
|
||||
All of the generic views listed above have matching Base* views that only
|
||||
differ in that the they do not include the
|
||||
:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`.
|
||||
All of the generic views listed above have matching ``Base`` views that
|
||||
only differ in that the they do not include the
|
||||
:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`:
|
||||
|
||||
.. class:: BaseArchiveIndexView
|
||||
|
||||
.. class:: BaseYearArchiveView
|
||||
|
||||
.. class:: BaseMonthArchiveView
|
||||
|
||||
.. class:: BaseWeekArchiveView
|
||||
|
||||
.. class:: BaseDayArchiveView
|
||||
|
||||
.. class:: BaseTodayArchiveView
|
||||
|
||||
.. class:: BaseDateDetailView
|
||||
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ DetailView
|
|||
|
||||
**Ancestors (MRO)**
|
||||
|
||||
This view inherits methods and attributes from the following views:
|
||||
|
||||
* :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin`
|
||||
* :class:`django.views.generic.base.TemplateResponseMixin`
|
||||
* :class:`django.views.generic.detail.BaseDetailView`
|
||||
|
@ -71,7 +73,9 @@ ListView
|
|||
objects (usually, but not necessarily a queryset) that the view is
|
||||
operating upon.
|
||||
|
||||
**Mixins**
|
||||
**Ancestors (MRO)**
|
||||
|
||||
This view inherits methods and attributes from the following views:
|
||||
|
||||
* :class:`django.views.generic.list.ListView`
|
||||
* :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin`
|
||||
|
@ -90,3 +94,54 @@ ListView
|
|||
6. :meth:`get_context_data()`
|
||||
7. :meth:`get()`
|
||||
8. :meth:`render_to_response()`
|
||||
|
||||
|
||||
**Example views.py**::
|
||||
|
||||
from django.views.generic.list import ListView
|
||||
from django.utils import timezone
|
||||
|
||||
from articles.models import Article
|
||||
|
||||
class ArticleListView(ListView):
|
||||
|
||||
model = Article
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ArticleListView, self).get_context_data(**kwargs)
|
||||
context['now'] = timezone.now()
|
||||
return context
|
||||
|
||||
**Example urls.py**::
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
from article.views import ArticleListView
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', ArticleListView.as_view(), name='article-list'),
|
||||
)
|
||||
|
||||
.. class:: django.views.generic.list.BaseListView
|
||||
|
||||
A base view for displaying a list of objects. It is not intended to be used
|
||||
directly, but rather as a parent class of the
|
||||
:class:`django.views.generic.list.ListView` or other views representing
|
||||
lists of objects.
|
||||
|
||||
**Ancestors (MRO)**
|
||||
|
||||
This view inherits methods and attributes from the following views:
|
||||
|
||||
* :class:`django.views.generic.list.MultipleObjectMixin`
|
||||
* :class:`django.views.generic.base.View`
|
||||
|
||||
**Methods**
|
||||
|
||||
.. method:: get(request, *args, **kwargs)
|
||||
|
||||
Adds :attr:`object_list` to the context. If
|
||||
:attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty`
|
||||
is True then display an empty list. If
|
||||
:attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` is
|
||||
False then raise a 404 error.
|
||||
|
|
|
@ -23,7 +23,7 @@ it is safe to store state variables on the instance (i.e., ``self.foo = 3`` is
|
|||
a thread-safe operation).
|
||||
|
||||
A class-based view is deployed into a URL pattern using the
|
||||
:meth:`~View.as_view()` classmethod::
|
||||
:meth:`~django.views.generic.base.View.as_view()` classmethod::
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^view/$', MyView.as_view(size=42)),
|
||||
|
@ -37,9 +37,10 @@ A class-based view is deployed into a URL pattern using the
|
|||
is modified, the actions of one user visiting your view could have an
|
||||
effect on subsequent users visiting the same view.
|
||||
|
||||
Any argument passed into :meth:`~View.as_view()` will be assigned onto the
|
||||
instance that is used to service a request. Using the previous example,
|
||||
this means that every request on ``MyView`` is able to use ``self.size``.
|
||||
Any argument passed into :meth:`~django.views.generic.base.View.as_view()` will
|
||||
be assigned onto the instance that is used to service a request. Using the
|
||||
previous example, this means that every request on ``MyView`` is able to use
|
||||
``self.size``.
|
||||
|
||||
Base vs Generic views
|
||||
---------------------
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
Date-based mixins
|
||||
=================
|
||||
|
||||
.. currentmodule:: django.views.generic.dates
|
||||
|
||||
YearMixin
|
||||
---------
|
||||
|
||||
.. class:: django.views.generic.dates.YearMixin
|
||||
.. class:: YearMixin
|
||||
|
||||
A mixin that can be used to retrieve and provide parsing information for a
|
||||
year component of a date.
|
||||
|
@ -20,29 +21,45 @@ YearMixin
|
|||
|
||||
.. attribute:: year
|
||||
|
||||
**Optional** The value for the year (as a string). By default, set to
|
||||
**Optional** The value for the year, as a string. By default, set to
|
||||
``None``, which means the year will be determined using other means.
|
||||
|
||||
.. method:: get_year_format()
|
||||
|
||||
Returns the :func:`~time.strftime` format to use when parsing the year. Returns
|
||||
:attr:`YearMixin.year_format` by default.
|
||||
Returns the :func:`~time.strftime` format to use when parsing the
|
||||
year. Returns :attr:`~YearMixin.year_format` by default.
|
||||
|
||||
.. method:: get_year()
|
||||
|
||||
Returns the year for which this view will display data. Tries the
|
||||
following sources, in order:
|
||||
Returns the year for which this view will display data, as a string.
|
||||
Tries the following sources, in order:
|
||||
|
||||
* The value of the :attr:`YearMixin.year` attribute.
|
||||
* The value of the `year` argument captured in the URL pattern
|
||||
* The value of the `year` argument captured in the URL pattern.
|
||||
* The value of the `year` GET query argument.
|
||||
|
||||
Raises a 404 if no valid year specification can be found.
|
||||
|
||||
.. method:: get_next_year(date)
|
||||
|
||||
Returns a date object containing the first day of the year after the
|
||||
date provided. This function can also return ``None`` or raise an
|
||||
:class:`~django.http.Http404` exception, depending on the values of
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
.. method:: get_previous_year(date)
|
||||
|
||||
Returns a date object containing the first day of the year before the
|
||||
date provided. This function can also return ``None`` or raise an
|
||||
:class:`~django.http.Http404` exception, depending on the values of
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
MonthMixin
|
||||
----------
|
||||
|
||||
.. class:: django.views.generic.dates.MonthMixin
|
||||
.. class:: MonthMixin
|
||||
|
||||
A mixin that can be used to retrieve and provide parsing information for a
|
||||
month component of a date.
|
||||
|
@ -51,26 +68,26 @@ MonthMixin
|
|||
|
||||
.. attribute:: month_format
|
||||
|
||||
The :func:`~time.strftime` format to use when parsing the month. By default, this is
|
||||
``'%b'``.
|
||||
The :func:`~time.strftime` format to use when parsing the month. By
|
||||
default, this is ``'%b'``.
|
||||
|
||||
.. attribute:: month
|
||||
|
||||
**Optional** The value for the month (as a string). By default, set to
|
||||
**Optional** The value for the month, as a string. By default, set to
|
||||
``None``, which means the month will be determined using other means.
|
||||
|
||||
.. method:: get_month_format()
|
||||
|
||||
Returns the :func:`~time.strftime` format to use when parsing the month. Returns
|
||||
:attr:`MonthMixin.month_format` by default.
|
||||
Returns the :func:`~time.strftime` format to use when parsing the
|
||||
month. Returns :attr:`~MonthMixin.month_format` by default.
|
||||
|
||||
.. method:: get_month()
|
||||
|
||||
Returns the month for which this view will display data. Tries the
|
||||
following sources, in order:
|
||||
Returns the month for which this view will display data, as a string.
|
||||
Tries the following sources, in order:
|
||||
|
||||
* The value of the :attr:`MonthMixin.month` attribute.
|
||||
* The value of the `month` argument captured in the URL pattern
|
||||
* The value of the `month` argument captured in the URL pattern.
|
||||
* The value of the `month` GET query argument.
|
||||
|
||||
Raises a 404 if no valid month specification can be found.
|
||||
|
@ -78,20 +95,23 @@ MonthMixin
|
|||
.. method:: get_next_month(date)
|
||||
|
||||
Returns a date object containing the first day of the month after the
|
||||
date provided. Returns ``None`` if mixed with a view that sets
|
||||
``allow_future = False``, and the next month is in the future. If
|
||||
``allow_empty = False``, returns the next month that contains data.
|
||||
date provided. This function can also return ``None`` or raise an
|
||||
:class:`~django.http.Http404` exception, depending on the values of
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
.. method:: get_prev_month(date)
|
||||
|
||||
Returns a date object containing the first day of the month before the
|
||||
date provided. If ``allow_empty = False``, returns the previous month
|
||||
that contained data.
|
||||
date provided. This function can also return ``None`` or raise an
|
||||
:class:`~django.http.Http404` exception, depending on the values of
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
DayMixin
|
||||
--------
|
||||
|
||||
.. class:: django.views.generic.dates.DayMixin
|
||||
.. class:: DayMixin
|
||||
|
||||
A mixin that can be used to retrieve and provide parsing information for a
|
||||
day component of a date.
|
||||
|
@ -100,46 +120,50 @@ DayMixin
|
|||
|
||||
.. attribute:: day_format
|
||||
|
||||
The :func:`~time.strftime` format to use when parsing the day. By default, this is
|
||||
``'%d'``.
|
||||
The :func:`~time.strftime` format to use when parsing the day. By
|
||||
default, this is ``'%d'``.
|
||||
|
||||
.. attribute:: day
|
||||
|
||||
**Optional** The value for the day (as a string). By default, set to
|
||||
**Optional** The value for the day, as a string. By default, set to
|
||||
``None``, which means the day will be determined using other means.
|
||||
|
||||
.. method:: get_day_format()
|
||||
|
||||
Returns the :func:`~time.strftime` format to use when parsing the day. Returns
|
||||
:attr:`DayMixin.day_format` by default.
|
||||
Returns the :func:`~time.strftime` format to use when parsing the day.
|
||||
Returns :attr:`~DayMixin.day_format` by default.
|
||||
|
||||
.. method:: get_day()
|
||||
|
||||
Returns the day for which this view will display data. Tries the
|
||||
following sources, in order:
|
||||
Returns the day for which this view will display data, as a string.
|
||||
Tries the following sources, in order:
|
||||
|
||||
* The value of the :attr:`DayMixin.day` attribute.
|
||||
* The value of the `day` argument captured in the URL pattern
|
||||
* The value of the `day` argument captured in the URL pattern.
|
||||
* The value of the `day` GET query argument.
|
||||
|
||||
Raises a 404 if no valid day specification can be found.
|
||||
|
||||
.. method:: get_next_day(date)
|
||||
|
||||
Returns a date object containing the next day after the date provided.
|
||||
Returns ``None`` if mixed with a view that sets ``allow_future = False``,
|
||||
and the next day is in the future. If ``allow_empty = False``, returns
|
||||
the next day that contains data.
|
||||
Returns a date object containing the next valid day after the date
|
||||
provided. This function can also return ``None`` or raise an
|
||||
:class:`~django.http.Http404` exception, depending on the values of
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
.. method:: get_prev_day(date)
|
||||
|
||||
Returns a date object containing the previous day. If
|
||||
``allow_empty = False``, returns the previous day that contained data.
|
||||
Returns a date object containing the previous valid day. This function
|
||||
can also return ``None`` or raise an :class:`~django.http.Http404`
|
||||
exception, depending on the values of
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
WeekMixin
|
||||
---------
|
||||
|
||||
.. class:: django.views.generic.dates.WeekMixin
|
||||
.. class:: WeekMixin
|
||||
|
||||
A mixin that can be used to retrieve and provide parsing information for a
|
||||
week component of a date.
|
||||
|
@ -148,23 +172,24 @@ WeekMixin
|
|||
|
||||
.. attribute:: week_format
|
||||
|
||||
The :func:`~time.strftime` format to use when parsing the week. By default, this is
|
||||
``'%U'``.
|
||||
The :func:`~time.strftime` format to use when parsing the week. By
|
||||
default, this is ``'%U'``, which means the week starts on Sunday. Set
|
||||
it to ``'%W'`` if your week starts on Monday.
|
||||
|
||||
.. attribute:: week
|
||||
|
||||
**Optional** The value for the week (as a string). By default, set to
|
||||
**Optional** The value for the week, as a string. By default, set to
|
||||
``None``, which means the week will be determined using other means.
|
||||
|
||||
.. method:: get_week_format()
|
||||
|
||||
Returns the :func:`~time.strftime` format to use when parsing the week. Returns
|
||||
:attr:`WeekMixin.week_format` by default.
|
||||
Returns the :func:`~time.strftime` format to use when parsing the
|
||||
week. Returns :attr:`~WeekMixin.week_format` by default.
|
||||
|
||||
.. method:: get_week()
|
||||
|
||||
Returns the week for which this view will display data. Tries the
|
||||
following sources, in order:
|
||||
Returns the week for which this view will display data, as a string.
|
||||
Tries the following sources, in order:
|
||||
|
||||
* The value of the :attr:`WeekMixin.week` attribute.
|
||||
* The value of the `week` argument captured in the URL pattern
|
||||
|
@ -172,11 +197,26 @@ WeekMixin
|
|||
|
||||
Raises a 404 if no valid week specification can be found.
|
||||
|
||||
.. method:: get_next_week(date)
|
||||
|
||||
Returns a date object containing the first day of the week after the
|
||||
date provided. This function can also return ``None`` or raise an
|
||||
:class:`~django.http.Http404` exception, depending on the values of
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
.. method:: get_prev_week(date)
|
||||
|
||||
Returns a date object containing the first day of the week before the
|
||||
date provided. This function can also return ``None`` or raise an
|
||||
:class:`~django.http.Http404` exception, depending on the values of
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
DateMixin
|
||||
---------
|
||||
|
||||
.. class:: django.views.generic.dates.DateMixin
|
||||
.. class:: DateMixin
|
||||
|
||||
A mixin class providing common behavior for all date-based views.
|
||||
|
||||
|
@ -186,7 +226,7 @@ DateMixin
|
|||
|
||||
The name of the ``DateField`` or ``DateTimeField`` in the
|
||||
``QuerySet``'s model that the date-based archive should use to
|
||||
determine the objects on the page.
|
||||
determine the list of objects to display on the page.
|
||||
|
||||
When :doc:`time zone support </topics/i18n/timezones>` is enabled and
|
||||
``date_field`` is a ``DateTimeField``, dates are assumed to be in the
|
||||
|
@ -210,26 +250,26 @@ DateMixin
|
|||
.. method:: get_date_field()
|
||||
|
||||
Returns the name of the field that contains the date data that this
|
||||
view will operate on. Returns :attr:`DateMixin.date_field` by default.
|
||||
view will operate on. Returns :attr:`~DateMixin.date_field` by default.
|
||||
|
||||
.. method:: get_allow_future()
|
||||
|
||||
Determine whether to include "future" objects on this page, where
|
||||
"future" means objects in which the field specified in ``date_field``
|
||||
is greater than the current date/time. Returns
|
||||
:attr:`DateMixin.allow_future` by default.
|
||||
:attr:`~DateMixin.allow_future` by default.
|
||||
|
||||
BaseDateListView
|
||||
----------------
|
||||
|
||||
.. class:: django.views.generic.dates.BaseDateListView
|
||||
.. class:: BaseDateListView
|
||||
|
||||
A base class that provides common behavior for all date-based views. There
|
||||
won't normally be a reason to instantiate
|
||||
:class:`~django.views.generic.dates.BaseDateListView`; instantiate one of
|
||||
the subclasses instead.
|
||||
|
||||
While this view (and it's subclasses) are executing, ``self.object_list``
|
||||
While this view (and its subclasses) are executing, ``self.object_list``
|
||||
will contain the list of objects that the view is operating upon, and
|
||||
``self.date_list`` will contain the list of dates for which data is
|
||||
available.
|
||||
|
@ -245,10 +285,18 @@ BaseDateListView
|
|||
|
||||
A boolean specifying whether to display the page if no objects are
|
||||
available. If this is ``True`` and no objects are available, the view
|
||||
will display an empty page instead of raising a 404. By default, this
|
||||
is ``False``.
|
||||
will display an empty page instead of raising a 404.
|
||||
|
||||
.. method:: get_dated_items():
|
||||
This is identical to :attr:`MultipleObjectMixin.allow_empty`, except
|
||||
for the default value, which is ``False``.
|
||||
|
||||
.. attribute:: date_list_period
|
||||
|
||||
**Optional** A string defining the aggregation period for
|
||||
``date_list``. It must be one of ``'year'`` (default), ``'month'``, or
|
||||
``'day'``.
|
||||
|
||||
.. method:: get_dated_items()
|
||||
|
||||
Returns a 3-tuple containing (``date_list``, ``object_list``,
|
||||
``extra_context``).
|
||||
|
@ -265,10 +313,17 @@ BaseDateListView
|
|||
``lookup``. Enforces any restrictions on the queryset, such as
|
||||
``allow_empty`` and ``allow_future``.
|
||||
|
||||
.. method:: get_date_list(queryset, date_type)
|
||||
.. method:: get_date_list_period()
|
||||
|
||||
Returns the list of dates of type ``date_type`` for which
|
||||
``queryset`` contains entries. For example, ``get_date_list(qs,
|
||||
'year')`` will return the list of years for which ``qs`` has entries.
|
||||
See :meth:`~django.db.models.query.QuerySet.dates()` for the
|
||||
ways that the ``date_type`` argument can be used.
|
||||
Returns the aggregation period for ``date_list``. Returns
|
||||
:attr:`~BaseDateListView.date_list_period` by default.
|
||||
|
||||
.. method:: get_date_list(queryset, date_type=None)
|
||||
|
||||
Returns the list of dates of type ``date_type`` for which ``queryset``
|
||||
contains entries. For example, ``get_date_list(qs, 'year')`` will
|
||||
return the list of years for which ``qs`` has entries. If
|
||||
``date_type`` isn't provided, the result of
|
||||
:meth:`BaseDateListView.get_date_list_period` is used. See
|
||||
:meth:`~django.db.models.query.QuerySet.dates()` for the ways that the
|
||||
``date_type`` argument can be used.
|
||||
|
|
|
@ -86,7 +86,8 @@ MultipleObjectMixin
|
|||
|
||||
.. method:: get_queryset()
|
||||
|
||||
Returns the queryset that represents the data this view will display.
|
||||
Get the list of items for this view. This must be an iterable and may
|
||||
be a queryset (in which queryset-specific behavior will be enabled).
|
||||
|
||||
.. method:: paginate_queryset(queryset, page_size)
|
||||
|
||||
|
|
|
@ -9,16 +9,17 @@ ContextMixin
|
|||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
**classpath**
|
||||
|
||||
``django.views.generic.base.ContextMixin``
|
||||
|
||||
**Methods**
|
||||
|
||||
.. method:: get_context_data(**kwargs)
|
||||
|
||||
Returns a dictionary representing the template context. The keyword
|
||||
arguments provided will make up the returned context.
|
||||
arguments provided will make up the returned context. Example usage::
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(RandomNumberView, self).get_context_data(**kwargs)
|
||||
context['number'] = random.randrange(1, 100)
|
||||
return context
|
||||
|
||||
The template context of all class-based generic views include a
|
||||
``view`` variable that points to the ``View`` instance.
|
||||
|
@ -42,7 +43,13 @@ TemplateResponseMixin
|
|||
suitable context. The template to use is configurable and can be
|
||||
further customized by subclasses.
|
||||
|
||||
**Methods and Attributes**
|
||||
**Attributes**
|
||||
|
||||
.. attribute:: template_name
|
||||
|
||||
The full name of a template to use as defined by a string. Not defining
|
||||
a template_name will raise a
|
||||
:class:`django.core.exceptions.ImproperlyConfigured` exception.
|
||||
|
||||
.. attribute:: response_class
|
||||
|
||||
|
@ -57,12 +64,14 @@ TemplateResponseMixin
|
|||
instantiation, create a ``TemplateResponse`` subclass and assign it to
|
||||
``response_class``.
|
||||
|
||||
**Methods**
|
||||
|
||||
.. method:: render_to_response(context, **response_kwargs)
|
||||
|
||||
Returns a ``self.response_class`` instance.
|
||||
|
||||
If any keyword arguments are provided, they will be
|
||||
passed to the constructor of the response class.
|
||||
If any keyword arguments are provided, they will be passed to the
|
||||
constructor of the response class.
|
||||
|
||||
Calls :meth:`~TemplateResponseMixin.get_template_names()` to obtain the
|
||||
list of template names that will be searched looking for an existent
|
||||
|
|
|
@ -32,11 +32,11 @@ A simple example is the best illustration of this. Suppose we have the
|
|||
following model, which would represent entries in a Weblog::
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Entry(models.Model):
|
||||
title = models.CharField(maxlength=250)
|
||||
body = models.TextField()
|
||||
pub_date = models.DateTimeField()
|
||||
pub_date = models.DateField()
|
||||
enable_comments = models.BooleanField()
|
||||
|
||||
Now, suppose that we want the following steps to be applied whenever a
|
||||
|
@ -55,11 +55,11 @@ Accomplishing this is fairly straightforward and requires very little
|
|||
code::
|
||||
|
||||
from django.contrib.comments.moderation import CommentModerator, moderator
|
||||
|
||||
|
||||
class EntryModerator(CommentModerator):
|
||||
email_notification = True
|
||||
enable_field = 'enable_comments'
|
||||
|
||||
|
||||
moderator.register(Entry, EntryModerator)
|
||||
|
||||
The :class:`CommentModerator` class pre-defines a number of useful moderation
|
||||
|
|
|
@ -80,7 +80,7 @@ geospatial libraries:
|
|||
Program Description Required Supported Versions
|
||||
======================== ==================================== ================================ ==========================
|
||||
:ref:`GEOS <ref-geos>` Geometry Engine Open Source Yes 3.3, 3.2, 3.1, 3.0
|
||||
`PROJ.4`_ Cartographic Projections library Yes (PostgreSQL and SQLite only) 4.7, 4.6, 4.5, 4.4
|
||||
`PROJ.4`_ Cartographic Projections library Yes (PostgreSQL and SQLite only) 4.8, 4.7, 4.6, 4.5, 4.4
|
||||
:ref:`GDAL <ref-gdal>` Geospatial Data Abstraction Library No (but, required for SQLite) 1.9, 1.8, 1.7, 1.6, 1.5
|
||||
:ref:`GeoIP <ref-geoip>` IP-based geolocation library No 1.4
|
||||
`PostGIS`__ Spatial extensions for PostgreSQL Yes (PostgreSQL only) 1.5, 1.4, 1.3
|
||||
|
@ -140,16 +140,16 @@ internal geometry representation used by GeoDjango (it's behind the "lazy"
|
|||
geometries). Specifically, the C API library is called (e.g., ``libgeos_c.so``)
|
||||
directly from Python using ctypes.
|
||||
|
||||
First, download GEOS 3.3.0 from the refractions Web site and untar the source
|
||||
First, download GEOS 3.3.5 from the refractions Web site and untar the source
|
||||
archive::
|
||||
|
||||
$ wget http://download.osgeo.org/geos/geos-3.3.0.tar.bz2
|
||||
$ tar xjf geos-3.3.0.tar.bz2
|
||||
$ wget http://download.osgeo.org/geos/geos-3.3.5.tar.bz2
|
||||
$ tar xjf geos-3.3.5.tar.bz2
|
||||
|
||||
Next, change into the directory where GEOS was unpacked, run the configure
|
||||
script, compile, and install::
|
||||
|
||||
$ cd geos-3.3.0
|
||||
$ cd geos-3.3.5
|
||||
$ ./configure
|
||||
$ make
|
||||
$ sudo make install
|
||||
|
@ -203,15 +203,15 @@ reference systems.
|
|||
|
||||
First, download the PROJ.4 source code and datum shifting files [#]_::
|
||||
|
||||
$ wget http://download.osgeo.org/proj/proj-4.7.0.tar.gz
|
||||
$ wget http://download.osgeo.org/proj/proj-datumgrid-1.5.zip
|
||||
$ wget http://download.osgeo.org/proj/proj-4.8.0.tar.gz
|
||||
$ wget http://download.osgeo.org/proj/proj-datumgrid-1.5.tar.gz
|
||||
|
||||
Next, untar the source code archive, and extract the datum shifting files in the
|
||||
``nad`` subdirectory. This must be done *prior* to configuration::
|
||||
|
||||
$ tar xzf proj-4.7.0.tar.gz
|
||||
$ cd proj-4.7.0/nad
|
||||
$ unzip ../../proj-datumgrid-1.5.zip
|
||||
$ tar xzf proj-4.8.0.tar.gz
|
||||
$ cd proj-4.8.0/nad
|
||||
$ tar xzf ../../proj-datumgrid-1.5.tar.gz
|
||||
$ cd ..
|
||||
|
||||
Finally, configure, make and install PROJ.4::
|
||||
|
@ -239,9 +239,9 @@ installed prior to building PostGIS.
|
|||
|
||||
First download the source archive, and extract::
|
||||
|
||||
$ wget http://postgis.refractions.net/download/postgis-1.5.2.tar.gz
|
||||
$ tar xzf postgis-1.5.2.tar.gz
|
||||
$ cd postgis-1.5.2
|
||||
$ wget http://postgis.refractions.net/download/postgis-1.5.5.tar.gz
|
||||
$ tar xzf postgis-1.5.5.tar.gz
|
||||
$ cd postgis-1.5.5
|
||||
|
||||
Next, configure, make and install PostGIS::
|
||||
|
||||
|
@ -959,15 +959,15 @@ Ubuntu & Debian GNU/Linux
|
|||
Ubuntu
|
||||
^^^^^^
|
||||
|
||||
11.10
|
||||
~~~~~
|
||||
11.10 through 12.04
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In Ubuntu 11.10, PostgreSQL was upgraded to 9.1. The installation commands are:
|
||||
In Ubuntu 11.10, PostgreSQL was upgraded to 9.1. The installation command is:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo apt-get install binutils gdal-bin libproj-dev postgresql-9.1-postgis \
|
||||
postgresql-server-dev-9.1 python-psycopg2
|
||||
$ sudo apt-get install binutils gdal-bin libproj-dev \
|
||||
postgresql-9.1-postgis postgresql-server-dev-9.1 python-psycopg2
|
||||
|
||||
.. _ubuntu10:
|
||||
|
||||
|
@ -976,7 +976,7 @@ In Ubuntu 11.10, PostgreSQL was upgraded to 9.1. The installation commands are:
|
|||
|
||||
In Ubuntu 10.04, PostgreSQL was upgraded to 8.4 and GDAL was upgraded to 1.6.
|
||||
Ubuntu 10.04 uses PostGIS 1.4, while Ubuntu 10.10 uses PostGIS 1.5 (with
|
||||
geography support). The installation commands are:
|
||||
geography support). The installation command is:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
|
|
@ -5,6 +5,9 @@ django.contrib.markup
|
|||
.. module:: django.contrib.markup
|
||||
:synopsis: A collection of template filters that implement common markup languages.
|
||||
|
||||
.. deprecated:: 1.5
|
||||
This module has been deprecated.
|
||||
|
||||
Django provides template filters that implement the following markup
|
||||
languages:
|
||||
|
||||
|
|
|
@ -5,14 +5,16 @@ The messages framework
|
|||
.. module:: django.contrib.messages
|
||||
:synopsis: Provides cookie- and session-based temporary message storage.
|
||||
|
||||
Quite commonly in web applications, you may need to display a one-time
|
||||
notification message (also know as "flash message") to the user after
|
||||
processing a form or some other types of user input. For this, Django provides
|
||||
full support for cookie- and session-based messaging, for both anonymous and
|
||||
authenticated users. The messages framework allows you to temporarily store
|
||||
messages in one request and retrieve them for display in a subsequent request
|
||||
(usually the next one). Every message is tagged with a specific ``level`` that
|
||||
determines its priority (e.g., ``info``, ``warning``, or ``error``).
|
||||
Quite commonly in web applications, you need to display a one-time
|
||||
notification message (also known as "flash message") to the user after
|
||||
processing a form or some other types of user input.
|
||||
|
||||
For this, Django provides full support for cookie- and session-based
|
||||
messaging, for both anonymous and authenticated users. The messages framework
|
||||
allows you to temporarily store messages in one request and retrieve them for
|
||||
display in a subsequent request (usually the next one). Every message is
|
||||
tagged with a specific ``level`` that determines its priority (e.g., ``info``,
|
||||
``warning``, or ``error``).
|
||||
|
||||
Enabling messages
|
||||
=================
|
||||
|
@ -20,32 +22,27 @@ Enabling messages
|
|||
Messages are implemented through a :doc:`middleware </ref/middleware>`
|
||||
class and corresponding :doc:`context processor </ref/templates/api>`.
|
||||
|
||||
To enable message functionality, do the following:
|
||||
The default ``settings.py`` created by ``django-admin.py startproject``
|
||||
already contains all the settings required to enable message functionality:
|
||||
|
||||
* Edit the :setting:`MIDDLEWARE_CLASSES` setting and make sure
|
||||
it contains ``'django.contrib.messages.middleware.MessageMiddleware'``.
|
||||
* ``'django.contrib.messages'`` is in :setting:`INSTALLED_APPS`.
|
||||
|
||||
If you are using a :ref:`storage backend <message-storage-backends>` that
|
||||
relies on :doc:`sessions </topics/http/sessions>` (the default),
|
||||
``'django.contrib.sessions.middleware.SessionMiddleware'`` must be
|
||||
enabled and appear before ``MessageMiddleware`` in your
|
||||
* :setting:`MIDDLEWARE_CLASSES` contains
|
||||
``'django.contrib.sessions.middleware.SessionMiddleware'`` and
|
||||
``'django.contrib.messages.middleware.MessageMiddleware'``.
|
||||
|
||||
The default :ref:`storage backend <message-storage-backends>` relies on
|
||||
:doc:`sessions </topics/http/sessions>`. That's why ``SessionMiddleware``
|
||||
must be enabled and appear before ``MessageMiddleware`` in
|
||||
:setting:`MIDDLEWARE_CLASSES`.
|
||||
|
||||
* Edit the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting and make sure
|
||||
it contains ``'django.contrib.messages.context_processors.messages'``.
|
||||
* :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains
|
||||
``'django.contrib.messages.context_processors.messages'``.
|
||||
|
||||
* Add ``'django.contrib.messages'`` to your :setting:`INSTALLED_APPS`
|
||||
setting
|
||||
|
||||
The default ``settings.py`` created by ``django-admin.py startproject`` has
|
||||
``MessageMiddleware`` activated and the ``django.contrib.messages`` app
|
||||
installed. Also, the default value for :setting:`TEMPLATE_CONTEXT_PROCESSORS`
|
||||
contains ``'django.contrib.messages.context_processors.messages'``.
|
||||
|
||||
If you don't want to use messages, you can remove the
|
||||
``MessageMiddleware`` line from :setting:`MIDDLEWARE_CLASSES`, the ``messages``
|
||||
context processor from :setting:`TEMPLATE_CONTEXT_PROCESSORS` and
|
||||
``'django.contrib.messages'`` from your :setting:`INSTALLED_APPS`.
|
||||
If you don't want to use messages, you can remove
|
||||
``'django.contrib.messages'`` from your :setting:`INSTALLED_APPS`, the
|
||||
``MessageMiddleware`` line from :setting:`MIDDLEWARE_CLASSES`, and the
|
||||
``messages`` context processor from :setting:`TEMPLATE_CONTEXT_PROCESSORS`.
|
||||
|
||||
Configuring the message engine
|
||||
==============================
|
||||
|
@ -56,34 +53,35 @@ Storage backends
|
|||
----------------
|
||||
|
||||
The messages framework can use different backends to store temporary messages.
|
||||
If the default FallbackStorage isn't suitable to your needs, you can change
|
||||
which backend is being used by adding a `MESSAGE_STORAGE`_ to your
|
||||
settings, referencing the module and class of the storage class. For
|
||||
example::
|
||||
|
||||
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
||||
Django provides three built-in storage classes:
|
||||
|
||||
The value should be the full path of the desired storage class.
|
||||
.. class:: django.contrib.messages.storage.session.SessionStorage
|
||||
|
||||
Three storage classes are available:
|
||||
This class stores all messages inside of the request's session. Therefore
|
||||
it requires Django's ``contrib.sessions`` application.
|
||||
|
||||
``'django.contrib.messages.storage.session.SessionStorage'``
|
||||
This class stores all messages inside of the request's session. It
|
||||
requires Django's ``contrib.sessions`` application.
|
||||
.. class:: django.contrib.messages.storage.cookie.CookieStorage
|
||||
|
||||
``'django.contrib.messages.storage.cookie.CookieStorage'``
|
||||
This class stores the message data in a cookie (signed with a secret hash
|
||||
to prevent manipulation) to persist notifications across requests. Old
|
||||
messages are dropped if the cookie data size would exceed 4096 bytes.
|
||||
messages are dropped if the cookie data size would exceed 2048 bytes.
|
||||
|
||||
``'django.contrib.messages.storage.fallback.FallbackStorage'``
|
||||
This is the default storage class.
|
||||
.. class:: django.contrib.messages.storage.fallback.FallbackStorage
|
||||
|
||||
This class first uses CookieStorage for all messages, falling back to using
|
||||
SessionStorage for the messages that could not fit in a single cookie.
|
||||
This class first uses ``CookieStorage``, and falls back to using
|
||||
``SessionStorage`` for the messages that could not fit in a single cookie.
|
||||
It also requires Django's ``contrib.sessions`` application.
|
||||
|
||||
Since it is uses SessionStorage, it also requires Django's
|
||||
``contrib.sessions`` application.
|
||||
This behavior avoids writing to the session whenever possible. It should
|
||||
provide the best performance in the general case.
|
||||
|
||||
:class:`~django.contrib.messages.storage.fallback.FallbackStorage` is the
|
||||
default storage class. If it isn't suitable to your needs, you can select
|
||||
another storage class by setting `MESSAGE_STORAGE`_ to its full import path,
|
||||
for example::
|
||||
|
||||
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
||||
|
||||
To write your own storage class, subclass the ``BaseStorage`` class in
|
||||
``django.contrib.messages.storage.base`` and implement the ``_get`` and
|
||||
|
@ -97,8 +95,8 @@ to that of the Python logging module. Message levels allow you to group
|
|||
messages by type so they can be filtered or displayed differently in views and
|
||||
templates.
|
||||
|
||||
The built-in levels (which can be imported from ``django.contrib.messages``
|
||||
directly) are:
|
||||
The built-in levels, which can be imported from ``django.contrib.messages``
|
||||
directly, are:
|
||||
|
||||
=========== ========
|
||||
Constant Purpose
|
||||
|
|
|
@ -398,11 +398,21 @@ For each field, we describe the default widget used if you don't specify
|
|||
|
||||
If no ``input_formats`` argument is provided, the default input formats are::
|
||||
|
||||
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
|
||||
'%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
|
||||
'%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006'
|
||||
'%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
|
||||
'%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
|
||||
'%Y-%m-%d', # '2006-10-25'
|
||||
'%m/%d/%Y', # '10/25/2006'
|
||||
'%m/%d/%y', # '10/25/06'
|
||||
|
||||
Additionally, if you specify :setting:`USE_L10N=False<USE_L10N>` in your settings, the
|
||||
following will also be included in the default input formats::
|
||||
|
||||
'%b %m %d', # 'Oct 25 2006'
|
||||
'%b %d, %Y', # 'Oct 25, 2006'
|
||||
'%d %b %Y', # '25 Oct 2006'
|
||||
'%d %b, %Y', # '25 Oct, 2006'
|
||||
'%B %d %Y', # 'October 25 2006'
|
||||
'%B %d, %Y', # 'October 25, 2006'
|
||||
'%d %B %Y', # '25 October 2006'
|
||||
'%d %B, %Y', # '25 October, 2006'
|
||||
|
||||
``DateTimeField``
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -70,9 +70,8 @@ overridden:
|
|||
formfield-specific piece of validation and, possibly,
|
||||
cleaning/normalizing the data.
|
||||
|
||||
Just like the general field ``clean()`` method, above, this method
|
||||
should return the cleaned data, regardless of whether it changed
|
||||
anything or not.
|
||||
This method should return the cleaned value obtained from cleaned_data,
|
||||
regardless of whether it changed anything or not.
|
||||
|
||||
* The Form subclass's ``clean()`` method. This method can perform
|
||||
any validation that requires access to multiple fields from the form at
|
||||
|
|
|
@ -310,6 +310,10 @@ commonly used groups of widgets:
|
|||
A callable that takes the value of the CheckBoxInput and returns
|
||||
``True`` if the checkbox should be checked for that value.
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
Exceptions from ``check_test`` used to be silenced by its caller,
|
||||
this is no longer the case, they will propagate upwards.
|
||||
|
||||
``Select``
|
||||
~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -195,6 +195,14 @@ support tablespaces for indexes, this option is ignored.
|
|||
The default value for the field. This can be a value or a callable object. If
|
||||
callable it will be called every time a new object is created.
|
||||
|
||||
The default cannot be a mutable object (model instance, list, set, etc.), as a
|
||||
reference to the same instance of that object would be used as the default
|
||||
value in all new model instances. Instead, wrap the desired default in a
|
||||
callable. For example, if you had a custom ``JSONField`` and wanted to specify
|
||||
a dictionary as the default, use a ``lambda`` as follows::
|
||||
|
||||
contact_info = JSONField("ContactInfo", default=lambda:{"email": "to1@example.com"})
|
||||
|
||||
``editable``
|
||||
------------
|
||||
|
||||
|
@ -983,10 +991,10 @@ define the details of how the relation works.
|
|||
this with functions from the Python ``datetime`` module to limit choices of
|
||||
objects by date. For example::
|
||||
|
||||
limit_choices_to = {'pub_date__lte': datetime.now}
|
||||
limit_choices_to = {'pub_date__lte': datetime.date.today}
|
||||
|
||||
only allows the choice of related objects with a ``pub_date`` before the
|
||||
current date/time to be chosen.
|
||||
current date to be chosen.
|
||||
|
||||
Instead of a dictionary this can also be a :class:`~django.db.models.Q`
|
||||
object for more :ref:`complex queries <complex-lookups-with-q>`. However,
|
||||
|
|
|
@ -135,7 +135,7 @@ access to more than a single field::
|
|||
raise ValidationError('Draft entries may not have a publication date.')
|
||||
# Set the pub_date for published items if it hasn't been set already.
|
||||
if self.status == 'published' and self.pub_date is None:
|
||||
self.pub_date = datetime.datetime.now()
|
||||
self.pub_date = datetime.date.today()
|
||||
|
||||
Any :exc:`~django.core.exceptions.ValidationError` exceptions raised by
|
||||
``Model.clean()`` will be stored in a special key error dictionary key,
|
||||
|
|
|
@ -83,9 +83,10 @@ Django quotes column and table names behind the scenes.
|
|||
|
||||
.. attribute:: Options.get_latest_by
|
||||
|
||||
The name of a :class:`DateField` or :class:`DateTimeField` in the model.
|
||||
This specifies the default field to use in your model :class:`Manager`'s
|
||||
:class:`~QuerySet.latest` method.
|
||||
The name of an orderable field in the model, typically a :class:`DateField`,
|
||||
:class:`DateTimeField`, or :class:`IntegerField`. This specifies the default
|
||||
field to use in your model :class:`Manager`'s :class:`~QuerySet.latest`
|
||||
method.
|
||||
|
||||
Example::
|
||||
|
||||
|
|
|
@ -31,6 +31,9 @@ You can evaluate a ``QuerySet`` in the following ways:
|
|||
for e in Entry.objects.all():
|
||||
print(e.headline)
|
||||
|
||||
Note: Don't use this if all you want to do is determine if at least one
|
||||
result exists. It's more efficient to use :meth:`~QuerySet.exists`.
|
||||
|
||||
* **Slicing.** As explained in :ref:`limiting-querysets`, a ``QuerySet`` can
|
||||
be sliced, using Python's array-slicing syntax. Slicing an unevaluated
|
||||
``QuerySet`` usually returns another unevaluated ``QuerySet``, but Django
|
||||
|
@ -75,7 +78,7 @@ You can evaluate a ``QuerySet`` in the following ways:
|
|||
|
||||
Note: *Don't* use this if all you want to do is determine if at least one
|
||||
result exists, and don't need the actual objects. It's more efficient to
|
||||
use :meth:`exists() <QuerySet.exists>` (see below).
|
||||
use :meth:`~QuerySet.exists` (see below).
|
||||
|
||||
.. _pickling QuerySets:
|
||||
|
||||
|
@ -1523,9 +1526,40 @@ exists
|
|||
|
||||
Returns ``True`` if the :class:`.QuerySet` contains any results, and ``False``
|
||||
if not. This tries to perform the query in the simplest and fastest way
|
||||
possible, but it *does* execute nearly the same query. This means that calling
|
||||
:meth:`.QuerySet.exists` is faster than ``bool(some_query_set)``, but not by
|
||||
a large degree. If ``some_query_set`` has not yet been evaluated, but you know
|
||||
possible, but it *does* execute nearly the same query as a normal
|
||||
:class:`.QuerySet` query.
|
||||
|
||||
:meth:`~.QuerySet.exists` is useful for searches relating to both
|
||||
object membership in a :class:`.QuerySet` and to the existence of any objects in
|
||||
a :class:`.QuerySet`, particularly in the context of a large :class:`.QuerySet`.
|
||||
|
||||
The most efficient method of finding whether a model with a unique field
|
||||
(e.g. ``primary_key``) is a member of a :class:`.QuerySet` is::
|
||||
|
||||
entry = Entry.objects.get(pk=123)
|
||||
if some_query_set.filter(pk=entry.pk).exists():
|
||||
print("Entry contained in queryset")
|
||||
|
||||
Which will be faster than the following which requires evaluating and iterating
|
||||
through the entire queryset::
|
||||
|
||||
if entry in some_query_set:
|
||||
print("Entry contained in QuerySet")
|
||||
|
||||
And to find whether a queryset contains any items::
|
||||
|
||||
if some_query_set.exists():
|
||||
print("There is at least one object in some_query_set")
|
||||
|
||||
Which will be faster than::
|
||||
|
||||
if some_query_set:
|
||||
print("There is at least one object in some_query_set")
|
||||
|
||||
... but not by a large degree (hence needing a large queryset for efficiency
|
||||
gains).
|
||||
|
||||
Additionally, if a ``some_query_set`` has not yet been evaluated, but you know
|
||||
that it will be at some point, then using ``some_query_set.exists()`` will do
|
||||
more overall work (one query for the existence check plus an extra one to later
|
||||
retrieve the results) than simply using ``bool(some_query_set)``, which
|
||||
|
@ -1945,6 +1979,17 @@ SQL equivalent::
|
|||
You can use ``range`` anywhere you can use ``BETWEEN`` in SQL — for dates,
|
||||
numbers and even characters.
|
||||
|
||||
.. warning::
|
||||
|
||||
Filtering a ``DateTimeField`` with dates won't include items on the last
|
||||
day, because the bounds are interpreted as "0am on the given date". If
|
||||
``pub_date`` was a ``DateTimeField``, the above expression would be turned
|
||||
into this SQL::
|
||||
|
||||
SELECT ... WHERE pub_date BETWEEN '2005-01-01 00:00:00' and '2005-03-31 00:00:00';
|
||||
|
||||
Generally speaking, you can't mix dates and datetimes.
|
||||
|
||||
.. fieldlookup:: year
|
||||
|
||||
year
|
||||
|
@ -1958,7 +2003,7 @@ Example::
|
|||
|
||||
SQL equivalent::
|
||||
|
||||
SELECT ... WHERE pub_date BETWEEN '2005-01-01' AND '2005-12-31 23:59:59.999999';
|
||||
SELECT ... WHERE pub_date BETWEEN '2005-01-01' AND '2005-12-31';
|
||||
|
||||
(The exact SQL syntax varies for each database engine.)
|
||||
|
||||
|
|
|
@ -1313,25 +1313,13 @@ The URL where requests are redirected after login when the
|
|||
This is used by the :func:`~django.contrib.auth.decorators.login_required`
|
||||
decorator, for example.
|
||||
|
||||
.. _`note on LOGIN_REDIRECT_URL setting`:
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
.. note::
|
||||
You can use :func:`~django.core.urlresolvers.reverse_lazy` to reference
|
||||
URLs by their name instead of providing a hardcoded value. Assuming a
|
||||
``urls.py`` with an URLpattern named ``home``::
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url('^welcome/$', 'test_app.views.home', name='home'),
|
||||
)
|
||||
|
||||
You can use :func:`~django.core.urlresolvers.reverse_lazy` like this::
|
||||
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
|
||||
LOGIN_REDIRECT_URL = reverse_lazy('home')
|
||||
|
||||
This also works fine with localized URLs using
|
||||
:func:`~django.conf.urls.i18n.i18n_patterns`.
|
||||
This setting now also accepts view function names and
|
||||
:ref:`named URL patterns <naming-url-patterns>` which can be used to reduce
|
||||
configuration duplication since you no longer have to define the URL in two
|
||||
places (``settings`` and URLconf).
|
||||
For backward compatibility reasons the default remains unchanged.
|
||||
|
||||
.. setting:: LOGIN_URL
|
||||
|
||||
|
@ -1343,8 +1331,13 @@ Default: ``'/accounts/login/'``
|
|||
The URL where requests are redirected for login, especially when using the
|
||||
:func:`~django.contrib.auth.decorators.login_required` decorator.
|
||||
|
||||
.. note::
|
||||
See the `note on LOGIN_REDIRECT_URL setting`_
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
This setting now also accepts view function names and
|
||||
:ref:`named URL patterns <naming-url-patterns>` which can be used to reduce
|
||||
configuration duplication since you no longer have to define the URL in two
|
||||
places (``settings`` and URLconf).
|
||||
For backward compatibility reasons the default remains unchanged.
|
||||
|
||||
.. setting:: LOGOUT_URL
|
||||
|
||||
|
|
|
@ -121,6 +121,12 @@ Django 1.5 also includes several smaller improvements worth noting:
|
|||
argument. By default the batch_size is unlimited except for SQLite where
|
||||
single batch is limited so that 999 parameters per query isn't exceeded.
|
||||
|
||||
* The :setting:`LOGIN_URL` and :setting:`LOGIN_REDIRECT_URL` settings now also
|
||||
accept view function names and
|
||||
:ref:`named URL patterns <naming-url-patterns>`. This allows you to reduce
|
||||
configuration duplication. More information can be found in the
|
||||
:func:`~django.contrib.auth.decorators.login_required` documentation.
|
||||
|
||||
Backwards incompatible changes in 1.5
|
||||
=====================================
|
||||
|
||||
|
@ -358,3 +364,11 @@ the built-in :func:`itertools.product` instead.
|
|||
The :class:`~django.utils.encoding.StrAndUnicode` mix-in has been deprecated.
|
||||
Define a ``__str__`` method and apply the
|
||||
:func:`~django.utils.encoding.python_2_unicode_compatible` decorator instead.
|
||||
|
||||
``django.utils.markup``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The markup contrib module has been deprecated and will follow an accelerated
|
||||
deprecation schedule. Direct use of python markup libraries or 3rd party tag
|
||||
libraries is preferred to Django maintaining this functionality in the
|
||||
framework.
|
||||
|
|
|
@ -951,6 +951,13 @@ The login_required decorator
|
|||
|
||||
(r'^accounts/login/$', 'django.contrib.auth.views.login'),
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
As of version 1.5 :setting:`settings.LOGIN_URL <LOGIN_URL>` now also accepts
|
||||
view function names and :ref:`named URL patterns <naming-url-patterns>`.
|
||||
This allows you to freely remap your login view within your URLconf
|
||||
without having to update the setting.
|
||||
|
||||
.. function:: views.login(request, [template_name, redirect_field_name, authentication_form])
|
||||
|
||||
**URL name:** ``login``
|
||||
|
|
|
@ -84,6 +84,50 @@ function-like entry to class-based views::
|
|||
For more information on how to use the built in generic views, consult the next
|
||||
topic on :doc:`generic class based views</topics/class-based-views/generic-display>`.
|
||||
|
||||
.. _supporting-other-http-methods:
|
||||
|
||||
Supporting other HTTP methods
|
||||
-----------------------------
|
||||
|
||||
Suppose somebody wants to access our book library over HTTP using the views
|
||||
as an API. The API client would connect every now and then and download book
|
||||
data for the books published since last visit. But if no new books appeared
|
||||
since then, it is a waste of CPU time and bandwidth to fetch the books from the
|
||||
database, render a full response and send it to the client. It might be
|
||||
preferable to ask the API when the most recent book was published.
|
||||
|
||||
We map the URL to book list view in the URLconf::
|
||||
|
||||
from django.conf.urls import patterns
|
||||
from books.views import BookListView
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^books/$', BookListView.as_view()),
|
||||
)
|
||||
|
||||
And the view::
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.views.generic import ListView
|
||||
from books.models import Book
|
||||
|
||||
class BookListView(ListView):
|
||||
model = Book
|
||||
|
||||
def head(self, *args, **kwargs):
|
||||
last_book = self.get_queryset().latest('publication_date')
|
||||
response = HttpResponse('')
|
||||
# RFC 1123 date format
|
||||
response['Last-Modified'] = last_book.publication_date.strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||
return response
|
||||
|
||||
If the view is accessed from a ``GET`` request, a plain-and-simple object
|
||||
list is returned in the response (using ``book_list.html`` template). But if
|
||||
the client issues a ``HEAD`` request, the response has an empty body and
|
||||
the ``Last-Modified`` header indicates when the most recent book was published.
|
||||
Based on this information, the client may or may not download the full object
|
||||
list.
|
||||
|
||||
Decorating class-based views
|
||||
============================
|
||||
|
||||
|
|
|
@ -42,8 +42,8 @@ Create a few Reporters::
|
|||
|
||||
Create an Article::
|
||||
|
||||
>>> from datetime import datetime
|
||||
>>> a = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter=r)
|
||||
>>> from datetime import date
|
||||
>>> a = Article(id=None, headline="This is a test", pub_date=date(2005, 7, 27), reporter=r)
|
||||
>>> a.save()
|
||||
|
||||
>>> a.reporter.id
|
||||
|
@ -65,7 +65,7 @@ database, which always returns unicode strings)::
|
|||
|
||||
Create an Article via the Reporter object::
|
||||
|
||||
>>> new_article = r.article_set.create(headline="John's second story", pub_date=datetime(2005, 7, 29))
|
||||
>>> new_article = r.article_set.create(headline="John's second story", pub_date=date(2005, 7, 29))
|
||||
>>> new_article
|
||||
<Article: John's second story>
|
||||
>>> new_article.reporter
|
||||
|
@ -75,7 +75,7 @@ Create an Article via the Reporter object::
|
|||
|
||||
Create a new article, and add it to the article set::
|
||||
|
||||
>>> new_article2 = Article(headline="Paul's story", pub_date=datetime(2006, 1, 17))
|
||||
>>> new_article2 = Article(headline="Paul's story", pub_date=date(2006, 1, 17))
|
||||
>>> r.article_set.add(new_article2)
|
||||
>>> new_article2.reporter
|
||||
<Reporter: John Smith>
|
||||
|
|
|
@ -201,73 +201,129 @@ An example
|
|||
write to propagate to the slaves). It also doesn't consider the
|
||||
interaction of transactions with the database utilization strategy.
|
||||
|
||||
So - what does this mean in practice? Say you want ``myapp`` to
|
||||
exist on the ``other`` database, and you want all other models in a
|
||||
master/slave relationship between the databases ``master``, ``slave1`` and
|
||||
``slave2``. To implement this, you would need 2 routers::
|
||||
So - what does this mean in practice? Let's consider another sample
|
||||
configuration. This one will have several databases: one for the
|
||||
``auth`` application, and all other apps using a master/slave setup
|
||||
with two read slaves. Here are the settings specifying these
|
||||
databases::
|
||||
|
||||
class MyAppRouter(object):
|
||||
"""A router to control all database operations on models in
|
||||
the myapp application"""
|
||||
DATABASES = {
|
||||
'auth_db': {
|
||||
'NAME': 'auth_db',
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'USER': 'mysql_user',
|
||||
'PASSWORD': 'swordfish',
|
||||
},
|
||||
'master': {
|
||||
'NAME': 'master',
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'USER': 'mysql_user',
|
||||
'PASSWORD': 'spam',
|
||||
},
|
||||
'slave1': {
|
||||
'NAME': 'slave1',
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'USER': 'mysql_user',
|
||||
'PASSWORD': 'eggs',
|
||||
},
|
||||
'slave2': {
|
||||
'NAME': 'slave2',
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'USER': 'mysql_user',
|
||||
'PASSWORD': 'bacon',
|
||||
},
|
||||
}
|
||||
|
||||
Now we'll need to handle routing. First we want a router that knows to
|
||||
send queries for the ``auth`` app to ``auth_db``::
|
||||
|
||||
class AuthRouter(object):
|
||||
"""
|
||||
A router to control all database operations on models in the
|
||||
auth application.
|
||||
"""
|
||||
def db_for_read(self, model, **hints):
|
||||
"Point all operations on myapp models to 'other'"
|
||||
if model._meta.app_label == 'myapp':
|
||||
return 'other'
|
||||
"""
|
||||
Attempts to read auth models go to auth_db.
|
||||
"""
|
||||
if model._meta.app_label == 'auth':
|
||||
return 'auth_db'
|
||||
return None
|
||||
|
||||
def db_for_write(self, model, **hints):
|
||||
"Point all operations on myapp models to 'other'"
|
||||
if model._meta.app_label == 'myapp':
|
||||
return 'other'
|
||||
"""
|
||||
Attempts to write auth models go to auth_db.
|
||||
"""
|
||||
if model._meta.app_label == 'auth':
|
||||
return 'auth_db'
|
||||
return None
|
||||
|
||||
def allow_relation(self, obj1, obj2, **hints):
|
||||
"Allow any relation if a model in myapp is involved"
|
||||
if obj1._meta.app_label == 'myapp' or obj2._meta.app_label == 'myapp':
|
||||
return True
|
||||
"""
|
||||
Allow relations if a model in the auth app is involved.
|
||||
"""
|
||||
if obj1._meta.app_label == 'auth' or \
|
||||
obj2._meta.app_label == 'auth':
|
||||
return True
|
||||
return None
|
||||
|
||||
def allow_syncdb(self, db, model):
|
||||
"Make sure the myapp app only appears on the 'other' db"
|
||||
if db == 'other':
|
||||
return model._meta.app_label == 'myapp'
|
||||
elif model._meta.app_label == 'myapp':
|
||||
"""
|
||||
Make sure the auth app only appears in the 'auth_db'
|
||||
database.
|
||||
"""
|
||||
if db == 'auth_db':
|
||||
return model._meta.app_label == 'auth'
|
||||
elif model._meta.app_label == 'auth':
|
||||
return False
|
||||
return None
|
||||
|
||||
class MasterSlaveRouter(object):
|
||||
"""A router that sets up a simple master/slave configuration"""
|
||||
And we also want a router that sends all other apps to the
|
||||
master/slave configuration, and randomly chooses a slave to read
|
||||
from::
|
||||
|
||||
import random
|
||||
|
||||
class MasterSlaveRouter(object):
|
||||
def db_for_read(self, model, **hints):
|
||||
"Point all read operations to a random slave"
|
||||
return random.choice(['slave1','slave2'])
|
||||
"""
|
||||
Reads go to a randomly-chosen slave.
|
||||
"""
|
||||
return random.choice(['slave1', 'slave2'])
|
||||
|
||||
def db_for_write(self, model, **hints):
|
||||
"Point all write operations to the master"
|
||||
"""
|
||||
Writes always go to master.
|
||||
"""
|
||||
return 'master'
|
||||
|
||||
def allow_relation(self, obj1, obj2, **hints):
|
||||
"Allow any relation between two objects in the db pool"
|
||||
db_list = ('master','slave1','slave2')
|
||||
if obj1._state.db in db_list and obj2._state.db in db_list:
|
||||
"""
|
||||
Relations between objects are allowed if both objects are
|
||||
in the master/slave pool.
|
||||
"""
|
||||
db_list = ('master', 'slave1', 'slave2')
|
||||
if obj1.state.db in db_list and obj2.state.db in db_list:
|
||||
return True
|
||||
return None
|
||||
|
||||
def allow_syncdb(self, db, model):
|
||||
"Explicitly put all models on all databases."
|
||||
"""
|
||||
All non-auth models end up in this pool.
|
||||
"""
|
||||
return True
|
||||
|
||||
Then, in your settings file, add the following (substituting ``path.to.`` with
|
||||
the actual python path to the module where you define the routers)::
|
||||
Finally, in the settings file, we add the following (substituting
|
||||
``path.to.`` with the actual python path to the module(s) where the
|
||||
routers are defined)::
|
||||
|
||||
DATABASE_ROUTERS = ['path.to.MyAppRouter', 'path.to.MasterSlaveRouter']
|
||||
DATABASE_ROUTERS = ['path.to.AuthRouter', 'path.to.MasterSlaveRouter']
|
||||
|
||||
The order in which routers are processed is significant. Routers will
|
||||
be queried in the order the are listed in the
|
||||
:setting:`DATABASE_ROUTERS` setting . In this example, the
|
||||
``MyAppRouter`` is processed before the ``MasterSlaveRouter``, and as a
|
||||
result, decisions concerning the models in ``myapp`` are processed
|
||||
``AuthRouter`` is processed before the ``MasterSlaveRouter``, and as a
|
||||
result, decisions concerning the models in ``auth`` are processed
|
||||
before any other decision is made. If the :setting:`DATABASE_ROUTERS`
|
||||
setting listed the two routers in the other order,
|
||||
``MasterSlaveRouter.allow_syncdb()`` would be processed first. The
|
||||
|
@ -276,11 +332,11 @@ that all models would be available on all databases.
|
|||
|
||||
With this setup installed, lets run some Django code::
|
||||
|
||||
>>> # This retrieval will be performed on the 'credentials' database
|
||||
>>> # This retrieval will be performed on the 'auth_db' database
|
||||
>>> fred = User.objects.get(username='fred')
|
||||
>>> fred.first_name = 'Frederick'
|
||||
|
||||
>>> # This save will also be directed to 'credentials'
|
||||
>>> # This save will also be directed to 'auth_db'
|
||||
>>> fred.save()
|
||||
|
||||
>>> # These retrieval will be randomly allocated to a slave database
|
||||
|
|
|
@ -35,8 +35,8 @@ models, which comprise a Weblog application:
|
|||
blog = models.ForeignKey(Blog)
|
||||
headline = models.CharField(max_length=255)
|
||||
body_text = models.TextField()
|
||||
pub_date = models.DateTimeField()
|
||||
mod_date = models.DateTimeField()
|
||||
pub_date = models.DateField()
|
||||
mod_date = models.DateField()
|
||||
authors = models.ManyToManyField(Author)
|
||||
n_comments = models.IntegerField()
|
||||
n_pingbacks = models.IntegerField()
|
||||
|
@ -233,7 +233,7 @@ refinements together. For example::
|
|||
>>> Entry.objects.filter(
|
||||
... headline__startswith='What'
|
||||
... ).exclude(
|
||||
... pub_date__gte=datetime.now()
|
||||
... pub_date__gte=datetime.date.today()
|
||||
... ).filter(
|
||||
... pub_date__gte=datetime(2005, 1, 30)
|
||||
... )
|
||||
|
@ -258,8 +258,8 @@ stored, used and reused.
|
|||
Example::
|
||||
|
||||
>> q1 = Entry.objects.filter(headline__startswith="What")
|
||||
>> q2 = q1.exclude(pub_date__gte=datetime.now())
|
||||
>> q3 = q1.filter(pub_date__gte=datetime.now())
|
||||
>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
|
||||
>> q3 = q1.filter(pub_date__gte=datetime.date.today())
|
||||
|
||||
These three ``QuerySets`` are separate. The first is a base
|
||||
:class:`~django.db.models.query.QuerySet` containing all entries that contain a
|
||||
|
@ -282,7 +282,7 @@ actually run the query until the :class:`~django.db.models.query.QuerySet` is
|
|||
*evaluated*. Take a look at this example::
|
||||
|
||||
>>> q = Entry.objects.filter(headline__startswith="What")
|
||||
>>> q = q.filter(pub_date__lte=datetime.now())
|
||||
>>> q = q.filter(pub_date__lte=datetime.date.today())
|
||||
>>> q = q.exclude(body_text__icontains="food")
|
||||
>>> print(q)
|
||||
|
||||
|
@ -968,11 +968,12 @@ Be aware that the ``update()`` method is converted directly to an SQL
|
|||
statement. It is a bulk operation for direct updates. It doesn't run any
|
||||
:meth:`~django.db.models.Model.save` methods on your models, or emit the
|
||||
``pre_save`` or ``post_save`` signals (which are a consequence of calling
|
||||
:meth:`~django.db.models.Model.save`). If you want to save every item in a
|
||||
:class:`~django.db.models.query.QuerySet` and make sure that the
|
||||
:meth:`~django.db.models.Model.save` method is called on each instance, you
|
||||
don't need any special function to handle that. Just loop over them and call
|
||||
:meth:`~django.db.models.Model.save`::
|
||||
:meth:`~django.db.models.Model.save`), or honor the
|
||||
:attr:`~django.db.models.DateField.auto_now` field option.
|
||||
If you want to save every item in a :class:`~django.db.models.query.QuerySet`
|
||||
and make sure that the :meth:`~django.db.models.Model.save` method is called on
|
||||
each instance, you don't need any special function to handle that. Just loop
|
||||
over them and call :meth:`~django.db.models.Model.save`::
|
||||
|
||||
for item in my_queryset:
|
||||
item.save()
|
||||
|
|
|
@ -311,18 +311,18 @@ model fields:
|
|||
to exclude from the form.
|
||||
|
||||
For example, if you want a form for the ``Author`` model (defined
|
||||
above) that includes only the ``name`` and ``title`` fields, you would
|
||||
above) that includes only the ``name`` and ``birth_date`` fields, you would
|
||||
specify ``fields`` or ``exclude`` like this::
|
||||
|
||||
class PartialAuthorForm(ModelForm):
|
||||
class Meta:
|
||||
model = Author
|
||||
fields = ('name', 'title')
|
||||
fields = ('name', 'birth_date')
|
||||
|
||||
class PartialAuthorForm(ModelForm):
|
||||
class Meta:
|
||||
model = Author
|
||||
exclude = ('birth_date',)
|
||||
exclude = ('title',)
|
||||
|
||||
Since the Author model has only 3 fields, 'name', 'title', and
|
||||
'birth_date', the forms above will contain exactly the same fields.
|
||||
|
|
|
@ -20,18 +20,18 @@ Overview
|
|||
========
|
||||
|
||||
To design URLs for an app, you create a Python module informally called a
|
||||
**URLconf** (URL configuration). This module is pure Python code and
|
||||
is a simple mapping between URL patterns (as simple regular expressions) to
|
||||
Python callback functions (your views).
|
||||
**URLconf** (URL configuration). This module is pure Python code and is a
|
||||
simple mapping between URL patterns (simple regular expressions) to Python
|
||||
functions (your views).
|
||||
|
||||
This mapping can be as short or as long as needed. It can reference other
|
||||
mappings. And, because it's pure Python code, it can be constructed
|
||||
dynamically.
|
||||
|
||||
.. versionadded:: 1.4
|
||||
Django also allows to translate URLs according to the active language.
|
||||
This process is described in the
|
||||
:ref:`internationalization docs <url-internationalization>`.
|
||||
Django also provides a way to translate URLs according to the active
|
||||
language. See the :ref:`internationalization documentation
|
||||
<url-internationalization>` for more information.
|
||||
|
||||
.. _how-django-processes-a-request:
|
||||
|
||||
|
@ -154,11 +154,12 @@ The matching/grouping algorithm
|
|||
Here's the algorithm the URLconf parser follows, with respect to named groups
|
||||
vs. non-named groups in a regular expression:
|
||||
|
||||
If there are any named arguments, it will use those, ignoring non-named arguments.
|
||||
Otherwise, it will pass all non-named arguments as positional arguments.
|
||||
1. If there are any named arguments, it will use those, ignoring non-named
|
||||
arguments.
|
||||
|
||||
In both cases, it will pass any extra keyword arguments as keyword arguments.
|
||||
See "Passing extra options to view functions" below.
|
||||
2. Otherwise, it will pass all non-named arguments as positional arguments.
|
||||
|
||||
In both cases, any extra keyword arguments that have been given as per `Passing extra options to view functions`_ (below) will also be passed to the view.
|
||||
|
||||
What the URLconf searches against
|
||||
=================================
|
||||
|
@ -176,6 +177,44 @@ The URLconf doesn't look at the request method. In other words, all request
|
|||
methods -- ``POST``, ``GET``, ``HEAD``, etc. -- will be routed to the same
|
||||
function for the same URL.
|
||||
|
||||
Notes on capturing text in URLs
|
||||
===============================
|
||||
|
||||
Each captured argument is sent to the view as a plain Python string, regardless
|
||||
of what sort of match the regular expression makes. For example, in this
|
||||
URLconf line::
|
||||
|
||||
(r'^articles/(?P<year>\d{4})/$', 'news.views.year_archive'),
|
||||
|
||||
...the ``year`` argument to ``news.views.year_archive()`` will be a string, not
|
||||
an integer, even though the ``\d{4}`` will only match integer strings.
|
||||
|
||||
A convenient trick is to specify default parameters for your views' arguments.
|
||||
Here's an example URLconf and view::
|
||||
|
||||
# URLconf
|
||||
urlpatterns = patterns('',
|
||||
(r'^blog/$', 'blog.views.page'),
|
||||
(r'^blog/page(?P<num>\d+)/$', 'blog.views.page'),
|
||||
)
|
||||
|
||||
# View (in blog/views.py)
|
||||
def page(request, num="1"):
|
||||
# Output the appropriate page of blog entries, according to num.
|
||||
|
||||
In the above example, both URL patterns point to the same view --
|
||||
``blog.views.page`` -- but the first pattern doesn't capture anything from the
|
||||
URL. If the first pattern matches, the ``page()`` function will use its
|
||||
default argument for ``num``, ``"1"``. If the second pattern matches,
|
||||
``page()`` will use whatever ``num`` value was captured by the regex.
|
||||
|
||||
Performance
|
||||
===========
|
||||
|
||||
Each regular expression in a ``urlpatterns`` is compiled the first time it's
|
||||
accessed. This makes the system blazingly fast.
|
||||
|
||||
|
||||
Syntax of the urlpatterns variable
|
||||
==================================
|
||||
|
||||
|
@ -209,10 +248,10 @@ The first argument to ``patterns()`` is a string ``prefix``. See
|
|||
|
||||
The remaining arguments should be tuples in this format::
|
||||
|
||||
(regular expression, Python callback function [, optional dictionary [, optional name]])
|
||||
(regular expression, Python callback function [, optional_dictionary [, optional_name]])
|
||||
|
||||
...where ``optional dictionary`` and ``optional name`` are optional. (See
|
||||
`Passing extra options to view functions`_ below.)
|
||||
The ``optional_dictionary`` and ``optional_name`` parameters are described in
|
||||
`Passing extra options to view functions`_ below.
|
||||
|
||||
.. note::
|
||||
Because `patterns()` is a function call, it accepts a maximum of 255
|
||||
|
@ -332,43 +371,6 @@ value should suffice.
|
|||
See the documentation about :ref:`the 500 (HTTP Internal Server Error) view
|
||||
<http_internal_server_error_view>` for more information.
|
||||
|
||||
Notes on capturing text in URLs
|
||||
===============================
|
||||
|
||||
Each captured argument is sent to the view as a plain Python string, regardless
|
||||
of what sort of match the regular expression makes. For example, in this
|
||||
URLconf line::
|
||||
|
||||
(r'^articles/(?P<year>\d{4})/$', 'news.views.year_archive'),
|
||||
|
||||
...the ``year`` argument to ``news.views.year_archive()`` will be a string, not
|
||||
an integer, even though the ``\d{4}`` will only match integer strings.
|
||||
|
||||
A convenient trick is to specify default parameters for your views' arguments.
|
||||
Here's an example URLconf and view::
|
||||
|
||||
# URLconf
|
||||
urlpatterns = patterns('',
|
||||
(r'^blog/$', 'blog.views.page'),
|
||||
(r'^blog/page(?P<num>\d+)/$', 'blog.views.page'),
|
||||
)
|
||||
|
||||
# View (in blog/views.py)
|
||||
def page(request, num="1"):
|
||||
# Output the appropriate page of blog entries, according to num.
|
||||
|
||||
In the above example, both URL patterns point to the same view --
|
||||
``blog.views.page`` -- but the first pattern doesn't capture anything from the
|
||||
URL. If the first pattern matches, the ``page()`` function will use its
|
||||
default argument for ``num``, ``"1"``. If the second pattern matches,
|
||||
``page()`` will use whatever ``num`` value was captured by the regex.
|
||||
|
||||
Performance
|
||||
===========
|
||||
|
||||
Each regular expression in a ``urlpatterns`` is compiled the first time it's
|
||||
accessed. This makes the system blazingly fast.
|
||||
|
||||
The view prefix
|
||||
===============
|
||||
|
||||
|
|
|
@ -1251,6 +1251,11 @@ As a convenience, Django comes with a view, :func:`django.views.i18n.set_languag
|
|||
that sets a user's language preference and redirects to a given URL or, by default,
|
||||
back to the previous page.
|
||||
|
||||
Make sure that the following item is in your
|
||||
:setting:`TEMPLATE_CONTEXT_PROCESSORS` list in your settings file::
|
||||
|
||||
'django.core.context_processors.i18n'
|
||||
|
||||
Activate this view by adding the following line to your URLconf::
|
||||
|
||||
(r'^i18n/', include('django.conf.urls.i18n')),
|
||||
|
|
|
@ -9,10 +9,8 @@ Install Python
|
|||
|
||||
Being a Python Web framework, Django requires Python.
|
||||
|
||||
It works with any Python version from 2.6.5 to 2.7 (due to backwards
|
||||
incompatibilities in Python 3.0, Django does not currently work with
|
||||
Python 3.0; see :doc:`the Django FAQ </faq/install>` for more
|
||||
information on supported Python versions and the 3.0 transition).
|
||||
It works with any Python version from 2.6.5 to 2.7. It also features
|
||||
experimental support for versions 3.2 and 3.3.
|
||||
|
||||
Get Python at http://www.python.org. If you're running Linux or Mac OS X, you
|
||||
probably already have it installed.
|
||||
|
|
|
@ -25,10 +25,11 @@ free to chose another strategy for your own code, especially if you don't need
|
|||
to stay compatible with Python 2. But authors of pluggable applications are
|
||||
encouraged to use the same porting strategy as Django itself.
|
||||
|
||||
Writing compatible code is much easier if you target Python ≥ 2.6. You will
|
||||
most likely take advantage of the compatibility functions introduced in Django
|
||||
1.5, like :mod:`django.utils.six`, so your application will also require
|
||||
Django ≥ 1.5.
|
||||
Writing compatible code is much easier if you target Python ≥ 2.6. Django 1.5
|
||||
introduces compatibility tools such as :mod:`django.utils.six`. For
|
||||
convenience, forwards-compatible aliases were introduced in Django 1.4.2. If
|
||||
your application takes advantage of these tools, it will require Django ≥
|
||||
1.4.2.
|
||||
|
||||
Obviously, writing compatible source code adds some overhead, and that can
|
||||
cause frustration. Django's developers have found that attempting to write
|
||||
|
@ -102,6 +103,8 @@ Old name New name
|
|||
For backwards compatibility, the old names still work on Python 2. Under
|
||||
Python 3, ``smart_str`` is an alias for ``smart_text``.
|
||||
|
||||
For forwards compatibility, the new names work as of Django 1.4.2.
|
||||
|
||||
.. note::
|
||||
|
||||
:mod:`django.utils.encoding` was deeply refactored in Django 1.5 to
|
||||
|
@ -126,6 +129,8 @@ For backwards compatibility, the old names still work on Python 2. Under
|
|||
Python 3, ``EscapeString`` and ``SafeString`` are aliases for ``EscapeText``
|
||||
and ``SafeText`` respectively.
|
||||
|
||||
For forwards compatibility, the new names work as of Django 1.4.2.
|
||||
|
||||
:meth:`__str__` and :meth:`__unicode__` methods
|
||||
-----------------------------------------------
|
||||
|
||||
|
@ -166,6 +171,8 @@ On Python 3, the decorator is a no-op. On Python 2, it defines appropriate
|
|||
|
||||
This technique is the best match for Django's porting philosophy.
|
||||
|
||||
For forwards compatibility, this decorator is available as of Django 1.4.2.
|
||||
|
||||
Finally, note that :meth:`__repr__` must return a :class:`str` on all versions
|
||||
of Python.
|
||||
|
||||
|
@ -317,7 +324,8 @@ Writing compatible code with six
|
|||
six_ is the canonical compatibility library for supporting Python 2 and 3 in
|
||||
a single codebase. Read its documentation!
|
||||
|
||||
:mod:`six` is bundled with Django: you can import it as :mod:`django.utils.six`.
|
||||
:mod:`six` is bundled with Django as of version 1.4.2. You can import it as
|
||||
:mod:`django.utils.six`.
|
||||
|
||||
Here are the most common changes required to write compatible code.
|
||||
|
||||
|
@ -392,5 +400,12 @@ The version of six bundled with Django includes one extra function:
|
|||
2 and :meth:`~django.utils.datastructures.MultiValueDict.lists()` on
|
||||
Python 3.
|
||||
|
||||
.. function:: assertRaisesRegex(testcase, *args, **kwargs)
|
||||
|
||||
This replaces ``testcase.assertRaisesRegexp`` on Python 2, and
|
||||
``testcase.assertRaisesRegex`` on Python 3. ``assertRaisesRegexp`` still
|
||||
exists in current Python3 versions, but issues a warning.
|
||||
|
||||
|
||||
In addition to six' defaults moves, Django's version provides ``thread`` as
|
||||
``_thread`` and ``dummy_thread`` as ``_dummy_thread``.
|
||||
|
|
|
@ -76,9 +76,17 @@ POST to your Web site and have another logged in user unwittingly submit that
|
|||
form. The malicious user would have to know the nonce, which is user specific
|
||||
(using a cookie).
|
||||
|
||||
When deployed with :ref:`HTTPS <security-recommendation-ssl>`,
|
||||
``CsrfViewMiddleware`` will check that the HTTP referer header is set to a
|
||||
URL on the same origin (including subdomain and port). Because HTTPS
|
||||
provides additional security, it is imperative to ensure connections use HTTPS
|
||||
where it is available by forwarding insecure connection requests and using
|
||||
HSTS for supported browsers.
|
||||
|
||||
Be very careful with marking views with the ``csrf_exempt`` decorator unless
|
||||
it is absolutely necessary.
|
||||
|
||||
|
||||
SQL injection protection
|
||||
========================
|
||||
|
||||
|
@ -112,6 +120,8 @@ The middleware is strongly recommended for any site that does not need to have
|
|||
its pages wrapped in a frame by third party sites, or only needs to allow that
|
||||
for a small section of the site.
|
||||
|
||||
.. _security-recommendation-ssl:
|
||||
|
||||
SSL/HTTPS
|
||||
=========
|
||||
|
||||
|
@ -147,7 +157,15 @@ server, there are some additional steps you may need:
|
|||
any POST data being accepted over HTTP (which will be fine if you are
|
||||
redirecting all HTTP traffic to HTTPS).
|
||||
|
||||
.. _additional-security-topics:
|
||||
* Use HTTP Strict Transport Security (HSTS)
|
||||
|
||||
HSTS is an HTTP header that informs a browser that all future connections
|
||||
to a particular site should always use HTTPS. Combined with redirecting
|
||||
requests over HTTP to HTTPS, this will ensure that connections always enjoy
|
||||
the added security of SSL provided one successful connection has occurred.
|
||||
HSTS is usually configured on the web server.
|
||||
|
||||
.. _host-headers-virtual-hosting:
|
||||
|
||||
Host headers and virtual hosting
|
||||
================================
|
||||
|
@ -158,15 +176,17 @@ Site Scripting attacks, they can be used for Cross-Site Request
|
|||
Forgery and cache poisoning attacks in some circumstances. We
|
||||
recommend you ensure your Web server is configured such that:
|
||||
|
||||
* It always validates incoming HTTP ``Host`` headers against the expected
|
||||
host name.
|
||||
* Disallows requests with no ``Host`` header.
|
||||
* Is *not* configured with a catch-all virtual host that forwards requests
|
||||
to a Django application.
|
||||
* It always validates incoming HTTP ``Host`` headers against the expected
|
||||
host name.
|
||||
* Disallows requests with no ``Host`` header.
|
||||
* Is *not* configured with a catch-all virtual host that forwards requests
|
||||
to a Django application.
|
||||
|
||||
Additionally, as of 1.3.1, Django requires you to explicitly enable support for
|
||||
the ``X-Forwarded-Host`` header if your configuration requires it.
|
||||
|
||||
.. _additional-security-topics:
|
||||
|
||||
Additional security topics
|
||||
==========================
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ from datetime import datetime
|
|||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db.models.fields import Field, FieldDoesNotExist
|
||||
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
|
||||
from django.utils.six import PY3
|
||||
from django.utils import six
|
||||
from django.utils.translation import ugettext_lazy
|
||||
|
||||
from .models import Article
|
||||
|
@ -82,7 +82,7 @@ class ModelTest(TestCase):
|
|||
|
||||
# Django raises an Article.DoesNotExist exception for get() if the
|
||||
# parameters don't match any object.
|
||||
self.assertRaisesRegexp(
|
||||
six.assertRaisesRegex(self,
|
||||
ObjectDoesNotExist,
|
||||
"Article matching query does not exist. Lookup parameters were "
|
||||
"{'id__exact': 2000}",
|
||||
|
@ -91,14 +91,14 @@ class ModelTest(TestCase):
|
|||
)
|
||||
# To avoid dict-ordering related errors check only one lookup
|
||||
# in single assert.
|
||||
self.assertRaisesRegexp(
|
||||
six.assertRaisesRegex(self,
|
||||
ObjectDoesNotExist,
|
||||
".*'pub_date__year': 2005.*",
|
||||
Article.objects.get,
|
||||
pub_date__year=2005,
|
||||
pub_date__month=8,
|
||||
)
|
||||
self.assertRaisesRegexp(
|
||||
six.assertRaisesRegex(self,
|
||||
ObjectDoesNotExist,
|
||||
".*'pub_date__month': 8.*",
|
||||
Article.objects.get,
|
||||
|
@ -106,7 +106,7 @@ class ModelTest(TestCase):
|
|||
pub_date__month=8,
|
||||
)
|
||||
|
||||
self.assertRaisesRegexp(
|
||||
six.assertRaisesRegex(self,
|
||||
ObjectDoesNotExist,
|
||||
"Article matching query does not exist. Lookup parameters were "
|
||||
"{'pub_date__week_day': 6}",
|
||||
|
@ -168,7 +168,7 @@ class ModelTest(TestCase):
|
|||
self.assertEqual(a4.headline, 'Fourth article')
|
||||
|
||||
# Don't use invalid keyword arguments.
|
||||
self.assertRaisesRegexp(
|
||||
six.assertRaisesRegex(self,
|
||||
TypeError,
|
||||
"'foo' is an invalid keyword argument for this function",
|
||||
Article,
|
||||
|
@ -259,13 +259,13 @@ class ModelTest(TestCase):
|
|||
"datetime.datetime(2005, 7, 28, 0, 0)"])
|
||||
|
||||
# dates() requires valid arguments.
|
||||
self.assertRaisesRegexp(
|
||||
six.assertRaisesRegex(self,
|
||||
TypeError,
|
||||
"dates\(\) takes at least 3 arguments \(1 given\)",
|
||||
Article.objects.dates,
|
||||
)
|
||||
|
||||
self.assertRaisesRegexp(
|
||||
six.assertRaisesRegex(self,
|
||||
FieldDoesNotExist,
|
||||
"Article has no field named 'invalid_field'",
|
||||
Article.objects.dates,
|
||||
|
@ -273,7 +273,7 @@ class ModelTest(TestCase):
|
|||
"year",
|
||||
)
|
||||
|
||||
self.assertRaisesRegexp(
|
||||
six.assertRaisesRegex(self,
|
||||
AssertionError,
|
||||
"'kind' must be one of 'year', 'month' or 'day'.",
|
||||
Article.objects.dates,
|
||||
|
@ -281,7 +281,7 @@ class ModelTest(TestCase):
|
|||
"bad_kind",
|
||||
)
|
||||
|
||||
self.assertRaisesRegexp(
|
||||
six.assertRaisesRegex(self,
|
||||
AssertionError,
|
||||
"'order' must be either 'ASC' or 'DESC'.",
|
||||
Article.objects.dates,
|
||||
|
@ -323,7 +323,7 @@ class ModelTest(TestCase):
|
|||
"<Article: Third article>"])
|
||||
|
||||
# Slicing works with longs (Python 2 only -- Python 3 doesn't have longs).
|
||||
if not PY3:
|
||||
if not six.PY3:
|
||||
self.assertEqual(Article.objects.all()[long(0)], a)
|
||||
self.assertQuerysetEqual(Article.objects.all()[long(1):long(3)],
|
||||
["<Article: Second article>", "<Article: Third article>"])
|
||||
|
@ -369,14 +369,14 @@ class ModelTest(TestCase):
|
|||
"<Article: Updated article 8>"])
|
||||
|
||||
# Also, once you have sliced you can't filter, re-order or combine
|
||||
self.assertRaisesRegexp(
|
||||
six.assertRaisesRegex(self,
|
||||
AssertionError,
|
||||
"Cannot filter a query once a slice has been taken.",
|
||||
Article.objects.all()[0:5].filter,
|
||||
id=a.id,
|
||||
)
|
||||
|
||||
self.assertRaisesRegexp(
|
||||
six.assertRaisesRegex(self,
|
||||
AssertionError,
|
||||
"Cannot reorder a query once a slice has been taken.",
|
||||
Article.objects.all()[0:5].order_by,
|
||||
|
@ -411,7 +411,7 @@ class ModelTest(TestCase):
|
|||
|
||||
# An Article instance doesn't have access to the "objects" attribute.
|
||||
# That's only available on the class.
|
||||
self.assertRaisesRegexp(
|
||||
six.assertRaisesRegex(self,
|
||||
AttributeError,
|
||||
"Manager isn't accessible via Article instances",
|
||||
getattr,
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db.models.loading import get_app
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.utils import six
|
||||
|
||||
from .models import Empty
|
||||
|
||||
|
@ -14,12 +14,13 @@ class EmptyModelTests(TestCase):
|
|||
m = Empty()
|
||||
self.assertEqual(m.id, None)
|
||||
m.save()
|
||||
m2 = Empty.objects.create()
|
||||
Empty.objects.create()
|
||||
self.assertEqual(len(Empty.objects.all()), 2)
|
||||
self.assertTrue(m.id is not None)
|
||||
existing = Empty(m.id)
|
||||
existing.save()
|
||||
|
||||
|
||||
class NoModelTests(TestCase):
|
||||
"""
|
||||
Test for #7198 to ensure that the proper error message is raised
|
||||
|
@ -32,6 +33,6 @@ class NoModelTests(TestCase):
|
|||
"""
|
||||
@override_settings(INSTALLED_APPS=("modeltests.empty.no_models",))
|
||||
def test_no_models(self):
|
||||
with self.assertRaisesRegexp(ImproperlyConfigured,
|
||||
with six.assertRaisesRegex(self, ImproperlyConfigured,
|
||||
'App with label no_models is missing a models.py module.'):
|
||||
get_app('no_models')
|
||||
|
|
|
@ -4,7 +4,7 @@ from django.contrib.sites.models import Site
|
|||
from django.core import management
|
||||
from django.db import connection, IntegrityError
|
||||
from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature
|
||||
from django.utils.six import StringIO
|
||||
from django.utils import six
|
||||
|
||||
from .models import Article, Book, Spy, Tag, Visa
|
||||
|
||||
|
@ -21,16 +21,17 @@ class TestCaseFixtureLoadingTests(TestCase):
|
|||
'<Article: Poker has no place on ESPN>',
|
||||
])
|
||||
|
||||
|
||||
class FixtureLoadingTests(TestCase):
|
||||
|
||||
def _dumpdata_assert(self, args, output, format='json', natural_keys=False,
|
||||
use_base_manager=False, exclude_list=[]):
|
||||
new_io = StringIO()
|
||||
management.call_command('dumpdata', *args, **{'format':format,
|
||||
'stdout':new_io,
|
||||
'stderr':new_io,
|
||||
'use_natural_keys':natural_keys,
|
||||
'use_base_manager':use_base_manager,
|
||||
new_io = six.StringIO()
|
||||
management.call_command('dumpdata', *args, **{'format': format,
|
||||
'stdout': new_io,
|
||||
'stderr': new_io,
|
||||
'use_natural_keys': natural_keys,
|
||||
'use_base_manager': use_base_manager,
|
||||
'exclude': exclude_list})
|
||||
command_output = new_io.getvalue().strip()
|
||||
self.assertEqual(command_output, output)
|
||||
|
@ -42,8 +43,6 @@ class FixtureLoadingTests(TestCase):
|
|||
])
|
||||
|
||||
def test_loading_and_dumping(self):
|
||||
new_io = StringIO()
|
||||
|
||||
Site.objects.all().delete()
|
||||
# Load fixture 1. Single JSON file, with two objects.
|
||||
management.call_command('loaddata', 'fixture1.json', verbosity=0, commit=False)
|
||||
|
@ -184,12 +183,12 @@ class FixtureLoadingTests(TestCase):
|
|||
exclude_list=['fixtures.Article', 'fixtures.Book', 'sites'])
|
||||
|
||||
# Excluding a bogus app should throw an error
|
||||
with self.assertRaisesRegexp(management.CommandError,
|
||||
with six.assertRaisesRegex(self, management.CommandError,
|
||||
"Unknown app in excludes: foo_app"):
|
||||
self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['foo_app'])
|
||||
|
||||
# Excluding a bogus model should throw an error
|
||||
with self.assertRaisesRegexp(management.CommandError,
|
||||
with six.assertRaisesRegex(self, management.CommandError,
|
||||
"Unknown model in excludes: fixtures.FooModel"):
|
||||
self._dumpdata_assert(['fixtures', 'sites'], '', exclude_list=['fixtures.FooModel'])
|
||||
|
||||
|
@ -199,7 +198,7 @@ class FixtureLoadingTests(TestCase):
|
|||
self.assertQuerysetEqual(Spy.objects.all(),
|
||||
['<Spy: Paul>'])
|
||||
# Use the default manager
|
||||
self._dumpdata_assert(['fixtures.Spy'],'[{"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": false}}]' % spy1.pk)
|
||||
self._dumpdata_assert(['fixtures.Spy'], '[{"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": false}}]' % spy1.pk)
|
||||
# Dump using Django's base manager. Should return all objects,
|
||||
# even those normally filtered by the manager
|
||||
self._dumpdata_assert(['fixtures.Spy'], '[{"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": true}}, {"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": false}}]' % (spy2.pk, spy1.pk), use_base_manager=True)
|
||||
|
@ -227,7 +226,7 @@ class FixtureLoadingTests(TestCase):
|
|||
|
||||
def test_ambiguous_compressed_fixture(self):
|
||||
# The name "fixture5" is ambigous, so loading it will raise an error
|
||||
with self.assertRaisesRegexp(management.CommandError,
|
||||
with six.assertRaisesRegex(self, management.CommandError,
|
||||
"Multiple fixtures named 'fixture5'"):
|
||||
management.call_command('loaddata', 'fixture5', verbosity=0, commit=False)
|
||||
|
||||
|
@ -251,7 +250,7 @@ class FixtureLoadingTests(TestCase):
|
|||
# is closed at the end of each test.
|
||||
if connection.vendor == 'mysql':
|
||||
connection.cursor().execute("SET sql_mode = 'TRADITIONAL'")
|
||||
with self.assertRaisesRegexp(IntegrityError,
|
||||
with six.assertRaisesRegex(self, IntegrityError,
|
||||
"Could not load fixtures.Article\(pk=1\): .*$"):
|
||||
management.call_command('loaddata', 'invalid.json', verbosity=0, commit=False)
|
||||
|
||||
|
@ -290,10 +289,11 @@ class FixtureLoadingTests(TestCase):
|
|||
self._dumpdata_assert(['fixtures'], """<?xml version="1.0" encoding="utf-8"?>
|
||||
<django-objects version="1.0"><object pk="1" model="fixtures.category"><field type="CharField" name="title">News Stories</field><field type="TextField" name="description">Latest news stories</field></object><object pk="2" model="fixtures.article"><field type="CharField" name="headline">Poker has no place on ESPN</field><field type="DateTimeField" name="pub_date">2006-06-16T12:00:00</field></object><object pk="3" model="fixtures.article"><field type="CharField" name="headline">Time to reform copyright</field><field type="DateTimeField" name="pub_date">2006-06-16T13:00:00</field></object><object pk="1" model="fixtures.tag"><field type="CharField" name="name">copyright</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="2" model="fixtures.tag"><field type="CharField" name="name">law</field><field to="contenttypes.contenttype" name="tagged_type" rel="ManyToOneRel"><natural>fixtures</natural><natural>article</natural></field><field type="PositiveIntegerField" name="tagged_id">3</field></object><object pk="1" model="fixtures.person"><field type="CharField" name="name">Django Reinhardt</field></object><object pk="2" model="fixtures.person"><field type="CharField" name="name">Stephane Grappelli</field></object><object pk="3" model="fixtures.person"><field type="CharField" name="name">Prince</field></object><object pk="10" model="fixtures.book"><field type="CharField" name="name">Achieving self-awareness of Python programs</field><field to="fixtures.person" name="authors" rel="ManyToManyRel"></field></object></django-objects>""", format='xml', natural_keys=True)
|
||||
|
||||
|
||||
class FixtureTransactionTests(TransactionTestCase):
|
||||
def _dumpdata_assert(self, args, output, format='json'):
|
||||
new_io = StringIO()
|
||||
management.call_command('dumpdata', *args, **{'format':format, 'stdout':new_io})
|
||||
new_io = six.StringIO()
|
||||
management.call_command('dumpdata', *args, **{'format': format, 'stdout': new_io})
|
||||
command_output = new_io.getvalue().strip()
|
||||
self.assertEqual(command_output, output)
|
||||
|
||||
|
@ -308,7 +308,7 @@ class FixtureTransactionTests(TransactionTestCase):
|
|||
|
||||
# Try to load fixture 2 using format discovery; this will fail
|
||||
# because there are two fixture2's in the fixtures directory
|
||||
with self.assertRaisesRegexp(management.CommandError,
|
||||
with six.assertRaisesRegex(self, management.CommandError,
|
||||
"Multiple fixtures named 'fixture2'"):
|
||||
management.call_command('loaddata', 'fixture2', verbosity=0)
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django.test import TestCase
|
||||
from django.utils import six
|
||||
|
||||
from .models import Article, Publication
|
||||
|
||||
|
@ -52,7 +53,7 @@ class ManyToManyTests(TestCase):
|
|||
])
|
||||
|
||||
# Adding an object of the wrong type raises TypeError
|
||||
with self.assertRaisesRegexp(TypeError, "'Publication' instance expected, got <Article.*"):
|
||||
with six.assertRaisesRegex(self, TypeError, "'Publication' instance expected, got <Article.*"):
|
||||
a6.publications.add(a5)
|
||||
# Add a Publication directly via publications.add by using keyword arguments.
|
||||
p4 = a6.publications.create(title='Highlights for Adults')
|
||||
|
|
|
@ -70,7 +70,7 @@ class ManyToOneTests(TestCase):
|
|||
self.assertQuerysetEqual(self.r2.article_set.all(), ["<Article: Paul's story>"])
|
||||
|
||||
# Adding an object of the wrong type raises TypeError.
|
||||
with self.assertRaisesRegexp(TypeError, "'Article' instance expected, got <Reporter.*"):
|
||||
with six.assertRaisesRegex(self, TypeError, "'Article' instance expected, got <Reporter.*"):
|
||||
self.r.article_set.add(self.r2)
|
||||
self.assertQuerysetEqual(self.r.article_set.all(),
|
||||
[
|
||||
|
|
|
@ -2,7 +2,7 @@ from __future__ import absolute_import
|
|||
|
||||
from datetime import date
|
||||
|
||||
from django.db.models.sql.query import InvalidQuery
|
||||
from django.db.models.query_utils import InvalidQuery
|
||||
from django.test import TestCase
|
||||
|
||||
from .models import Author, Book, Coffee, Reviewer, FriendlyAuthor
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django.test import TestCase
|
||||
from django.db.models.signals import pre_save, post_save
|
||||
from django.test import TestCase
|
||||
|
||||
from .models import Person, Employee, ProxyEmployee, Profile, Account
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.utils import six
|
||||
from django.utils.unittest import TestCase
|
||||
|
||||
|
||||
|
@ -18,7 +19,7 @@ class ValidationMessagesTest(TestCase):
|
|||
self._test_validation_messages(f, 'fõo',
|
||||
["'fõo' value must be an integer."])
|
||||
# primary_key must be True. Refs #12467.
|
||||
with self.assertRaisesRegexp(AssertionError,
|
||||
with six.assertRaisesRegex(self, AssertionError,
|
||||
"AutoFields must have primary_key=True."):
|
||||
models.AutoField(primary_key=False)
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ from django.contrib.auth.admin import UserAdmin
|
|||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import TestCase, RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
from django.test.utils import override_settings, six
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from .models import Book, Department, Employee
|
||||
|
@ -18,6 +18,7 @@ from .models import Book, Department, Employee
|
|||
def select_by(dictlist, key, value):
|
||||
return [x for x in dictlist if x[key] == value][0]
|
||||
|
||||
|
||||
class DecadeListFilter(SimpleListFilter):
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
|
@ -520,7 +521,7 @@ class ListFiltersTests(TestCase):
|
|||
"""
|
||||
modeladmin = DecadeFilterBookAdminWithoutTitle(Book, site)
|
||||
request = self.request_factory.get('/', {})
|
||||
self.assertRaisesRegexp(ImproperlyConfigured,
|
||||
six.assertRaisesRegex(self, ImproperlyConfigured,
|
||||
"The list filter 'DecadeListFilterWithoutTitle' does not specify a 'title'.",
|
||||
self.get_changelist, request, Book, modeladmin)
|
||||
|
||||
|
@ -530,7 +531,7 @@ class ListFiltersTests(TestCase):
|
|||
"""
|
||||
modeladmin = DecadeFilterBookAdminWithoutParameter(Book, site)
|
||||
request = self.request_factory.get('/', {})
|
||||
self.assertRaisesRegexp(ImproperlyConfigured,
|
||||
six.assertRaisesRegex(self, ImproperlyConfigured,
|
||||
"The list filter 'DecadeListFilterWithoutParameter' does not specify a 'parameter_name'.",
|
||||
self.get_changelist, request, Book, modeladmin)
|
||||
|
||||
|
|
|
@ -8,10 +8,11 @@ from django.test import TestCase
|
|||
from django.test.utils import override_settings
|
||||
|
||||
# local test models
|
||||
from .admin import InnerInline
|
||||
from .admin import InnerInline, TitleInline, site
|
||||
from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person,
|
||||
OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile,
|
||||
ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2)
|
||||
ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2,
|
||||
Title)
|
||||
|
||||
|
||||
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
||||
|
@ -408,6 +409,47 @@ class SeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
|
|||
fixtures = ['admin-views-users.xml']
|
||||
urls = "regressiontests.admin_inlines.urls"
|
||||
|
||||
def test_add_stackeds(self):
|
||||
"""
|
||||
Ensure that the "Add another XXX" link correctly adds items to the
|
||||
stacked formset.
|
||||
"""
|
||||
self.admin_login(username='super', password='secret')
|
||||
self.selenium.get('%s%s' % (self.live_server_url,
|
||||
'/admin/admin_inlines/holder4/add/'))
|
||||
|
||||
inline_id = '#inner4stacked_set-group'
|
||||
rows_length = lambda: len(self.selenium.find_elements_by_css_selector(
|
||||
'%s .dynamic-inner4stacked_set' % inline_id))
|
||||
self.assertEqual(rows_length(), 3)
|
||||
|
||||
add_button = self.selenium.find_element_by_link_text(
|
||||
'Add another Inner4 Stacked')
|
||||
add_button.click()
|
||||
|
||||
self.assertEqual(rows_length(), 4)
|
||||
|
||||
def test_delete_stackeds(self):
|
||||
self.admin_login(username='super', password='secret')
|
||||
self.selenium.get('%s%s' % (self.live_server_url,
|
||||
'/admin/admin_inlines/holder4/add/'))
|
||||
|
||||
inline_id = '#inner4stacked_set-group'
|
||||
rows_length = lambda: len(self.selenium.find_elements_by_css_selector(
|
||||
'%s .dynamic-inner4stacked_set' % inline_id))
|
||||
self.assertEqual(rows_length(), 3)
|
||||
|
||||
add_button = self.selenium.find_element_by_link_text(
|
||||
'Add another Inner4 Stacked')
|
||||
add_button.click()
|
||||
add_button.click()
|
||||
|
||||
self.assertEqual(rows_length(), 5, msg="sanity check")
|
||||
for delete_link in self.selenium.find_elements_by_css_selector(
|
||||
'%s .inline-deletelink' % inline_id):
|
||||
delete_link.click()
|
||||
self.assertEqual(rows_length(), 3)
|
||||
|
||||
def test_add_inlines(self):
|
||||
"""
|
||||
Ensure that the "Add another XXX" link correctly adds items to the
|
||||
|
@ -516,6 +558,21 @@ class SeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
|
|||
self.assertEqual(len(self.selenium.find_elements_by_css_selector(
|
||||
'form#profilecollection_form tr.dynamic-profile_set#profile_set-2')), 1)
|
||||
|
||||
def test_alternating_rows(self):
|
||||
self.admin_login(username='super', password='secret')
|
||||
self.selenium.get('%s%s' % (self.live_server_url,
|
||||
'/admin/admin_inlines/profilecollection/add/'))
|
||||
|
||||
# Add a few inlines
|
||||
self.selenium.find_element_by_link_text('Add another Profile').click()
|
||||
self.selenium.find_element_by_link_text('Add another Profile').click()
|
||||
|
||||
row_selector = 'form#profilecollection_form tr.dynamic-profile_set'
|
||||
self.assertEqual(len(self.selenium.find_elements_by_css_selector(
|
||||
"%s.row1" % row_selector)), 2, msg="Expect two row1 styled rows")
|
||||
self.assertEqual(len(self.selenium.find_elements_by_css_selector(
|
||||
"%s.row2" % row_selector)), 1, msg="Expect one row2 styled row")
|
||||
|
||||
|
||||
class SeleniumChromeTests(SeleniumFirefoxTests):
|
||||
webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver'
|
||||
|
|
|
@ -181,11 +181,11 @@ class DjangoAdminNoSettings(AdminScriptTestCase):
|
|||
"A series of tests for django-admin.py when there is no settings.py file."
|
||||
|
||||
def test_builtin_command(self):
|
||||
"no settings: django-admin builtin commands fail with an import error when no settings provided"
|
||||
"no settings: django-admin builtin commands fail with an error when no settings provided"
|
||||
args = ['sqlall', 'admin_scripts']
|
||||
out, err = self.run_django_admin(args)
|
||||
self.assertNoOutput(out)
|
||||
self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
|
||||
self.assertOutput(err, 'settings are not configured')
|
||||
|
||||
def test_builtin_with_bad_settings(self):
|
||||
"no settings: django-admin builtin commands fail if settings file (from argument) doesn't exist"
|
||||
|
@ -213,11 +213,11 @@ class DjangoAdminDefaultSettings(AdminScriptTestCase):
|
|||
self.remove_settings('settings.py')
|
||||
|
||||
def test_builtin_command(self):
|
||||
"default: django-admin builtin commands fail with an import error when no settings provided"
|
||||
"default: django-admin builtin commands fail with an error when no settings provided"
|
||||
args = ['sqlall', 'admin_scripts']
|
||||
out, err = self.run_django_admin(args)
|
||||
self.assertNoOutput(out)
|
||||
self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
|
||||
self.assertOutput(err, 'settings are not configured')
|
||||
|
||||
def test_builtin_with_settings(self):
|
||||
"default: django-admin builtin commands succeed if settings are provided as argument"
|
||||
|
@ -279,11 +279,11 @@ class DjangoAdminFullPathDefaultSettings(AdminScriptTestCase):
|
|||
self.remove_settings('settings.py')
|
||||
|
||||
def test_builtin_command(self):
|
||||
"fulldefault: django-admin builtin commands fail with an import error when no settings provided"
|
||||
"fulldefault: django-admin builtin commands fail with an error when no settings provided"
|
||||
args = ['sqlall', 'admin_scripts']
|
||||
out, err = self.run_django_admin(args)
|
||||
self.assertNoOutput(out)
|
||||
self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
|
||||
self.assertOutput(err, 'settings are not configured')
|
||||
|
||||
def test_builtin_with_settings(self):
|
||||
"fulldefault: django-admin builtin commands succeed if a settings file is provided"
|
||||
|
@ -345,11 +345,11 @@ class DjangoAdminMinimalSettings(AdminScriptTestCase):
|
|||
self.remove_settings('settings.py')
|
||||
|
||||
def test_builtin_command(self):
|
||||
"minimal: django-admin builtin commands fail with an import error when no settings provided"
|
||||
"minimal: django-admin builtin commands fail with an error when no settings provided"
|
||||
args = ['sqlall', 'admin_scripts']
|
||||
out, err = self.run_django_admin(args)
|
||||
self.assertNoOutput(out)
|
||||
self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
|
||||
self.assertOutput(err, 'settings are not configured')
|
||||
|
||||
def test_builtin_with_settings(self):
|
||||
"minimal: django-admin builtin commands fail if settings are provided as argument"
|
||||
|
@ -411,11 +411,11 @@ class DjangoAdminAlternateSettings(AdminScriptTestCase):
|
|||
self.remove_settings('alternate_settings.py')
|
||||
|
||||
def test_builtin_command(self):
|
||||
"alternate: django-admin builtin commands fail with an import error when no settings provided"
|
||||
"alternate: django-admin builtin commands fail with an error when no settings provided"
|
||||
args = ['sqlall', 'admin_scripts']
|
||||
out, err = self.run_django_admin(args)
|
||||
self.assertNoOutput(out)
|
||||
self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
|
||||
self.assertOutput(err, 'settings are not configured')
|
||||
|
||||
def test_builtin_with_settings(self):
|
||||
"alternate: django-admin builtin commands succeed if settings are provided as argument"
|
||||
|
@ -482,11 +482,11 @@ class DjangoAdminMultipleSettings(AdminScriptTestCase):
|
|||
self.remove_settings('alternate_settings.py')
|
||||
|
||||
def test_builtin_command(self):
|
||||
"alternate: django-admin builtin commands fail with an import error when no settings provided"
|
||||
"alternate: django-admin builtin commands fail with an error when no settings provided"
|
||||
args = ['sqlall', 'admin_scripts']
|
||||
out, err = self.run_django_admin(args)
|
||||
self.assertNoOutput(out)
|
||||
self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
|
||||
self.assertOutput(err, 'settings are not configured')
|
||||
|
||||
def test_builtin_with_settings(self):
|
||||
"alternate: django-admin builtin commands succeed if settings are provided as argument"
|
||||
|
@ -570,11 +570,11 @@ class DjangoAdminSettingsDirectory(AdminScriptTestCase):
|
|||
self.assertTrue(os.path.exists(os.path.join(app_path, 'api.py')))
|
||||
|
||||
def test_builtin_command(self):
|
||||
"directory: django-admin builtin commands fail with an import error when no settings provided"
|
||||
"directory: django-admin builtin commands fail with an error when no settings provided"
|
||||
args = ['sqlall', 'admin_scripts']
|
||||
out, err = self.run_django_admin(args)
|
||||
self.assertNoOutput(out)
|
||||
self.assertOutput(err, 'environment variable DJANGO_SETTINGS_MODULE is undefined')
|
||||
self.assertOutput(err, 'settings are not configured')
|
||||
|
||||
def test_builtin_with_bad_settings(self):
|
||||
"directory: django-admin builtin commands fail if settings file (from argument) doesn't exist"
|
||||
|
@ -621,7 +621,7 @@ class ManageNoSettings(AdminScriptTestCase):
|
|||
"A series of tests for manage.py when there is no settings.py file."
|
||||
|
||||
def test_builtin_command(self):
|
||||
"no settings: manage.py builtin commands fail with an import error when no settings provided"
|
||||
"no settings: manage.py builtin commands fail with an error when no settings provided"
|
||||
args = ['sqlall', 'admin_scripts']
|
||||
out, err = self.run_manage(args)
|
||||
self.assertNoOutput(out)
|
||||
|
@ -786,7 +786,7 @@ class ManageMinimalSettings(AdminScriptTestCase):
|
|||
self.remove_settings('settings.py')
|
||||
|
||||
def test_builtin_command(self):
|
||||
"minimal: manage.py builtin commands fail with an import error when no settings provided"
|
||||
"minimal: manage.py builtin commands fail with an error when no settings provided"
|
||||
args = ['sqlall', 'admin_scripts']
|
||||
out, err = self.run_manage(args)
|
||||
self.assertNoOutput(out)
|
||||
|
@ -852,7 +852,7 @@ class ManageAlternateSettings(AdminScriptTestCase):
|
|||
self.remove_settings('alternate_settings.py')
|
||||
|
||||
def test_builtin_command(self):
|
||||
"alternate: manage.py builtin commands fail with an import error when no default settings provided"
|
||||
"alternate: manage.py builtin commands fail with an error when no default settings provided"
|
||||
args = ['sqlall', 'admin_scripts']
|
||||
out, err = self.run_manage(args)
|
||||
self.assertNoOutput(out)
|
||||
|
@ -895,7 +895,7 @@ class ManageAlternateSettings(AdminScriptTestCase):
|
|||
args = ['noargs_command']
|
||||
out, err = self.run_manage(args)
|
||||
self.assertNoOutput(out)
|
||||
self.assertOutput(err, "Unknown command: 'noargs_command'")
|
||||
self.assertOutput(err, "Could not import settings 'regressiontests.settings'")
|
||||
|
||||
def test_custom_command_with_settings(self):
|
||||
"alternate: manage.py can execute user commands if settings are provided as argument"
|
||||
|
@ -927,7 +927,7 @@ class ManageMultipleSettings(AdminScriptTestCase):
|
|||
self.remove_settings('alternate_settings.py')
|
||||
|
||||
def test_builtin_command(self):
|
||||
"multiple: manage.py builtin commands fail with an import error when no settings provided"
|
||||
"multiple: manage.py builtin commands fail with an error when no settings provided"
|
||||
args = ['sqlall', 'admin_scripts']
|
||||
out, err = self.run_manage(args)
|
||||
self.assertNoOutput(out)
|
||||
|
@ -1576,7 +1576,6 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
|
|||
self.assertOutput(err, "Destination directory '%s' does not exist, please create it first." % testproject_dir)
|
||||
self.assertFalse(os.path.exists(testproject_dir))
|
||||
|
||||
|
||||
def test_custom_project_template_with_non_ascii_templates(self):
|
||||
"Ticket 18091: Make sure the startproject management command is able to render templates with non-ASCII content"
|
||||
template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template')
|
||||
|
@ -1588,5 +1587,6 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
|
|||
self.assertNoOutput(err)
|
||||
self.assertTrue(os.path.isdir(testproject_dir))
|
||||
path = os.path.join(testproject_dir, 'ticket-18091-non-ascii-template.txt')
|
||||
self.assertEqual(codecs.open(path, 'r', 'utf-8').read(),
|
||||
'Some non-ASCII text for testing ticket #18091:\nüäö €\n')
|
||||
with codecs.open(path, 'r', 'utf-8') as f:
|
||||
self.assertEqual(f.read(),
|
||||
'Some non-ASCII text for testing ticket #18091:\nüäö €\n')
|
||||
|
|
|
@ -27,11 +27,14 @@ from .models import (Article, Chapter, Account, Media, Child, Parent, Picture,
|
|||
Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug,
|
||||
AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod,
|
||||
AdminOrderedCallable, Report, Color2, UnorderedObject, MainPrepopulated,
|
||||
RelatedPrepopulated, UndeletableObject)
|
||||
RelatedPrepopulated, UndeletableObject, Simple)
|
||||
|
||||
|
||||
def callable_year(dt_value):
|
||||
return dt_value.year
|
||||
try:
|
||||
return dt_value.year
|
||||
except AttributeError:
|
||||
return None
|
||||
callable_year.admin_order_field = 'date'
|
||||
|
||||
|
||||
|
@ -575,6 +578,14 @@ class UndeletableObjectAdmin(admin.ModelAdmin):
|
|||
return super(UndeletableObjectAdmin, self).change_view(*args, **kwargs)
|
||||
|
||||
|
||||
def callable_on_unknown(obj):
|
||||
return obj.unknown
|
||||
|
||||
|
||||
class AttributeErrorRaisingAdmin(admin.ModelAdmin):
|
||||
list_display = [callable_on_unknown, ]
|
||||
|
||||
|
||||
site = admin.AdminSite(name="admin")
|
||||
site.register(Article, ArticleAdmin)
|
||||
site.register(CustomArticle, CustomArticleAdmin)
|
||||
|
@ -648,6 +659,7 @@ site.register(AdminOrderedModelMethod, AdminOrderedModelMethodAdmin)
|
|||
site.register(AdminOrderedAdminMethod, AdminOrderedAdminMethodAdmin)
|
||||
site.register(AdminOrderedCallable, AdminOrderedCallableAdmin)
|
||||
site.register(Color2, CustomTemplateFilterColorAdmin)
|
||||
site.register(Simple, AttributeErrorRaisingAdmin)
|
||||
|
||||
# Register core models we need in our tests
|
||||
from django.contrib.auth.models import User, Group
|
||||
|
|
|
@ -49,3 +49,4 @@ site.register(models.Fabric, base_admin.FabricAdmin)
|
|||
site.register(models.ChapterXtra1, base_admin.ChapterXtra1Admin)
|
||||
site.register(User, UserLimitedAdmin)
|
||||
site.register(models.UndeletableObject, base_admin.UndeletableObjectAdmin)
|
||||
site.register(models.Simple, base_admin.AttributeErrorRaisingAdmin)
|
||||
|
|
|
@ -649,3 +649,9 @@ class UndeletableObject(models.Model):
|
|||
Refs #10057.
|
||||
"""
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
|
||||
class Simple(models.Model):
|
||||
"""
|
||||
Simple model with nothing on it for use in testing
|
||||
"""
|
||||
|
|
|
@ -46,7 +46,7 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount,
|
|||
OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField,
|
||||
AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable,
|
||||
Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject,
|
||||
UndeletableObject)
|
||||
Simple, UndeletableObject)
|
||||
|
||||
|
||||
ERROR_MESSAGE = "Please enter the correct username and password \
|
||||
|
@ -579,6 +579,19 @@ class AdminViewBasicTest(TestCase):
|
|||
(self.urlbit, instance.pk))
|
||||
self.assertNotContains(response, 'deletelink')
|
||||
|
||||
def test_allows_attributeerror_to_bubble_up(self):
|
||||
"""
|
||||
Ensure that AttributeErrors are allowed to bubble when raised inside
|
||||
a change list view.
|
||||
|
||||
Requires a model to be created so there's something to be displayed
|
||||
|
||||
Refs: #16655, #18593, and #18747
|
||||
"""
|
||||
Simple.objects.create()
|
||||
with self.assertRaises(AttributeError):
|
||||
self.client.get('/test_admin/%s/admin_views/simple/' % self.urlbit)
|
||||
|
||||
|
||||
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
||||
class AdminViewFormUrlTest(TestCase):
|
||||
|
|
|
@ -655,7 +655,7 @@ class ThreadTests(TestCase):
|
|||
|
||||
class BackendLoadingTests(TestCase):
|
||||
def test_old_style_backends_raise_useful_exception(self):
|
||||
self.assertRaisesRegexp(ImproperlyConfigured,
|
||||
six.assertRaisesRegex(self, ImproperlyConfigured,
|
||||
"Try using django.db.backends.sqlite3 instead",
|
||||
load_backend, 'sqlite3')
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import warnings
|
|||
|
||||
from django.conf import settings
|
||||
from django.core import management
|
||||
from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS
|
||||
from django.core.cache import get_cache
|
||||
from django.core.cache.backends.base import (CacheKeyWarning,
|
||||
InvalidCacheBackendError)
|
||||
from django.db import router
|
||||
|
@ -25,8 +25,7 @@ from django.middleware.cache import (FetchFromCacheMiddleware,
|
|||
from django.template import Template
|
||||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase, TransactionTestCase, RequestFactory
|
||||
from django.test.utils import (get_warnings_state, restore_warnings_state,
|
||||
override_settings)
|
||||
from django.test.utils import override_settings, six
|
||||
from django.utils import timezone, translation, unittest
|
||||
from django.utils.cache import (patch_vary_headers, get_cache_key,
|
||||
learn_cache_key, patch_cache_control, patch_response_headers)
|
||||
|
@ -821,7 +820,7 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase):
|
|||
self.perform_cull_test(50, 18)
|
||||
|
||||
def test_second_call_doesnt_crash(self):
|
||||
with self.assertRaisesRegexp(management.CommandError,
|
||||
with six.assertRaisesRegex(self, management.CommandError,
|
||||
"Cache table 'test cache table' could not be created"):
|
||||
management.call_command(
|
||||
'createcachetable',
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue