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:
commit
9313dea700
4
AUTHORS
4
AUTHORS
|
@ -31,6 +31,8 @@ The PRIMARY AUTHORS are (and/or have been):
|
|||
* Claude Paroz
|
||||
* Anssi Kääriäinen
|
||||
* Florian Apolloner
|
||||
* Jeremy Dunck
|
||||
* Bryan Veloso
|
||||
|
||||
More information on the main contributors to Django can be found in
|
||||
docs/internals/committers.txt.
|
||||
|
@ -167,7 +169,6 @@ answer newbie questions, and generally made Django that much better:
|
|||
dready <wil@mojipage.com>
|
||||
Maximillian Dornseif <md@hudora.de>
|
||||
Daniel Duan <DaNmarner@gmail.com>
|
||||
Jeremy Dunck <http://dunck.us/>
|
||||
Andrew Durdin <adurdin@gmail.com>
|
||||
dusk@woofle.net
|
||||
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>
|
||||
Nowell Strite <http://nowell.strite.org/>
|
||||
Thomas Stromberg <tstromberg@google.com>
|
||||
Travis Swicegood <travis@domain51.com>
|
||||
Pascal Varet
|
||||
SuperJared
|
||||
Radek Švarz <http://www.svarz.cz/translate/>
|
||||
|
|
|
@ -7,7 +7,6 @@ a list of all possible variables.
|
|||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import time # Needed for Windows
|
||||
import warnings
|
||||
|
||||
|
@ -26,7 +25,7 @@ class LazySettings(LazyObject):
|
|||
The user can manually configure settings prior to using them. Otherwise,
|
||||
Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE.
|
||||
"""
|
||||
def _setup(self):
|
||||
def _setup(self, name):
|
||||
"""
|
||||
Load the settings module pointed to by the environment variable. This
|
||||
is used the first time we need any settings at all, if the user has not
|
||||
|
@ -37,12 +36,21 @@ class LazySettings(LazyObject):
|
|||
if not settings_module: # If it's set but is an empty string.
|
||||
raise KeyError
|
||||
except KeyError:
|
||||
# NOTE: This is arguably an EnvironmentError, but that causes
|
||||
# problems with Python's interactive help.
|
||||
raise ImportError("Settings cannot be imported, because environment variable %s is undefined." % ENVIRONMENT_VARIABLE)
|
||||
raise ImproperlyConfigured(
|
||||
"Requested setting %s, but settings are not configured. "
|
||||
"You must either define the environment variable %s "
|
||||
"or call settings.configure() before accessing settings."
|
||||
% (name, ENVIRONMENT_VARIABLE))
|
||||
|
||||
self._wrapped = Settings(settings_module)
|
||||
|
||||
|
||||
def __getattr__(self, name):
|
||||
if self._wrapped is empty:
|
||||
self._setup(name)
|
||||
return getattr(self._wrapped, name)
|
||||
|
||||
|
||||
def configure(self, default_settings=global_settings, **options):
|
||||
"""
|
||||
Called to manually configure the settings. The 'default_settings'
|
||||
|
|
|
@ -8,13 +8,13 @@ certain test -- e.g. being a DateField or ForeignKey.
|
|||
import datetime
|
||||
|
||||
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.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
|
||||
from django.contrib.admin.util import (get_model_from_relation,
|
||||
reverse_field_path, get_limit_choices_to_from_path, prepare_lookup_value)
|
||||
from django.contrib.admin.options import IncorrectLookupParameters
|
||||
|
||||
class ListFilter(object):
|
||||
title = None # Human-readable title to appear in the right sidebar.
|
||||
|
@ -129,7 +129,10 @@ class FieldListFilter(ListFilter):
|
|||
return True
|
||||
|
||||
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
|
||||
def register(cls, test, list_filter_class, take_priority=False):
|
||||
|
|
|
@ -14,9 +14,10 @@ from django.core.exceptions import PermissionDenied, ValidationError
|
|||
from django.core.paginator import Paginator
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models, transaction, router
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.related import RelatedObject
|
||||
from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
|
||||
from django.db.models.sql.constants import LOOKUP_SEP, QUERY_TERMS
|
||||
from django.db.models.sql.constants import QUERY_TERMS
|
||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template.response import SimpleTemplateResponse, TemplateResponse
|
||||
|
@ -1456,8 +1457,10 @@ class InlineModelAdmin(BaseModelAdmin):
|
|||
return request.user.has_perm(
|
||||
self.opts.app_label + '.' + self.opts.get_delete_permission())
|
||||
|
||||
|
||||
class StackedInline(InlineModelAdmin):
|
||||
template = 'admin/edit_inline/stacked.html'
|
||||
|
||||
|
||||
class TabularInline(InlineModelAdmin):
|
||||
template = 'admin/edit_inline/tabular.html'
|
||||
|
|
|
@ -9,128 +9,264 @@
|
|||
* All rights reserved.
|
||||
*
|
||||
* Spiced up with Code from Zain Memon's GSoC project 2009
|
||||
* and modified for Django by Jannis Leidel
|
||||
* and modified for Django by Jannis Leidel, Travis Swicegood and Julien Phalip.
|
||||
*
|
||||
* Licensed under the New BSD License
|
||||
* See: http://www.opensource.org/licenses/bsd-license.php
|
||||
*/
|
||||
(function($) {
|
||||
$.fn.formset = function(opts) {
|
||||
var options = $.extend({}, $.fn.formset.defaults, opts);
|
||||
var updateElementIndex = function(el, prefix, ndx) {
|
||||
var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))");
|
||||
var replacement = prefix + "-" + ndx;
|
||||
if ($(el).attr("for")) {
|
||||
$(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
|
||||
}
|
||||
if (el.id) {
|
||||
el.id = el.id.replace(id_regex, replacement);
|
||||
}
|
||||
if (el.name) {
|
||||
el.name = el.name.replace(id_regex, replacement);
|
||||
}
|
||||
};
|
||||
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off");
|
||||
var nextIndex = parseInt(totalForms.val(), 10);
|
||||
var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").attr("autocomplete", "off");
|
||||
// only show the add button if we are allowed to add more items,
|
||||
$.fn.formset = function(opts) {
|
||||
var options = $.extend({}, $.fn.formset.defaults, opts);
|
||||
var $this = $(this);
|
||||
var $parent = $this.parent();
|
||||
var updateElementIndex = function(el, prefix, ndx) {
|
||||
var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))");
|
||||
var replacement = prefix + "-" + ndx;
|
||||
if ($(el).attr("for")) {
|
||||
$(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
|
||||
}
|
||||
if (el.id) {
|
||||
el.id = el.id.replace(id_regex, replacement);
|
||||
}
|
||||
if (el.name) {
|
||||
el.name = el.name.replace(id_regex, replacement);
|
||||
}
|
||||
};
|
||||
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").attr("autocomplete", "off");
|
||||
var nextIndex = parseInt(totalForms.val(), 10);
|
||||
var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").attr("autocomplete", "off");
|
||||
// only show the add button if we are allowed to add more items,
|
||||
// note that max_num = None translates to a blank string.
|
||||
var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0;
|
||||
$(this).each(function(i) {
|
||||
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
|
||||
});
|
||||
if ($(this).length && showAddButton) {
|
||||
var addButton;
|
||||
if ($(this).attr("tagName") == "TR") {
|
||||
// If forms are laid out as table rows, insert the
|
||||
// "add" button in a new table row:
|
||||
var numCols = this.eq(-1).children().length;
|
||||
$(this).parent().append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a href="javascript:void(0)">' + options.addText + "</a></tr>");
|
||||
addButton = $(this).parent().find("tr:last a");
|
||||
} else {
|
||||
// Otherwise, insert it immediately after the last form:
|
||||
$(this).filter(":last").after('<div class="' + options.addCssClass + '"><a href="javascript:void(0)">' + options.addText + "</a></div>");
|
||||
addButton = $(this).filter(":last").next().find("a");
|
||||
}
|
||||
addButton.click(function(e) {
|
||||
e.preventDefault();
|
||||
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS");
|
||||
var template = $("#" + options.prefix + "-empty");
|
||||
var row = template.clone(true);
|
||||
row.removeClass(options.emptyCssClass)
|
||||
.addClass(options.formCssClass)
|
||||
.attr("id", options.prefix + "-" + nextIndex);
|
||||
if (row.is("tr")) {
|
||||
// If the forms are laid out in table rows, insert
|
||||
// the remove button into the last table cell:
|
||||
row.children(":last").append('<div><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></div>");
|
||||
} else if (row.is("ul") || row.is("ol")) {
|
||||
// If they're laid out as an ordered/unordered list,
|
||||
// insert an <li> after the last list item:
|
||||
row.append('<li><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></li>");
|
||||
} else {
|
||||
// Otherwise, just insert the remove button as the
|
||||
// last child element of the form's container:
|
||||
row.children(":first").append('<span><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText + "</a></span>");
|
||||
}
|
||||
row.find("*").each(function() {
|
||||
updateElementIndex(this, options.prefix, totalForms.val());
|
||||
});
|
||||
// Insert the new form when it has been fully edited
|
||||
row.insertBefore($(template));
|
||||
// Update number of total forms
|
||||
$(totalForms).val(parseInt(totalForms.val(), 10) + 1);
|
||||
nextIndex += 1;
|
||||
// Hide add button in case we've hit the max, except we want to add infinitely
|
||||
if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) {
|
||||
addButton.parent().hide();
|
||||
}
|
||||
// The delete button of each row triggers a bunch of other things
|
||||
row.find("a." + options.deleteCssClass).click(function(e) {
|
||||
e.preventDefault();
|
||||
// Remove the parent form containing this button:
|
||||
var row = $(this).parents("." + options.formCssClass);
|
||||
row.remove();
|
||||
nextIndex -= 1;
|
||||
// If a post-delete callback was provided, call it with the deleted form:
|
||||
if (options.removed) {
|
||||
options.removed(row);
|
||||
}
|
||||
// Update the TOTAL_FORMS form count.
|
||||
var forms = $("." + options.formCssClass);
|
||||
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
|
||||
// Show add button again once we drop below max
|
||||
if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) {
|
||||
addButton.parent().show();
|
||||
}
|
||||
// Also, update names and ids for all remaining form controls
|
||||
// so they remain in sequence:
|
||||
for (var i=0, formCount=forms.length; i<formCount; i++)
|
||||
{
|
||||
updateElementIndex($(forms).get(i), options.prefix, i);
|
||||
$(forms.get(i)).find("*").each(function() {
|
||||
updateElementIndex(this, options.prefix, i);
|
||||
});
|
||||
}
|
||||
});
|
||||
// If a post-add callback was supplied, call it with the added form:
|
||||
if (options.added) {
|
||||
options.added(row);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
};
|
||||
/* Setup plugin defaults */
|
||||
$.fn.formset.defaults = {
|
||||
prefix: "form", // The form prefix for your django formset
|
||||
addText: "add another", // Text for the add link
|
||||
deleteText: "remove", // Text for the delete link
|
||||
addCssClass: "add-row", // CSS class applied to the add link
|
||||
deleteCssClass: "delete-row", // CSS class applied to the delete link
|
||||
emptyCssClass: "empty-row", // CSS class applied to the empty row
|
||||
formCssClass: "dynamic-form", // CSS class applied to each form in a formset
|
||||
added: null, // Function called each time a new form is added
|
||||
removed: null // Function called each time a form is deleted
|
||||
};
|
||||
var showAddButton = maxForms.val() === '' || (maxForms.val()-totalForms.val()) > 0;
|
||||
$this.each(function(i) {
|
||||
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
|
||||
});
|
||||
if ($this.length && showAddButton) {
|
||||
var addButton;
|
||||
if ($this.attr("tagName") == "TR") {
|
||||
// If forms are laid out as table rows, insert the
|
||||
// "add" button in a new table row:
|
||||
var numCols = this.eq(-1).children().length;
|
||||
$parent.append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a href="javascript:void(0)">' + options.addText + "</a></tr>");
|
||||
addButton = $parent.find("tr:last a");
|
||||
} else {
|
||||
// Otherwise, insert it immediately after the last form:
|
||||
$this.filter(":last").after('<div class="' + options.addCssClass + '"><a href="javascript:void(0)">' + options.addText + "</a></div>");
|
||||
addButton = $this.filter(":last").next().find("a");
|
||||
}
|
||||
addButton.click(function(e) {
|
||||
e.preventDefault();
|
||||
var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS");
|
||||
var template = $("#" + options.prefix + "-empty");
|
||||
var row = template.clone(true);
|
||||
row.removeClass(options.emptyCssClass)
|
||||
.addClass(options.formCssClass)
|
||||
.attr("id", options.prefix + "-" + nextIndex);
|
||||
if (row.is("tr")) {
|
||||
// If the forms are laid out in table rows, insert
|
||||
// the remove button into the last table cell:
|
||||
row.children(":last").append('<div><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></div>");
|
||||
} else if (row.is("ul") || row.is("ol")) {
|
||||
// If they're laid out as an ordered/unordered list,
|
||||
// insert an <li> after the last list item:
|
||||
row.append('<li><a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + "</a></li>");
|
||||
} else {
|
||||
// Otherwise, just insert the remove button as the
|
||||
// last child element of the form's container:
|
||||
row.children(":first").append('<span><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText + "</a></span>");
|
||||
}
|
||||
row.find("*").each(function() {
|
||||
updateElementIndex(this, options.prefix, totalForms.val());
|
||||
});
|
||||
// Insert the new form when it has been fully edited
|
||||
row.insertBefore($(template));
|
||||
// Update number of total forms
|
||||
$(totalForms).val(parseInt(totalForms.val(), 10) + 1);
|
||||
nextIndex += 1;
|
||||
// Hide add button in case we've hit the max, except we want to add infinitely
|
||||
if ((maxForms.val() !== '') && (maxForms.val()-totalForms.val()) <= 0) {
|
||||
addButton.parent().hide();
|
||||
}
|
||||
// The delete button of each row triggers a bunch of other things
|
||||
row.find("a." + options.deleteCssClass).click(function(e) {
|
||||
e.preventDefault();
|
||||
// Remove the parent form containing this button:
|
||||
var row = $(this).parents("." + options.formCssClass);
|
||||
row.remove();
|
||||
nextIndex -= 1;
|
||||
// If a post-delete callback was provided, call it with the deleted form:
|
||||
if (options.removed) {
|
||||
options.removed(row);
|
||||
}
|
||||
// Update the TOTAL_FORMS form count.
|
||||
var forms = $("." + options.formCssClass);
|
||||
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
|
||||
// Show add button again once we drop below max
|
||||
if ((maxForms.val() === '') || (maxForms.val()-forms.length) > 0) {
|
||||
addButton.parent().show();
|
||||
}
|
||||
// Also, update names and ids for all remaining form controls
|
||||
// so they remain in sequence:
|
||||
for (var i=0, formCount=forms.length; i<formCount; i++)
|
||||
{
|
||||
updateElementIndex($(forms).get(i), options.prefix, i);
|
||||
$(forms.get(i)).find("*").each(function() {
|
||||
updateElementIndex(this, options.prefix, i);
|
||||
});
|
||||
}
|
||||
});
|
||||
// If a post-add callback was supplied, call it with the added form:
|
||||
if (options.added) {
|
||||
options.added(row);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/* Setup plugin defaults */
|
||||
$.fn.formset.defaults = {
|
||||
prefix: "form", // The form prefix for your django formset
|
||||
addText: "add another", // Text for the add link
|
||||
deleteText: "remove", // Text for the delete link
|
||||
addCssClass: "add-row", // CSS class applied to the add link
|
||||
deleteCssClass: "delete-row", // CSS class applied to the delete link
|
||||
emptyCssClass: "empty-row", // CSS class applied to the empty row
|
||||
formCssClass: "dynamic-form", // CSS class applied to each form in a formset
|
||||
added: null, // Function called each time a new form is added
|
||||
removed: null // Function called each time a form is deleted
|
||||
};
|
||||
|
||||
|
||||
// Tabular inlines ---------------------------------------------------------
|
||||
$.fn.tabularFormset = function(options) {
|
||||
var $rows = $(this);
|
||||
var alternatingRows = function(row) {
|
||||
$($rows.selector).not(".add-row").removeClass("row1 row2")
|
||||
.filter(":even").addClass("row1").end()
|
||||
.filter(":odd").addClass("row2");
|
||||
};
|
||||
|
||||
var reinitDateTimeShortCuts = function() {
|
||||
// Reinitialize the calendar and clock widgets by force
|
||||
if (typeof DateTimeShortcuts != "undefined") {
|
||||
$(".datetimeshortcuts").remove();
|
||||
DateTimeShortcuts.init();
|
||||
}
|
||||
};
|
||||
|
||||
var updateSelectFilter = function() {
|
||||
// If any SelectFilter widgets are a part of the new form,
|
||||
// instantiate a new SelectFilter instance for it.
|
||||
if (typeof SelectFilter != 'undefined'){
|
||||
$('.selectfilter').each(function(index, value){
|
||||
var namearr = value.name.split('-');
|
||||
SelectFilter.init(value.id, namearr[namearr.length-1], false, options.adminStaticPrefix );
|
||||
});
|
||||
$('.selectfilterstacked').each(function(index, value){
|
||||
var namearr = value.name.split('-');
|
||||
SelectFilter.init(value.id, namearr[namearr.length-1], true, options.adminStaticPrefix );
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var initPrepopulatedFields = function(row) {
|
||||
row.find('.prepopulated_field').each(function() {
|
||||
var field = $(this),
|
||||
input = field.find('input, select, textarea'),
|
||||
dependency_list = input.data('dependency_list') || [],
|
||||
dependencies = [];
|
||||
$.each(dependency_list, function(i, field_name) {
|
||||
dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id'));
|
||||
});
|
||||
if (dependencies.length) {
|
||||
input.prepopulate(dependencies, input.attr('maxlength'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$rows.formset({
|
||||
prefix: options.prefix,
|
||||
addText: options.addText,
|
||||
formCssClass: "dynamic-" + options.prefix,
|
||||
deleteCssClass: "inline-deletelink",
|
||||
deleteText: options.deleteText,
|
||||
emptyCssClass: "empty-form",
|
||||
removed: alternatingRows,
|
||||
added: function(row) {
|
||||
initPrepopulatedFields(row);
|
||||
reinitDateTimeShortCuts();
|
||||
updateSelectFilter();
|
||||
alternatingRows(row);
|
||||
}
|
||||
});
|
||||
|
||||
return $rows;
|
||||
};
|
||||
|
||||
// Stacked inlines ---------------------------------------------------------
|
||||
$.fn.stackedFormset = function(options) {
|
||||
var $rows = $(this);
|
||||
var updateInlineLabel = function(row) {
|
||||
$($rows.selector).find(".inline_label").each(function(i) {
|
||||
var count = i + 1;
|
||||
$(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
|
||||
});
|
||||
};
|
||||
|
||||
var reinitDateTimeShortCuts = function() {
|
||||
// Reinitialize the calendar and clock widgets by force, yuck.
|
||||
if (typeof DateTimeShortcuts != "undefined") {
|
||||
$(".datetimeshortcuts").remove();
|
||||
DateTimeShortcuts.init();
|
||||
}
|
||||
};
|
||||
|
||||
var updateSelectFilter = function() {
|
||||
// If any SelectFilter widgets were added, instantiate a new instance.
|
||||
if (typeof SelectFilter != "undefined"){
|
||||
$(".selectfilter").each(function(index, value){
|
||||
var namearr = value.name.split('-');
|
||||
SelectFilter.init(value.id, namearr[namearr.length-1], false, options.adminStaticPrefix);
|
||||
});
|
||||
$(".selectfilterstacked").each(function(index, value){
|
||||
var namearr = value.name.split('-');
|
||||
SelectFilter.init(value.id, namearr[namearr.length-1], true, options.adminStaticPrefix);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var initPrepopulatedFields = function(row) {
|
||||
row.find('.prepopulated_field').each(function() {
|
||||
var field = $(this),
|
||||
input = field.find('input, select, textarea'),
|
||||
dependency_list = input.data('dependency_list') || [],
|
||||
dependencies = [];
|
||||
$.each(dependency_list, function(i, field_name) {
|
||||
dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id'));
|
||||
});
|
||||
if (dependencies.length) {
|
||||
input.prepopulate(dependencies, input.attr('maxlength'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$rows.formset({
|
||||
prefix: options.prefix,
|
||||
addText: options.addText,
|
||||
formCssClass: "dynamic-" + options.prefix,
|
||||
deleteCssClass: "inline-deletelink",
|
||||
deleteText: options.deleteText,
|
||||
emptyCssClass: "empty-form",
|
||||
removed: updateInlineLabel,
|
||||
added: (function(row) {
|
||||
initPrepopulatedFields(row);
|
||||
reinitDateTimeShortCuts();
|
||||
updateSelectFilter();
|
||||
updateInlineLabel(row);
|
||||
})
|
||||
});
|
||||
|
||||
return $rows;
|
||||
};
|
||||
})(django.jQuery);
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
(function(b){b.fn.formset=function(g){var a=b.extend({},b.fn.formset.defaults,g),k=function(c,f,e){var d=RegExp("("+f+"-(\\d+|__prefix__))");f=f+"-"+e;b(c).attr("for")&&b(c).attr("for",b(c).attr("for").replace(d,f));if(c.id)c.id=c.id.replace(d,f);if(c.name)c.name=c.name.replace(d,f)};g=b("#id_"+a.prefix+"-TOTAL_FORMS").attr("autocomplete","off");var l=parseInt(g.val(),10),h=b("#id_"+a.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off");g=h.val()===""||h.val()-g.val()>0;b(this).each(function(){b(this).not("."+
|
||||
a.emptyCssClass).addClass(a.formCssClass)});if(b(this).length&&g){var j;if(b(this).attr("tagName")=="TR"){g=this.eq(-1).children().length;b(this).parent().append('<tr class="'+a.addCssClass+'"><td colspan="'+g+'"><a href="javascript:void(0)">'+a.addText+"</a></tr>");j=b(this).parent().find("tr:last a")}else{b(this).filter(":last").after('<div class="'+a.addCssClass+'"><a href="javascript:void(0)">'+a.addText+"</a></div>");j=b(this).filter(":last").next().find("a")}j.click(function(c){c.preventDefault();
|
||||
var f=b("#id_"+a.prefix+"-TOTAL_FORMS");c=b("#"+a.prefix+"-empty");var e=c.clone(true);e.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+l);if(e.is("tr"))e.children(":last").append('<div><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></div>");else e.is("ul")||e.is("ol")?e.append('<li><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></li>"):e.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+
|
||||
a.deleteText+"</a></span>");e.find("*").each(function(){k(this,a.prefix,f.val())});e.insertBefore(b(c));b(f).val(parseInt(f.val(),10)+1);l+=1;h.val()!==""&&h.val()-f.val()<=0&&j.parent().hide();e.find("a."+a.deleteCssClass).click(function(d){d.preventDefault();d=b(this).parents("."+a.formCssClass);d.remove();l-=1;a.removed&&a.removed(d);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);if(h.val()===""||h.val()-d.length>0)j.parent().show();for(var i=0,m=d.length;i<m;i++){k(b(d).get(i),
|
||||
a.prefix,i);b(d.get(i)).find("*").each(function(){k(this,a.prefix,i)})}});a.added&&a.added(e)})}return this};b.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null}})(django.jQuery);
|
||||
(function(b){b.fn.formset=function(d){var a=b.extend({},b.fn.formset.defaults,d),c=b(this),d=c.parent(),i=function(a,e,g){var d=RegExp("("+e+"-(\\d+|__prefix__))"),e=e+"-"+g;b(a).attr("for")&&b(a).attr("for",b(a).attr("for").replace(d,e));a.id&&(a.id=a.id.replace(d,e));a.name&&(a.name=a.name.replace(d,e))},f=b("#id_"+a.prefix+"-TOTAL_FORMS").attr("autocomplete","off"),g=parseInt(f.val(),10),e=b("#id_"+a.prefix+"-MAX_NUM_FORMS").attr("autocomplete","off"),f=""===e.val()||0<e.val()-f.val();c.each(function(){b(this).not("."+
|
||||
a.emptyCssClass).addClass(a.formCssClass)});if(c.length&&f){var h;"TR"==c.attr("tagName")?(c=this.eq(-1).children().length,d.append('<tr class="'+a.addCssClass+'"><td colspan="'+c+'"><a href="javascript:void(0)">'+a.addText+"</a></tr>"),h=d.find("tr:last a")):(c.filter(":last").after('<div class="'+a.addCssClass+'"><a href="javascript:void(0)">'+a.addText+"</a></div>"),h=c.filter(":last").next().find("a"));h.click(function(d){d.preventDefault();var f=b("#id_"+a.prefix+"-TOTAL_FORMS"),d=b("#"+a.prefix+
|
||||
"-empty"),c=d.clone(true);c.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+g);c.is("tr")?c.children(":last").append('<div><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></div>"):c.is("ul")||c.is("ol")?c.append('<li><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></li>"):c.children(":first").append('<span><a class="'+a.deleteCssClass+'" href="javascript:void(0)">'+a.deleteText+"</a></span>");c.find("*").each(function(){i(this,
|
||||
a.prefix,f.val())});c.insertBefore(b(d));b(f).val(parseInt(f.val(),10)+1);g=g+1;e.val()!==""&&e.val()-f.val()<=0&&h.parent().hide();c.find("a."+a.deleteCssClass).click(function(d){d.preventDefault();d=b(this).parents("."+a.formCssClass);d.remove();g=g-1;a.removed&&a.removed(d);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);(e.val()===""||e.val()-d.length>0)&&h.parent().show();for(var c=0,f=d.length;c<f;c++){i(b(d).get(c),a.prefix,c);b(d.get(c)).find("*").each(function(){i(this,
|
||||
a.prefix,c)})}});a.added&&a.added(c)})}return this};b.fn.formset.defaults={prefix:"form",addText:"add another",deleteText:"remove",addCssClass:"add-row",deleteCssClass:"delete-row",emptyCssClass:"empty-row",formCssClass:"dynamic-form",added:null,removed:null};b.fn.tabularFormset=function(d){var a=b(this),c=function(){b(a.selector).not(".add-row").removeClass("row1 row2").filter(":even").addClass("row1").end().filter(":odd").addClass("row2")};a.formset({prefix:d.prefix,addText:d.addText,formCssClass:"dynamic-"+
|
||||
d.prefix,deleteCssClass:"inline-deletelink",deleteText:d.deleteText,emptyCssClass:"empty-form",removed:c,added:function(a){a.find(".prepopulated_field").each(function(){var d=b(this).find("input, select, textarea"),c=d.data("dependency_list")||[],e=[];b.each(c,function(d,b){e.push("#"+a.find(".field-"+b).find("input, select, textarea").attr("id"))});e.length&&d.prepopulate(e,d.attr("maxlength"))});"undefined"!=typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),DateTimeShortcuts.init());"undefined"!=
|
||||
typeof SelectFilter&&(b(".selectfilter").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],false,d.adminStaticPrefix)}),b(".selectfilterstacked").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],true,d.adminStaticPrefix)}));c(a)}});return a};b.fn.stackedFormset=function(d){var a=b(this),c=function(){b(a.selector).find(".inline_label").each(function(a){a+=1;b(this).html(b(this).html().replace(/(#\d+)/g,"#"+a))})};a.formset({prefix:d.prefix,
|
||||
addText:d.addText,formCssClass:"dynamic-"+d.prefix,deleteCssClass:"inline-deletelink",deleteText:d.deleteText,emptyCssClass:"empty-form",removed:c,added:function(a){a.find(".prepopulated_field").each(function(){var d=b(this).find("input, select, textarea"),c=d.data("dependency_list")||[],e=[];b.each(c,function(d,b){e.push("#"+a.find(".form-row .field-"+b).find("input, select, textarea").attr("id"))});e.length&&d.prepopulate(e,d.attr("maxlength"))});"undefined"!=typeof DateTimeShortcuts&&(b(".datetimeshortcuts").remove(),
|
||||
DateTimeShortcuts.init());"undefined"!=typeof SelectFilter&&(b(".selectfilter").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],false,d.adminStaticPrefix)}),b(".selectfilterstacked").each(function(a,b){var c=b.name.split("-");SelectFilter.init(b.id,c[c.length-1],true,d.adminStaticPrefix)}));c(a)}});return a}})(django.jQuery);
|
||||
|
|
|
@ -20,63 +20,11 @@
|
|||
|
||||
<script type="text/javascript">
|
||||
(function($) {
|
||||
$(document).ready(function() {
|
||||
var rows = "#{{ inline_admin_formset.formset.prefix }}-group .inline-related";
|
||||
var updateInlineLabel = function(row) {
|
||||
$(rows).find(".inline_label").each(function(i) {
|
||||
var count = i + 1;
|
||||
$(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
|
||||
});
|
||||
};
|
||||
var reinitDateTimeShortCuts = function() {
|
||||
// Reinitialize the calendar and clock widgets by force, yuck.
|
||||
if (typeof DateTimeShortcuts != "undefined") {
|
||||
$(".datetimeshortcuts").remove();
|
||||
DateTimeShortcuts.init();
|
||||
}
|
||||
};
|
||||
var updateSelectFilter = function() {
|
||||
// If any SelectFilter widgets were added, instantiate a new instance.
|
||||
if (typeof SelectFilter != "undefined"){
|
||||
$(".selectfilter").each(function(index, value){
|
||||
var namearr = value.name.split('-');
|
||||
SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% static "admin/" %}");
|
||||
});
|
||||
$(".selectfilterstacked").each(function(index, value){
|
||||
var namearr = value.name.split('-');
|
||||
SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% static "admin/" %}");
|
||||
});
|
||||
}
|
||||
};
|
||||
var initPrepopulatedFields = function(row) {
|
||||
row.find('.prepopulated_field').each(function() {
|
||||
var field = $(this);
|
||||
var input = field.find('input, select, textarea');
|
||||
var dependency_list = input.data('dependency_list') || [];
|
||||
var dependencies = [];
|
||||
$.each(dependency_list, function(i, field_name) {
|
||||
dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id'));
|
||||
});
|
||||
if (dependencies.length) {
|
||||
input.prepopulate(dependencies, input.attr('maxlength'));
|
||||
}
|
||||
});
|
||||
};
|
||||
$(rows).formset({
|
||||
prefix: "{{ inline_admin_formset.formset.prefix }}",
|
||||
addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}",
|
||||
formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
|
||||
deleteCssClass: "inline-deletelink",
|
||||
deleteText: "{% trans "Remove" %}",
|
||||
emptyCssClass: "empty-form",
|
||||
removed: updateInlineLabel,
|
||||
added: (function(row) {
|
||||
initPrepopulatedFields(row);
|
||||
reinitDateTimeShortCuts();
|
||||
updateSelectFilter();
|
||||
updateInlineLabel(row);
|
||||
})
|
||||
});
|
||||
});
|
||||
$("#{{ inline_admin_formset.formset.prefix }}-group .inline-related").stackedFormset({
|
||||
prefix: '{{ inline_admin_formset.formset.prefix }}',
|
||||
adminStaticPrefix: '{% static "admin/" %}',
|
||||
deleteText: "{% trans "Remove" %}",
|
||||
addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}"
|
||||
});
|
||||
})(django.jQuery);
|
||||
</script>
|
||||
|
|
|
@ -67,64 +67,13 @@
|
|||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
(function($) {
|
||||
$(document).ready(function($) {
|
||||
var rows = "#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr";
|
||||
var alternatingRows = function(row) {
|
||||
$(rows).not(".add-row").removeClass("row1 row2")
|
||||
.filter(":even").addClass("row1").end()
|
||||
.filter(rows + ":odd").addClass("row2");
|
||||
}
|
||||
var reinitDateTimeShortCuts = function() {
|
||||
// Reinitialize the calendar and clock widgets by force
|
||||
if (typeof DateTimeShortcuts != "undefined") {
|
||||
$(".datetimeshortcuts").remove();
|
||||
DateTimeShortcuts.init();
|
||||
}
|
||||
}
|
||||
var updateSelectFilter = function() {
|
||||
// If any SelectFilter widgets are a part of the new form,
|
||||
// instantiate a new SelectFilter instance for it.
|
||||
if (typeof SelectFilter != "undefined"){
|
||||
$(".selectfilter").each(function(index, value){
|
||||
var namearr = value.name.split('-');
|
||||
SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% static "admin/" %}");
|
||||
});
|
||||
$(".selectfilterstacked").each(function(index, value){
|
||||
var namearr = value.name.split('-');
|
||||
SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% static "admin/" %}");
|
||||
});
|
||||
}
|
||||
}
|
||||
var initPrepopulatedFields = function(row) {
|
||||
row.find('.prepopulated_field').each(function() {
|
||||
var field = $(this);
|
||||
var input = field.find('input, select, textarea');
|
||||
var dependency_list = input.data('dependency_list') || [];
|
||||
var dependencies = [];
|
||||
$.each(dependency_list, function(i, field_name) {
|
||||
dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id'));
|
||||
});
|
||||
if (dependencies.length) {
|
||||
input.prepopulate(dependencies, input.attr('maxlength'));
|
||||
}
|
||||
});
|
||||
}
|
||||
$(rows).formset({
|
||||
prefix: "{{ inline_admin_formset.formset.prefix }}",
|
||||
addText: "{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|title %}Add another {{ verbose_name }}{% endblocktrans %}",
|
||||
formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
|
||||
deleteCssClass: "inline-deletelink",
|
||||
deleteText: "{% trans "Remove" %}",
|
||||
emptyCssClass: "empty-form",
|
||||
removed: alternatingRows,
|
||||
added: (function(row) {
|
||||
initPrepopulatedFields(row);
|
||||
reinitDateTimeShortCuts();
|
||||
updateSelectFilter();
|
||||
alternatingRows(row);
|
||||
})
|
||||
});
|
||||
});
|
||||
$("#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr").tabularFormset({
|
||||
prefix: "{{ inline_admin_formset.formset.prefix }}",
|
||||
adminStaticPrefix: '{% static "admin/" %}',
|
||||
addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
|
||||
deleteText: "{% trans 'Remove' %}"
|
||||
});
|
||||
})(django.jQuery);
|
||||
</script>
|
||||
|
|
|
@ -182,7 +182,7 @@ def items_for_result(cl, result, form):
|
|||
row_class = ''
|
||||
try:
|
||||
f, attr, value = lookup_field(field_name, result, cl.model_admin)
|
||||
except (AttributeError, ObjectDoesNotExist):
|
||||
except ObjectDoesNotExist:
|
||||
result_repr = EMPTY_CHANGELIST_VALUE
|
||||
else:
|
||||
if f is None:
|
||||
|
|
|
@ -4,7 +4,7 @@ import datetime
|
|||
import decimal
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.sql.constants import LOOKUP_SEP
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.deletion import Collector
|
||||
from django.db.models.related import RelatedObject
|
||||
from django.forms.forms import pretty_name
|
||||
|
|
|
@ -8,6 +8,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME
|
|||
from django.core.exceptions import PermissionDenied
|
||||
from django.utils.decorators import available_attrs
|
||||
from django.utils.encoding import force_str
|
||||
from django.shortcuts import resolve_url
|
||||
|
||||
|
||||
def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
|
||||
|
@ -23,17 +24,19 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE
|
|||
if test_func(request.user):
|
||||
return view_func(request, *args, **kwargs)
|
||||
path = request.build_absolute_uri()
|
||||
# urlparse chokes on lazy objects in Python 3
|
||||
login_url_as_str = force_str(login_url or settings.LOGIN_URL)
|
||||
# urlparse chokes on lazy objects in Python 3, force to str
|
||||
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
|
||||
# use the path as the "next" url.
|
||||
login_scheme, login_netloc = urlparse(login_url_as_str)[:2]
|
||||
login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
|
||||
current_scheme, current_netloc = urlparse(path)[:2]
|
||||
if ((not login_scheme or login_scheme == current_scheme) and
|
||||
(not login_netloc or login_netloc == current_netloc)):
|
||||
path = request.get_full_path()
|
||||
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 decorator
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ from django.utils.translation import ugettext, ugettext_lazy as _
|
|||
|
||||
from django.contrib.auth import authenticate
|
||||
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.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):
|
||||
def render(self, name, value, attrs):
|
||||
encoded = value
|
||||
|
||||
if not is_password_usable(encoded):
|
||||
return "None"
|
||||
|
||||
final_attrs = self.build_attrs(attrs)
|
||||
|
||||
try:
|
||||
hasher = identify_hasher(encoded)
|
||||
except ValueError:
|
||||
summary = mark_safe("<strong>Invalid password format or unknown hashing algorithm.</strong>")
|
||||
if encoded == '' or encoded == UNUSABLE_PASSWORD:
|
||||
summary = mark_safe("<strong>%s</strong>" % ugettext("No password set."))
|
||||
else:
|
||||
summary = format_html_join('',
|
||||
"<strong>{0}</strong>: {1} ",
|
||||
((ugettext(key), value)
|
||||
for key, value in hasher.safe_summary(encoded).items())
|
||||
)
|
||||
try:
|
||||
hasher = identify_hasher(encoded)
|
||||
except ValueError:
|
||||
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)
|
||||
|
||||
|
|
|
@ -28,7 +28,13 @@ def reset_hashers(**kwargs):
|
|||
|
||||
|
||||
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'):
|
||||
|
|
|
@ -25,7 +25,7 @@ def update_last_login(sender, user, **kwargs):
|
|||
the user logging in.
|
||||
"""
|
||||
user.last_login = timezone.now()
|
||||
user.save()
|
||||
user.save(update_fields=['last_login'])
|
||||
user_logged_in.connect(update_last_login)
|
||||
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ class LoginRequiredTestCase(AuthViewsTestCase):
|
|||
pass
|
||||
login_required(normal_view)
|
||||
|
||||
def testLoginRequired(self, view_url='/login_required/', login_url=settings.LOGIN_URL):
|
||||
def testLoginRequired(self, view_url='/login_required/', login_url='/login/'):
|
||||
"""
|
||||
Check that login_required works on a simple view wrapped in a
|
||||
login_required decorator.
|
||||
|
|
|
@ -236,23 +236,29 @@ class UserChangeFormTest(TestCase):
|
|||
# Just check we can create it
|
||||
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):
|
||||
user = User.objects.get(username='empty_password')
|
||||
form = UserChangeForm(instance=user)
|
||||
# Just check that no error is raised.
|
||||
form.as_table()
|
||||
self.assertIn(_("No password set."), form.as_table())
|
||||
|
||||
def test_bug_17944_unmanageable_password(self):
|
||||
user = User.objects.get(username='unmanageable_password')
|
||||
form = UserChangeForm(instance=user)
|
||||
# Just check that no error is raised.
|
||||
form.as_table()
|
||||
self.assertIn(_("Invalid password format or unknown hashing algorithm."),
|
||||
form.as_table())
|
||||
|
||||
def test_bug_17944_unknown_password_algorithm(self):
|
||||
user = User.objects.get(username='unknown_password')
|
||||
form = UserChangeForm(instance=user)
|
||||
# Just check that no error is raised.
|
||||
form.as_table()
|
||||
self.assertIn(_("Invalid password format or unknown hashing algorithm."),
|
||||
form.as_table())
|
||||
|
||||
|
||||
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
||||
|
|
|
@ -100,6 +100,10 @@ class TestUtilsHashPass(unittest.TestCase):
|
|||
self.assertRaises(ValueError, doit)
|
||||
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):
|
||||
hasher = PBKDF2PasswordHasher()
|
||||
encoded = hasher.encode('letmein', 'seasalt')
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import os
|
||||
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.auth.models import User
|
||||
from django.core import mail
|
||||
|
@ -23,7 +23,8 @@ from django.contrib.auth.forms import (AuthenticationForm, PasswordChangeForm,
|
|||
('en', 'English'),
|
||||
),
|
||||
LANGUAGE_CODE='en',
|
||||
TEMPLATE_DIRS = (
|
||||
TEMPLATE_LOADERS=global_settings.TEMPLATE_LOADERS,
|
||||
TEMPLATE_DIRS=(
|
||||
os.path.join(os.path.dirname(__file__), 'templates'),
|
||||
),
|
||||
USE_TZ=False,
|
||||
|
|
|
@ -7,9 +7,9 @@ from django.conf import settings
|
|||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponseRedirect, QueryDict
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.http import base36_to_int
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.shortcuts import resolve_url
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
|
@ -38,16 +38,16 @@ def login(request, template_name='registration/login.html',
|
|||
if request.method == "POST":
|
||||
form = authentication_form(data=request.POST)
|
||||
if form.is_valid():
|
||||
netloc = urlparse(redirect_to)[1]
|
||||
|
||||
# Use default setting if redirect_to is empty
|
||||
if not redirect_to:
|
||||
redirect_to = settings.LOGIN_REDIRECT_URL
|
||||
redirect_to = resolve_url(redirect_to)
|
||||
|
||||
netloc = urlparse(redirect_to)[1]
|
||||
# Heavier security check -- don't allow redirection to a different
|
||||
# host.
|
||||
elif netloc and netloc != request.get_host():
|
||||
redirect_to = settings.LOGIN_REDIRECT_URL
|
||||
if netloc and netloc != request.get_host():
|
||||
redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
|
||||
|
||||
# Okay, security checks complete. Log the user in.
|
||||
auth_login(request, form.get_user())
|
||||
|
@ -110,6 +110,7 @@ def logout_then_login(request, login_url=None, current_app=None, extra_context=N
|
|||
"""
|
||||
if not login_url:
|
||||
login_url = settings.LOGIN_URL
|
||||
login_url = resolve_url(login_url)
|
||||
return logout(request, login_url, current_app=current_app, extra_context=extra_context)
|
||||
|
||||
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
|
||||
"""
|
||||
# urlparse chokes on lazy objects in Python 3
|
||||
login_url_as_str = force_str(login_url or settings.LOGIN_URL)
|
||||
resolved_url = resolve_url(login_url or settings.LOGIN_URL)
|
||||
|
||||
login_url_parts = list(urlparse(login_url_as_str))
|
||||
login_url_parts = list(urlparse(resolved_url))
|
||||
if redirect_field_name:
|
||||
querystring = QueryDict(login_url_parts[4], mutable=True)
|
||||
querystring[redirect_field_name] = next
|
||||
|
@ -229,7 +229,7 @@ def password_reset_complete(request,
|
|||
template_name='registration/password_reset_complete.html',
|
||||
current_app=None, extra_context=None):
|
||||
context = {
|
||||
'login_url': settings.LOGIN_URL
|
||||
'login_url': resolve_url(settings.LOGIN_URL)
|
||||
}
|
||||
if extra_context is not None:
|
||||
context.update(extra_context)
|
||||
|
|
|
@ -90,8 +90,6 @@ class BaseSpatialOperations(object):
|
|||
|
||||
# For quoting column values, rather than columns.
|
||||
def geo_quote_name(self, name):
|
||||
if isinstance(name, six.text_type):
|
||||
name = name.encode('ascii')
|
||||
return "'%s'" % name
|
||||
|
||||
# GeometryField operations
|
||||
|
|
|
@ -3,20 +3,6 @@ A collection of utility routines and classes used by the spatial
|
|||
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):
|
||||
"""
|
||||
Base class for generating spatial SQL.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
from django.db.models.sql.constants import LOOKUP_SEP
|
||||
from django.db.models.sql.expressions import SQLEvaluator
|
||||
from django.db.models.sql.where import Constraint, WhereNode
|
||||
from django.contrib.gis.db.models.fields import GeometryField
|
||||
|
|
|
@ -181,7 +181,11 @@ class DataSourceTest(unittest.TestCase):
|
|||
|
||||
# Making sure the SpatialReference is as expected.
|
||||
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):
|
||||
"Testing the Layer.spatial_filter property."
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import json
|
||||
from binascii import b2a_hex
|
||||
try:
|
||||
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:
|
||||
geom = OGRGeometry(g.wkt)
|
||||
if not hasattr(g, 'not_equal'):
|
||||
self.assertEqual(g.json, geom.json)
|
||||
self.assertEqual(g.json, geom.geojson)
|
||||
# Loading jsons to prevent decimal differences
|
||||
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))
|
||||
|
||||
def test02_points(self):
|
||||
|
|
|
@ -110,7 +110,7 @@ def geos_version_info():
|
|||
is a release candidate (and what number release candidate), and the C API
|
||||
version.
|
||||
"""
|
||||
ver = geos_version()
|
||||
ver = geos_version().decode()
|
||||
m = version_regex.match(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'))
|
||||
|
|
|
@ -215,15 +215,18 @@ class ListMixin(object):
|
|||
"Standard list reverse method"
|
||||
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"
|
||||
if key:
|
||||
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]
|
||||
else:
|
||||
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
|
||||
|
||||
### Private routines ###
|
||||
|
|
|
@ -16,7 +16,8 @@ test_suites = [
|
|||
def suite():
|
||||
"Builds a test suite for the GEOS tests."
|
||||
s = TestSuite()
|
||||
map(s.addTest, test_suites)
|
||||
for suite in test_suites:
|
||||
s.addTest(suite)
|
||||
return s
|
||||
|
||||
def run(verbosity=1):
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import ctypes
|
||||
import json
|
||||
import random
|
||||
|
||||
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:
|
||||
geom = GEOSGeometry(g.wkt)
|
||||
if not hasattr(g, 'not_equal'):
|
||||
self.assertEqual(g.json, geom.json)
|
||||
self.assertEqual(g.json, geom.geojson)
|
||||
# Loading jsons to prevent decimal differences
|
||||
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))
|
||||
|
||||
def test_fromfile(self):
|
||||
|
|
|
@ -55,14 +55,14 @@ class ListMixinTest(unittest.TestCase):
|
|||
|
||||
def lists_of_len(self, length=None):
|
||||
if length is None: length = self.limit
|
||||
pl = range(length)
|
||||
pl = list(range(length))
|
||||
return pl, self.listType(pl)
|
||||
|
||||
def limits_plus(self, b):
|
||||
return range(-self.limit - b, self.limit + b)
|
||||
|
||||
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):
|
||||
'Slice retrieval'
|
||||
|
@ -160,13 +160,13 @@ class ListMixinTest(unittest.TestCase):
|
|||
del pl[i:j]
|
||||
del ul[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)
|
||||
del pl[i:j:k]
|
||||
del ul[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)
|
||||
del pl[:i:k]
|
||||
del ul[:i:k]
|
||||
|
@ -177,7 +177,7 @@ class ListMixinTest(unittest.TestCase):
|
|||
del ul[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)
|
||||
del pl[::k]
|
||||
del ul[::k]
|
||||
|
@ -320,7 +320,7 @@ class ListMixinTest(unittest.TestCase):
|
|||
pl.sort()
|
||||
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)
|
||||
ul.sort(key=lambda x: (mid-x)**2)
|
||||
self.assertEqual(pl[:], ul[:], 'sort w/ key')
|
||||
|
@ -330,7 +330,7 @@ class ListMixinTest(unittest.TestCase):
|
|||
pl.sort(reverse=True)
|
||||
ul.sort(reverse=True)
|
||||
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)
|
||||
ul.sort(key=lambda x: (mid-x)**2)
|
||||
self.assertEqual(pl[:], ul[:], 'sort w/ key')
|
||||
|
@ -338,7 +338,7 @@ class ListMixinTest(unittest.TestCase):
|
|||
def test_12_arithmetic(self):
|
||||
'Arithmetic'
|
||||
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(type(ul), type(ul + al), 'type of add result')
|
||||
self.assertEqual(list(al + pl), list(al + ul), 'radd')
|
||||
|
|
|
@ -191,7 +191,8 @@ class GeoModelTest(TestCase):
|
|||
cities1 = City.objects.all()
|
||||
# Only PostGIS would support a 'select *' query because of its recognized
|
||||
# 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.assertTrue(isinstance(cities2[0].point, Point))
|
||||
|
||||
|
|
|
@ -8,9 +8,11 @@ from django.utils import unittest
|
|||
test_srs = ({'srid' : 4326,
|
||||
'auth_name' : ('EPSG', True),
|
||||
'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"]]',
|
||||
'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"]]',
|
||||
'proj4' : '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ',
|
||||
# Only the beginning, because there are differences depending on installed libs
|
||||
'srtext' : 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84"',
|
||||
'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',
|
||||
'geographic' : True, 'projected' : False, 'spatialite' : True,
|
||||
'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,
|
||||
'auth_name' : ('EPSG', False),
|
||||
'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"]]',
|
||||
'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 ',
|
||||
'srtext' : 'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980"',
|
||||
'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',
|
||||
'geographic' : False, 'projected' : True, 'spatialite' : False,
|
||||
'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 :(
|
||||
if postgis:
|
||||
if connection.ops.spatial_version >= (1, 4, 0):
|
||||
srtext = sd['srtext14']
|
||||
else:
|
||||
srtext = sd['srtext']
|
||||
self.assertEqual(srtext, srs.wkt)
|
||||
self.assertEqual(sd['proj4'], srs.proj4text)
|
||||
self.assertTrue(srs.wkt.startswith(sd['srtext']))
|
||||
self.assertTrue(srs.proj4text in sd['proj4'])
|
||||
|
||||
@no_mysql
|
||||
def test02_osr(self):
|
||||
"Testing getting OSR objects from SpatialRefSys model objects."
|
||||
from django.contrib.gis.gdal import GDAL_VERSION
|
||||
for sd in test_srs:
|
||||
sr = SpatialRefSys.objects.get(srid=sd['srid'])
|
||||
self.assertEqual(True, sr.spheroid.startswith(sd['spheroid']))
|
||||
|
@ -76,15 +73,10 @@ class SpatialRefSysTest(unittest.TestCase):
|
|||
# Testing the SpatialReference object directly.
|
||||
if postgis or spatialite:
|
||||
srs = sr.srs
|
||||
if GDAL_VERSION <= (1, 8):
|
||||
self.assertEqual(sd['proj4'], srs.proj4)
|
||||
self.assertTrue(srs.proj4 in sd['proj4'])
|
||||
# No `srtext` field in the `spatial_ref_sys` table in SpatiaLite
|
||||
if not spatialite:
|
||||
if connection.ops.spatial_version >= (1, 4, 0):
|
||||
srtext = sd['srtext14']
|
||||
else:
|
||||
srtext = sd['srtext']
|
||||
self.assertEqual(srtext, srs.wkt)
|
||||
self.assertTrue(srs.wkt.startswith(sd['srtext']))
|
||||
|
||||
@no_mysql
|
||||
def test03_ellipsoid(self):
|
||||
|
|
|
@ -47,6 +47,9 @@ def markdown(value, arg=''):
|
|||
they will be silently ignored.
|
||||
|
||||
"""
|
||||
import warnings
|
||||
warnings.warn('The markdown filter has been deprecated',
|
||||
category=DeprecationWarning)
|
||||
try:
|
||||
import markdown
|
||||
except ImportError:
|
||||
|
@ -72,6 +75,9 @@ def markdown(value, arg=''):
|
|||
|
||||
@register.filter(is_safe=True)
|
||||
def restructuredtext(value):
|
||||
import warnings
|
||||
warnings.warn('The restructuredtext filter has been deprecated',
|
||||
category=DeprecationWarning)
|
||||
try:
|
||||
from docutils.core import publish_parts
|
||||
except ImportError:
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# Quick tests for the markup templatetags (django.contrib.markup)
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from django.template import Template, Context
|
||||
from django import test
|
||||
from django.utils import unittest
|
||||
from django.utils.html import escape
|
||||
|
||||
|
@ -21,7 +23,7 @@ try:
|
|||
except ImportError:
|
||||
docutils = None
|
||||
|
||||
class Templates(unittest.TestCase):
|
||||
class Templates(test.TestCase):
|
||||
|
||||
textile_content = """Paragraph 1
|
||||
|
||||
|
@ -37,6 +39,13 @@ Paragraph 2 with a link_
|
|||
|
||||
.. _link: http://www.example.com/"""
|
||||
|
||||
def setUp(self):
|
||||
self.save_warnings_state()
|
||||
warnings.filterwarnings('ignore', category=DeprecationWarning, module='django.contrib.markup')
|
||||
|
||||
def tearDown(self):
|
||||
self.restore_warnings_state()
|
||||
|
||||
@unittest.skipUnless(textile, 'textile not installed')
|
||||
def test_textile(self):
|
||||
t = Template("{% load markup %}{{ textile_content|textile }}")
|
||||
|
|
|
@ -46,10 +46,10 @@ class CookieStorage(BaseStorage):
|
|||
Stores messages in a cookie.
|
||||
"""
|
||||
cookie_name = 'messages'
|
||||
# We should be able to store 4K in a cookie, but Internet Explorer
|
||||
# imposes 4K as the *total* limit for a domain. To allow other
|
||||
# cookies, we go for 3/4 of 4K.
|
||||
max_cookie_size = 3072
|
||||
# uwsgi's default configuration enforces a maximum size of 4kb for all the
|
||||
# HTTP headers. In order to leave some room for other cookies and headers,
|
||||
# restrict the session cookie to 1/2 of 4kb. See #18781.
|
||||
max_cookie_size = 2048
|
||||
not_finished = '__messagesnotfinished__'
|
||||
|
||||
def _get(self, *args, **kwargs):
|
||||
|
|
|
@ -152,7 +152,7 @@ class BaseTest(TestCase):
|
|||
cycle.
|
||||
"""
|
||||
data = {
|
||||
'messages': ['Test message %d' % x for x in range(10)],
|
||||
'messages': ['Test message %d' % x for x in range(5)],
|
||||
}
|
||||
show_url = reverse('django.contrib.messages.tests.urls.show')
|
||||
for level in ('debug', 'info', 'success', 'warning', 'error'):
|
||||
|
@ -170,7 +170,7 @@ class BaseTest(TestCase):
|
|||
@override_settings(MESSAGE_LEVEL=constants.DEBUG)
|
||||
def test_with_template_response(self):
|
||||
data = {
|
||||
'messages': ['Test message %d' % x for x in range(10)],
|
||||
'messages': ['Test message %d' % x for x in range(5)],
|
||||
}
|
||||
show_url = reverse('django.contrib.messages.tests.urls.show_template_response')
|
||||
for level in self.levels.keys():
|
||||
|
@ -194,7 +194,7 @@ class BaseTest(TestCase):
|
|||
before a GET.
|
||||
"""
|
||||
data = {
|
||||
'messages': ['Test message %d' % x for x in range(10)],
|
||||
'messages': ['Test message %d' % x for x in range(5)],
|
||||
}
|
||||
show_url = reverse('django.contrib.messages.tests.urls.show')
|
||||
messages = []
|
||||
|
@ -226,7 +226,7 @@ class BaseTest(TestCase):
|
|||
when one attempts to store a message.
|
||||
"""
|
||||
data = {
|
||||
'messages': ['Test message %d' % x for x in range(10)],
|
||||
'messages': ['Test message %d' % x for x in range(5)],
|
||||
}
|
||||
show_url = reverse('django.contrib.messages.tests.urls.show')
|
||||
for level in ('debug', 'info', 'success', 'warning', 'error'):
|
||||
|
@ -251,7 +251,7 @@ class BaseTest(TestCase):
|
|||
raised if 'fail_silently' = True
|
||||
"""
|
||||
data = {
|
||||
'messages': ['Test message %d' % x for x in range(10)],
|
||||
'messages': ['Test message %d' % x for x in range(5)],
|
||||
'fail_silently': True,
|
||||
}
|
||||
show_url = reverse('django.contrib.messages.tests.urls.show')
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
import shutil
|
||||
import string
|
||||
import tempfile
|
||||
|
@ -302,11 +302,11 @@ class CacheDBSessionTests(SessionTestsMixin, TestCase):
|
|||
self.assertTrue(self.session.exists(self.session.session_key))
|
||||
|
||||
def test_load_overlong_key(self):
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter("always")
|
||||
# Some backends might issue a warning
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
self.session._session_key = (string.ascii_letters + string.digits) * 20
|
||||
self.assertEqual(self.session.load(), {})
|
||||
self.assertEqual(len(w), 1)
|
||||
|
||||
|
||||
@override_settings(USE_TZ=True)
|
||||
|
@ -352,11 +352,11 @@ class CacheSessionTests(SessionTestsMixin, unittest.TestCase):
|
|||
backend = CacheSession
|
||||
|
||||
def test_load_overlong_key(self):
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter("always")
|
||||
# Some backends might issue a warning
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
self.session._session_key = (string.ascii_letters + string.digits) * 20
|
||||
self.assertEqual(self.session.load(), {})
|
||||
self.assertEqual(len(w), 1)
|
||||
|
||||
|
||||
class SessionMiddlewareTests(unittest.TestCase):
|
||||
|
|
|
@ -223,18 +223,17 @@ class WSGIHandler(base.BaseHandler):
|
|||
set_script_prefix(base.get_script_name(environ))
|
||||
signals.request_started.send(sender=self.__class__)
|
||||
try:
|
||||
try:
|
||||
request = self.request_class(environ)
|
||||
except UnicodeDecodeError:
|
||||
logger.warning('Bad Request (UnicodeDecodeError)',
|
||||
exc_info=sys.exc_info(),
|
||||
extra={
|
||||
'status_code': 400,
|
||||
}
|
||||
)
|
||||
response = http.HttpResponseBadRequest()
|
||||
else:
|
||||
response = self.get_response(request)
|
||||
request = self.request_class(environ)
|
||||
except UnicodeDecodeError:
|
||||
logger.warning('Bad Request (UnicodeDecodeError)',
|
||||
exc_info=sys.exc_info(),
|
||||
extra={
|
||||
'status_code': 400,
|
||||
}
|
||||
)
|
||||
response = http.HttpResponseBadRequest()
|
||||
else:
|
||||
response = self.get_response(request)
|
||||
finally:
|
||||
signals.request_finished.send(sender=self.__class__)
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ from optparse import OptionParser, NO_DEFAULT
|
|||
import imp
|
||||
import warnings
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.management.base import BaseCommand, CommandError, handle_default_options
|
||||
from django.core.management.color import color_style
|
||||
from django.utils.importlib import import_module
|
||||
|
@ -105,7 +106,7 @@ def get_commands():
|
|||
try:
|
||||
from django.conf import settings
|
||||
apps = settings.INSTALLED_APPS
|
||||
except (AttributeError, EnvironmentError, ImportError):
|
||||
except (AttributeError, ImproperlyConfigured):
|
||||
apps = []
|
||||
|
||||
# Find and load the management module for each installed app.
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from optparse import make_option
|
||||
from datetime import datetime
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
@ -90,10 +91,12 @@ class Command(BaseCommand):
|
|||
self.stdout.write("Validating models...\n\n")
|
||||
self.validate(display_num_errors=True)
|
||||
self.stdout.write((
|
||||
"%(started_at)s\n"
|
||||
"Django version %(version)s, using settings %(settings)r\n"
|
||||
"Development server is running at http://%(addr)s:%(port)s/\n"
|
||||
"Quit the server with %(quit_command)s.\n"
|
||||
) % {
|
||||
"started_at": datetime.now().strftime('%B %d, %Y - %X'),
|
||||
"version": self.get_version(),
|
||||
"settings": settings.SETTINGS_MODULE,
|
||||
"addr": self._raw_ipv6 and '[%s]' % self.addr or self.addr,
|
||||
|
|
|
@ -1054,9 +1054,12 @@ class BaseDatabaseIntrospection(object):
|
|||
|
||||
def get_primary_key_column(self, cursor, table_name):
|
||||
"""
|
||||
Backends can override this to return the column name of the primary key for the given table.
|
||||
Returns the name of the primary key column for the given table.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
for column in six.iteritems(self.get_indexes(cursor, table_name)):
|
||||
if column[1]['primary_key']:
|
||||
return column[0]
|
||||
return None
|
||||
|
||||
def get_indexes(self, cursor, table_name):
|
||||
"""
|
||||
|
|
|
@ -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.validation import DatabaseValidation
|
||||
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.safestring import SafeBytes, SafeText
|
||||
from django.utils import six
|
||||
|
@ -392,7 +393,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||
if settings_dict['NAME']:
|
||||
kwargs['db'] = settings_dict['NAME']
|
||||
if settings_dict['PASSWORD']:
|
||||
kwargs['passwd'] = settings_dict['PASSWORD']
|
||||
kwargs['passwd'] = force_str(settings_dict['PASSWORD'])
|
||||
if settings_dict['HOST'].startswith('/'):
|
||||
kwargs['unix_socket'] = settings_dict['HOST']
|
||||
elif settings_dict['HOST']:
|
||||
|
|
|
@ -2,7 +2,6 @@ import re
|
|||
from .base import FIELD_TYPE
|
||||
|
||||
from django.db.backends import BaseDatabaseIntrospection
|
||||
from django.utils import six
|
||||
|
||||
|
||||
foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)")
|
||||
|
@ -88,15 +87,6 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
|
|||
key_columns.extend(cursor.fetchall())
|
||||
return key_columns
|
||||
|
||||
def get_primary_key_column(self, cursor, table_name):
|
||||
"""
|
||||
Returns the name of the primary key column for the given table
|
||||
"""
|
||||
for column in six.iteritems(self.get_indexes(cursor, table_name)):
|
||||
if column[1]['primary_key']:
|
||||
return column[0]
|
||||
return None
|
||||
|
||||
def get_indexes(self, cursor, table_name):
|
||||
cursor.execute("SHOW INDEX FROM %s" % self.connection.ops.quote_name(table_name))
|
||||
# Do a two-pass search for indexes: on first pass check which indexes
|
||||
|
|
|
@ -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.introspection import DatabaseIntrospection
|
||||
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.safestring import SafeText, SafeBytes
|
||||
from django.utils import six
|
||||
|
@ -175,7 +176,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||
if settings_dict['USER']:
|
||||
conn_params['user'] = settings_dict['USER']
|
||||
if settings_dict['PASSWORD']:
|
||||
conn_params['password'] = settings_dict['PASSWORD']
|
||||
conn_params['password'] = force_str(settings_dict['PASSWORD'])
|
||||
if settings_dict['HOST']:
|
||||
conn_params['host'] = settings_dict['HOST']
|
||||
if settings_dict['PORT']:
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
"""
|
||||
Constants used across the ORM in general.
|
||||
"""
|
||||
|
||||
# Separator used to split filter strings apart.
|
||||
LOOKUP_SEP = '__'
|
||||
|
|
@ -8,6 +8,7 @@ import sys
|
|||
|
||||
from django.core import exceptions
|
||||
from django.db import connections, router, transaction, IntegrityError
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.fields import AutoField
|
||||
from django.db.models.query_utils import (Q, select_related_descend,
|
||||
deferred_class_factory, InvalidQuery)
|
||||
|
@ -1613,8 +1614,6 @@ def prefetch_related_objects(result_cache, related_lookups):
|
|||
Populates prefetched objects caches for a list of results
|
||||
from a QuerySet
|
||||
"""
|
||||
from django.db.models.sql.constants import LOOKUP_SEP
|
||||
|
||||
if len(result_cache) == 0:
|
||||
return # nothing to do
|
||||
|
||||
|
|
|
@ -3,9 +3,10 @@ from django.utils.six.moves import zip
|
|||
from django.core.exceptions import FieldError
|
||||
from django.db import transaction
|
||||
from django.db.backends.util import truncate_name
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.query_utils import select_related_descend
|
||||
from django.db.models.sql.constants import (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.expressions import SQLEvaluator
|
||||
from django.db.models.sql.query import get_order_dir, Query
|
||||
|
@ -608,8 +609,12 @@ class SQLCompiler(object):
|
|||
restricted = False
|
||||
|
||||
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,
|
||||
only_load.get(model or self.query.model)):
|
||||
only_load.get(field_model)):
|
||||
continue
|
||||
# The "avoid" set is aliases we want to avoid just for this
|
||||
# particular branch of the recursion. They aren't permanently
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
"""
|
||||
Constants specific to the SQL storage portion of the ORM.
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
import re
|
||||
|
||||
# Valid query types (a set is used for speedy lookups).
|
||||
# Valid query types (a set is used for speedy lookups). These are (currently)
|
||||
# considered SQL-specific; other storage systems may choose to use different
|
||||
# lookup types.
|
||||
QUERY_TERMS = set([
|
||||
'exact', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte', 'in',
|
||||
'startswith', 'istartswith', 'endswith', 'iendswith', 'range', 'year',
|
||||
|
@ -12,9 +18,6 @@ QUERY_TERMS = set([
|
|||
# Larger values are slightly faster at the expense of more storage space.
|
||||
GET_ITERATOR_CHUNK_SIZE = 100
|
||||
|
||||
# Separator used to split filter strings apart.
|
||||
LOOKUP_SEP = '__'
|
||||
|
||||
# Constants to make looking up tuple values clearer.
|
||||
# Join lists (indexes into the tuples that are values in the alias_map
|
||||
# dictionary in the Query class).
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.core.exceptions import FieldError
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
from django.db.models.sql.constants import LOOKUP_SEP
|
||||
|
||||
class SQLEvaluator(object):
|
||||
def __init__(self, expression, query, allow_joins=True):
|
||||
|
|
|
@ -15,11 +15,12 @@ from django.utils.tree import Node
|
|||
from django.utils import six
|
||||
from django.db import connections, DEFAULT_DB_ALIAS
|
||||
from django.db.models import signals
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.expressions import ExpressionNode
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
from django.db.models.sql import aggregates as base_aggregates_module
|
||||
from django.db.models.sql.constants import (QUERY_TERMS, LOOKUP_SEP, ORDER_DIR,
|
||||
SINGLE, ORDER_PATTERN, JoinInfo)
|
||||
from django.db.models.sql.constants import (QUERY_TERMS, ORDER_DIR, SINGLE,
|
||||
ORDER_PATTERN, JoinInfo)
|
||||
from django.db.models.sql.datastructures import EmptyResultSet, Empty, MultiJoin
|
||||
from django.db.models.sql.expressions import SQLEvaluator
|
||||
from django.db.models.sql.where import (WhereNode, Constraint, EverythingNode,
|
||||
|
|
|
@ -3,6 +3,7 @@ Query subclasses which provide extra functionality beyond simple data retrieval.
|
|||
"""
|
||||
|
||||
from django.core.exceptions import FieldError
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.db.models.fields import DateField, FieldDoesNotExist
|
||||
from django.db.models.sql.constants import *
|
||||
from django.db.models.sql.datastructures import Date
|
||||
|
|
|
@ -199,7 +199,7 @@ class CharField(Field):
|
|||
|
||||
def widget_attrs(self, 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.
|
||||
attrs.update({'maxlength': str(self.max_length)})
|
||||
return attrs
|
||||
|
|
|
@ -260,10 +260,17 @@ class Input(Widget):
|
|||
final_attrs['value'] = force_text(self._format_value(value))
|
||||
return format_html('<input{0} />', flatatt(final_attrs))
|
||||
|
||||
|
||||
class TextInput(Input):
|
||||
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'
|
||||
|
||||
def __init__(self, attrs=None, render_value=False):
|
||||
|
@ -400,9 +407,8 @@ class Textarea(Widget):
|
|||
flatatt(final_attrs),
|
||||
force_text(value))
|
||||
|
||||
class DateInput(Input):
|
||||
input_type = 'text'
|
||||
|
||||
class DateInput(TextInput):
|
||||
def __init__(self, attrs=None, format=None):
|
||||
super(DateInput, self).__init__(attrs)
|
||||
if format:
|
||||
|
@ -431,9 +437,8 @@ class DateInput(Input):
|
|||
pass
|
||||
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):
|
||||
super(DateTimeInput, self).__init__(attrs)
|
||||
if format:
|
||||
|
@ -462,9 +467,8 @@ class DateTimeInput(Input):
|
|||
pass
|
||||
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):
|
||||
super(TimeInput, self).__init__(attrs)
|
||||
if format:
|
||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals
|
|||
|
||||
import copy
|
||||
import datetime
|
||||
from email.header import Header
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
@ -560,31 +561,44 @@ class HttpResponse(object):
|
|||
else:
|
||||
__str__ = serialize
|
||||
|
||||
def _convert_to_ascii(self, *values):
|
||||
"""Converts all values to ascii strings."""
|
||||
for value in values:
|
||||
if not isinstance(value, six.string_types):
|
||||
value = str(value)
|
||||
try:
|
||||
if six.PY3:
|
||||
# Ensure string only contains ASCII
|
||||
value.encode('us-ascii')
|
||||
def _convert_to_charset(self, value, charset, mime_encode=False):
|
||||
"""Converts headers key/value to ascii/latin1 native strings.
|
||||
|
||||
`charset` must be 'ascii' or 'latin-1'. If `mime_encode` is True and
|
||||
`value` value can't be represented in the given charset, MIME-encoding
|
||||
is applied.
|
||||
"""
|
||||
if not isinstance(value, (bytes, six.text_type)):
|
||||
value = str(value)
|
||||
try:
|
||||
if six.PY3:
|
||||
if isinstance(value, str):
|
||||
# Ensure string is valid in given charset
|
||||
value.encode(charset)
|
||||
else:
|
||||
if isinstance(value, str):
|
||||
# Ensure string only contains ASCII
|
||||
value.decode('us-ascii')
|
||||
else:
|
||||
# Convert unicode to an ASCII string
|
||||
value = value.encode('us-ascii')
|
||||
except UnicodeError as e:
|
||||
e.reason += ', HTTP response headers must be in US-ASCII format'
|
||||
# Convert bytestring using given charset
|
||||
value = value.decode(charset)
|
||||
else:
|
||||
if isinstance(value, str):
|
||||
# Ensure string is valid in given charset
|
||||
value.decode(charset)
|
||||
else:
|
||||
# Convert unicode string to given charset
|
||||
value = value.encode(charset)
|
||||
except UnicodeError as e:
|
||||
if mime_encode:
|
||||
# Wrapping in str() is a workaround for #12422 under Python 2.
|
||||
value = str(Header(value, 'utf-8').encode())
|
||||
else:
|
||||
e.reason += ', HTTP response headers must be in %s format' % charset
|
||||
raise
|
||||
if '\n' in value or '\r' in value:
|
||||
raise BadHeaderError("Header values can't contain newlines (got %r)" % value)
|
||||
yield value
|
||||
if str('\n') in value or str('\r') in value:
|
||||
raise BadHeaderError("Header values can't contain newlines (got %r)" % value)
|
||||
return value
|
||||
|
||||
def __setitem__(self, header, value):
|
||||
header, value = self._convert_to_ascii(header, value)
|
||||
header = self._convert_to_charset(header, 'ascii')
|
||||
value = self._convert_to_charset(value, 'latin1', mime_encode=True)
|
||||
self._headers[header.lower()] = (header, value)
|
||||
|
||||
def __delitem__(self, header):
|
||||
|
|
|
@ -105,7 +105,7 @@ class CsrfViewMiddleware(object):
|
|||
if getattr(callback, 'csrf_exempt', False):
|
||||
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 getattr(request, '_dont_enforce_csrf_checks', False):
|
||||
# Mechanism to turn off CSRF checks for test suite.
|
||||
|
|
|
@ -66,23 +66,7 @@ def redirect(to, *args, **kwargs):
|
|||
else:
|
||||
redirect_class = HttpResponseRedirect
|
||||
|
||||
# If it's a model, use get_absolute_url()
|
||||
if hasattr(to, 'get_absolute_url'):
|
||||
return redirect_class(to.get_absolute_url())
|
||||
|
||||
# Next try a reverse URL resolution.
|
||||
try:
|
||||
return redirect_class(urlresolvers.reverse(to, args=args, kwargs=kwargs))
|
||||
except urlresolvers.NoReverseMatch:
|
||||
# If this is a callable, re-raise.
|
||||
if callable(to):
|
||||
raise
|
||||
# If this doesn't "feel" like a URL, re-raise.
|
||||
if '/' not in to and '.' not in to:
|
||||
raise
|
||||
|
||||
# Finally, fall back and assume it's a URL
|
||||
return redirect_class(to)
|
||||
return redirect_class(resolve_url(to, *args, **kwargs))
|
||||
|
||||
def _get_queryset(klass):
|
||||
"""
|
||||
|
@ -128,3 +112,34 @@ def get_list_or_404(klass, *args, **kwargs):
|
|||
raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
|
||||
return obj_list
|
||||
|
||||
def resolve_url(to, *args, **kwargs):
|
||||
"""
|
||||
Return a URL appropriate for the arguments passed.
|
||||
|
||||
The arguments could be:
|
||||
|
||||
* A model: the model's `get_absolute_url()` function will be called.
|
||||
|
||||
* A view name, possibly with arguments: `urlresolvers.reverse()` will
|
||||
be used to reverse-resolve the name.
|
||||
|
||||
* A URL, which will be returned as-is.
|
||||
|
||||
"""
|
||||
# If it's a model, use get_absolute_url()
|
||||
if hasattr(to, 'get_absolute_url'):
|
||||
return to.get_absolute_url()
|
||||
|
||||
# Next try a reverse URL resolution.
|
||||
try:
|
||||
return urlresolvers.reverse(to, args=args, kwargs=kwargs)
|
||||
except urlresolvers.NoReverseMatch:
|
||||
# If this is a callable, re-raise.
|
||||
if callable(to):
|
||||
raise
|
||||
# If this doesn't "feel" like a URL, re-raise.
|
||||
if '/' not in to and '.' not in to:
|
||||
raise
|
||||
|
||||
# Finally, fall back and assume it's a URL
|
||||
return to
|
||||
|
|
|
@ -531,11 +531,9 @@ def cycle(parser, token):
|
|||
The optional flag "silent" can be used to prevent the cycle declaration
|
||||
from returning any value::
|
||||
|
||||
{% cycle 'row1' 'row2' as rowcolors silent %}{# no value here #}
|
||||
{% for o in some_list %}
|
||||
<tr class="{% cycle rowcolors %}">{# first value will be "row1" #}
|
||||
...
|
||||
</tr>
|
||||
{% cycle 'row1' 'row2' as rowcolors silent %}
|
||||
<tr class="{{ rowcolors }}">{% include "subtemplate.html " %}</tr>
|
||||
{% endfor %}
|
||||
|
||||
"""
|
||||
|
|
|
@ -51,6 +51,13 @@ def clear_context_processors_cache(**kwargs):
|
|||
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)
|
||||
def clear_serializers_cache(**kwargs):
|
||||
if kwargs['setting'] == 'SERIALIZATION_MODULES':
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import os
|
||||
import stat
|
||||
from os.path import join, normcase, normpath, abspath, isabs, sep
|
||||
from os.path import join, normcase, normpath, abspath, isabs, sep, dirname
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils import six
|
||||
|
||||
|
@ -41,13 +41,16 @@ def safe_join(base, *paths):
|
|||
paths = [force_text(p) for p in paths]
|
||||
final_path = abspathu(join(base, *paths))
|
||||
base_path = abspathu(base)
|
||||
base_path_len = len(base_path)
|
||||
# Ensure final_path starts with base_path (using normcase to ensure we
|
||||
# don't false-negative on case insensitive operating systems like Windows)
|
||||
# and that the next character after the final path is os.sep (or nothing,
|
||||
# in which case final_path must be equal to base_path).
|
||||
if not normcase(final_path).startswith(normcase(base_path)) \
|
||||
or final_path[base_path_len:base_path_len+1] not in ('', sep):
|
||||
# don't false-negative on case insensitive operating systems like Windows),
|
||||
# further, one of the following conditions must be true:
|
||||
# a) The next character is the path separator (to prevent conditions like
|
||||
# safe_join("/dir", "/../d"))
|
||||
# b) The final path must be the same as the base path.
|
||||
# c) The base path must be the most root path (meaning either "/" or "C:\\")
|
||||
if (not normcase(final_path).startswith(normcase(base_path + sep)) and
|
||||
normcase(final_path) != normcase(base_path) and
|
||||
dirname(normcase(base_path)) != normcase(base_path)):
|
||||
raise ValueError('The joined path (%s) is located outside of the base '
|
||||
'path component (%s)' % (final_path, base_path))
|
||||
return final_path
|
||||
|
|
|
@ -5,8 +5,7 @@ import sys
|
|||
current_version = sys.version_info
|
||||
|
||||
use_workaround = (
|
||||
(current_version < (2, 6, 8)) or
|
||||
(current_version >= (2, 7) and current_version < (2, 7, 3)) or
|
||||
(current_version < (2, 7, 3)) or
|
||||
(current_version >= (3, 0) and current_version < (3, 2, 3))
|
||||
)
|
||||
|
||||
|
|
|
@ -372,6 +372,9 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View):
|
|||
return qs
|
||||
|
||||
def get_date_list_period(self):
|
||||
"""
|
||||
Get the aggregation period for the list of dates: 'year', 'month', or 'day'.
|
||||
"""
|
||||
return self.date_list_period
|
||||
|
||||
def get_date_list(self, queryset, date_type=None):
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
The documentation in this tree is in plain text files and can be viewed using
|
||||
any text file viewer.
|
||||
|
||||
Technically speaking, it uses ReST (reStructuredText) [1], and the Sphinx
|
||||
documentation system [2]. This allows it to be built into other forms for
|
||||
easier viewing and browsing.
|
||||
It uses ReST (reStructuredText) [1], and the Sphinx documentation system [2].
|
||||
This allows it to be built into other forms for easier viewing and browsing.
|
||||
|
||||
To create an HTML version of the docs:
|
||||
|
||||
|
|
|
@ -28,14 +28,3 @@ Indices, glossary and tables
|
|||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :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
|
||||
|
|
|
@ -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
|
||||
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
|
||||
like to make should be possible by editing the stylesheet. We've got a
|
||||
:doc:`guide to the CSS used in the admin </obsolete/admin-css>` to get you started.
|
||||
like to make should be possible by editing the stylesheet.
|
||||
|
||||
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
|
||||
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/
|
||||
|
||||
|
|
|
@ -16,8 +16,9 @@ How do I get started?
|
|||
What are Django's prerequisites?
|
||||
--------------------------------
|
||||
|
||||
Django requires Python_, specifically Python 2.6.5 - 2.7.x. No other Python
|
||||
libraries are required for basic Django usage.
|
||||
Django requires Python, specifically Python 2.6.5 - 2.7.x. No other Python
|
||||
libraries are required for basic Django usage. Django 1.5 also has
|
||||
experimental support for Python 3.2 and above.
|
||||
|
||||
For a development environment -- if you just want to experiment with Django --
|
||||
you don't need to have a separate Web server installed; Django comes with its
|
||||
|
@ -50,15 +51,12 @@ aren't available under older versions of Python.
|
|||
Third-party applications for use with Django are, of course, free to set their
|
||||
own version requirements.
|
||||
|
||||
Over the next year or two Django will begin dropping support for older Python
|
||||
versions as part of a migration which will end with Django running on Python 3
|
||||
(see below for details).
|
||||
|
||||
All else being equal, we recommend that you use the latest 2.x release
|
||||
(currently Python 2.7). This will let you take advantage of the numerous
|
||||
improvements and optimizations to the Python language since version 2.6, and
|
||||
will help ease the process of dropping support for older Python versions on
|
||||
the road to Python 3.
|
||||
improvements and optimizations to the Python language since version 2.6.
|
||||
|
||||
Generally speaking, we don't recommend running Django on Python 3 yet; see
|
||||
below for more.
|
||||
|
||||
What Python version can I use with Django?
|
||||
------------------------------------------
|
||||
|
@ -71,25 +69,21 @@ Django version Python versions
|
|||
1.2 2.4, 2.5, 2.6, 2.7
|
||||
1.3 2.4, 2.5, 2.6, 2.7
|
||||
**1.4** **2.5, 2.6, 2.7**
|
||||
*1.5 (future)* *2.6, 2.7, 3.x (experimental)*
|
||||
*1.5 (future)* *2.6, 2.7* and *3.2, 3.3 (experimental)*
|
||||
============== ===============
|
||||
|
||||
Can I use Django with Python 3?
|
||||
-------------------------------
|
||||
|
||||
Not at the moment. Python 3.0 introduced a number of
|
||||
backwards-incompatible changes to the Python language, and although
|
||||
these changes are generally a good thing for Python's future, it will
|
||||
be a while before most Python software catches up and is able to run
|
||||
on Python 3.0. For larger Python-based software like Django, the
|
||||
transition is expected to take at least a year or two (since it
|
||||
involves dropping support for older Python releases and so must be
|
||||
done gradually).
|
||||
Django 1.5 introduces experimental support for Python 3.2 and 3.3. However, we
|
||||
don't yet suggest that you use Django and Python 3 in production.
|
||||
|
||||
In the meantime, Python 2.x releases will be supported and provided
|
||||
with bug fixes and security updates by the Python development team, so
|
||||
continuing to use a Python 2.x release during the transition should
|
||||
not present any risk.
|
||||
Python 3 support should be considered a "preview". It's offered to bootstrap
|
||||
the transition of the Django ecosystem to Python 3, and to help you start
|
||||
porting your apps for future Python 3 compatibility. But we're not yet
|
||||
confident enough to promise stability in production.
|
||||
|
||||
Our current plan is to make Django 1.6 suitable for general use with Python 3.
|
||||
|
||||
Will Django run under shared hosting (like TextDrive or Dreamhost)?
|
||||
-------------------------------------------------------------------
|
||||
|
|
|
@ -11,7 +11,7 @@ Then, just do this::
|
|||
|
||||
>>> from django.db import connection
|
||||
>>> connection.queries
|
||||
[{'sql': 'SELECT polls_polls.id,polls_polls.question,polls_polls.pub_date FROM polls_polls',
|
||||
[{'sql': 'SELECT polls_polls.id, polls_polls.question, polls_polls.pub_date FROM polls_polls',
|
||||
'time': '0.002'}]
|
||||
|
||||
``connection.queries`` is only available if :setting:`DEBUG` is ``True``.
|
||||
|
|
|
@ -181,10 +181,10 @@ card values plus their suits; 104 characters in total.
|
|||
Many of Django's model fields accept options that they don't do anything
|
||||
with. For example, you can pass both
|
||||
:attr:`~django.db.models.Field.editable` and
|
||||
:attr:`~django.db.models.Field.auto_now` to a
|
||||
:attr:`~django.db.models.DateField.auto_now` to a
|
||||
:class:`django.db.models.DateField` and it will simply ignore the
|
||||
:attr:`~django.db.models.Field.editable` parameter
|
||||
(:attr:`~django.db.models.Field.auto_now` being set implies
|
||||
(:attr:`~django.db.models.DateField.auto_now` being set implies
|
||||
``editable=False``). No error is raised in this case.
|
||||
|
||||
This behavior simplifies the field classes, because they don't need to
|
||||
|
@ -516,8 +516,8 @@ for the first time, the ``add`` parameter will be ``True``, otherwise it will be
|
|||
You only need to override this method if you want to preprocess the value
|
||||
somehow, just before saving. For example, Django's
|
||||
:class:`~django.db.models.DateTimeField` uses this method to set the attribute
|
||||
correctly in the case of :attr:`~django.db.models.Field.auto_now` or
|
||||
:attr:`~django.db.models.Field.auto_now_add`.
|
||||
correctly in the case of :attr:`~django.db.models.DateField.auto_now` or
|
||||
:attr:`~django.db.models.DateField.auto_now_add`.
|
||||
|
||||
If you do override this method, you must return the value of the attribute at
|
||||
the end. You should also update the model's attribute if you make any changes
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
Django documentation
|
||||
====================
|
||||
|
||||
.. rubric:: Everything you need to know about Django (and then some).
|
||||
.. rubric:: Everything you need to know about Django.
|
||||
|
||||
Getting help
|
||||
============
|
||||
|
|
|
@ -379,6 +379,34 @@ Florian Apolloner
|
|||
.. _Graz University of Technology: http://tugraz.at/
|
||||
.. _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
|
||||
-----------
|
||||
|
||||
|
@ -403,16 +431,6 @@ Ian Kelly
|
|||
Matt Boersma
|
||||
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 discovered Django 0.96 during his Computer Science PhD research and
|
||||
has been developing with it full-time ever since. His core code
|
||||
|
|
|
@ -264,6 +264,9 @@ these changes.
|
|||
in 1.4. The backward compatibility will be removed --
|
||||
``HttpRequest.raw_post_data`` will no longer work.
|
||||
|
||||
* ``django.contrib.markup`` will be removed following an accelerated
|
||||
deprecation.
|
||||
|
||||
1.7
|
||||
---
|
||||
|
||||
|
|
|
@ -10,11 +10,9 @@ Install Python
|
|||
--------------
|
||||
|
||||
Being a Python Web framework, Django requires Python. It works with any Python
|
||||
version from 2.6.5 to 2.7 (due to backwards incompatibilities in Python 3.0,
|
||||
Django does not currently work with Python 3.0; see :doc:`the Django FAQ
|
||||
</faq/install>` for more information on supported Python versions and the 3.0
|
||||
transition), these versions of Python include a lightweight database called
|
||||
SQLite_ so you won't need to set up a database just yet.
|
||||
version from 2.6.5 to 2.7. It also features experimental support for versions
|
||||
3.2 and 3.3. All these versions of Python include a lightweight database
|
||||
called SQLite_ so you won't need to set up a database just yet.
|
||||
|
||||
.. _sqlite: http://sqlite.org/
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ the file ``mysite/news/models.py``::
|
|||
return self.full_name
|
||||
|
||||
class Article(models.Model):
|
||||
pub_date = models.DateTimeField()
|
||||
pub_date = models.DateField()
|
||||
headline = models.CharField(max_length=200)
|
||||
content = models.TextField()
|
||||
reporter = models.ForeignKey(Reporter)
|
||||
|
@ -96,8 +96,8 @@ access your data. The API is created on the fly, no code generation necessary::
|
|||
DoesNotExist: Reporter matching query does not exist. Lookup parameters were {'id': 2}
|
||||
|
||||
# Create an article.
|
||||
>>> from datetime import datetime
|
||||
>>> a = Article(pub_date=datetime.now(), headline='Django is cool',
|
||||
>>> from datetime import date
|
||||
>>> a = Article(pub_date=date.today(), headline='Django is cool',
|
||||
... content='Yeah.', reporter=r)
|
||||
>>> a.save()
|
||||
|
||||
|
@ -140,7 +140,7 @@ as registering your model in the admin site::
|
|||
from django.db import models
|
||||
|
||||
class Article(models.Model):
|
||||
pub_date = models.DateTimeField()
|
||||
pub_date = models.DateField()
|
||||
headline = models.CharField(max_length=200)
|
||||
content = models.TextField()
|
||||
reporter = models.ForeignKey(Reporter)
|
||||
|
|
|
@ -513,8 +513,9 @@ Here's what happens if a user goes to "/polls/34/" in this system:
|
|||
further processing.
|
||||
|
||||
Now that we've decoupled that, we need to decouple the ``polls.urls``
|
||||
URLconf by removing the leading "polls/" from each line, and removing the
|
||||
lines registering the admin site. Your ``polls/urls.py`` file should now look like
|
||||
URLconf by removing the leading "polls/" from each line, removing the
|
||||
lines registering the admin site, and removing the ``include`` import which
|
||||
is no longer used. Your ``polls/urls.py`` file should now look like
|
||||
this::
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
|
|
@ -218,7 +218,7 @@ Read on for details.
|
|||
First, open the ``polls/urls.py`` URLconf. It looks like this, according to the
|
||||
tutorial so far::
|
||||
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns('polls.views',
|
||||
url(r'^$', 'index'),
|
||||
|
@ -229,7 +229,7 @@ tutorial so far::
|
|||
|
||||
Change it like so::
|
||||
|
||||
from django.conf.urls import patterns, include, url
|
||||
from django.conf.urls import patterns, url
|
||||
from django.views.generic import DetailView, ListView
|
||||
from polls.models import Poll
|
||||
|
||||
|
|
|
@ -67,8 +67,7 @@ different needs:
|
|||
whathaveyou.
|
||||
|
||||
* Finally, there's some "specialized" documentation not usually relevant to
|
||||
most developers. This includes the :doc:`release notes </releases/index>`,
|
||||
:doc:`documentation of obsolete features </obsolete/index>`,
|
||||
most developers. This includes the :doc:`release notes </releases/index>` and
|
||||
: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
|
||||
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 |
|
@ -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``.
|
|
@ -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
|
|
@ -8,6 +8,11 @@ themselves or inherited from. They may not provide all the capabilities
|
|||
required for projects, in which case there are Mixins and Generic class-based
|
||||
views.
|
||||
|
||||
Many of Django's built-in class-based views inherit from other class-based
|
||||
views or various mixins. Because this inheritence chain is very important, the
|
||||
ancestor classes are documented under the section title of **Ancestors (MRO)**.
|
||||
MRO is an acronym for Method Resolution Order.
|
||||
|
||||
View
|
||||
----
|
||||
|
||||
|
@ -20,6 +25,7 @@ View
|
|||
|
||||
1. :meth:`dispatch()`
|
||||
2. :meth:`http_method_not_allowed()`
|
||||
3. :meth:`options()`
|
||||
|
||||
**Example views.py**::
|
||||
|
||||
|
@ -41,8 +47,20 @@ View
|
|||
url(r'^mine/$', MyView.as_view(), name='my-view'),
|
||||
)
|
||||
|
||||
**Attributes**
|
||||
|
||||
.. attribute:: http_method_names = ['get', 'post', 'put', 'delete', 'head', 'options', 'trace']
|
||||
|
||||
The default list of HTTP method names that this view will accept.
|
||||
|
||||
**Methods**
|
||||
|
||||
.. classmethod:: as_view(**initkwargs)
|
||||
|
||||
Returns a callable view that takes a request and returns a response::
|
||||
|
||||
response = MyView.as_view()(request)
|
||||
|
||||
.. method:: dispatch(request, *args, **kwargs)
|
||||
|
||||
The ``view`` part of the view -- the method that accepts a ``request``
|
||||
|
@ -53,6 +71,11 @@ View
|
|||
delegated to :meth:`~View.get()`, a ``POST`` to :meth:`~View.post()`,
|
||||
and so on.
|
||||
|
||||
By default, a ``HEAD`` request will be delegated to :meth:`~View.get()`.
|
||||
If you need to handle ``HEAD`` requests in a different way than ``GET``,
|
||||
you can override the :meth:`~View.head()` method. See
|
||||
:ref:`supporting-other-http-methods` for an example.
|
||||
|
||||
The default implementation also sets ``request``, ``args`` and
|
||||
``kwargs`` as instance variables, so any method on the view can know
|
||||
the full details of the request that was made to invoke the view.
|
||||
|
@ -62,14 +85,13 @@ View
|
|||
If the view was called with a HTTP method it doesn't support, this
|
||||
method is called instead.
|
||||
|
||||
The default implementation returns ``HttpResponseNotAllowed`` with list
|
||||
of allowed methods in plain text.
|
||||
The default implementation returns ``HttpResponseNotAllowed`` with a
|
||||
list of allowed methods in plain text.
|
||||
|
||||
.. note::
|
||||
.. method:: options(request, *args, **kwargs)
|
||||
|
||||
Documentation on class-based views is a work in progress. As yet, only the
|
||||
methods defined directly on the class are documented here, not methods
|
||||
defined on superclasses.
|
||||
Handles responding to requests for the OPTIONS HTTP verb. Returns a
|
||||
list of the allowed HTTP method names for the view.
|
||||
|
||||
TemplateView
|
||||
------------
|
||||
|
@ -81,6 +103,8 @@ TemplateView
|
|||
|
||||
**Ancestors (MRO)**
|
||||
|
||||
This view inherits methods and attributes from the following views:
|
||||
|
||||
* :class:`django.views.generic.base.TemplateView`
|
||||
* :class:`django.views.generic.base.TemplateResponseMixin`
|
||||
* :class:`django.views.generic.base.View`
|
||||
|
@ -116,28 +140,11 @@ TemplateView
|
|||
url(r'^$', HomePageView.as_view(), name='home'),
|
||||
)
|
||||
|
||||
**Methods and Attributes**
|
||||
|
||||
.. attribute:: template_name
|
||||
|
||||
The full name of a template to use.
|
||||
|
||||
.. method:: get_context_data(**kwargs)
|
||||
|
||||
Return a context data dictionary consisting of the contents of
|
||||
``kwargs`` stored in the context variable ``params``.
|
||||
|
||||
**Context**
|
||||
|
||||
* ``params``: The dictionary of keyword arguments captured from the URL
|
||||
pattern that served the view.
|
||||
|
||||
.. note::
|
||||
|
||||
Documentation on class-based views is a work in progress. As yet, only the
|
||||
methods defined directly on the class are documented here, not methods
|
||||
defined on superclasses.
|
||||
|
||||
RedirectView
|
||||
------------
|
||||
|
||||
|
@ -156,6 +163,8 @@ RedirectView
|
|||
|
||||
**Ancestors (MRO)**
|
||||
|
||||
This view inherits methods and attributes from the following view:
|
||||
|
||||
* :class:`django.views.generic.base.View`
|
||||
|
||||
**Method Flowchart**
|
||||
|
@ -194,7 +203,7 @@ RedirectView
|
|||
url(r'^go-to-django/$', RedirectView.as_view(url='http://djangoproject.com'), name='go-to-django'),
|
||||
)
|
||||
|
||||
**Methods and Attributes**
|
||||
**Attributes**
|
||||
|
||||
.. attribute:: url
|
||||
|
||||
|
@ -215,6 +224,8 @@ RedirectView
|
|||
then the query string is discarded. By default, ``query_string`` is
|
||||
``False``.
|
||||
|
||||
**Methods**
|
||||
|
||||
.. method:: get_redirect_url(**kwargs)
|
||||
|
||||
Constructs the target URL for redirection.
|
||||
|
@ -225,9 +236,3 @@ RedirectView
|
|||
:attr:`~RedirectView.query_string`. Subclasses may implement any
|
||||
behavior they wish, as long as the method returns a redirect-ready URL
|
||||
string.
|
||||
|
||||
.. note::
|
||||
|
||||
Documentation on class-based views is a work in progress. As yet, only the
|
||||
methods defined directly on the class are documented here, not methods
|
||||
defined on superclasses.
|
||||
|
|
|
@ -2,13 +2,15 @@
|
|||
Generic date views
|
||||
==================
|
||||
|
||||
Date-based generic views (in the module :mod:`django.views.generic.dates`)
|
||||
are views for displaying drilldown pages for date-based data.
|
||||
.. module:: django.views.generic.dates
|
||||
|
||||
Date-based generic views, provided in :mod:`django.views.generic.dates`, are
|
||||
views for displaying drilldown pages for date-based data.
|
||||
|
||||
ArchiveIndexView
|
||||
----------------
|
||||
|
||||
.. class:: django.views.generic.dates.ArchiveIndexView
|
||||
.. class:: ArchiveIndexView
|
||||
|
||||
A top-level index page showing the "latest" objects, by date. Objects with
|
||||
a date in the *future* are not included unless you set ``allow_future`` to
|
||||
|
@ -36,7 +38,7 @@ ArchiveIndexView
|
|||
YearArchiveView
|
||||
---------------
|
||||
|
||||
.. class:: django.views.generic.dates.YearArchiveView
|
||||
.. class:: YearArchiveView
|
||||
|
||||
A yearly archive page showing all available months in a given year. Objects
|
||||
with a date in the *future* are not displayed unless you set
|
||||
|
@ -58,13 +60,15 @@ YearArchiveView
|
|||
|
||||
A boolean specifying whether to retrieve the full list of objects for
|
||||
this year and pass those to the template. If ``True``, the list of
|
||||
objects will be made available to the context. By default, this is
|
||||
objects will be made available to the context. If ``False``, the
|
||||
``None`` queryset will be used as the object list. By default, this is
|
||||
``False``.
|
||||
|
||||
.. method:: get_make_object_list()
|
||||
|
||||
Determine if an object list will be returned as part of the context. If
|
||||
``False``, the ``None`` queryset will be used as the object list.
|
||||
Determine if an object list will be returned as part of the context.
|
||||
Returns :attr:`~YearArchiveView.make_object_list` by default.
|
||||
|
||||
|
||||
**Context**
|
||||
|
||||
|
@ -80,16 +84,18 @@ YearArchiveView
|
|||
:class:`datetime.datetime<python:datetime.datetime>` objects, in
|
||||
ascending order.
|
||||
|
||||
* ``year``: A :class:`datetime.date<python:datetime.date>` object
|
||||
* ``year``: A :class:`~datetime.date` object
|
||||
representing the given year.
|
||||
|
||||
* ``next_year``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the first day of the next year. If the next year is in the
|
||||
future, this will be ``None``.
|
||||
* ``next_year``: A :class:`~datetime.date` object
|
||||
representing the first day of the next year, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
* ``previous_year``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the first day of the previous year. Unlike ``next_year``,
|
||||
this will never be ``None``.
|
||||
* ``previous_year``: A :class:`~datetime.date` object
|
||||
representing the first day of the previous year, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
**Notes**
|
||||
|
||||
|
@ -98,7 +104,7 @@ YearArchiveView
|
|||
MonthArchiveView
|
||||
----------------
|
||||
|
||||
.. class:: django.views.generic.dates.MonthArchiveView
|
||||
.. class:: MonthArchiveView
|
||||
|
||||
A monthly archive page showing all objects in a given month. Objects with a
|
||||
date in the *future* are not displayed unless you set ``allow_future`` to
|
||||
|
@ -131,16 +137,18 @@ MonthArchiveView
|
|||
:class:`datetime.datetime<python:datetime.datetime>` objects, in
|
||||
ascending order.
|
||||
|
||||
* ``month``: A :class:`datetime.date<python:datetime.date>` object
|
||||
* ``month``: A :class:`~datetime.date` object
|
||||
representing the given month.
|
||||
|
||||
* ``next_month``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the first day of the next month. If the next month is in the
|
||||
future, this will be ``None``.
|
||||
* ``next_month``: A :class:`~datetime.date` object
|
||||
representing the first day of the next month, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
* ``previous_month``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the first day of the previous month. Unlike ``next_month``,
|
||||
this will never be ``None``.
|
||||
* ``previous_month``: A :class:`~datetime.date` object
|
||||
representing the first day of the previous month, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
**Notes**
|
||||
|
||||
|
@ -149,7 +157,7 @@ MonthArchiveView
|
|||
WeekArchiveView
|
||||
---------------
|
||||
|
||||
.. class:: django.views.generic.dates.WeekArchiveView
|
||||
.. class:: WeekArchiveView
|
||||
|
||||
A weekly archive page showing all objects in a given week. Objects with a
|
||||
date in the *future* are not displayed unless you set ``allow_future`` to
|
||||
|
@ -175,16 +183,18 @@ WeekArchiveView
|
|||
:class:`~django.views.generic.dates.BaseDateListView`), the template's
|
||||
context will be:
|
||||
|
||||
* ``week``: A :class:`datetime.date<python:datetime.date>` object
|
||||
* ``week``: A :class:`~datetime.date` object
|
||||
representing the first day of the given week.
|
||||
|
||||
* ``next_week``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the first day of the next week. If the next week is in the
|
||||
future, this will be ``None``.
|
||||
* ``next_week``: A :class:`~datetime.date` object
|
||||
representing the first day of the next week, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
* ``previous_week``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the first day of the previous week. Unlike ``next_week``,
|
||||
this will never be ``None``.
|
||||
* ``previous_week``: A :class:`~datetime.date` object
|
||||
representing the first day of the previous week, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
**Notes**
|
||||
|
||||
|
@ -193,7 +203,7 @@ WeekArchiveView
|
|||
DayArchiveView
|
||||
--------------
|
||||
|
||||
.. class:: django.views.generic.dates.DayArchiveView
|
||||
.. class:: DayArchiveView
|
||||
|
||||
A day archive page showing all objects in a given day. Days in the future
|
||||
throw a 404 error, regardless of whether any objects exist for future days,
|
||||
|
@ -220,24 +230,28 @@ DayArchiveView
|
|||
:class:`~django.views.generic.dates.BaseDateListView`), the template's
|
||||
context will be:
|
||||
|
||||
* ``day``: A :class:`datetime.date<python:datetime.date>` object
|
||||
* ``day``: A :class:`~datetime.date` object
|
||||
representing the given day.
|
||||
|
||||
* ``next_day``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the next day. If the next day is in the future, this will be
|
||||
``None``.
|
||||
* ``next_day``: A :class:`~datetime.date` object
|
||||
representing the next day, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
* ``previous_day``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the previous day. Unlike ``next_day``, this will never be
|
||||
``None``.
|
||||
* ``previous_day``: A :class:`~datetime.date` object
|
||||
representing the previous day, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
* ``next_month``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the first day of the next month. If the next month is in the
|
||||
future, this will be ``None``.
|
||||
* ``next_month``: A :class:`~datetime.date` object
|
||||
representing the first day of the next month, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
* ``previous_month``: A :class:`datetime.date<python:datetime.date>` object
|
||||
representing the first day of the previous month. Unlike ``next_month``,
|
||||
this will never be ``None``.
|
||||
* ``previous_month``: A :class:`~datetime.date` object
|
||||
representing the first day of the previous month, according to
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
**Notes**
|
||||
|
||||
|
@ -246,7 +260,7 @@ DayArchiveView
|
|||
TodayArchiveView
|
||||
----------------
|
||||
|
||||
.. class:: django.views.generic.dates.TodayArchiveView
|
||||
.. class:: TodayArchiveView
|
||||
|
||||
A day archive page showing all objects for *today*. This is exactly the
|
||||
same as :class:`django.views.generic.dates.DayArchiveView`, except today's
|
||||
|
@ -271,7 +285,7 @@ TodayArchiveView
|
|||
DateDetailView
|
||||
--------------
|
||||
|
||||
.. class:: django.views.generic.dates.DateDetailView
|
||||
.. class:: DateDetailView
|
||||
|
||||
A page representing an individual object. If the object has a date value in
|
||||
the future, the view will throw a 404 error by default, unless you set
|
||||
|
@ -293,6 +307,22 @@ DateDetailView
|
|||
|
||||
.. note::
|
||||
|
||||
All of the generic views listed above have matching Base* views that only
|
||||
differ in that the they do not include the
|
||||
:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`.
|
||||
All of the generic views listed above have matching ``Base`` views that
|
||||
only differ in that the they do not include the
|
||||
:class:`~django.views.generic.detail.SingleObjectTemplateResponseMixin`:
|
||||
|
||||
.. class:: BaseArchiveIndexView
|
||||
|
||||
.. class:: BaseYearArchiveView
|
||||
|
||||
.. class:: BaseMonthArchiveView
|
||||
|
||||
.. class:: BaseWeekArchiveView
|
||||
|
||||
.. class:: BaseDayArchiveView
|
||||
|
||||
.. class:: BaseTodayArchiveView
|
||||
|
||||
.. class:: BaseDateDetailView
|
||||
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ DetailView
|
|||
|
||||
**Ancestors (MRO)**
|
||||
|
||||
This view inherits methods and attributes from the following views:
|
||||
|
||||
* :class:`django.views.generic.detail.SingleObjectTemplateResponseMixin`
|
||||
* :class:`django.views.generic.base.TemplateResponseMixin`
|
||||
* :class:`django.views.generic.detail.BaseDetailView`
|
||||
|
@ -71,7 +73,9 @@ ListView
|
|||
objects (usually, but not necessarily a queryset) that the view is
|
||||
operating upon.
|
||||
|
||||
**Mixins**
|
||||
**Ancestors (MRO)**
|
||||
|
||||
This view inherits methods and attributes from the following views:
|
||||
|
||||
* :class:`django.views.generic.list.ListView`
|
||||
* :class:`django.views.generic.list.MultipleObjectTemplateResponseMixin`
|
||||
|
@ -90,3 +94,54 @@ ListView
|
|||
6. :meth:`get_context_data()`
|
||||
7. :meth:`get()`
|
||||
8. :meth:`render_to_response()`
|
||||
|
||||
|
||||
**Example views.py**::
|
||||
|
||||
from django.views.generic.list import ListView
|
||||
from django.utils import timezone
|
||||
|
||||
from articles.models import Article
|
||||
|
||||
class ArticleListView(ListView):
|
||||
|
||||
model = Article
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ArticleListView, self).get_context_data(**kwargs)
|
||||
context['now'] = timezone.now()
|
||||
return context
|
||||
|
||||
**Example urls.py**::
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
from article.views import ArticleListView
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', ArticleListView.as_view(), name='article-list'),
|
||||
)
|
||||
|
||||
.. class:: django.views.generic.list.BaseListView
|
||||
|
||||
A base view for displaying a list of objects. It is not intended to be used
|
||||
directly, but rather as a parent class of the
|
||||
:class:`django.views.generic.list.ListView` or other views representing
|
||||
lists of objects.
|
||||
|
||||
**Ancestors (MRO)**
|
||||
|
||||
This view inherits methods and attributes from the following views:
|
||||
|
||||
* :class:`django.views.generic.list.MultipleObjectMixin`
|
||||
* :class:`django.views.generic.base.View`
|
||||
|
||||
**Methods**
|
||||
|
||||
.. method:: get(request, *args, **kwargs)
|
||||
|
||||
Adds :attr:`object_list` to the context. If
|
||||
:attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty`
|
||||
is True then display an empty list. If
|
||||
:attr:`~django.views.generic.list.MultipleObjectMixin.allow_empty` is
|
||||
False then raise a 404 error.
|
||||
|
|
|
@ -23,7 +23,7 @@ it is safe to store state variables on the instance (i.e., ``self.foo = 3`` is
|
|||
a thread-safe operation).
|
||||
|
||||
A class-based view is deployed into a URL pattern using the
|
||||
:meth:`~View.as_view()` classmethod::
|
||||
:meth:`~django.views.generic.base.View.as_view()` classmethod::
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^view/$', MyView.as_view(size=42)),
|
||||
|
@ -37,9 +37,10 @@ A class-based view is deployed into a URL pattern using the
|
|||
is modified, the actions of one user visiting your view could have an
|
||||
effect on subsequent users visiting the same view.
|
||||
|
||||
Any argument passed into :meth:`~View.as_view()` will be assigned onto the
|
||||
instance that is used to service a request. Using the previous example,
|
||||
this means that every request on ``MyView`` is able to use ``self.size``.
|
||||
Any argument passed into :meth:`~django.views.generic.base.View.as_view()` will
|
||||
be assigned onto the instance that is used to service a request. Using the
|
||||
previous example, this means that every request on ``MyView`` is able to use
|
||||
``self.size``.
|
||||
|
||||
Base vs Generic views
|
||||
---------------------
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
Date-based mixins
|
||||
=================
|
||||
|
||||
.. currentmodule:: django.views.generic.dates
|
||||
|
||||
YearMixin
|
||||
---------
|
||||
|
||||
.. class:: django.views.generic.dates.YearMixin
|
||||
.. class:: YearMixin
|
||||
|
||||
A mixin that can be used to retrieve and provide parsing information for a
|
||||
year component of a date.
|
||||
|
@ -20,29 +21,45 @@ YearMixin
|
|||
|
||||
.. attribute:: year
|
||||
|
||||
**Optional** The value for the year (as a string). By default, set to
|
||||
**Optional** The value for the year, as a string. By default, set to
|
||||
``None``, which means the year will be determined using other means.
|
||||
|
||||
.. method:: get_year_format()
|
||||
|
||||
Returns the :func:`~time.strftime` format to use when parsing the year. Returns
|
||||
:attr:`YearMixin.year_format` by default.
|
||||
Returns the :func:`~time.strftime` format to use when parsing the
|
||||
year. Returns :attr:`~YearMixin.year_format` by default.
|
||||
|
||||
.. method:: get_year()
|
||||
|
||||
Returns the year for which this view will display data. Tries the
|
||||
following sources, in order:
|
||||
Returns the year for which this view will display data, as a string.
|
||||
Tries the following sources, in order:
|
||||
|
||||
* The value of the :attr:`YearMixin.year` attribute.
|
||||
* The value of the `year` argument captured in the URL pattern
|
||||
* The value of the `year` argument captured in the URL pattern.
|
||||
* The value of the `year` GET query argument.
|
||||
|
||||
Raises a 404 if no valid year specification can be found.
|
||||
|
||||
.. method:: get_next_year(date)
|
||||
|
||||
Returns a date object containing the first day of the year after the
|
||||
date provided. This function can also return ``None`` or raise an
|
||||
:class:`~django.http.Http404` exception, depending on the values of
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
.. method:: get_previous_year(date)
|
||||
|
||||
Returns a date object containing the first day of the year before the
|
||||
date provided. This function can also return ``None`` or raise an
|
||||
:class:`~django.http.Http404` exception, depending on the values of
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
MonthMixin
|
||||
----------
|
||||
|
||||
.. class:: django.views.generic.dates.MonthMixin
|
||||
.. class:: MonthMixin
|
||||
|
||||
A mixin that can be used to retrieve and provide parsing information for a
|
||||
month component of a date.
|
||||
|
@ -51,26 +68,26 @@ MonthMixin
|
|||
|
||||
.. attribute:: month_format
|
||||
|
||||
The :func:`~time.strftime` format to use when parsing the month. By default, this is
|
||||
``'%b'``.
|
||||
The :func:`~time.strftime` format to use when parsing the month. By
|
||||
default, this is ``'%b'``.
|
||||
|
||||
.. attribute:: month
|
||||
|
||||
**Optional** The value for the month (as a string). By default, set to
|
||||
**Optional** The value for the month, as a string. By default, set to
|
||||
``None``, which means the month will be determined using other means.
|
||||
|
||||
.. method:: get_month_format()
|
||||
|
||||
Returns the :func:`~time.strftime` format to use when parsing the month. Returns
|
||||
:attr:`MonthMixin.month_format` by default.
|
||||
Returns the :func:`~time.strftime` format to use when parsing the
|
||||
month. Returns :attr:`~MonthMixin.month_format` by default.
|
||||
|
||||
.. method:: get_month()
|
||||
|
||||
Returns the month for which this view will display data. Tries the
|
||||
following sources, in order:
|
||||
Returns the month for which this view will display data, as a string.
|
||||
Tries the following sources, in order:
|
||||
|
||||
* The value of the :attr:`MonthMixin.month` attribute.
|
||||
* The value of the `month` argument captured in the URL pattern
|
||||
* The value of the `month` argument captured in the URL pattern.
|
||||
* The value of the `month` GET query argument.
|
||||
|
||||
Raises a 404 if no valid month specification can be found.
|
||||
|
@ -78,20 +95,23 @@ MonthMixin
|
|||
.. method:: get_next_month(date)
|
||||
|
||||
Returns a date object containing the first day of the month after the
|
||||
date provided. Returns ``None`` if mixed with a view that sets
|
||||
``allow_future = False``, and the next month is in the future. If
|
||||
``allow_empty = False``, returns the next month that contains data.
|
||||
date provided. This function can also return ``None`` or raise an
|
||||
:class:`~django.http.Http404` exception, depending on the values of
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
.. method:: get_prev_month(date)
|
||||
|
||||
Returns a date object containing the first day of the month before the
|
||||
date provided. If ``allow_empty = False``, returns the previous month
|
||||
that contained data.
|
||||
date provided. This function can also return ``None`` or raise an
|
||||
:class:`~django.http.Http404` exception, depending on the values of
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
DayMixin
|
||||
--------
|
||||
|
||||
.. class:: django.views.generic.dates.DayMixin
|
||||
.. class:: DayMixin
|
||||
|
||||
A mixin that can be used to retrieve and provide parsing information for a
|
||||
day component of a date.
|
||||
|
@ -100,46 +120,50 @@ DayMixin
|
|||
|
||||
.. attribute:: day_format
|
||||
|
||||
The :func:`~time.strftime` format to use when parsing the day. By default, this is
|
||||
``'%d'``.
|
||||
The :func:`~time.strftime` format to use when parsing the day. By
|
||||
default, this is ``'%d'``.
|
||||
|
||||
.. attribute:: day
|
||||
|
||||
**Optional** The value for the day (as a string). By default, set to
|
||||
**Optional** The value for the day, as a string. By default, set to
|
||||
``None``, which means the day will be determined using other means.
|
||||
|
||||
.. method:: get_day_format()
|
||||
|
||||
Returns the :func:`~time.strftime` format to use when parsing the day. Returns
|
||||
:attr:`DayMixin.day_format` by default.
|
||||
Returns the :func:`~time.strftime` format to use when parsing the day.
|
||||
Returns :attr:`~DayMixin.day_format` by default.
|
||||
|
||||
.. method:: get_day()
|
||||
|
||||
Returns the day for which this view will display data. Tries the
|
||||
following sources, in order:
|
||||
Returns the day for which this view will display data, as a string.
|
||||
Tries the following sources, in order:
|
||||
|
||||
* The value of the :attr:`DayMixin.day` attribute.
|
||||
* The value of the `day` argument captured in the URL pattern
|
||||
* The value of the `day` argument captured in the URL pattern.
|
||||
* The value of the `day` GET query argument.
|
||||
|
||||
Raises a 404 if no valid day specification can be found.
|
||||
|
||||
.. method:: get_next_day(date)
|
||||
|
||||
Returns a date object containing the next day after the date provided.
|
||||
Returns ``None`` if mixed with a view that sets ``allow_future = False``,
|
||||
and the next day is in the future. If ``allow_empty = False``, returns
|
||||
the next day that contains data.
|
||||
Returns a date object containing the next valid day after the date
|
||||
provided. This function can also return ``None`` or raise an
|
||||
:class:`~django.http.Http404` exception, depending on the values of
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
.. method:: get_prev_day(date)
|
||||
|
||||
Returns a date object containing the previous day. If
|
||||
``allow_empty = False``, returns the previous day that contained data.
|
||||
Returns a date object containing the previous valid day. This function
|
||||
can also return ``None`` or raise an :class:`~django.http.Http404`
|
||||
exception, depending on the values of
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
WeekMixin
|
||||
---------
|
||||
|
||||
.. class:: django.views.generic.dates.WeekMixin
|
||||
.. class:: WeekMixin
|
||||
|
||||
A mixin that can be used to retrieve and provide parsing information for a
|
||||
week component of a date.
|
||||
|
@ -148,23 +172,24 @@ WeekMixin
|
|||
|
||||
.. attribute:: week_format
|
||||
|
||||
The :func:`~time.strftime` format to use when parsing the week. By default, this is
|
||||
``'%U'``.
|
||||
The :func:`~time.strftime` format to use when parsing the week. By
|
||||
default, this is ``'%U'``, which means the week starts on Sunday. Set
|
||||
it to ``'%W'`` if your week starts on Monday.
|
||||
|
||||
.. attribute:: week
|
||||
|
||||
**Optional** The value for the week (as a string). By default, set to
|
||||
**Optional** The value for the week, as a string. By default, set to
|
||||
``None``, which means the week will be determined using other means.
|
||||
|
||||
.. method:: get_week_format()
|
||||
|
||||
Returns the :func:`~time.strftime` format to use when parsing the week. Returns
|
||||
:attr:`WeekMixin.week_format` by default.
|
||||
Returns the :func:`~time.strftime` format to use when parsing the
|
||||
week. Returns :attr:`~WeekMixin.week_format` by default.
|
||||
|
||||
.. method:: get_week()
|
||||
|
||||
Returns the week for which this view will display data. Tries the
|
||||
following sources, in order:
|
||||
Returns the week for which this view will display data, as a string.
|
||||
Tries the following sources, in order:
|
||||
|
||||
* The value of the :attr:`WeekMixin.week` attribute.
|
||||
* The value of the `week` argument captured in the URL pattern
|
||||
|
@ -172,11 +197,26 @@ WeekMixin
|
|||
|
||||
Raises a 404 if no valid week specification can be found.
|
||||
|
||||
.. method:: get_next_week(date)
|
||||
|
||||
Returns a date object containing the first day of the week after the
|
||||
date provided. This function can also return ``None`` or raise an
|
||||
:class:`~django.http.Http404` exception, depending on the values of
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
.. method:: get_prev_week(date)
|
||||
|
||||
Returns a date object containing the first day of the week before the
|
||||
date provided. This function can also return ``None`` or raise an
|
||||
:class:`~django.http.Http404` exception, depending on the values of
|
||||
:attr:`~BaseDateListView.allow_empty` and
|
||||
:attr:`~DateMixin.allow_future`.
|
||||
|
||||
DateMixin
|
||||
---------
|
||||
|
||||
.. class:: django.views.generic.dates.DateMixin
|
||||
.. class:: DateMixin
|
||||
|
||||
A mixin class providing common behavior for all date-based views.
|
||||
|
||||
|
@ -186,7 +226,7 @@ DateMixin
|
|||
|
||||
The name of the ``DateField`` or ``DateTimeField`` in the
|
||||
``QuerySet``'s model that the date-based archive should use to
|
||||
determine the objects on the page.
|
||||
determine the list of objects to display on the page.
|
||||
|
||||
When :doc:`time zone support </topics/i18n/timezones>` is enabled and
|
||||
``date_field`` is a ``DateTimeField``, dates are assumed to be in the
|
||||
|
@ -210,26 +250,26 @@ DateMixin
|
|||
.. method:: get_date_field()
|
||||
|
||||
Returns the name of the field that contains the date data that this
|
||||
view will operate on. Returns :attr:`DateMixin.date_field` by default.
|
||||
view will operate on. Returns :attr:`~DateMixin.date_field` by default.
|
||||
|
||||
.. method:: get_allow_future()
|
||||
|
||||
Determine whether to include "future" objects on this page, where
|
||||
"future" means objects in which the field specified in ``date_field``
|
||||
is greater than the current date/time. Returns
|
||||
:attr:`DateMixin.allow_future` by default.
|
||||
:attr:`~DateMixin.allow_future` by default.
|
||||
|
||||
BaseDateListView
|
||||
----------------
|
||||
|
||||
.. class:: django.views.generic.dates.BaseDateListView
|
||||
.. class:: BaseDateListView
|
||||
|
||||
A base class that provides common behavior for all date-based views. There
|
||||
won't normally be a reason to instantiate
|
||||
:class:`~django.views.generic.dates.BaseDateListView`; instantiate one of
|
||||
the subclasses instead.
|
||||
|
||||
While this view (and it's subclasses) are executing, ``self.object_list``
|
||||
While this view (and its subclasses) are executing, ``self.object_list``
|
||||
will contain the list of objects that the view is operating upon, and
|
||||
``self.date_list`` will contain the list of dates for which data is
|
||||
available.
|
||||
|
@ -245,10 +285,18 @@ BaseDateListView
|
|||
|
||||
A boolean specifying whether to display the page if no objects are
|
||||
available. If this is ``True`` and no objects are available, the view
|
||||
will display an empty page instead of raising a 404. By default, this
|
||||
is ``False``.
|
||||
will display an empty page instead of raising a 404.
|
||||
|
||||
.. method:: get_dated_items():
|
||||
This is identical to :attr:`MultipleObjectMixin.allow_empty`, except
|
||||
for the default value, which is ``False``.
|
||||
|
||||
.. attribute:: date_list_period
|
||||
|
||||
**Optional** A string defining the aggregation period for
|
||||
``date_list``. It must be one of ``'year'`` (default), ``'month'``, or
|
||||
``'day'``.
|
||||
|
||||
.. method:: get_dated_items()
|
||||
|
||||
Returns a 3-tuple containing (``date_list``, ``object_list``,
|
||||
``extra_context``).
|
||||
|
@ -265,10 +313,17 @@ BaseDateListView
|
|||
``lookup``. Enforces any restrictions on the queryset, such as
|
||||
``allow_empty`` and ``allow_future``.
|
||||
|
||||
.. method:: get_date_list(queryset, date_type)
|
||||
.. method:: get_date_list_period()
|
||||
|
||||
Returns the list of dates of type ``date_type`` for which
|
||||
``queryset`` contains entries. For example, ``get_date_list(qs,
|
||||
'year')`` will return the list of years for which ``qs`` has entries.
|
||||
See :meth:`~django.db.models.query.QuerySet.dates()` for the
|
||||
ways that the ``date_type`` argument can be used.
|
||||
Returns the aggregation period for ``date_list``. Returns
|
||||
:attr:`~BaseDateListView.date_list_period` by default.
|
||||
|
||||
.. method:: get_date_list(queryset, date_type=None)
|
||||
|
||||
Returns the list of dates of type ``date_type`` for which ``queryset``
|
||||
contains entries. For example, ``get_date_list(qs, 'year')`` will
|
||||
return the list of years for which ``qs`` has entries. If
|
||||
``date_type`` isn't provided, the result of
|
||||
:meth:`BaseDateListView.get_date_list_period` is used. See
|
||||
:meth:`~django.db.models.query.QuerySet.dates()` for the ways that the
|
||||
``date_type`` argument can be used.
|
||||
|
|
|
@ -86,7 +86,8 @@ MultipleObjectMixin
|
|||
|
||||
.. method:: get_queryset()
|
||||
|
||||
Returns the queryset that represents the data this view will display.
|
||||
Get the list of items for this view. This must be an iterable and may
|
||||
be a queryset (in which queryset-specific behavior will be enabled).
|
||||
|
||||
.. method:: paginate_queryset(queryset, page_size)
|
||||
|
||||
|
|
|
@ -9,16 +9,17 @@ ContextMixin
|
|||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
**classpath**
|
||||
|
||||
``django.views.generic.base.ContextMixin``
|
||||
|
||||
**Methods**
|
||||
|
||||
.. method:: get_context_data(**kwargs)
|
||||
|
||||
Returns a dictionary representing the template context. The keyword
|
||||
arguments provided will make up the returned context.
|
||||
arguments provided will make up the returned context. Example usage::
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(RandomNumberView, self).get_context_data(**kwargs)
|
||||
context['number'] = random.randrange(1, 100)
|
||||
return context
|
||||
|
||||
The template context of all class-based generic views include a
|
||||
``view`` variable that points to the ``View`` instance.
|
||||
|
@ -42,7 +43,13 @@ TemplateResponseMixin
|
|||
suitable context. The template to use is configurable and can be
|
||||
further customized by subclasses.
|
||||
|
||||
**Methods and Attributes**
|
||||
**Attributes**
|
||||
|
||||
.. attribute:: template_name
|
||||
|
||||
The full name of a template to use as defined by a string. Not defining
|
||||
a template_name will raise a
|
||||
:class:`django.core.exceptions.ImproperlyConfigured` exception.
|
||||
|
||||
.. attribute:: response_class
|
||||
|
||||
|
@ -57,12 +64,14 @@ TemplateResponseMixin
|
|||
instantiation, create a ``TemplateResponse`` subclass and assign it to
|
||||
``response_class``.
|
||||
|
||||
**Methods**
|
||||
|
||||
.. method:: render_to_response(context, **response_kwargs)
|
||||
|
||||
Returns a ``self.response_class`` instance.
|
||||
|
||||
If any keyword arguments are provided, they will be
|
||||
passed to the constructor of the response class.
|
||||
If any keyword arguments are provided, they will be passed to the
|
||||
constructor of the response class.
|
||||
|
||||
Calls :meth:`~TemplateResponseMixin.get_template_names()` to obtain the
|
||||
list of template names that will be searched looking for an existent
|
||||
|
|
|
@ -32,11 +32,11 @@ A simple example is the best illustration of this. Suppose we have the
|
|||
following model, which would represent entries in a Weblog::
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Entry(models.Model):
|
||||
title = models.CharField(maxlength=250)
|
||||
body = models.TextField()
|
||||
pub_date = models.DateTimeField()
|
||||
pub_date = models.DateField()
|
||||
enable_comments = models.BooleanField()
|
||||
|
||||
Now, suppose that we want the following steps to be applied whenever a
|
||||
|
@ -55,11 +55,11 @@ Accomplishing this is fairly straightforward and requires very little
|
|||
code::
|
||||
|
||||
from django.contrib.comments.moderation import CommentModerator, moderator
|
||||
|
||||
|
||||
class EntryModerator(CommentModerator):
|
||||
email_notification = True
|
||||
enable_field = 'enable_comments'
|
||||
|
||||
|
||||
moderator.register(Entry, EntryModerator)
|
||||
|
||||
The :class:`CommentModerator` class pre-defines a number of useful moderation
|
||||
|
|
|
@ -187,6 +187,14 @@ The ``ContentTypeManager``
|
|||
probably won't ever need to call this method yourself; Django will call
|
||||
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])
|
||||
|
||||
Takes either a model class or an instance of a model, and returns the
|
||||
|
|
|
@ -155,7 +155,8 @@ or the
|
|||
:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names()`
|
||||
method, which are documented in 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
|
||||
it:
|
||||
|
@ -238,6 +239,65 @@ wizard's :meth:`as_view` method takes a list of your
|
|||
(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:
|
||||
|
||||
Advanced ``WizardView`` methods
|
||||
|
|
|
@ -959,15 +959,15 @@ Ubuntu & Debian GNU/Linux
|
|||
Ubuntu
|
||||
^^^^^^
|
||||
|
||||
11.10
|
||||
~~~~~
|
||||
11.10 through 12.04
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In Ubuntu 11.10, PostgreSQL was upgraded to 9.1. The installation commands are:
|
||||
In Ubuntu 11.10, PostgreSQL was upgraded to 9.1. The installation command is:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo apt-get install binutils gdal-bin libproj-dev postgresql-9.1-postgis \
|
||||
postgresql-server-dev-9.1 python-psycopg2
|
||||
$ sudo apt-get install binutils gdal-bin libproj-dev \
|
||||
postgresql-9.1-postgis postgresql-server-dev-9.1 python-psycopg2
|
||||
|
||||
.. _ubuntu10:
|
||||
|
||||
|
@ -976,7 +976,7 @@ In Ubuntu 11.10, PostgreSQL was upgraded to 9.1. The installation commands are:
|
|||
|
||||
In Ubuntu 10.04, PostgreSQL was upgraded to 8.4 and GDAL was upgraded to 1.6.
|
||||
Ubuntu 10.04 uses PostGIS 1.4, while Ubuntu 10.10 uses PostGIS 1.5 (with
|
||||
geography support). The installation commands are:
|
||||
geography support). The installation command is:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
|
|
@ -674,8 +674,8 @@ __ http://spatialreference.org/ref/epsg/32140/
|
|||
.. admonition:: Raw queries
|
||||
|
||||
When using :doc:`raw queries </topics/db/sql>`, you should generally wrap
|
||||
your geometry fields with the ``asText()`` SQL function so as the field
|
||||
value will be recognized by GEOS::
|
||||
your geometry fields with the ``asText()`` SQL function (or ``ST_AsText``
|
||||
for PostGIS) so as the field value will be recognized by GEOS::
|
||||
|
||||
City.objects.raw('SELECT id, name, asText(point) from myapp_city')
|
||||
|
||||
|
|
|
@ -5,6 +5,9 @@ django.contrib.markup
|
|||
.. module:: django.contrib.markup
|
||||
:synopsis: A collection of template filters that implement common markup languages.
|
||||
|
||||
.. deprecated:: 1.5
|
||||
This module has been deprecated.
|
||||
|
||||
Django provides template filters that implement the following markup
|
||||
languages:
|
||||
|
||||
|
|
|
@ -5,14 +5,16 @@ The messages framework
|
|||
.. module:: django.contrib.messages
|
||||
:synopsis: Provides cookie- and session-based temporary message storage.
|
||||
|
||||
Quite commonly in web applications, you may need to display a one-time
|
||||
notification message (also know as "flash message") to the user after
|
||||
processing a form or some other types of user input. For this, Django provides
|
||||
full support for cookie- and session-based messaging, for both anonymous and
|
||||
authenticated users. The messages framework allows you to temporarily store
|
||||
messages in one request and retrieve them for display in a subsequent request
|
||||
(usually the next one). Every message is tagged with a specific ``level`` that
|
||||
determines its priority (e.g., ``info``, ``warning``, or ``error``).
|
||||
Quite commonly in web applications, you need to display a one-time
|
||||
notification message (also known as "flash message") to the user after
|
||||
processing a form or some other types of user input.
|
||||
|
||||
For this, Django provides full support for cookie- and session-based
|
||||
messaging, for both anonymous and authenticated users. The messages framework
|
||||
allows you to temporarily store messages in one request and retrieve them for
|
||||
display in a subsequent request (usually the next one). Every message is
|
||||
tagged with a specific ``level`` that determines its priority (e.g., ``info``,
|
||||
``warning``, or ``error``).
|
||||
|
||||
Enabling messages
|
||||
=================
|
||||
|
@ -20,32 +22,27 @@ Enabling messages
|
|||
Messages are implemented through a :doc:`middleware </ref/middleware>`
|
||||
class and corresponding :doc:`context processor </ref/templates/api>`.
|
||||
|
||||
To enable message functionality, do the following:
|
||||
The default ``settings.py`` created by ``django-admin.py startproject``
|
||||
already contains all the settings required to enable message functionality:
|
||||
|
||||
* Edit the :setting:`MIDDLEWARE_CLASSES` setting and make sure
|
||||
it contains ``'django.contrib.messages.middleware.MessageMiddleware'``.
|
||||
* ``'django.contrib.messages'`` is in :setting:`INSTALLED_APPS`.
|
||||
|
||||
If you are using a :ref:`storage backend <message-storage-backends>` that
|
||||
relies on :doc:`sessions </topics/http/sessions>` (the default),
|
||||
``'django.contrib.sessions.middleware.SessionMiddleware'`` must be
|
||||
enabled and appear before ``MessageMiddleware`` in your
|
||||
* :setting:`MIDDLEWARE_CLASSES` contains
|
||||
``'django.contrib.sessions.middleware.SessionMiddleware'`` and
|
||||
``'django.contrib.messages.middleware.MessageMiddleware'``.
|
||||
|
||||
The default :ref:`storage backend <message-storage-backends>` relies on
|
||||
:doc:`sessions </topics/http/sessions>`. That's why ``SessionMiddleware``
|
||||
must be enabled and appear before ``MessageMiddleware`` in
|
||||
:setting:`MIDDLEWARE_CLASSES`.
|
||||
|
||||
* Edit the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting and make sure
|
||||
it contains ``'django.contrib.messages.context_processors.messages'``.
|
||||
* :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains
|
||||
``'django.contrib.messages.context_processors.messages'``.
|
||||
|
||||
* Add ``'django.contrib.messages'`` to your :setting:`INSTALLED_APPS`
|
||||
setting
|
||||
|
||||
The default ``settings.py`` created by ``django-admin.py startproject`` has
|
||||
``MessageMiddleware`` activated and the ``django.contrib.messages`` app
|
||||
installed. Also, the default value for :setting:`TEMPLATE_CONTEXT_PROCESSORS`
|
||||
contains ``'django.contrib.messages.context_processors.messages'``.
|
||||
|
||||
If you don't want to use messages, you can remove the
|
||||
``MessageMiddleware`` line from :setting:`MIDDLEWARE_CLASSES`, the ``messages``
|
||||
context processor from :setting:`TEMPLATE_CONTEXT_PROCESSORS` and
|
||||
``'django.contrib.messages'`` from your :setting:`INSTALLED_APPS`.
|
||||
If you don't want to use messages, you can remove
|
||||
``'django.contrib.messages'`` from your :setting:`INSTALLED_APPS`, the
|
||||
``MessageMiddleware`` line from :setting:`MIDDLEWARE_CLASSES`, and the
|
||||
``messages`` context processor from :setting:`TEMPLATE_CONTEXT_PROCESSORS`.
|
||||
|
||||
Configuring the message engine
|
||||
==============================
|
||||
|
@ -56,34 +53,35 @@ Storage backends
|
|||
----------------
|
||||
|
||||
The messages framework can use different backends to store temporary messages.
|
||||
If the default FallbackStorage isn't suitable to your needs, you can change
|
||||
which backend is being used by adding a `MESSAGE_STORAGE`_ to your
|
||||
settings, referencing the module and class of the storage class. For
|
||||
example::
|
||||
|
||||
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
||||
Django provides three built-in storage classes:
|
||||
|
||||
The value should be the full path of the desired storage class.
|
||||
.. class:: django.contrib.messages.storage.session.SessionStorage
|
||||
|
||||
Three storage classes are available:
|
||||
This class stores all messages inside of the request's session. Therefore
|
||||
it requires Django's ``contrib.sessions`` application.
|
||||
|
||||
``'django.contrib.messages.storage.session.SessionStorage'``
|
||||
This class stores all messages inside of the request's session. It
|
||||
requires Django's ``contrib.sessions`` application.
|
||||
.. class:: django.contrib.messages.storage.cookie.CookieStorage
|
||||
|
||||
``'django.contrib.messages.storage.cookie.CookieStorage'``
|
||||
This class stores the message data in a cookie (signed with a secret hash
|
||||
to prevent manipulation) to persist notifications across requests. Old
|
||||
messages are dropped if the cookie data size would exceed 4096 bytes.
|
||||
messages are dropped if the cookie data size would exceed 2048 bytes.
|
||||
|
||||
``'django.contrib.messages.storage.fallback.FallbackStorage'``
|
||||
This is the default storage class.
|
||||
.. class:: django.contrib.messages.storage.fallback.FallbackStorage
|
||||
|
||||
This class first uses CookieStorage for all messages, falling back to using
|
||||
SessionStorage for the messages that could not fit in a single cookie.
|
||||
This class first uses ``CookieStorage``, and falls back to using
|
||||
``SessionStorage`` for the messages that could not fit in a single cookie.
|
||||
It also requires Django's ``contrib.sessions`` application.
|
||||
|
||||
Since it is uses SessionStorage, it also requires Django's
|
||||
``contrib.sessions`` application.
|
||||
This behavior avoids writing to the session whenever possible. It should
|
||||
provide the best performance in the general case.
|
||||
|
||||
:class:`~django.contrib.messages.storage.fallback.FallbackStorage` is the
|
||||
default storage class. If it isn't suitable to your needs, you can select
|
||||
another storage class by setting `MESSAGE_STORAGE`_ to its full import path,
|
||||
for example::
|
||||
|
||||
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
||||
|
||||
To write your own storage class, subclass the ``BaseStorage`` class in
|
||||
``django.contrib.messages.storage.base`` and implement the ``_get`` and
|
||||
|
@ -97,8 +95,8 @@ to that of the Python logging module. Message levels allow you to group
|
|||
messages by type so they can be filtered or displayed differently in views and
|
||||
templates.
|
||||
|
||||
The built-in levels (which can be imported from ``django.contrib.messages``
|
||||
directly) are:
|
||||
The built-in levels, which can be imported from ``django.contrib.messages``
|
||||
directly, are:
|
||||
|
||||
=========== ========
|
||||
Constant Purpose
|
||||
|
|
|
@ -398,11 +398,21 @@ For each field, we describe the default widget used if you don't specify
|
|||
|
||||
If no ``input_formats`` argument is provided, the default input formats are::
|
||||
|
||||
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
|
||||
'%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
|
||||
'%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006'
|
||||
'%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
|
||||
'%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
|
||||
'%Y-%m-%d', # '2006-10-25'
|
||||
'%m/%d/%Y', # '10/25/2006'
|
||||
'%m/%d/%y', # '10/25/06'
|
||||
|
||||
Additionally, if you specify :setting:`USE_L10N=False<USE_L10N>` in your settings, the
|
||||
following will also be included in the default input formats::
|
||||
|
||||
'%b %m %d', # 'Oct 25 2006'
|
||||
'%b %d, %Y', # 'Oct 25, 2006'
|
||||
'%d %b %Y', # '25 Oct 2006'
|
||||
'%d %b, %Y', # '25 Oct, 2006'
|
||||
'%B %d %Y', # 'October 25 2006'
|
||||
'%B %d, %Y', # 'October 25, 2006'
|
||||
'%d %B %Y', # '25 October 2006'
|
||||
'%d %B, %Y', # '25 October, 2006'
|
||||
|
||||
``DateTimeField``
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
@ -842,7 +852,7 @@ Slightly complex built-in ``Field`` classes
|
|||
``MultiValueField``
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. class:: MultiValueField(**kwargs)
|
||||
.. class:: MultiValueField(fields=(), **kwargs)
|
||||
|
||||
* Default widget: ``TextInput``
|
||||
* Empty value: ``''`` (an empty string)
|
||||
|
@ -851,22 +861,39 @@ Slightly complex built-in ``Field`` classes
|
|||
as an argument to the ``MultiValueField``.
|
||||
* Error message keys: ``required``, ``invalid``
|
||||
|
||||
This abstract field (must be subclassed) aggregates the logic of multiple
|
||||
fields. Subclasses should not have to implement clean(). Instead, they must
|
||||
implement compress(), which takes a list of valid values and returns a
|
||||
"compressed" version of those values -- a single value. For example,
|
||||
:class:`SplitDateTimeField` is a subclass which combines a time field and
|
||||
a date field into a datetime object.
|
||||
Aggregates the logic of multiple fields that together produce a single
|
||||
value.
|
||||
|
||||
This field is abstract and must be subclassed. In contrast with the
|
||||
single-value fields, subclasses of :class:`MultiValueField` must not
|
||||
implement :meth:`~django.forms.Field.clean` but instead - implement
|
||||
:meth:`~MultiValueField.compress`.
|
||||
|
||||
Takes one extra required argument:
|
||||
|
||||
.. attribute:: fields
|
||||
|
||||
A list of fields which are cleaned into a single field. Each value in
|
||||
``clean`` is cleaned by the corresponding field in ``fields`` -- the first
|
||||
value is cleaned by the first field, the second value is cleaned by
|
||||
the second field, etc. Once all fields are cleaned, the list of clean
|
||||
values is "compressed" into a single value.
|
||||
A tuple of fields whose values are cleaned and subsequently combined
|
||||
into a single value. Each value of the field is cleaned by the
|
||||
corresponding field in ``fields`` -- the first value is cleaned by the
|
||||
first field, the second value is cleaned by the second field, etc.
|
||||
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``
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -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
|
||||
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
|
||||
------------------
|
||||
|
||||
|
@ -95,15 +105,23 @@ choices are inherent to the model and not just the representational widget.
|
|||
Customizing widget instances
|
||||
----------------------------
|
||||
|
||||
When Django renders a widget as HTML, it only renders the bare minimum
|
||||
HTML - Django doesn't add a class definition, or any other widget-specific
|
||||
attributes. This means that all :class:`TextInput` widgets will appear the same
|
||||
on your Web page.
|
||||
When Django renders a widget as HTML, it only renders very minimal markup -
|
||||
Django doesn't add class names, or any other widget-specific attributes. This
|
||||
means, for example, that all :class:`TextInput` widgets will appear the same
|
||||
on your Web pages.
|
||||
|
||||
If you want to make one widget look different to another, you need to
|
||||
specify additional attributes for each widget. When you specify a
|
||||
widget, you can provide a list of attributes that will be added to the
|
||||
rendered HTML for the widget.
|
||||
There are two ways to customize widgets: :ref:`per widget instance
|
||||
<styling-widget-instances>` and :ref:`per widget class <styling-widget-classes>`.
|
||||
|
||||
.. _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::
|
||||
|
||||
|
@ -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
|
||||
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
|
||||
:attr:`Widget.attrs` argument when creating the widget:
|
||||
|
||||
For example::
|
||||
'name' widget to have some special CSS class. It is also possible to specify
|
||||
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::
|
||||
|
||||
class CommentForm(forms.Form):
|
||||
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>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
|
||||
commonly used groups of widgets:
|
||||
With widgets, it is possible to add media (``css`` and ``javascript``)
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
@ -171,6 +205,74 @@ commonly used groups of widgets:
|
|||
>>> name.render('name', 'A name')
|
||||
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``
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
|
@ -204,39 +306,8 @@ commonly used groups of widgets:
|
|||
|
||||
Hidden input: ``<input type='hidden' ...>``
|
||||
|
||||
``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`.
|
||||
|
||||
``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.
|
||||
Note that there also is a :class:`MultipleHiddenInput` widget that
|
||||
encapsulates a set of hidden input elements.
|
||||
|
||||
``DateInput``
|
||||
~~~~~~~~~~~~~
|
||||
|
@ -245,7 +316,7 @@ commonly used groups of widgets:
|
|||
|
||||
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
|
||||
|
||||
|
@ -262,7 +333,7 @@ commonly used groups of widgets:
|
|||
|
||||
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
|
||||
|
||||
|
@ -279,7 +350,7 @@ commonly used groups of widgets:
|
|||
|
||||
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
|
||||
|
||||
|
@ -296,6 +367,11 @@ commonly used groups of widgets:
|
|||
|
||||
Text area: ``<textarea>...</textarea>``
|
||||
|
||||
.. _selector-widgets:
|
||||
|
||||
Selector and checkbox widgets
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``CheckboxInput``
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -439,6 +515,50 @@ commonly used groups of widgets:
|
|||
...
|
||||
</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``
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -195,6 +195,14 @@ support tablespaces for indexes, this option is ignored.
|
|||
The default value for the field. This can be a value or a callable object. If
|
||||
callable it will be called every time a new object is created.
|
||||
|
||||
The default cannot be a mutable object (model instance, list, set, etc.), as a
|
||||
reference to the same instance of that object would be used as the default
|
||||
value in all new model instances. Instead, wrap the desired default in a
|
||||
callable. For example, if you had a custom ``JSONField`` and wanted to specify
|
||||
a dictionary as the default, use a ``lambda`` as follows::
|
||||
|
||||
contact_info = JSONField("ContactInfo", default=lambda:{"email": "to1@example.com"})
|
||||
|
||||
``editable``
|
||||
------------
|
||||
|
||||
|
@ -983,10 +991,10 @@ define the details of how the relation works.
|
|||
this with functions from the Python ``datetime`` module to limit choices of
|
||||
objects by date. For example::
|
||||
|
||||
limit_choices_to = {'pub_date__lte': datetime.now}
|
||||
limit_choices_to = {'pub_date__lte': datetime.date.today}
|
||||
|
||||
only allows the choice of related objects with a ``pub_date`` before the
|
||||
current date/time to be chosen.
|
||||
current date to be chosen.
|
||||
|
||||
Instead of a dictionary this can also be a :class:`~django.db.models.Q`
|
||||
object for more :ref:`complex queries <complex-lookups-with-q>`. However,
|
||||
|
|
|
@ -135,7 +135,7 @@ access to more than a single field::
|
|||
raise ValidationError('Draft entries may not have a publication date.')
|
||||
# Set the pub_date for published items if it hasn't been set already.
|
||||
if self.status == 'published' and self.pub_date is None:
|
||||
self.pub_date = datetime.datetime.now()
|
||||
self.pub_date = datetime.date.today()
|
||||
|
||||
Any :exc:`~django.core.exceptions.ValidationError` exceptions raised by
|
||||
``Model.clean()`` will be stored in a special key error dictionary key,
|
||||
|
|
|
@ -31,6 +31,9 @@ You can evaluate a ``QuerySet`` in the following ways:
|
|||
for e in Entry.objects.all():
|
||||
print(e.headline)
|
||||
|
||||
Note: Don't use this if all you want to do is determine if at least one
|
||||
result exists. It's more efficient to use :meth:`~QuerySet.exists`.
|
||||
|
||||
* **Slicing.** As explained in :ref:`limiting-querysets`, a ``QuerySet`` can
|
||||
be sliced, using Python's array-slicing syntax. Slicing an unevaluated
|
||||
``QuerySet`` usually returns another unevaluated ``QuerySet``, but Django
|
||||
|
@ -75,7 +78,7 @@ You can evaluate a ``QuerySet`` in the following ways:
|
|||
|
||||
Note: *Don't* use this if all you want to do is determine if at least one
|
||||
result exists, and don't need the actual objects. It's more efficient to
|
||||
use :meth:`exists() <QuerySet.exists>` (see below).
|
||||
use :meth:`~QuerySet.exists` (see below).
|
||||
|
||||
.. _pickling QuerySets:
|
||||
|
||||
|
@ -1047,7 +1050,7 @@ defer
|
|||
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),
|
||||
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
|
||||
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``
|
||||
if not. This tries to perform the query in the simplest and fastest way
|
||||
possible, but it *does* execute nearly the same query. This means that calling
|
||||
:meth:`.QuerySet.exists` is faster than ``bool(some_query_set)``, but not by
|
||||
a large degree. If ``some_query_set`` has not yet been evaluated, but you know
|
||||
possible, but it *does* execute nearly the same query as a normal
|
||||
:class:`.QuerySet` query.
|
||||
|
||||
:meth:`~.QuerySet.exists` is useful for searches relating to both
|
||||
object membership in a :class:`.QuerySet` and to the existence of any objects in
|
||||
a :class:`.QuerySet`, particularly in the context of a large :class:`.QuerySet`.
|
||||
|
||||
The most efficient method of finding whether a model with a unique field
|
||||
(e.g. ``primary_key``) is a member of a :class:`.QuerySet` is::
|
||||
|
||||
entry = Entry.objects.get(pk=123)
|
||||
if some_query_set.filter(pk=entry.pk).exists():
|
||||
print("Entry contained in queryset")
|
||||
|
||||
Which will be faster than the following which requires evaluating and iterating
|
||||
through the entire queryset::
|
||||
|
||||
if entry in some_query_set:
|
||||
print("Entry contained in QuerySet")
|
||||
|
||||
And to find whether a queryset contains any items::
|
||||
|
||||
if some_query_set.exists():
|
||||
print("There is at least one object in some_query_set")
|
||||
|
||||
Which will be faster than::
|
||||
|
||||
if some_query_set:
|
||||
print("There is at least one object in some_query_set")
|
||||
|
||||
... but not by a large degree (hence needing a large queryset for efficiency
|
||||
gains).
|
||||
|
||||
Additionally, if a ``some_query_set`` has not yet been evaluated, but you know
|
||||
that it will be at some point, then using ``some_query_set.exists()`` will do
|
||||
more overall work (one query for the existence check plus an extra one to later
|
||||
retrieve the results) than simply using ``bool(some_query_set)``, which
|
||||
|
@ -1945,6 +1979,17 @@ SQL equivalent::
|
|||
You can use ``range`` anywhere you can use ``BETWEEN`` in SQL — for dates,
|
||||
numbers and even characters.
|
||||
|
||||
.. warning::
|
||||
|
||||
Filtering a ``DateTimeField`` with dates won't include items on the last
|
||||
day, because the bounds are interpreted as "0am on the given date". If
|
||||
``pub_date`` was a ``DateTimeField``, the above expression would be turned
|
||||
into this SQL::
|
||||
|
||||
SELECT ... WHERE pub_date BETWEEN '2005-01-01 00:00:00' and '2005-03-31 00:00:00';
|
||||
|
||||
Generally speaking, you can't mix dates and datetimes.
|
||||
|
||||
.. fieldlookup:: year
|
||||
|
||||
year
|
||||
|
@ -1958,7 +2003,7 @@ Example::
|
|||
|
||||
SQL equivalent::
|
||||
|
||||
SELECT ... WHERE pub_date BETWEEN '2005-01-01' AND '2005-12-31 23:59:59.999999';
|
||||
SELECT ... WHERE pub_date BETWEEN '2005-01-01' AND '2005-12-31';
|
||||
|
||||
(The exact SQL syntax varies for each database engine.)
|
||||
|
||||
|
|
|
@ -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`
|
||||
decorator, for example.
|
||||
|
||||
.. _`note on LOGIN_REDIRECT_URL setting`:
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
.. note::
|
||||
You can use :func:`~django.core.urlresolvers.reverse_lazy` to reference
|
||||
URLs by their name instead of providing a hardcoded value. Assuming a
|
||||
``urls.py`` with an URLpattern named ``home``::
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url('^welcome/$', 'test_app.views.home', name='home'),
|
||||
)
|
||||
|
||||
You can use :func:`~django.core.urlresolvers.reverse_lazy` like this::
|
||||
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
|
||||
LOGIN_REDIRECT_URL = reverse_lazy('home')
|
||||
|
||||
This also works fine with localized URLs using
|
||||
:func:`~django.conf.urls.i18n.i18n_patterns`.
|
||||
This setting now also accepts view function names and
|
||||
:ref:`named URL patterns <naming-url-patterns>` which can be used to reduce
|
||||
configuration duplication since you no longer have to define the URL in two
|
||||
places (``settings`` and URLconf).
|
||||
For backward compatibility reasons the default remains unchanged.
|
||||
|
||||
.. setting:: LOGIN_URL
|
||||
|
||||
|
@ -1334,8 +1322,13 @@ Default: ``'/accounts/login/'``
|
|||
The URL where requests are redirected for login, especially when using the
|
||||
:func:`~django.contrib.auth.decorators.login_required` decorator.
|
||||
|
||||
.. note::
|
||||
See the `note on LOGIN_REDIRECT_URL setting`_
|
||||
.. versionchanged:: 1.5
|
||||
|
||||
This setting now also accepts view function names and
|
||||
:ref:`named URL patterns <naming-url-patterns>` which can be used to reduce
|
||||
configuration duplication since you no longer have to define the URL in two
|
||||
places (``settings`` and URLconf).
|
||||
For backward compatibility reasons the default remains unchanged.
|
||||
|
||||
.. setting:: LOGOUT_URL
|
||||
|
||||
|
@ -1346,9 +1339,6 @@ Default: ``'/accounts/logout/'``
|
|||
|
||||
LOGIN_URL counterpart.
|
||||
|
||||
.. note::
|
||||
See the `note on LOGIN_REDIRECT_URL setting`_
|
||||
|
||||
.. setting:: MANAGERS
|
||||
|
||||
MANAGERS
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue