Merge remote-tracking branch 'core/master' into schema-alteration

Conflicts:
	django/db/backends/mysql/base.py
	django/db/backends/postgresql_psycopg2/base.py
This commit is contained in:
Andrew Godwin 2012-09-17 20:00:14 +01:00
commit 9313dea700
136 changed files with 2043 additions and 1254 deletions

View File

@ -31,6 +31,8 @@ The PRIMARY AUTHORS are (and/or have been):
* Claude Paroz * Claude Paroz
* Anssi Kääriäinen * Anssi Kääriäinen
* Florian Apolloner * Florian Apolloner
* Jeremy Dunck
* Bryan Veloso
More information on the main contributors to Django can be found in More information on the main contributors to Django can be found in
docs/internals/committers.txt. docs/internals/committers.txt.
@ -167,7 +169,6 @@ answer newbie questions, and generally made Django that much better:
dready <wil@mojipage.com> dready <wil@mojipage.com>
Maximillian Dornseif <md@hudora.de> Maximillian Dornseif <md@hudora.de>
Daniel Duan <DaNmarner@gmail.com> Daniel Duan <DaNmarner@gmail.com>
Jeremy Dunck <http://dunck.us/>
Andrew Durdin <adurdin@gmail.com> Andrew Durdin <adurdin@gmail.com>
dusk@woofle.net dusk@woofle.net
Andy Dustman <farcepest@gmail.com> Andy Dustman <farcepest@gmail.com>
@ -506,6 +507,7 @@ answer newbie questions, and generally made Django that much better:
Johan C. Stöver <johan@nilling.nl> Johan C. Stöver <johan@nilling.nl>
Nowell Strite <http://nowell.strite.org/> Nowell Strite <http://nowell.strite.org/>
Thomas Stromberg <tstromberg@google.com> Thomas Stromberg <tstromberg@google.com>
Travis Swicegood <travis@domain51.com>
Pascal Varet Pascal Varet
SuperJared SuperJared
Radek Švarz <http://www.svarz.cz/translate/> Radek Švarz <http://www.svarz.cz/translate/>

View File

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

View File

@ -8,13 +8,13 @@ certain test -- e.g. being a DateField or ForeignKey.
import datetime import datetime
from django.db import models from django.db import models
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.utils.encoding import smart_text from django.utils.encoding import smart_text
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
from django.contrib.admin.util import (get_model_from_relation, from django.contrib.admin.util import (get_model_from_relation,
reverse_field_path, get_limit_choices_to_from_path, prepare_lookup_value) reverse_field_path, get_limit_choices_to_from_path, prepare_lookup_value)
from django.contrib.admin.options import IncorrectLookupParameters
class ListFilter(object): class ListFilter(object):
title = None # Human-readable title to appear in the right sidebar. title = None # Human-readable title to appear in the right sidebar.
@ -129,7 +129,10 @@ class FieldListFilter(ListFilter):
return True return True
def queryset(self, request, queryset): def queryset(self, request, queryset):
return queryset.filter(**self.used_parameters) try:
return queryset.filter(**self.used_parameters)
except ValidationError as e:
raise IncorrectLookupParameters(e)
@classmethod @classmethod
def register(cls, test, list_filter_class, take_priority=False): def register(cls, test, list_filter_class, take_priority=False):

View File

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

View File

@ -9,128 +9,264 @@
* All rights reserved. * All rights reserved.
* *
* Spiced up with Code from Zain Memon's GSoC project 2009 * Spiced up with Code from Zain Memon's GSoC project 2009
* and modified for Django by Jannis Leidel * and modified for Django by Jannis Leidel, Travis Swicegood and Julien Phalip.
* *
* Licensed under the New BSD License * Licensed under the New BSD License
* See: http://www.opensource.org/licenses/bsd-license.php * See: http://www.opensource.org/licenses/bsd-license.php
*/ */
(function($) { (function($) {
$.fn.formset = function(opts) { $.fn.formset = function(opts) {
var options = $.extend({}, $.fn.formset.defaults, opts); var options = $.extend({}, $.fn.formset.defaults, opts);
var updateElementIndex = function(el, prefix, ndx) { var $this = $(this);
var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))"); var $parent = $this.parent();
var replacement = prefix + "-" + ndx; var updateElementIndex = function(el, prefix, ndx) {
if ($(el).attr("for")) { var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))");
$(el).attr("for", $(el).attr("for").replace(id_regex, replacement)); var replacement = prefix + "-" + ndx;
} if ($(el).attr("for")) {
if (el.id) { $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
el.id = el.id.replace(id_regex, replacement); }
} if (el.id) {
if (el.name) { el.id = el.id.replace(id_regex, replacement);
el.name = el.name.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"); var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off");
// only show the add button if we are allowed to add more items, 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. // note that max_num = None translates to a blank string.
var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0; var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0;
$(this).each(function(i) { $this.each(function(i) {
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass); $(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
}); });
if ($(this).length && showAddButton) { if ($this.length && showAddButton) {
var addButton; var addButton;
if ($(this).attr("tagName") == "TR") { if ($this.attr("tagName") == "TR") {
// If forms are laid out as table rows, insert the // If forms are laid out as table rows, insert the
// "add" button in a new table row: // "add" button in a new table row:
var numCols = this.eq(-1).children().length; var numCols = this.eq(-1).children().length;
$(this).parent().append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a href="javascript:void(0)">' + options.addText + "</a></tr>"); $parent.append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a href="javascript:void(0)">' + options.addText + "</a></tr>");
addButton = $(this).parent().find("tr:last a"); addButton = $parent.find("tr:last a");
} else { } else {
// Otherwise, insert it immediately after the last form: // Otherwise, insert it immediately after the last form:
$(this).filter(":last").after('<div class="' + options.addCssClass + '"><a href="javascript:void(0)">' + options.addText + "</a></div>"); $this.filter(":last").after('<div class="' + options.addCssClass + '"><a href="javascript:void(0)">' + options.addText + "</a></div>");
addButton = $(this).filter(":last").next().find("a"); addButton = $this.filter(":last").next().find("a");
} }
addButton.click(function(e) { addButton.click(function(e) {
e.preventDefault(); e.preventDefault();
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS"); var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS");
var template = $("#" + options.prefix + "-empty"); var template = $("#" + options.prefix + "-empty");
var row = template.clone(true); var row = template.clone(true);
row.removeClass(options.emptyCssClass) row.removeClass(options.emptyCssClass)
.addClass(options.formCssClass) .addClass(options.formCssClass)
.attr("id", options.prefix + "-" + nextIndex); .attr("id", options.prefix + "-" + nextIndex);
if (row.is("tr")) { if (row.is("tr")) {
// If the forms are laid out in table rows, insert // If the forms are laid out in table rows, insert
// the remove button into the last table cell: // the remove button into the last table cell:
row.children(":last").append('<div><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></div>"); 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")) { } else if (row.is("ul") || row.is("ol")) {
// If they're laid out as an ordered/unordered list, // If they're laid out as an ordered/unordered list,
// insert an <li> after the last list item: // insert an <li> after the last list item:
row.append('<li><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></li>"); row.append('<li><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></li>");
} else { } else {
// Otherwise, just insert the remove button as the // Otherwise, just insert the remove button as the
// last child element of the form's container: // 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.children(":first").append('<span><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText + "</a></span>");
} }
row.find("*").each(function() { row.find("*").each(function() {
updateElementIndex(this, options.prefix, totalForms.val()); updateElementIndex(this, options.prefix, totalForms.val());
}); });
// Insert the new form when it has been fully edited // Insert the new form when it has been fully edited
row.insertBefore($(template)); row.insertBefore($(template));
// Update number of total forms // Update number of total forms
$(totalForms).val(parseInt(totalForms.val(), 10) + 1); $(totalForms).val(parseInt(totalForms.val(), 10) + 1);
nextIndex += 1; nextIndex += 1;
// Hide add button in case we've hit the max, except we want to add infinitely // Hide add button in case we've hit the max, except we want to add infinitely
if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) { if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) {
addButton.parent().hide(); addButton.parent().hide();
} }
// The delete button of each row triggers a bunch of other things // The delete button of each row triggers a bunch of other things
row.find("a." + options.deleteCssClass).click(function(e) { row.find("a." + options.deleteCssClass).click(function(e) {
e.preventDefault(); e.preventDefault();
// Remove the parent form containing this button: // Remove the parent form containing this button:
var row = $(this).parents("." + options.formCssClass); var row = $(this).parents("." + options.formCssClass);
row.remove(); row.remove();
nextIndex -= 1; nextIndex -= 1;
// If a post-delete callback was provided, call it with the deleted form: // If a post-delete callback was provided, call it with the deleted form:
if (options.removed) { if (options.removed) {
options.removed(row); options.removed(row);
} }
// Update the TOTAL_FORMS form count. // Update the TOTAL_FORMS form count.
var forms = $("." + options.formCssClass); var forms = $("." + options.formCssClass);
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
// Show add button again once we drop below max // Show add button again once we drop below max
if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) { if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) {
addButton.parent().show(); addButton.parent().show();
} }
// Also, update names and ids for all remaining form controls // Also, update names and ids for all remaining form controls
// so they remain in sequence: // so they remain in sequence:
for (var i=0, formCount=forms.length; i<formCount; i++) for (var i=0, formCount=forms.length; i<formCount; i++)
{ {
updateElementIndex($(forms).get(i), options.prefix, i); updateElementIndex($(forms).get(i), options.prefix, i);
$(forms.get(i)).find("*").each(function() { $(forms.get(i)).find("*").each(function() {
updateElementIndex(this, options.prefix, i); updateElementIndex(this, options.prefix, i);
}); });
} }
}); });
// If a post-add callback was supplied, call it with the added form: // If a post-add callback was supplied, call it with the added form:
if (options.added) { if (options.added) {
options.added(row); options.added(row);
} }
}); });
} }
return this; return this;
}; };
/* Setup plugin defaults */
$.fn.formset.defaults = { /* Setup plugin defaults */
prefix: "form", // The form prefix for your django formset $.fn.formset.defaults = {
addText: "add another", // Text for the add link prefix: "form", // The form prefix for your django formset
deleteText: "remove", // Text for the delete link addText: "add another", // Text for the add link
addCssClass: "add-row", // CSS class applied to the add link deleteText: "remove", // Text for the delete link
deleteCssClass: "delete-row", // CSS class applied to the delete link addCssClass: "add-row", // CSS class applied to the add link
emptyCssClass: "empty-row", // CSS class applied to the empty row deleteCssClass: "delete-row", // CSS class applied to the delete link
formCssClass: "dynamic-form", // CSS class applied to each form in a formset emptyCssClass: "empty-row", // CSS class applied to the empty row
added: null, // Function called each time a new form is added formCssClass: "dynamic-form", // CSS class applied to each form in a formset
removed: null // Function called each time a form is deleted 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); })(django.jQuery);

View File

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

View File

@ -20,63 +20,11 @@
<script type="text/javascript"> <script type="text/javascript">
(function($) { (function($) {
$(document).ready(function() { $("#{{ inline_admin_formset.formset.prefix }}-group .inline-related").stackedFormset({
var rows = "#{{ inline_admin_formset.formset.prefix }}-group .inline-related"; prefix: '{{ inline_admin_formset.formset.prefix }}',
var updateInlineLabel = function(row) { adminStaticPrefix: '{% static "admin/" %}',
$(rows).find(".inline_label").each(function(i) { deleteText: "{% trans "Remove" %}",
var count = i + 1; addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}"
$(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);
})
});
});
})(django.jQuery); })(django.jQuery);
</script> </script>

View File

@ -67,64 +67,13 @@
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
(function($) { (function($) {
$(document).ready(function($) { $("#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr").tabularFormset({
var rows = "#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr"; prefix: "{{ inline_admin_formset.formset.prefix }}",
var alternatingRows = function(row) { adminStaticPrefix: '{% static "admin/" %}',
$(rows).not(".add-row").removeClass("row1 row2") addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
.filter(":even").addClass("row1").end() deleteText: "{% trans 'Remove' %}"
.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);
})
});
});
})(django.jQuery); })(django.jQuery);
</script> </script>

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ from django.utils.translation import ugettext, ugettext_lazy as _
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.hashers import UNUSABLE_PASSWORD, is_password_usable, identify_hasher from django.contrib.auth.hashers import UNUSABLE_PASSWORD, identify_hasher
from django.contrib.auth.tokens import default_token_generator from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.models import get_current_site from django.contrib.sites.models import get_current_site
@ -24,22 +24,22 @@ mask_password = lambda p: "%s%s" % (p[:UNMASKED_DIGITS_TO_SHOW], "*" * max(len(p
class ReadOnlyPasswordHashWidget(forms.Widget): class ReadOnlyPasswordHashWidget(forms.Widget):
def render(self, name, value, attrs): def render(self, name, value, attrs):
encoded = value encoded = value
if not is_password_usable(encoded):
return "None"
final_attrs = self.build_attrs(attrs) final_attrs = self.build_attrs(attrs)
try: if encoded == '' or encoded == UNUSABLE_PASSWORD:
hasher = identify_hasher(encoded) summary = mark_safe("<strong>%s</strong>" % ugettext("No password set."))
except ValueError:
summary = mark_safe("<strong>Invalid password format or unknown hashing algorithm.</strong>")
else: else:
summary = format_html_join('', try:
"<strong>{0}</strong>: {1} ", hasher = identify_hasher(encoded)
((ugettext(key), value) except ValueError:
for key, value in hasher.safe_summary(encoded).items()) summary = mark_safe("<strong>%s</strong>" % ugettext(
) "Invalid password format or unknown hashing algorithm."))
else:
summary = format_html_join('',
"<strong>{0}</strong>: {1} ",
((ugettext(key), value)
for key, value in hasher.safe_summary(encoded).items())
)
return format_html("<div{0}>{1}</div>", flatatt(final_attrs), summary) return format_html("<div{0}>{1}</div>", flatatt(final_attrs), summary)

View File

@ -28,7 +28,13 @@ def reset_hashers(**kwargs):
def is_password_usable(encoded): def is_password_usable(encoded):
return (encoded is not None and encoded != UNUSABLE_PASSWORD) if encoded is None or encoded == UNUSABLE_PASSWORD:
return False
try:
hasher = identify_hasher(encoded)
except ValueError:
return False
return True
def check_password(password, encoded, setter=None, preferred='default'): def check_password(password, encoded, setter=None, preferred='default'):

View File

@ -25,7 +25,7 @@ def update_last_login(sender, user, **kwargs):
the user logging in. the user logging in.
""" """
user.last_login = timezone.now() user.last_login = timezone.now()
user.save() user.save(update_fields=['last_login'])
user_logged_in.connect(update_last_login) user_logged_in.connect(update_last_login)

View File

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

View File

@ -236,23 +236,29 @@ class UserChangeFormTest(TestCase):
# Just check we can create it # Just check we can create it
form = MyUserForm({}) form = MyUserForm({})
def test_unsuable_password(self):
user = User.objects.get(username='empty_password')
user.set_unusable_password()
user.save()
form = UserChangeForm(instance=user)
self.assertIn(_("No password set."), form.as_table())
def test_bug_17944_empty_password(self): def test_bug_17944_empty_password(self):
user = User.objects.get(username='empty_password') user = User.objects.get(username='empty_password')
form = UserChangeForm(instance=user) form = UserChangeForm(instance=user)
# Just check that no error is raised. self.assertIn(_("No password set."), form.as_table())
form.as_table()
def test_bug_17944_unmanageable_password(self): def test_bug_17944_unmanageable_password(self):
user = User.objects.get(username='unmanageable_password') user = User.objects.get(username='unmanageable_password')
form = UserChangeForm(instance=user) form = UserChangeForm(instance=user)
# Just check that no error is raised. self.assertIn(_("Invalid password format or unknown hashing algorithm."),
form.as_table() form.as_table())
def test_bug_17944_unknown_password_algorithm(self): def test_bug_17944_unknown_password_algorithm(self):
user = User.objects.get(username='unknown_password') user = User.objects.get(username='unknown_password')
form = UserChangeForm(instance=user) form = UserChangeForm(instance=user)
# Just check that no error is raised. self.assertIn(_("Invalid password format or unknown hashing algorithm."),
form.as_table() form.as_table())
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) @override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))

View File

@ -100,6 +100,10 @@ class TestUtilsHashPass(unittest.TestCase):
self.assertRaises(ValueError, doit) self.assertRaises(ValueError, doit)
self.assertRaises(ValueError, identify_hasher, "lolcat$salt$hash") self.assertRaises(ValueError, identify_hasher, "lolcat$salt$hash")
def test_bad_encoded(self):
self.assertFalse(is_password_usable('letmein_badencoded'))
self.assertFalse(is_password_usable(''))
def test_low_level_pkbdf2(self): def test_low_level_pkbdf2(self):
hasher = PBKDF2PasswordHasher() hasher = PBKDF2PasswordHasher()
encoded = hasher.encode('letmein', 'seasalt') encoded = hasher.encode('letmein', 'seasalt')

View File

@ -1,7 +1,7 @@
import os import os
import re import re
from django.conf import settings from django.conf import global_settings, settings
from django.contrib.sites.models import Site, RequestSite from django.contrib.sites.models import Site, RequestSite
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core import mail from django.core import mail
@ -23,7 +23,8 @@ from django.contrib.auth.forms import (AuthenticationForm, PasswordChangeForm,
('en', 'English'), ('en', 'English'),
), ),
LANGUAGE_CODE='en', LANGUAGE_CODE='en',
TEMPLATE_DIRS = ( TEMPLATE_LOADERS=global_settings.TEMPLATE_LOADERS,
TEMPLATE_DIRS=(
os.path.join(os.path.dirname(__file__), 'templates'), os.path.join(os.path.dirname(__file__), 'templates'),
), ),
USE_TZ=False, USE_TZ=False,

View File

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

View File

@ -90,8 +90,6 @@ class BaseSpatialOperations(object):
# For quoting column values, rather than columns. # For quoting column values, rather than columns.
def geo_quote_name(self, name): def geo_quote_name(self, name):
if isinstance(name, six.text_type):
name = name.encode('ascii')
return "'%s'" % name return "'%s'" % name
# GeometryField operations # GeometryField operations

View File

@ -3,20 +3,6 @@ A collection of utility routines and classes used by the spatial
backends. backends.
""" """
from django.utils import six
def gqn(val):
"""
The geographic quote name function; used for quoting tables and
geometries (they use single rather than the double quotes of the
backend quotename function).
"""
if isinstance(val, six.string_types):
if isinstance(val, six.text_type): val = val.encode('ascii')
return "'%s'" % val
else:
return str(val)
class SpatialOperation(object): class SpatialOperation(object):
""" """
Base class for generating spatial SQL. Base class for generating spatial SQL.

View File

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

View File

@ -181,7 +181,11 @@ class DataSourceTest(unittest.TestCase):
# Making sure the SpatialReference is as expected. # Making sure the SpatialReference is as expected.
if hasattr(source, 'srs_wkt'): if hasattr(source, 'srs_wkt'):
self.assertEqual(source.srs_wkt, g.srs.wkt) self.assertEqual(
source.srs_wkt,
# Depending on lib versions, WGS_84 might be WGS_1984
g.srs.wkt.replace('SPHEROID["WGS_84"', 'SPHEROID["WGS_1984"')
)
def test06_spatial_filter(self): def test06_spatial_filter(self):
"Testing the Layer.spatial_filter property." "Testing the Layer.spatial_filter property."

View File

@ -1,3 +1,4 @@
import json
from binascii import b2a_hex from binascii import b2a_hex
try: try:
from django.utils.six.moves import cPickle as pickle from django.utils.six.moves import cPickle as pickle
@ -111,8 +112,9 @@ class OGRGeomTest(unittest.TestCase, TestDataMixin):
for g in self.geometries.json_geoms: for g in self.geometries.json_geoms:
geom = OGRGeometry(g.wkt) geom = OGRGeometry(g.wkt)
if not hasattr(g, 'not_equal'): if not hasattr(g, 'not_equal'):
self.assertEqual(g.json, geom.json) # Loading jsons to prevent decimal differences
self.assertEqual(g.json, geom.geojson) self.assertEqual(json.loads(g.json), json.loads(geom.json))
self.assertEqual(json.loads(g.json), json.loads(geom.geojson))
self.assertEqual(OGRGeometry(g.wkt), OGRGeometry(geom.json)) self.assertEqual(OGRGeometry(g.wkt), OGRGeometry(geom.json))
def test02_points(self): def test02_points(self):

View File

@ -110,7 +110,7 @@ def geos_version_info():
is a release candidate (and what number release candidate), and the C API is a release candidate (and what number release candidate), and the C API
version. version.
""" """
ver = geos_version() ver = geos_version().decode()
m = version_regex.match(ver) m = version_regex.match(ver)
if not m: raise GEOSException('Could not parse version info string "%s"' % ver) if not m: raise GEOSException('Could not parse version info string "%s"' % ver)
return dict((key, m.group(key)) for key in ('version', 'release_candidate', 'capi_version', 'major', 'minor', 'subminor')) return dict((key, m.group(key)) for key in ('version', 'release_candidate', 'capi_version', 'major', 'minor', 'subminor'))

View File

@ -215,15 +215,18 @@ class ListMixin(object):
"Standard list reverse method" "Standard list reverse method"
self[:] = self[-1::-1] self[:] = self[-1::-1]
def sort(self, cmp=cmp, key=None, reverse=False): def sort(self, cmp=None, key=None, reverse=False):
"Standard list sort method" "Standard list sort method"
if key: if key:
temp = [(key(v),v) for v in self] temp = [(key(v),v) for v in self]
temp.sort(cmp=cmp, key=lambda x: x[0], reverse=reverse) temp.sort(key=lambda x: x[0], reverse=reverse)
self[:] = [v[1] for v in temp] self[:] = [v[1] for v in temp]
else: else:
temp = list(self) temp = list(self)
temp.sort(cmp=cmp, reverse=reverse) if cmp is not None:
temp.sort(cmp=cmp, reverse=reverse)
else:
temp.sort(reverse=reverse)
self[:] = temp self[:] = temp
### Private routines ### ### Private routines ###

View File

@ -16,7 +16,8 @@ test_suites = [
def suite(): def suite():
"Builds a test suite for the GEOS tests." "Builds a test suite for the GEOS tests."
s = TestSuite() s = TestSuite()
map(s.addTest, test_suites) for suite in test_suites:
s.addTest(suite)
return s return s
def run(verbosity=1): def run(verbosity=1):

View File

@ -1,4 +1,5 @@
import ctypes import ctypes
import json
import random import random
from django.contrib.gis.geos import (GEOSException, GEOSIndexError, GEOSGeometry, from django.contrib.gis.geos import (GEOSException, GEOSIndexError, GEOSGeometry,
@ -204,8 +205,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
for g in self.geometries.json_geoms: for g in self.geometries.json_geoms:
geom = GEOSGeometry(g.wkt) geom = GEOSGeometry(g.wkt)
if not hasattr(g, 'not_equal'): if not hasattr(g, 'not_equal'):
self.assertEqual(g.json, geom.json) # Loading jsons to prevent decimal differences
self.assertEqual(g.json, geom.geojson) self.assertEqual(json.loads(g.json), json.loads(geom.json))
self.assertEqual(json.loads(g.json), json.loads(geom.geojson))
self.assertEqual(GEOSGeometry(g.wkt), GEOSGeometry(geom.json)) self.assertEqual(GEOSGeometry(g.wkt), GEOSGeometry(geom.json))
def test_fromfile(self): def test_fromfile(self):

View File

@ -55,14 +55,14 @@ class ListMixinTest(unittest.TestCase):
def lists_of_len(self, length=None): def lists_of_len(self, length=None):
if length is None: length = self.limit if length is None: length = self.limit
pl = range(length) pl = list(range(length))
return pl, self.listType(pl) return pl, self.listType(pl)
def limits_plus(self, b): def limits_plus(self, b):
return range(-self.limit - b, self.limit + b) return range(-self.limit - b, self.limit + b)
def step_range(self): def step_range(self):
return range(-1 - self.limit, 0) + range(1, 1 + self.limit) return list(range(-1 - self.limit, 0)) + list(range(1, 1 + self.limit))
def test01_getslice(self): def test01_getslice(self):
'Slice retrieval' 'Slice retrieval'
@ -160,13 +160,13 @@ class ListMixinTest(unittest.TestCase):
del pl[i:j] del pl[i:j]
del ul[i:j] del ul[i:j]
self.assertEqual(pl[:], ul[:], 'del slice [%d:%d]' % (i,j)) self.assertEqual(pl[:], ul[:], 'del slice [%d:%d]' % (i,j))
for k in range(-Len - 1,0) + range(1,Len): for k in list(range(-Len - 1, 0)) + list(range(1, Len)):
pl, ul = self.lists_of_len(Len) pl, ul = self.lists_of_len(Len)
del pl[i:j:k] del pl[i:j:k]
del ul[i:j:k] del ul[i:j:k]
self.assertEqual(pl[:], ul[:], 'del slice [%d:%d:%d]' % (i,j,k)) self.assertEqual(pl[:], ul[:], 'del slice [%d:%d:%d]' % (i,j,k))
for k in range(-Len - 1,0) + range(1,Len): for k in list(range(-Len - 1, 0)) + list(range(1, Len)):
pl, ul = self.lists_of_len(Len) pl, ul = self.lists_of_len(Len)
del pl[:i:k] del pl[:i:k]
del ul[:i:k] del ul[:i:k]
@ -177,7 +177,7 @@ class ListMixinTest(unittest.TestCase):
del ul[i::k] del ul[i::k]
self.assertEqual(pl[:], ul[:], 'del slice [%d::%d]' % (i,k)) self.assertEqual(pl[:], ul[:], 'del slice [%d::%d]' % (i,k))
for k in range(-Len - 1,0) + range(1,Len): for k in list(range(-Len - 1, 0)) + list(range(1, Len)):
pl, ul = self.lists_of_len(Len) pl, ul = self.lists_of_len(Len)
del pl[::k] del pl[::k]
del ul[::k] del ul[::k]
@ -320,7 +320,7 @@ class ListMixinTest(unittest.TestCase):
pl.sort() pl.sort()
ul.sort() ul.sort()
self.assertEqual(pl[:], ul[:], 'sort') self.assertEqual(pl[:], ul[:], 'sort')
mid = pl[len(pl) / 2] mid = pl[len(pl) // 2]
pl.sort(key=lambda x: (mid-x)**2) pl.sort(key=lambda x: (mid-x)**2)
ul.sort(key=lambda x: (mid-x)**2) ul.sort(key=lambda x: (mid-x)**2)
self.assertEqual(pl[:], ul[:], 'sort w/ key') self.assertEqual(pl[:], ul[:], 'sort w/ key')
@ -330,7 +330,7 @@ class ListMixinTest(unittest.TestCase):
pl.sort(reverse=True) pl.sort(reverse=True)
ul.sort(reverse=True) ul.sort(reverse=True)
self.assertEqual(pl[:], ul[:], 'sort w/ reverse') self.assertEqual(pl[:], ul[:], 'sort w/ reverse')
mid = pl[len(pl) / 2] mid = pl[len(pl) // 2]
pl.sort(key=lambda x: (mid-x)**2) pl.sort(key=lambda x: (mid-x)**2)
ul.sort(key=lambda x: (mid-x)**2) ul.sort(key=lambda x: (mid-x)**2)
self.assertEqual(pl[:], ul[:], 'sort w/ key') self.assertEqual(pl[:], ul[:], 'sort w/ key')
@ -338,7 +338,7 @@ class ListMixinTest(unittest.TestCase):
def test_12_arithmetic(self): def test_12_arithmetic(self):
'Arithmetic' 'Arithmetic'
pl, ul = self.lists_of_len() pl, ul = self.lists_of_len()
al = range(10,14) al = list(range(10,14))
self.assertEqual(list(pl + al), list(ul + al), 'add') self.assertEqual(list(pl + al), list(ul + al), 'add')
self.assertEqual(type(ul), type(ul + al), 'type of add result') self.assertEqual(type(ul), type(ul + al), 'type of add result')
self.assertEqual(list(al + pl), list(al + ul), 'radd') self.assertEqual(list(al + pl), list(al + ul), 'radd')

View File

@ -191,7 +191,8 @@ class GeoModelTest(TestCase):
cities1 = City.objects.all() cities1 = City.objects.all()
# Only PostGIS would support a 'select *' query because of its recognized # Only PostGIS would support a 'select *' query because of its recognized
# HEXEWKB format for geometry fields # HEXEWKB format for geometry fields
cities2 = City.objects.raw('select id, name, asText(point) from geoapp_city') as_text = 'ST_AsText' if postgis else 'asText'
cities2 = City.objects.raw('select id, name, %s(point) from geoapp_city' % as_text)
self.assertEqual(len(cities1), len(list(cities2))) self.assertEqual(len(cities1), len(list(cities2)))
self.assertTrue(isinstance(cities2[0].point, Point)) self.assertTrue(isinstance(cities2[0].point, Point))

View File

@ -8,9 +8,11 @@ from django.utils import unittest
test_srs = ({'srid' : 4326, test_srs = ({'srid' : 4326,
'auth_name' : ('EPSG', True), 'auth_name' : ('EPSG', True),
'auth_srid' : 4326, 'auth_srid' : 4326,
'srtext' : 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]', # Only the beginning, because there are differences depending on installed libs
'srtext14' : 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]', 'srtext' : 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84"',
'proj4' : '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ', 'proj4' : ['+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ',
# +ellps=WGS84 has been removed in the 4326 proj string in proj-4.8
'+proj=longlat +datum=WGS84 +no_defs '],
'spheroid' : 'WGS 84', 'name' : 'WGS 84', 'spheroid' : 'WGS 84', 'name' : 'WGS 84',
'geographic' : True, 'projected' : False, 'spatialite' : True, 'geographic' : True, 'projected' : False, 'spatialite' : True,
'ellipsoid' : (6378137.0, 6356752.3, 298.257223563), # From proj's "cs2cs -le" and Wikipedia (semi-minor only) 'ellipsoid' : (6378137.0, 6356752.3, 298.257223563), # From proj's "cs2cs -le" and Wikipedia (semi-minor only)
@ -19,9 +21,9 @@ test_srs = ({'srid' : 4326,
{'srid' : 32140, {'srid' : 32140,
'auth_name' : ('EPSG', False), 'auth_name' : ('EPSG', False),
'auth_srid' : 32140, 'auth_srid' : 32140,
'srtext' : 'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",30.28333333333333],PARAMETER["standard_parallel_2",28.38333333333333],PARAMETER["latitude_of_origin",27.83333333333333],PARAMETER["central_meridian",-99],PARAMETER["false_easting",600000],PARAMETER["false_northing",4000000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AUTHORITY["EPSG","32140"]]', 'srtext' : 'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980"',
'srtext14': 'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],UNIT["metre",1,AUTHORITY["EPSG","9001"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",30.28333333333333],PARAMETER["standard_parallel_2",28.38333333333333],PARAMETER["latitude_of_origin",27.83333333333333],PARAMETER["central_meridian",-99],PARAMETER["false_easting",600000],PARAMETER["false_northing",4000000],AUTHORITY["EPSG","32140"],AXIS["X",EAST],AXIS["Y",NORTH]]', 'proj4' : ['+proj=lcc +lat_1=30.28333333333333 +lat_2=28.38333333333333 +lat_0=27.83333333333333 +lon_0=-99 +x_0=600000 +y_0=4000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs ',
'proj4' : '+proj=lcc +lat_1=30.28333333333333 +lat_2=28.38333333333333 +lat_0=27.83333333333333 +lon_0=-99 +x_0=600000 +y_0=4000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs ', '+proj=lcc +lat_1=30.28333333333333 +lat_2=28.38333333333333 +lat_0=27.83333333333333 +lon_0=-99 +x_0=600000 +y_0=4000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs '],
'spheroid' : 'GRS 1980', 'name' : 'NAD83 / Texas South Central', 'spheroid' : 'GRS 1980', 'name' : 'NAD83 / Texas South Central',
'geographic' : False, 'projected' : True, 'spatialite' : False, 'geographic' : False, 'projected' : True, 'spatialite' : False,
'ellipsoid' : (6378137.0, 6356752.31414, 298.257222101), # From proj's "cs2cs -le" and Wikipedia (semi-minor only) 'ellipsoid' : (6378137.0, 6356752.31414, 298.257222101), # From proj's "cs2cs -le" and Wikipedia (semi-minor only)
@ -51,17 +53,12 @@ class SpatialRefSysTest(unittest.TestCase):
# No proj.4 and different srtext on oracle backends :( # No proj.4 and different srtext on oracle backends :(
if postgis: if postgis:
if connection.ops.spatial_version >= (1, 4, 0): self.assertTrue(srs.wkt.startswith(sd['srtext']))
srtext = sd['srtext14'] self.assertTrue(srs.proj4text in sd['proj4'])
else:
srtext = sd['srtext']
self.assertEqual(srtext, srs.wkt)
self.assertEqual(sd['proj4'], srs.proj4text)
@no_mysql @no_mysql
def test02_osr(self): def test02_osr(self):
"Testing getting OSR objects from SpatialRefSys model objects." "Testing getting OSR objects from SpatialRefSys model objects."
from django.contrib.gis.gdal import GDAL_VERSION
for sd in test_srs: for sd in test_srs:
sr = SpatialRefSys.objects.get(srid=sd['srid']) sr = SpatialRefSys.objects.get(srid=sd['srid'])
self.assertEqual(True, sr.spheroid.startswith(sd['spheroid'])) self.assertEqual(True, sr.spheroid.startswith(sd['spheroid']))
@ -76,15 +73,10 @@ class SpatialRefSysTest(unittest.TestCase):
# Testing the SpatialReference object directly. # Testing the SpatialReference object directly.
if postgis or spatialite: if postgis or spatialite:
srs = sr.srs srs = sr.srs
if GDAL_VERSION <= (1, 8): self.assertTrue(srs.proj4 in sd['proj4'])
self.assertEqual(sd['proj4'], srs.proj4)
# No `srtext` field in the `spatial_ref_sys` table in SpatiaLite # No `srtext` field in the `spatial_ref_sys` table in SpatiaLite
if not spatialite: if not spatialite:
if connection.ops.spatial_version >= (1, 4, 0): self.assertTrue(srs.wkt.startswith(sd['srtext']))
srtext = sd['srtext14']
else:
srtext = sd['srtext']
self.assertEqual(srtext, srs.wkt)
@no_mysql @no_mysql
def test03_ellipsoid(self): def test03_ellipsoid(self):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -223,18 +223,17 @@ class WSGIHandler(base.BaseHandler):
set_script_prefix(base.get_script_name(environ)) set_script_prefix(base.get_script_name(environ))
signals.request_started.send(sender=self.__class__) signals.request_started.send(sender=self.__class__)
try: try:
try: request = self.request_class(environ)
request = self.request_class(environ) except UnicodeDecodeError:
except UnicodeDecodeError: logger.warning('Bad Request (UnicodeDecodeError)',
logger.warning('Bad Request (UnicodeDecodeError)', exc_info=sys.exc_info(),
exc_info=sys.exc_info(), extra={
extra={ 'status_code': 400,
'status_code': 400, }
} )
) response = http.HttpResponseBadRequest()
response = http.HttpResponseBadRequest() else:
else: response = self.get_response(request)
response = self.get_response(request)
finally: finally:
signals.request_finished.send(sender=self.__class__) signals.request_finished.send(sender=self.__class__)

View File

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

View File

@ -1,4 +1,5 @@
from optparse import make_option from optparse import make_option
from datetime import datetime
import os import os
import re import re
import sys import sys
@ -90,10 +91,12 @@ class Command(BaseCommand):
self.stdout.write("Validating models...\n\n") self.stdout.write("Validating models...\n\n")
self.validate(display_num_errors=True) self.validate(display_num_errors=True)
self.stdout.write(( self.stdout.write((
"%(started_at)s\n"
"Django version %(version)s, using settings %(settings)r\n" "Django version %(version)s, using settings %(settings)r\n"
"Development server is running at http://%(addr)s:%(port)s/\n" "Development server is running at http://%(addr)s:%(port)s/\n"
"Quit the server with %(quit_command)s.\n" "Quit the server with %(quit_command)s.\n"
) % { ) % {
"started_at": datetime.now().strftime('%B %d, %Y - %X'),
"version": self.get_version(), "version": self.get_version(),
"settings": settings.SETTINGS_MODULE, "settings": settings.SETTINGS_MODULE,
"addr": self._raw_ipv6 and '[%s]' % self.addr or self.addr, "addr": self._raw_ipv6 and '[%s]' % self.addr or self.addr,

View File

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

View File

@ -38,6 +38,7 @@ from django.db.backends.mysql.creation import DatabaseCreation
from django.db.backends.mysql.introspection import DatabaseIntrospection from django.db.backends.mysql.introspection import DatabaseIntrospection
from django.db.backends.mysql.validation import DatabaseValidation from django.db.backends.mysql.validation import DatabaseValidation
from django.db.backends.mysql.schema import DatabaseSchemaEditor from django.db.backends.mysql.schema import DatabaseSchemaEditor
from django.utils.encoding import force_str
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.safestring import SafeBytes, SafeText from django.utils.safestring import SafeBytes, SafeText
from django.utils import six from django.utils import six
@ -392,7 +393,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
if settings_dict['NAME']: if settings_dict['NAME']:
kwargs['db'] = settings_dict['NAME'] kwargs['db'] = settings_dict['NAME']
if settings_dict['PASSWORD']: if settings_dict['PASSWORD']:
kwargs['passwd'] = settings_dict['PASSWORD'] kwargs['passwd'] = force_str(settings_dict['PASSWORD'])
if settings_dict['HOST'].startswith('/'): if settings_dict['HOST'].startswith('/'):
kwargs['unix_socket'] = settings_dict['HOST'] kwargs['unix_socket'] = settings_dict['HOST']
elif settings_dict['HOST']: elif settings_dict['HOST']:

View File

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

View File

@ -14,6 +14,7 @@ from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation
from django.db.backends.postgresql_psycopg2.version import get_version from django.db.backends.postgresql_psycopg2.version import get_version
from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection
from django.db.backends.postgresql_psycopg2.schema import DatabaseSchemaEditor from django.db.backends.postgresql_psycopg2.schema import DatabaseSchemaEditor
from django.utils.encoding import force_str
from django.utils.log import getLogger from django.utils.log import getLogger
from django.utils.safestring import SafeText, SafeBytes from django.utils.safestring import SafeText, SafeBytes
from django.utils import six from django.utils import six
@ -175,7 +176,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
if settings_dict['USER']: if settings_dict['USER']:
conn_params['user'] = settings_dict['USER'] conn_params['user'] = settings_dict['USER']
if settings_dict['PASSWORD']: if settings_dict['PASSWORD']:
conn_params['password'] = settings_dict['PASSWORD'] conn_params['password'] = force_str(settings_dict['PASSWORD'])
if settings_dict['HOST']: if settings_dict['HOST']:
conn_params['host'] = settings_dict['HOST'] conn_params['host'] = settings_dict['HOST']
if settings_dict['PORT']: if settings_dict['PORT']:

View File

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

View File

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

View File

@ -3,9 +3,10 @@ from django.utils.six.moves import zip
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from django.db import transaction from django.db import transaction
from django.db.backends.util import truncate_name from django.db.backends.util import truncate_name
from django.db.models.constants import LOOKUP_SEP
from django.db.models.query_utils import select_related_descend from django.db.models.query_utils import select_related_descend
from django.db.models.sql.constants import (SINGLE, MULTI, ORDER_DIR, from django.db.models.sql.constants import (SINGLE, MULTI, ORDER_DIR,
LOOKUP_SEP, GET_ITERATOR_CHUNK_SIZE) GET_ITERATOR_CHUNK_SIZE)
from django.db.models.sql.datastructures import EmptyResultSet from django.db.models.sql.datastructures import EmptyResultSet
from django.db.models.sql.expressions import SQLEvaluator from django.db.models.sql.expressions import SQLEvaluator
from django.db.models.sql.query import get_order_dir, Query from django.db.models.sql.query import get_order_dir, Query
@ -608,8 +609,12 @@ class SQLCompiler(object):
restricted = False restricted = False
for f, model in opts.get_fields_with_model(): for f, model in opts.get_fields_with_model():
# The get_fields_with_model() returns None for fields that live
# in the field's local model. So, for those fields we want to use
# the f.model - that is the field's local model.
field_model = model or f.model
if not select_related_descend(f, restricted, requested, if not select_related_descend(f, restricted, requested,
only_load.get(model or self.query.model)): only_load.get(field_model)):
continue continue
# The "avoid" set is aliases we want to avoid just for this # The "avoid" set is aliases we want to avoid just for this
# particular branch of the recursion. They aren't permanently # particular branch of the recursion. They aren't permanently

View File

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

View File

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

View File

@ -15,11 +15,12 @@ from django.utils.tree import Node
from django.utils import six from django.utils import six
from django.db import connections, DEFAULT_DB_ALIAS from django.db import connections, DEFAULT_DB_ALIAS
from django.db.models import signals from django.db.models import signals
from django.db.models.constants import LOOKUP_SEP
from django.db.models.expressions import ExpressionNode from django.db.models.expressions import ExpressionNode
from django.db.models.fields import FieldDoesNotExist from django.db.models.fields import FieldDoesNotExist
from django.db.models.sql import aggregates as base_aggregates_module from django.db.models.sql import aggregates as base_aggregates_module
from django.db.models.sql.constants import (QUERY_TERMS, LOOKUP_SEP, ORDER_DIR, from django.db.models.sql.constants import (QUERY_TERMS, ORDER_DIR, SINGLE,
SINGLE, ORDER_PATTERN, JoinInfo) ORDER_PATTERN, JoinInfo)
from django.db.models.sql.datastructures import EmptyResultSet, Empty, MultiJoin from django.db.models.sql.datastructures import EmptyResultSet, Empty, MultiJoin
from django.db.models.sql.expressions import SQLEvaluator from django.db.models.sql.expressions import SQLEvaluator
from django.db.models.sql.where import (WhereNode, Constraint, EverythingNode, from django.db.models.sql.where import (WhereNode, Constraint, EverythingNode,

View File

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

View File

@ -199,7 +199,7 @@ class CharField(Field):
def widget_attrs(self, widget): def widget_attrs(self, widget):
attrs = super(CharField, self).widget_attrs(widget) attrs = super(CharField, self).widget_attrs(widget)
if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)): if self.max_length is not None and isinstance(widget, TextInput):
# The HTML attribute is maxlength, not max_length. # The HTML attribute is maxlength, not max_length.
attrs.update({'maxlength': str(self.max_length)}) attrs.update({'maxlength': str(self.max_length)})
return attrs return attrs

View File

@ -260,10 +260,17 @@ class Input(Widget):
final_attrs['value'] = force_text(self._format_value(value)) final_attrs['value'] = force_text(self._format_value(value))
return format_html('<input{0} />', flatatt(final_attrs)) return format_html('<input{0} />', flatatt(final_attrs))
class TextInput(Input): class TextInput(Input):
input_type = 'text' input_type = 'text'
class PasswordInput(Input): def __init__(self, attrs=None):
if attrs is not None:
self.input_type = attrs.pop('type', self.input_type)
super(TextInput, self).__init__(attrs)
class PasswordInput(TextInput):
input_type = 'password' input_type = 'password'
def __init__(self, attrs=None, render_value=False): def __init__(self, attrs=None, render_value=False):
@ -400,9 +407,8 @@ class Textarea(Widget):
flatatt(final_attrs), flatatt(final_attrs),
force_text(value)) force_text(value))
class DateInput(Input):
input_type = 'text'
class DateInput(TextInput):
def __init__(self, attrs=None, format=None): def __init__(self, attrs=None, format=None):
super(DateInput, self).__init__(attrs) super(DateInput, self).__init__(attrs)
if format: if format:
@ -431,9 +437,8 @@ class DateInput(Input):
pass pass
return super(DateInput, self)._has_changed(self._format_value(initial), data) return super(DateInput, self)._has_changed(self._format_value(initial), data)
class DateTimeInput(Input):
input_type = 'text'
class DateTimeInput(TextInput):
def __init__(self, attrs=None, format=None): def __init__(self, attrs=None, format=None):
super(DateTimeInput, self).__init__(attrs) super(DateTimeInput, self).__init__(attrs)
if format: if format:
@ -462,9 +467,8 @@ class DateTimeInput(Input):
pass pass
return super(DateTimeInput, self)._has_changed(self._format_value(initial), data) return super(DateTimeInput, self)._has_changed(self._format_value(initial), data)
class TimeInput(Input):
input_type = 'text'
class TimeInput(TextInput):
def __init__(self, attrs=None, format=None): def __init__(self, attrs=None, format=None):
super(TimeInput, self).__init__(attrs) super(TimeInput, self).__init__(attrs)
if format: if format:

View File

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

View File

@ -105,7 +105,7 @@ class CsrfViewMiddleware(object):
if getattr(callback, 'csrf_exempt', False): if getattr(callback, 'csrf_exempt', False):
return None return None
# Assume that anything not defined as 'safe' by RC2616 needs protection # Assume that anything not defined as 'safe' by RFC2616 needs protection
if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'): if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
if getattr(request, '_dont_enforce_csrf_checks', False): if getattr(request, '_dont_enforce_csrf_checks', False):
# Mechanism to turn off CSRF checks for test suite. # Mechanism to turn off CSRF checks for test suite.

View File

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

View File

@ -531,11 +531,9 @@ def cycle(parser, token):
The optional flag "silent" can be used to prevent the cycle declaration The optional flag "silent" can be used to prevent the cycle declaration
from returning any value:: from returning any value::
{% cycle 'row1' 'row2' as rowcolors silent %}{# no value here #}
{% for o in some_list %} {% for o in some_list %}
<tr class="{% cycle rowcolors %}">{# first value will be "row1" #} {% cycle 'row1' 'row2' as rowcolors silent %}
... <tr class="{{ rowcolors }}">{% include "subtemplate.html " %}</tr>
</tr>
{% endfor %} {% endfor %}
""" """

View File

@ -51,6 +51,13 @@ def clear_context_processors_cache(**kwargs):
context._standard_context_processors = None context._standard_context_processors = None
@receiver(setting_changed)
def clear_template_loaders_cache(**kwargs):
if kwargs['setting'] == 'TEMPLATE_LOADERS':
from django.template import loader
loader.template_source_loaders = None
@receiver(setting_changed) @receiver(setting_changed)
def clear_serializers_cache(**kwargs): def clear_serializers_cache(**kwargs):
if kwargs['setting'] == 'SERIALIZATION_MODULES': if kwargs['setting'] == 'SERIALIZATION_MODULES':

View File

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

View File

@ -5,8 +5,7 @@ import sys
current_version = sys.version_info current_version = sys.version_info
use_workaround = ( use_workaround = (
(current_version < (2, 6, 8)) or (current_version < (2, 7, 3)) or
(current_version >= (2, 7) and current_version < (2, 7, 3)) or
(current_version >= (3, 0) and current_version < (3, 2, 3)) (current_version >= (3, 0) and current_version < (3, 2, 3))
) )

View File

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

View File

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

View File

@ -28,14 +28,3 @@ Indices, glossary and tables
* :ref:`genindex` * :ref:`genindex`
* :ref:`modindex` * :ref:`modindex`
* :ref:`glossary` * :ref:`glossary`
Deprecated/obsolete documentation
=================================
The following documentation covers features that have been deprecated or that
have been replaced in newer versions of Django.
.. toctree::
:maxdepth: 2
obsolete/index

View File

@ -91,8 +91,7 @@ The dynamically-generated admin site is ugly! How can I change it?
We like it, but if you don't agree, you can modify the admin site's We like it, but if you don't agree, you can modify the admin site's
presentation by editing the CSS stylesheet and/or associated image files. The presentation by editing the CSS stylesheet and/or associated image files. The
site is built using semantic HTML and plenty of CSS hooks, so any changes you'd site is built using semantic HTML and plenty of CSS hooks, so any changes you'd
like to make should be possible by editing the stylesheet. We've got a like to make should be possible by editing the stylesheet.
:doc:`guide to the CSS used in the admin </obsolete/admin-css>` to get you started.
What browsers are supported for using the admin? What browsers are supported for using the admin?
------------------------------------------------ ------------------------------------------------
@ -104,5 +103,5 @@ There *may* be minor stylistic differences between supported browsers—for
example, some browsers may not support rounded corners. These are considered example, some browsers may not support rounded corners. These are considered
acceptable variations in rendering. acceptable variations in rendering.
.. _YUI's A-grade: http://yuilibrary.com/yui/docs/tutorials/gbs/ .. _YUI's A-grade: http://yuilibrary.com/yui/docs/tutorials/gbs/

View File

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

View File

@ -11,7 +11,7 @@ Then, just do this::
>>> from django.db import connection >>> from django.db import connection
>>> connection.queries >>> 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'}] 'time': '0.002'}]
``connection.queries`` is only available if :setting:`DEBUG` is ``True``. ``connection.queries`` is only available if :setting:`DEBUG` is ``True``.

View File

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

View File

@ -5,7 +5,7 @@
Django documentation Django documentation
==================== ====================
.. rubric:: Everything you need to know about Django (and then some). .. rubric:: Everything you need to know about Django.
Getting help Getting help
============ ============

View File

@ -379,6 +379,34 @@ Florian Apolloner
.. _Graz University of Technology: http://tugraz.at/ .. _Graz University of Technology: http://tugraz.at/
.. _Ubuntuusers webteam: http://wiki.ubuntuusers.de/ubuntuusers/Webteam .. _Ubuntuusers webteam: http://wiki.ubuntuusers.de/ubuntuusers/Webteam
Jeremy Dunck
Jeremy was rescued from corporate IT drudgery by Free Software and, in part,
Django. Many of Jeremy's interests center around access to information.
Jeremy was the lead developer of Pegasus News, one of the first uses of
Django outside World Online, and has since joined Votizen, a startup intent
on reducing the influence of money in politics.
He serves as DSF Secretary, organizes and helps organize sprints, cares
about the health and equity of the Django community. He has gone an
embarrassingly long time without a working blog.
Jeremy lives in Mountain View, CA, USA.
`Bryan Veloso`_
Bryan found Django 0.96 through a fellow designer who was evangelizing
its use. It was his first foray outside of the land that was PHP-based
templating. Although he has only ever used Django for personal projects,
it is the very reason he considers himself a designer/developer
hybrid and is working to further design within the Django community.
Bryan works as a designer at GitHub by day, and masquerades as a `vlogger`_
and `shoutcaster`_ in the after-hours. Bryan lives in Los Angeles, CA, USA.
.. _bryan veloso: http://avalonstar.com/
.. _vlogger: http://youtube.com/bryanveloso/
.. _shoutcaster: http://twitch.tv/vlogalonstar/
Specialists Specialists
----------- -----------
@ -403,16 +431,6 @@ Ian Kelly
Matt Boersma Matt Boersma
Matt is also responsible for Django's Oracle support. Matt is also responsible for Django's Oracle support.
Jeremy Dunck
Jeremy is the lead developer of Pegasus News, a personalized local site based
in Dallas, Texas. An early contributor to Greasemonkey and Django, he sees
technology as a tool for communication and access to knowledge.
Jeremy helped kick off GeoDjango development, and is mostly responsible for
the serious speed improvements that signals received in Django 1.0.
Jeremy lives in Dallas, Texas, USA.
`Simon Meers`_ `Simon Meers`_
Simon discovered Django 0.96 during his Computer Science PhD research and Simon discovered Django 0.96 during his Computer Science PhD research and
has been developing with it full-time ever since. His core code has been developing with it full-time ever since. His core code

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -67,8 +67,7 @@ different needs:
whathaveyou. whathaveyou.
* Finally, there's some "specialized" documentation not usually relevant to * Finally, there's some "specialized" documentation not usually relevant to
most developers. This includes the :doc:`release notes </releases/index>`, most developers. This includes the :doc:`release notes </releases/index>` and
:doc:`documentation of obsolete features </obsolete/index>`,
:doc:`internals documentation </internals/index>` for those who want to add :doc:`internals documentation </internals/index>` for those who want to add
code to Django itself, and a :doc:`few other things that simply don't fit code to Django itself, and a :doc:`few other things that simply don't fit
elsewhere </misc/index>`. elsewhere </misc/index>`.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,186 +0,0 @@
======================================
Customizing the Django admin interface
======================================
.. warning::
The design of the admin has changed somewhat since this document was
written, and parts may not apply any more. This document is no longer
maintained since an official API for customizing the Django admin interface
is in development.
Django's dynamic admin interface gives you a fully-functional admin for free
with no hand-coding required. The dynamic admin is designed to be
production-ready, not just a starting point, so you can use it as-is on a real
site. While the underlying format of the admin pages is built in to Django, you
can customize the look and feel by editing the admin stylesheet and images.
Here's a quick and dirty overview some of the main styles and classes used in
the Django admin CSS.
Modules
=======
The ``.module`` class is a basic building block for grouping content in the
admin. It's generally applied to a ``div`` or a ``fieldset``. It wraps the content
group in a box and applies certain styles to the elements within. An ``h2``
within a ``div.module`` will align to the top of the ``div`` as a header for the
whole group.
.. image:: _images/module.png
:alt: Example use of module class on admin homepage
Column Types
============
.. note::
All admin pages (except the dashboard) are fluid-width. All fixed-width
classes from previous Django versions have been removed.
The base template for each admin page has a block that defines the column
structure for the page. This sets a class on the page content area
(``div#content``) so everything on the page knows how wide it should be. There
are three column types available.
colM
This is the default column setting for all pages. The "M" stands for "main".
Assumes that all content on the page is in one main column
(``div#content-main``).
colMS
This is for pages with one main column and a sidebar on the right. The "S"
stands for "sidebar". Assumes that main content is in ``div#content-main``
and sidebar content is in ``div#content-related``. This is used on the main
admin page.
colSM
Same as above, with the sidebar on the left. The source order of the columns
doesn't matter.
For instance, you could stick this in a template to make a two-column page with
the sidebar on the right:
.. code-block:: html+django
{% block coltype %}colMS{% endblock %}
Text Styles
===========
Font Sizes
----------
Most HTML elements (headers, lists, etc.) have base font sizes in the stylesheet
based on context. There are three classes are available for forcing text to a
certain size in any context.
small
11px
tiny
10px
mini
9px (use sparingly)
Font Styles and Alignment
-------------------------
There are also a few styles for styling text.
.quiet
Sets font color to light gray. Good for side notes in instructions. Combine
with ``.small`` or ``.tiny`` for sheer excitement.
.help
This is a custom class for blocks of inline help text explaining the
function of form elements. It makes text smaller and gray, and when applied
to ``p`` elements within ``.form-row`` elements (see Form Styles below),
it will offset the text to align with the form field. Use this for help
text, instead of ``small quiet``. It works on other elements, but try to
put the class on a ``p`` whenever you can.
.align-left
It aligns the text left. Only works on block elements containing inline
elements.
.align-right
Are you paying attention?
.nowrap
Keeps text and inline objects from wrapping. Comes in handy for table
headers you want to stay on one line.
Floats and Clears
-----------------
float-left
floats left
float-right
floats right
clear
clears all
Object Tools
============
Certain actions which apply directly to an object are used in form and
changelist pages. These appear in a "toolbar" row above the form or changelist,
to the right of the page. The tools are wrapped in a ``ul`` with the class
``object-tools``. There are two custom tool types which can be defined with an
additional class on the ``a`` for that tool. These are ``.addlink`` and
``.viewsitelink``.
Example from a changelist page:
.. code-block:: html+django
<ul class="object-tools">
<li><a href="/stories/add/" class="addlink">Add redirect</a></li>
</ul>
.. image:: _images/objecttools_01.png
:alt: Object tools on a changelist page
and from a form page:
.. code-block:: html+django
<ul class="object-tools">
<li><a href="/history/303/152383/">History</a></li>
<li><a href="/r/303/152383/" class="viewsitelink">View on site</a></li>
</ul>
.. image:: _images/objecttools_02.png
:alt: Object tools on a form page
Form Styles
===========
Fieldsets
---------
Admin forms are broken up into groups by ``fieldset`` elements. Each form fieldset
should have a class ``.module``. Each fieldset should have a header ``h2`` within the
fieldset at the top (except the first group in the form, and in some cases where the
group of fields doesn't have a logical label).
Each fieldset can also take extra classes in addition to ``.module`` to apply
appropriate formatting to the group of fields.
.aligned
This will align the labels and inputs side by side on the same line.
.wide
Used in combination with ``.aligned`` to widen the space available for the
labels.
Form Rows
---------
Each row of the form (within the ``fieldset``) should be enclosed in a ``div``
with class ``form-row``. If the field in the row is required, a class of
``required`` should also be added to the ``div.form-row``.
.. image:: _images/formrow.png
:alt: Example use of form-row class
Labels
------
Form labels should always precede the field, except in the case
of checkboxes and radio buttons, where the ``input`` should come first. Any
explanation or help text should follow the ``label`` in a ``p`` with class
``.help``.

View File

@ -1,12 +0,0 @@
Deprecated/obsolete documentation
=================================
These documents cover features that have been deprecated or that have been
replaced in newer versions of Django. They're preserved here for folks using old
versions of Django or those still using deprecated APIs. No new code based on
these APIs should be written.
.. toctree::
:maxdepth: 1
admin-css

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -187,6 +187,14 @@ The ``ContentTypeManager``
probably won't ever need to call this method yourself; Django will call probably won't ever need to call this method yourself; Django will call
it automatically when it's needed. it automatically when it's needed.
.. method:: get_for_id(id)
Lookup a :class:`~django.contrib.contenttypes.models.ContentType` by ID.
Since this method uses the same shared cache as
:meth:`~django.contrib.contenttypes.models.ContentTypeManager.get_for_model`,
it's preferred to use this method over the usual
``ContentType.objects.get(pk=id)``
.. method:: get_for_model(model[, for_concrete_model=True]) .. method:: get_for_model(model[, for_concrete_model=True])
Takes either a model class or an instance of a model, and returns the Takes either a model class or an instance of a model, and returns the

View File

@ -155,7 +155,8 @@ or the
:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names()` :meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names()`
method, which are documented in the method, which are documented in the
:class:`~django.views.generic.base.TemplateResponseMixin` documentation. The :class:`~django.views.generic.base.TemplateResponseMixin` documentation. The
latter one allows you to use a different template for each form. latter one allows you to use a different template for each form (:ref:`see the
example below <wizard-template-for-each-form>`).
This template expects a ``wizard`` object that has various items attached to This template expects a ``wizard`` object that has various items attached to
it: it:
@ -238,6 +239,65 @@ wizard's :meth:`as_view` method takes a list of your
(r'^contact/$', ContactWizard.as_view([ContactForm1, ContactForm2])), (r'^contact/$', ContactWizard.as_view([ContactForm1, ContactForm2])),
) )
.. _wizard-template-for-each-form:
Using a different template for each form
----------------------------------------
As mentioned above, you may specify a different template for each form.
Consider an example using a form wizard to implement a multi-step checkout
process for an online store. In the first step, the user specifies a billing
and shipping address. In the second step, the user chooses payment type. If
they chose to pay by credit card, they will enter credit card information in
the next step. In the final step, they will confirm the purchase.
Here's what the view code might look like::
from django.http import HttpResponseRedirect
from django.contrib.formtools.wizard.views import SessionWizardView
FORMS = [("address", myapp.forms.AddressForm),
("paytype", myapp.forms.PaymentChoiceForm),
("cc", myapp.forms.CreditCardForm),
("confirmation", myapp.forms.OrderForm)]
TEMPLATES = {"address": "checkout/billingaddress.html",
"paytype": "checkout/paymentmethod.html",
"cc": "checkout/creditcard.html",
"confirmation": "checkout/confirmation.html"}
def pay_by_credit_card(wizard):
"""Return true if user opts to pay by credit card"""
# Get cleaned data from payment step
cleaned_data = wizard.get_cleaned_data_for_step('paytype') or {'method': 'none'}
# Return true if the user selected credit card
return cleaned_data['method'] == 'cc'
class OrderWizard(SessionWizardView):
def get_template_names(self):
return [TEMPLATES[self.steps.current]]
def done(self, form_list, **kwargs):
do_something_with_the_form_data(form_list)
return HttpResponseRedirect('/page-to-redirect-to-when-done/')
...
The ``urls.py`` file would contain something like::
urlpatterns = patterns('',
(r'^checkout/$', OrderWizard.as_view(FORMS, condition_dict={'cc': pay_by_credit_card})),
)
Note that the ``OrderWizard`` object is initialized with a list of pairs.
The first element in the pair is a string that corresponds to the name of the
step and the second is the form class.
In this example, the
:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names()`
method returns a list containing a single template, which is selected based on
the name of the current step.
.. _wizardview-advanced-methods: .. _wizardview-advanced-methods:
Advanced ``WizardView`` methods Advanced ``WizardView`` methods

View File

@ -959,15 +959,15 @@ Ubuntu & Debian GNU/Linux
Ubuntu Ubuntu
^^^^^^ ^^^^^^
11.10 11.10 through 12.04
~~~~~ ~~~~~~~~~~~~~~~~~~~
In Ubuntu 11.10, PostgreSQL was upgraded to 9.1. The installation commands are: In Ubuntu 11.10, PostgreSQL was upgraded to 9.1. The installation command is:
.. code-block:: bash .. code-block:: bash
$ sudo apt-get install binutils gdal-bin libproj-dev postgresql-9.1-postgis \ $ sudo apt-get install binutils gdal-bin libproj-dev \
postgresql-server-dev-9.1 python-psycopg2 postgresql-9.1-postgis postgresql-server-dev-9.1 python-psycopg2
.. _ubuntu10: .. _ubuntu10:
@ -976,7 +976,7 @@ In Ubuntu 11.10, PostgreSQL was upgraded to 9.1. The installation commands are:
In Ubuntu 10.04, PostgreSQL was upgraded to 8.4 and GDAL was upgraded to 1.6. In Ubuntu 10.04, PostgreSQL was upgraded to 8.4 and GDAL was upgraded to 1.6.
Ubuntu 10.04 uses PostGIS 1.4, while Ubuntu 10.10 uses PostGIS 1.5 (with Ubuntu 10.04 uses PostGIS 1.4, while Ubuntu 10.10 uses PostGIS 1.5 (with
geography support). The installation commands are: geography support). The installation command is:
.. code-block:: bash .. code-block:: bash

View File

@ -674,8 +674,8 @@ __ http://spatialreference.org/ref/epsg/32140/
.. admonition:: Raw queries .. admonition:: Raw queries
When using :doc:`raw queries </topics/db/sql>`, you should generally wrap When using :doc:`raw queries </topics/db/sql>`, you should generally wrap
your geometry fields with the ``asText()`` SQL function so as the field your geometry fields with the ``asText()`` SQL function (or ``ST_AsText``
value will be recognized by GEOS:: for PostGIS) so as the field value will be recognized by GEOS::
City.objects.raw('SELECT id, name, asText(point) from myapp_city') City.objects.raw('SELECT id, name, asText(point) from myapp_city')

View File

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

View File

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

View File

@ -398,11 +398,21 @@ For each field, we describe the default widget used if you don't specify
If no ``input_formats`` argument is provided, the default input formats are:: If no ``input_formats`` argument is provided, the default input formats are::
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' '%Y-%m-%d', # '2006-10-25'
'%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006' '%m/%d/%Y', # '10/25/2006'
'%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006' '%m/%d/%y', # '10/25/06'
'%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
'%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006' Additionally, if you specify :setting:`USE_L10N=False<USE_L10N>` in your settings, the
following will also be included in the default input formats::
'%b %m %d', # 'Oct 25 2006'
'%b %d, %Y', # 'Oct 25, 2006'
'%d %b %Y', # '25 Oct 2006'
'%d %b, %Y', # '25 Oct, 2006'
'%B %d %Y', # 'October 25 2006'
'%B %d, %Y', # 'October 25, 2006'
'%d %B %Y', # '25 October 2006'
'%d %B, %Y', # '25 October, 2006'
``DateTimeField`` ``DateTimeField``
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
@ -842,7 +852,7 @@ Slightly complex built-in ``Field`` classes
``MultiValueField`` ``MultiValueField``
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
.. class:: MultiValueField(**kwargs) .. class:: MultiValueField(fields=(), **kwargs)
* Default widget: ``TextInput`` * Default widget: ``TextInput``
* Empty value: ``''`` (an empty string) * Empty value: ``''`` (an empty string)
@ -851,22 +861,39 @@ Slightly complex built-in ``Field`` classes
as an argument to the ``MultiValueField``. as an argument to the ``MultiValueField``.
* Error message keys: ``required``, ``invalid`` * Error message keys: ``required``, ``invalid``
This abstract field (must be subclassed) aggregates the logic of multiple Aggregates the logic of multiple fields that together produce a single
fields. Subclasses should not have to implement clean(). Instead, they must value.
implement compress(), which takes a list of valid values and returns a
"compressed" version of those values -- a single value. For example, This field is abstract and must be subclassed. In contrast with the
:class:`SplitDateTimeField` is a subclass which combines a time field and single-value fields, subclasses of :class:`MultiValueField` must not
a date field into a datetime object. implement :meth:`~django.forms.Field.clean` but instead - implement
:meth:`~MultiValueField.compress`.
Takes one extra required argument: Takes one extra required argument:
.. attribute:: fields .. attribute:: fields
A list of fields which are cleaned into a single field. Each value in A tuple of fields whose values are cleaned and subsequently combined
``clean`` is cleaned by the corresponding field in ``fields`` -- the first into a single value. Each value of the field is cleaned by the
value is cleaned by the first field, the second value is cleaned by corresponding field in ``fields`` -- the first value is cleaned by the
the second field, etc. Once all fields are cleaned, the list of clean first field, the second value is cleaned by the second field, etc.
values is "compressed" into a single value. Once all fields are cleaned, the list of clean values is combined into
a single value by :meth:`~MultiValueField.compress`.
.. attribute:: MultiValueField.widget
Must be a subclass of :class:`django.forms.MultiWidget`.
Default value is :class:`~django.forms.widgets.TextInput`, which
probably is not very useful in this case.
.. method:: compress(data_list)
Takes a list of valid values and returns a "compressed" version of
those values -- in a single value. For example,
:class:`SplitDateTimeField` is a subclass which combines a time field
and a date field into a ``datetime`` object.
This method must be implemented in the subclasses.
``SplitDateTimeField`` ``SplitDateTimeField``
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~

View File

@ -11,6 +11,16 @@ A widget is Django's representation of a HTML input element. The widget
handles the rendering of the HTML, and the extraction of data from a GET/POST handles the rendering of the HTML, and the extraction of data from a GET/POST
dictionary that corresponds to the widget. dictionary that corresponds to the widget.
.. tip::
Widgets should not be confused with the :doc:`form fields </ref/forms/fields>`.
Form fields deal with the logic of input validation and are used directly
in templates. Widgets deal with rendering of HTML form input elements on
the web page and extraction of raw submitted data. However, widgets do
need to be :ref:`assigned <widget-to-field>` to form fields.
.. _widget-to-field:
Specifying widgets Specifying widgets
------------------ ------------------
@ -95,15 +105,23 @@ choices are inherent to the model and not just the representational widget.
Customizing widget instances Customizing widget instances
---------------------------- ----------------------------
When Django renders a widget as HTML, it only renders the bare minimum When Django renders a widget as HTML, it only renders very minimal markup -
HTML - Django doesn't add a class definition, or any other widget-specific Django doesn't add class names, or any other widget-specific attributes. This
attributes. This means that all :class:`TextInput` widgets will appear the same means, for example, that all :class:`TextInput` widgets will appear the same
on your Web page. on your Web pages.
If you want to make one widget look different to another, you need to There are two ways to customize widgets: :ref:`per widget instance
specify additional attributes for each widget. When you specify a <styling-widget-instances>` and :ref:`per widget class <styling-widget-classes>`.
widget, you can provide a list of attributes that will be added to the
rendered HTML for the widget. .. _styling-widget-instances:
Styling widget instances
^^^^^^^^^^^^^^^^^^^^^^^^
If you want to make one widget instance look different from another, you will
need to specify additional attributes at the time when the widget object is
instantiated and assigned to a form field (and perhaps add some rules to your
CSS files).
For example, take the following simple form:: For example, take the following simple form::
@ -126,10 +144,9 @@ provided for each widget will be rendered exactly the same::
On a real Web page, you probably don't want every widget to look the same. You On a real Web page, you probably don't want every widget to look the same. You
might want a larger input element for the comment, and you might want the might want a larger input element for the comment, and you might want the
'name' widget to have some special CSS class. To do this, you use the 'name' widget to have some special CSS class. It is also possible to specify
:attr:`Widget.attrs` argument when creating the widget: the 'type' attribute to take advantage of the new HTML5 input types. To do
this, you use the :attr:`Widget.attrs` argument when creating the widget::
For example::
class CommentForm(forms.Form): class CommentForm(forms.Form):
name = forms.CharField( name = forms.CharField(
@ -146,24 +163,41 @@ Django will then include the extra attributes in the rendered output:
<tr><th>Url:</th><td><input type="text" name="url"/></td></tr> <tr><th>Url:</th><td><input type="text" name="url"/></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" size="40"/></td></tr> <tr><th>Comment:</th><td><input type="text" name="comment" size="40"/></td></tr>
.. _built-in widgets: .. _styling-widget-classes:
Built-in widgets Styling widget classes
---------------- ^^^^^^^^^^^^^^^^^^^^^^
Django provides a representation of all the basic HTML widgets, plus some With widgets, it is possible to add media (``css`` and ``javascript``)
commonly used groups of widgets: and more deeply customize their appearance and behavior.
``Widget`` In a nutshell, you will need to subclass the widget and either
~~~~~~~~~~ :ref:`define a class "Media" <media-as-a-static-definition>` as a member of the
subclass, or :ref:`create a property "media" <dynamic-property>`, returning an
instance of that class.
.. class:: Widget These methods involve somewhat advanced Python programming and are described in
detail in the :doc:`Form Media </topics/forms/media>` topic guide.
This abstract class cannot be rendered, but provides the basic attribute :attr:`~Widget.attrs`. .. _base-widget-classes:
Base Widget classes
-------------------
Base widget classes :class:`Widget` and :class:`MultiWidget` are subclassed by
all the :ref:`built-in widgets <built-in widgets>` and may serve as a
foundation for custom widgets.
.. class:: Widget(attrs=None)
This abstract class cannot be rendered, but provides the basic attribute
:attr:`~Widget.attrs`. You may also implement or override the
:meth:`~Widget.render()` method on custom widgets.
.. attribute:: Widget.attrs .. attribute:: Widget.attrs
A dictionary containing HTML attributes to be set on the rendered widget. A dictionary containing HTML attributes to be set on the rendered
widget.
.. code-block:: python .. code-block:: python
@ -171,6 +205,74 @@ commonly used groups of widgets:
>>> name.render('name', 'A name') >>> name.render('name', 'A name')
u'<input title="Your name" type="text" name="name" value="A name" size="10" />' u'<input title="Your name" type="text" name="name" value="A name" size="10" />'
.. method:: render(name, value, attrs=None)
Returns HTML for the widget, as a Unicode string. This method must be
implemented by the subclass, otherwise ``NotImplementedError`` will be
raised.
The 'value' given is not guaranteed to be valid input, therefore
subclass implementations should program defensively.
.. class:: MultiWidget(widgets, attrs=None)
A widget that is composed of multiple widgets.
:class:`~django.forms.widgets.MultiWidget` works hand in hand with the
:class:`~django.forms.MultiValueField`.
.. method:: render(name, value, attrs=None)
Argument `value` is handled differently in this method from the
subclasses of :class:`~Widget`.
If `value` is a list, output of :meth:`~MultiWidget.render` will be a
concatenation of rendered child widgets. If `value` is not a list, it
will be first processed by the method :meth:`~MultiWidget.decompress()`
to create the list and then processed as above.
Unlike in the single value widgets, method :meth:`~MultiWidget.render`
need not be implemented in the subclasses.
.. method:: decompress(value)
Returns a list of "decompressed" values for the given value of the
multi-value field that makes use of the widget. The input value can be
assumed as valid, but not necessarily non-empty.
This method **must be implemented** by the subclass, and since the
value may be empty, the implementation must be defensive.
The rationale behind "decompression" is that it is necessary to "split"
the combined value of the form field into the values of the individual
field encapsulated within the multi-value field (e.g. when displaying
the partially or fully filled-out form).
.. tip::
Note that :class:`~django.forms.MultiValueField` has a
complementary method :meth:`~django.forms.MultiValueField.compress`
with the opposite responsibility - to combine cleaned values of
all member fields into one.
.. _built-in widgets:
Built-in widgets
----------------
Django provides a representation of all the basic HTML widgets, plus some
commonly used groups of widgets in the ``django.forms.widgets`` module,
including :ref:`the input of text <text-widgets>`, :ref:`various checkboxes
and selectors <selector-widgets>`, :ref:`uploading files <file-upload-widgets>`,
and :ref:`handling of multi-valued input <composite-widgets>`.
.. _text-widgets:
Widgets handling input of text
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
These widgets make use of the HTML elements ``input`` and ``textarea``.
``TextInput`` ``TextInput``
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
@ -204,39 +306,8 @@ commonly used groups of widgets:
Hidden input: ``<input type='hidden' ...>`` Hidden input: ``<input type='hidden' ...>``
``MultipleHiddenInput`` Note that there also is a :class:`MultipleHiddenInput` widget that
~~~~~~~~~~~~~~~~~~~~~~~ encapsulates a set of hidden input elements.
.. class:: MultipleHiddenInput
Multiple ``<input type='hidden' ...>`` widgets.
A widget that handles multiple hidden widgets for fields that have a list
of values.
.. attribute:: MultipleHiddenInput.choices
This attribute is optional when the field does not have a
:attr:`~Field.choices` attribute. If it does, it will override anything
you set here when the attribute is updated on the :class:`Field`.
``FileInput``
~~~~~~~~~~~~~
.. class:: FileInput
File upload input: ``<input type='file' ...>``
``ClearableFileInput``
~~~~~~~~~~~~~~~~~~~~~~
.. class:: ClearableFileInput
.. versionadded:: 1.3
File upload input: ``<input type='file' ...>``, with an additional checkbox
input to clear the field's value, if the field is not required and has
initial data.
``DateInput`` ``DateInput``
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
@ -245,7 +316,7 @@ commonly used groups of widgets:
Date input as a simple text box: ``<input type='text' ...>`` Date input as a simple text box: ``<input type='text' ...>``
Takes one optional argument: Takes same arguments as :class:`TextInput`, with one more optional argument:
.. attribute:: DateInput.format .. attribute:: DateInput.format
@ -262,7 +333,7 @@ commonly used groups of widgets:
Date/time input as a simple text box: ``<input type='text' ...>`` Date/time input as a simple text box: ``<input type='text' ...>``
Takes one optional argument: Takes same arguments as :class:`TextInput`, with one more optional argument:
.. attribute:: DateTimeInput.format .. attribute:: DateTimeInput.format
@ -279,7 +350,7 @@ commonly used groups of widgets:
Time input as a simple text box: ``<input type='text' ...>`` Time input as a simple text box: ``<input type='text' ...>``
Takes one optional argument: Takes same arguments as :class:`TextInput`, with one more optional argument:
.. attribute:: TimeInput.format .. attribute:: TimeInput.format
@ -296,6 +367,11 @@ commonly used groups of widgets:
Text area: ``<textarea>...</textarea>`` Text area: ``<textarea>...</textarea>``
.. _selector-widgets:
Selector and checkbox widgets
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
``CheckboxInput`` ``CheckboxInput``
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
@ -439,6 +515,50 @@ commonly used groups of widgets:
... ...
</ul> </ul>
.. _file-upload-widgets:
File upload widgets
^^^^^^^^^^^^^^^^^^^
``FileInput``
~~~~~~~~~~~~~
.. class:: FileInput
File upload input: ``<input type='file' ...>``
``ClearableFileInput``
~~~~~~~~~~~~~~~~~~~~~~
.. class:: ClearableFileInput
.. versionadded:: 1.3
File upload input: ``<input type='file' ...>``, with an additional checkbox
input to clear the field's value, if the field is not required and has
initial data.
.. _composite-widgets:
Composite widgets
^^^^^^^^^^^^^^^^^
``MultipleHiddenInput``
~~~~~~~~~~~~~~~~~~~~~~~
.. class:: MultipleHiddenInput
Multiple ``<input type='hidden' ...>`` widgets.
A widget that handles multiple hidden widgets for fields that have a list
of values.
.. attribute:: MultipleHiddenInput.choices
This attribute is optional when the field does not have a
:attr:`~Field.choices` attribute. If it does, it will override anything
you set here when the attribute is updated on the :class:`Field`.
``MultiWidget`` ``MultiWidget``
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~

View File

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

View File

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

View File

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

View File

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

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