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

Conflicts:
	django/db/models/loading.py
	django/db/models/options.py
This commit is contained in:
Andrew Godwin 2012-12-18 09:02:07 +00:00
commit b62e82365a
436 changed files with 11663 additions and 3600 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Normalize line endings to avoid spurious failures in the core test suite on Windows.
*html text eol=lf

View File

@ -91,6 +91,7 @@ answer newbie questions, and generally made Django that much better:
James Bennett James Bennett
Danilo Bargen Danilo Bargen
Shai Berger <shai@platonix.com> Shai Berger <shai@platonix.com>
berto
Julian Bez Julian Bez
Arvis Bickovskis <viestards.lists@gmail.com> Arvis Bickovskis <viestards.lists@gmail.com>
Natalia Bidart <nataliabidart@gmail.com> Natalia Bidart <nataliabidart@gmail.com>
@ -231,6 +232,7 @@ answer newbie questions, and generally made Django that much better:
Simon Greenhill <dev@simon.net.nz> Simon Greenhill <dev@simon.net.nz>
Owen Griffiths Owen Griffiths
Espen Grindhaug <http://grindhaug.org/> Espen Grindhaug <http://grindhaug.org/>
Mike Grouchy <http://mikegrouchy.com/>
Janos Guljas Janos Guljas
Thomas Güttler <hv@tbz-pariv.de> Thomas Güttler <hv@tbz-pariv.de>
Horst Gutmann <zerok@zerokspot.com> Horst Gutmann <zerok@zerokspot.com>
@ -380,6 +382,7 @@ answer newbie questions, and generally made Django that much better:
Christian Metts Christian Metts
michal@plovarna.cz michal@plovarna.cz
Slawek Mikula <slawek dot mikula at gmail dot com> Slawek Mikula <slawek dot mikula at gmail dot com>
Katie Miller <katie@sub50.com>
Shawn Milochik <shawn@milochik.com> Shawn Milochik <shawn@milochik.com>
mitakummaa@gmail.com mitakummaa@gmail.com
Taylor Mitchell <taylor.mitchell@gmail.com> Taylor Mitchell <taylor.mitchell@gmail.com>
@ -510,6 +513,7 @@ answer newbie questions, and generally made Django that much better:
Johan C. Stöver <johan@nilling.nl> Johan C. Stöver <johan@nilling.nl>
Nowell Strite <http://nowell.strite.org/> Nowell Strite <http://nowell.strite.org/>
Thomas Stromberg <tstromberg@google.com> Thomas Stromberg <tstromberg@google.com>
Ben Sturmfels <ben@sturm.com.au>
Travis Swicegood <travis@domain51.com> Travis Swicegood <travis@domain51.com>
Pascal Varet Pascal Varet
SuperJared SuperJared
@ -528,6 +532,7 @@ answer newbie questions, and generally made Django that much better:
Terry Huang <terryh.tp@gmail.com> Terry Huang <terryh.tp@gmail.com>
Travis Terry <tdterry7@gmail.com> Travis Terry <tdterry7@gmail.com>
thebjorn <bp@datakortet.no> thebjorn <bp@datakortet.no>
Lowe Thiderman <lowe.thiderman@gmail.com>
Zach Thompson <zthompson47@gmail.com> Zach Thompson <zthompson47@gmail.com>
Michael Thornhill <michael.thornhill@gmail.com> Michael Thornhill <michael.thornhill@gmail.com>
Deepak Thukral <deep.thukral@gmail.com> Deepak Thukral <deep.thukral@gmail.com>
@ -585,6 +590,7 @@ answer newbie questions, and generally made Django that much better:
Gasper Zejn <zejn@kiberpipa.org> Gasper Zejn <zejn@kiberpipa.org>
Jarek Zgoda <jarek.zgoda@gmail.com> Jarek Zgoda <jarek.zgoda@gmail.com>
Cheng Zhang Cheng Zhang
Hannes Struß <x@hannesstruss.de>
A big THANK YOU goes to: A big THANK YOU goes to:

View File

@ -1,4 +1,4 @@
VERSION = (1, 5, 0, 'alpha', 0) VERSION = (1, 6, 0, 'alpha', 0)
def get_version(*args, **kwargs): def get_version(*args, **kwargs):
# Don't litter django/__init__.py with all the get_version stuff. # Don't litter django/__init__.py with all the get_version stuff.

View File

@ -7,7 +7,13 @@ Can be run as a cronjob to clean out old data from the database (only expired
sessions at the moment). sessions at the moment).
""" """
import warnings
from django.core import management from django.core import management
if __name__ == "__main__": if __name__ == "__main__":
management.call_command('cleanup') warnings.warn(
"The `daily_cleanup` script has been deprecated "
"in favor of `django-admin.py clearsessions`.",
PendingDeprecationWarning)
management.call_command('clearsessions')

View File

@ -6,6 +6,7 @@ variable, and then from django.conf.global_settings; see the global settings fil
a list of all possible variables. a list of all possible variables.
""" """
import logging
import os import os
import time # Needed for Windows import time # Needed for Windows
import warnings import warnings
@ -55,6 +56,15 @@ class LazySettings(LazyObject):
""" """
Setup logging from LOGGING_CONFIG and LOGGING settings. Setup logging from LOGGING_CONFIG and LOGGING settings.
""" """
try:
# Route warnings through python logging
logging.captureWarnings(True)
# Allow DeprecationWarnings through the warnings filters
warnings.simplefilter("default", DeprecationWarning)
except AttributeError:
# No captureWarnings on Python 2.6, DeprecationWarnings are on anyway
pass
if self.LOGGING_CONFIG: if self.LOGGING_CONFIG:
from django.utils.log import DEFAULT_LOGGING from django.utils.log import DEFAULT_LOGGING
# First find the logging configuration function ... # First find the logging configuration function ...
@ -83,6 +93,7 @@ class LazySettings(LazyObject):
for name, value in options.items(): for name, value in options.items():
setattr(holder, name, value) setattr(holder, name, value)
self._wrapped = holder self._wrapped = holder
self._configure_logging()
@property @property
def configured(self): def configured(self):
@ -99,9 +110,6 @@ class BaseSettings(object):
def __setattr__(self, name, value): def __setattr__(self, name, value):
if name in ("MEDIA_URL", "STATIC_URL") and value and not value.endswith('/'): if name in ("MEDIA_URL", "STATIC_URL") and value and not value.endswith('/'):
raise ImproperlyConfigured("If set, %s must end with a slash" % name) raise ImproperlyConfigured("If set, %s must end with a slash" % name)
elif name == "ADMIN_MEDIA_PREFIX":
warnings.warn("The ADMIN_MEDIA_PREFIX setting has been removed; "
"use STATIC_URL instead.", DeprecationWarning)
elif name == "ALLOWED_INCLUDE_ROOTS" and isinstance(value, six.string_types): elif name == "ALLOWED_INCLUDE_ROOTS" and isinstance(value, six.string_types):
raise ValueError("The ALLOWED_INCLUDE_ROOTS setting must be set " raise ValueError("The ALLOWED_INCLUDE_ROOTS setting must be set "
"to a tuple, not a string.") "to a tuple, not a string.")

View File

@ -150,12 +150,8 @@ SERVER_EMAIL = 'root@localhost'
# Whether to send broken-link emails. # Whether to send broken-link emails.
SEND_BROKEN_LINK_EMAILS = False SEND_BROKEN_LINK_EMAILS = False
# Database connection info. # Database connection info. If left empty, will default to the dummy backend.
DATABASES = { DATABASES = {}
'default': {
'ENGINE': 'django.db.backends.dummy',
},
}
# Classes used to implement DB routing behavior. # Classes used to implement DB routing behavior.
DATABASE_ROUTERS = [] DATABASE_ROUTERS = []
@ -449,6 +445,7 @@ MIDDLEWARE_CLASSES = (
# SESSIONS # # SESSIONS #
############ ############
SESSION_CACHE_ALIAS = 'default' # Cache to store session data if using the cache session backend.
SESSION_COOKIE_NAME = 'sessionid' # Cookie name. This can be whatever you want. SESSION_COOKIE_NAME = 'sessionid' # Cookie name. This can be whatever you want.
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # Age of cookie, in seconds (default: 2 weeks). SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # Age of cookie, in seconds (default: 2 weeks).
SESSION_COOKIE_DOMAIN = None # A string like ".example.com", or None for standard domain cookie. SESSION_COOKIE_DOMAIN = None # A string like ".example.com", or None for standard domain cookie.

View File

@ -1,5 +1,8 @@
from __future__ import unicode_literals from __future__ import unicode_literals
# About name_local: capitalize it as if your language name was appearing
# inside a sentence in your language.
LANG_INFO = { LANG_INFO = {
'ar': { 'ar': {
'bidi': True, 'bidi': True,
@ -53,7 +56,7 @@ LANG_INFO = {
'bidi': False, 'bidi': False,
'code': 'da', 'code': 'da',
'name': 'Danish', 'name': 'Danish',
'name_local': 'Dansk', 'name_local': 'dansk',
}, },
'de': { 'de': {
'bidi': False, 'bidi': False,
@ -137,7 +140,7 @@ LANG_INFO = {
'bidi': False, 'bidi': False,
'code': 'fr', 'code': 'fr',
'name': 'French', 'name': 'French',
'name_local': 'Fran\xe7ais', 'name_local': 'fran\xe7ais',
}, },
'fy-nl': { 'fy-nl': {
'bidi': False, 'bidi': False,
@ -269,7 +272,7 @@ LANG_INFO = {
'bidi': False, 'bidi': False,
'code': 'nb', 'code': 'nb',
'name': 'Norwegian Bokmal', 'name': 'Norwegian Bokmal',
'name_local': 'Norsk (bokm\xe5l)', 'name_local': 'norsk (bokm\xe5l)',
}, },
'ne': { 'ne': {
'bidi': False, 'bidi': False,
@ -287,13 +290,13 @@ LANG_INFO = {
'bidi': False, 'bidi': False,
'code': 'nn', 'code': 'nn',
'name': 'Norwegian Nynorsk', 'name': 'Norwegian Nynorsk',
'name_local': 'Norsk (nynorsk)', 'name_local': 'norsk (nynorsk)',
}, },
'no': { 'no': {
'bidi': False, 'bidi': False,
'code': 'no', 'code': 'no',
'name': 'Norwegian', 'name': 'Norwegian',
'name_local': 'Norsk', 'name_local': 'norsk',
}, },
'pa': { 'pa': {
'bidi': False, 'bidi': False,
@ -365,7 +368,7 @@ LANG_INFO = {
'bidi': False, 'bidi': False,
'code': 'sv', 'code': 'sv',
'name': 'Swedish', 'name': 'Swedish',
'name_local': 'Svenska', 'name_local': 'svenska',
}, },
'sw': { 'sw': {
'bidi': False, 'bidi': False,

View File

@ -19,10 +19,6 @@ DATE_INPUT_FORMATS = (
# '31/12/2009', '31/12/09' # '31/12/2009', '31/12/09'
'%d/%m/%Y', '%d/%m/%y' '%d/%m/%Y', '%d/%m/%y'
) )
TIME_INPUT_FORMATS = (
# '14:30:59', '14:30'
'%H:%M:%S', '%H:%M'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d/%m/%Y %H:%M:%S', '%d/%m/%Y %H:%M:%S',
'%d/%m/%Y %H:%M', '%d/%m/%Y %H:%M',

View File

@ -17,21 +17,26 @@ FIRST_DAY_OF_WEEK = 1 # Monday
# The *_INPUT_FORMATS strings use the Python strftime format syntax, # The *_INPUT_FORMATS strings use the Python strftime format syntax,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%d.%m.%Y', '%d.%m.%y', # '25.10.2006', '25.10.06' '%d.%m.%Y', '%d.%m.%y', # '05.01.2006', '05.01.06'
'%Y-%m-%d', '%y-%m-%d', # '2006-10-25', '06-10-25' '%d. %m. %Y', '%d. %m. %y', # '5. 1. 2006', '5. 1. 06'
# '%d. %B %Y', '%d. %b. %Y', # '25. October 2006', '25. Oct. 2006' # '%d. %B %Y', '%d. %b. %Y', # '25. October 2006', '25. Oct. 2006'
) )
# Kept ISO formats as one is in first position
TIME_INPUT_FORMATS = ( TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59' '%H:%M:%S', # '04:30:59'
'%H:%M', # '14:30' '%H.%M', # '04.30'
'%H:%M', # '04:30'
) )
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59' '%d.%m.%Y %H:%M:%S', # '05.01.2006 04:30:59'
'%d.%m.%Y %H:%M', # '25.10.2006 14:30' '%d.%m.%Y %H.%M', # '05.01.2006 04.30'
'%d.%m.%Y', # '25.10.2006' '%d.%m.%Y %H:%M', # '05.01.2006 04:30'
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' '%d.%m.%Y', # '05.01.2006'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30' '%d. %m. %Y %H:%M:%S', # '05. 01. 2006 04:30:59'
'%Y-%m-%d', # '2006-10-25' '%d. %m. %Y %H.%M', # '05. 01. 2006 04.30'
'%d. %m. %Y %H:%M', # '05. 01. 2006 04:30'
'%d. %m. %Y', # '05. 01. 2006'
'%Y-%m-%d %H.%M', # '2006-01-05 04.30'
) )
DECIMAL_SEPARATOR = ',' DECIMAL_SEPARATOR = ','
THOUSAND_SEPARATOR = '\xa0' # non-breaking space THOUSAND_SEPARATOR = '\xa0' # non-breaking space

View File

@ -18,10 +18,6 @@ FIRST_DAY_OF_WEEK = 1
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%d.%m.%Y', # '25.10.2006' '%d.%m.%Y', # '25.10.2006'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59' '%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59'
'%d.%m.%Y %H:%M', # '25.10.2006 14:30' '%d.%m.%Y %H:%M', # '25.10.2006 14:30'

View File

@ -17,20 +17,12 @@ FIRST_DAY_OF_WEEK = 1 # Monday
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%d.%m.%Y', '%d.%m.%y', # '25.10.2006', '25.10.06' '%d.%m.%Y', '%d.%m.%y', # '25.10.2006', '25.10.06'
'%Y-%m-%d', '%y-%m-%d', # '2006-10-25', '06-10-25'
# '%d. %B %Y', '%d. %b. %Y', # '25. October 2006', '25. Oct. 2006' # '%d. %B %Y', '%d. %b. %Y', # '25. October 2006', '25. Oct. 2006'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59' '%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59'
'%d.%m.%Y %H:%M', # '25.10.2006 14:30' '%d.%m.%Y %H:%M', # '25.10.2006 14:30'
'%d.%m.%Y', # '25.10.2006' '%d.%m.%Y', # '25.10.2006'
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
) )
DECIMAL_SEPARATOR = ',' DECIMAL_SEPARATOR = ','
THOUSAND_SEPARATOR = '.' THOUSAND_SEPARATOR = '.'

View File

@ -19,20 +19,12 @@ FIRST_DAY_OF_WEEK = 1 # Monday
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%d.%m.%Y', '%d.%m.%y', # '25.10.2006', '25.10.06' '%d.%m.%Y', '%d.%m.%y', # '25.10.2006', '25.10.06'
'%Y-%m-%d', '%y-%m-%d', # '2006-10-25', '06-10-25'
# '%d. %B %Y', '%d. %b. %Y', # '25. October 2006', '25. Oct. 2006' # '%d. %B %Y', '%d. %b. %Y', # '25. October 2006', '25. Oct. 2006'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59' '%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59'
'%d.%m.%Y %H:%M', # '25.10.2006 14:30' '%d.%m.%Y %H:%M', # '25.10.2006 14:30'
'%d.%m.%Y', # '25.10.2006' '%d.%m.%Y', # '25.10.2006'
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
) )
# these are the separators for non-monetary numbers. For monetary numbers, # these are the separators for non-monetary numbers. For monetary numbers,

View File

@ -15,6 +15,7 @@ FIRST_DAY_OF_WEEK = 0 # Sunday
# The *_INPUT_FORMATS strings use the Python strftime format syntax, # The *_INPUT_FORMATS strings use the Python strftime format syntax,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
# Kept ISO formats as they are in first position
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' '%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' # '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
@ -22,10 +23,6 @@ DATE_INPUT_FORMATS = (
# '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 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' # '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30' '%Y-%m-%d %H:%M', # '2006-10-25 14:30'

View File

@ -17,16 +17,11 @@ FIRST_DAY_OF_WEEK = 0 # Sunday
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%d/%m/%Y', '%d/%m/%y', # '25/10/2006', '25/10/06' '%d/%m/%Y', '%d/%m/%y', # '25/10/2006', '25/10/06'
'%Y-%m-%d', # '2006-10-25'
# '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006' # '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
# '%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 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' # '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006'
# '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006' # '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30' '%Y-%m-%d %H:%M', # '2006-10-25 14:30'

View File

@ -19,10 +19,6 @@ DATE_INPUT_FORMATS = (
# '31/12/2009', '31/12/09' # '31/12/2009', '31/12/09'
'%d/%m/%Y', '%d/%m/%y' '%d/%m/%Y', '%d/%m/%y'
) )
TIME_INPUT_FORMATS = (
# '14:30:59', '14:30'
'%H:%M:%S', '%H:%M'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d/%m/%Y %H:%M:%S', '%d/%m/%Y %H:%M:%S',
'%d/%m/%Y %H:%M', '%d/%m/%Y %H:%M',

View File

@ -19,10 +19,6 @@ DATE_INPUT_FORMATS = (
'%d/%m/%Y', # '31/12/2009' '%d/%m/%Y', # '31/12/2009'
'%d/%m/%y', # '31/12/09' '%d/%m/%y', # '31/12/09'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d/%m/%Y %H:%M:%S', '%d/%m/%Y %H:%M:%S',
'%d/%m/%Y %H:%M', '%d/%m/%Y %H:%M',

View File

@ -15,9 +15,6 @@ DATE_INPUT_FORMATS = (
'%d/%m/%Y', '%d/%m/%y', # '25/10/2006', '25/10/06' '%d/%m/%Y', '%d/%m/%y', # '25/10/2006', '25/10/06'
'%Y%m%d', # '20061025' '%Y%m%d', # '20061025'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', '%H:%M', # '14:30:59', '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d/%m/%Y %H:%M:%S', '%d/%m/%Y %H:%M:%S',
'%d/%m/%Y %H:%M', '%d/%m/%Y %H:%M',

View File

@ -15,9 +15,6 @@ DATE_INPUT_FORMATS = (
'%Y%m%d', # '20061025' '%Y%m%d', # '20061025'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', '%H:%M', # '14:30:59', '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d/%m/%Y %H:%M:%S', '%d/%m/%Y %H:%M:%S',
'%d/%m/%Y %H:%M', '%d/%m/%Y %H:%M',

View File

@ -19,13 +19,8 @@ FIRST_DAY_OF_WEEK = 1 # Monday
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%d/%m/%Y', '%d/%m/%y', # '25/10/2006', '25/10/06' '%d/%m/%Y', '%d/%m/%y', # '25/10/2006', '25/10/06'
'%d.%m.%Y', '%d.%m.%y', # Swiss (fr_CH), '25.10.2006', '25.10.06' '%d.%m.%Y', '%d.%m.%y', # Swiss (fr_CH), '25.10.2006', '25.10.06'
'%Y-%m-%d', '%y-%m-%d', # '2006-10-25', '06-10-25'
# '%d %B %Y', '%d %b %Y', # '25 octobre 2006', '25 oct. 2006' # '%d %B %Y', '%d %b %Y', # '25 octobre 2006', '25 oct. 2006'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d/%m/%Y %H:%M:%S', # '25/10/2006 14:30:59' '%d/%m/%Y %H:%M:%S', # '25/10/2006 14:30:59'
'%d/%m/%Y %H:%M', # '25/10/2006 14:30' '%d/%m/%Y %H:%M', # '25/10/2006 14:30'
@ -33,9 +28,6 @@ DATETIME_INPUT_FORMATS = (
'%d.%m.%Y %H:%M:%S', # Swiss (fr_CH), '25.10.2006 14:30:59' '%d.%m.%Y %H:%M:%S', # Swiss (fr_CH), '25.10.2006 14:30:59'
'%d.%m.%Y %H:%M', # Swiss (fr_CH), '25.10.2006 14:30' '%d.%m.%Y %H:%M', # Swiss (fr_CH), '25.10.2006 14:30'
'%d.%m.%Y', # Swiss (fr_CH), '25.10.2006' '%d.%m.%Y', # Swiss (fr_CH), '25.10.2006'
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
) )
DECIMAL_SEPARATOR = ',' DECIMAL_SEPARATOR = ','
THOUSAND_SEPARATOR = '\xa0' # non-breaking space THOUSAND_SEPARATOR = '\xa0' # non-breaking space

View File

@ -15,15 +15,12 @@ FIRST_DAY_OF_WEEK = 1
# The *_INPUT_FORMATS strings use the Python strftime format syntax, # The *_INPUT_FORMATS strings use the Python strftime format syntax,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
# Kept ISO formats as they are in first position
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%Y-%m-%d', # '2006-10-25' '%Y-%m-%d', # '2006-10-25'
'%d.%m.%Y.', '%d.%m.%y.', # '25.10.2006.', '25.10.06.' '%d.%m.%Y.', '%d.%m.%y.', # '25.10.2006.', '25.10.06.'
'%d. %m. %Y.', '%d. %m. %y.', # '25. 10. 2006.', '25. 10. 06.' '%d. %m. %Y.', '%d. %m. %y.', # '25. 10. 2006.', '25. 10. 06.'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30' '%Y-%m-%d %H:%M', # '2006-10-25 14:30'

View File

@ -20,10 +20,6 @@ DATE_INPUT_FORMATS = (
'%d-%m-%Y', '%Y-%m-%d', # '25-10-2006', '2008-10-25' '%d-%m-%Y', '%Y-%m-%d', # '25-10-2006', '2008-10-25'
'%d-%m-%y', '%d/%m/%y', # '25-10-06', '25/10/06' '%d-%m-%y', '%d/%m/%y', # '25-10-06', '25/10/06'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d/%m/%Y %H:%M:%S', # '25/10/2006 14:30:59' '%d/%m/%Y %H:%M:%S', # '25/10/2006 14:30:59'
'%d/%m/%Y %H:%M', # '25/10/2006 14:30' '%d/%m/%Y %H:%M', # '25/10/2006 14:30'

View File

@ -15,16 +15,13 @@ FIRST_DAY_OF_WEEK = 1 # (Monday)
# The *_INPUT_FORMATS strings use the Python strftime format syntax, # The *_INPUT_FORMATS strings use the Python strftime format syntax,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
# Kept ISO formats as they are in first position
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
# '%d %b %Y', '%d %b, %Y', '%d %b. %Y', # '25 Oct 2006', '25 Oct, 2006', '25 Oct. 2006' # '%d %b %Y', '%d %b, %Y', '%d %b. %Y', # '25 Oct 2006', '25 Oct, 2006', '25 Oct. 2006'
# '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006' # '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
# '%d.%m.%Y', '%d.%m.%y', # '25.10.2006', '25.10.06' # '%d.%m.%Y', '%d.%m.%y', # '25.10.2006', '25.10.06'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30' '%Y-%m-%d %H:%M', # '2006-10-25 14:30'

View File

@ -16,6 +16,7 @@ SHORT_DATETIME_FORMAT = 'Y-n-j H:i'
# The *_INPUT_FORMATS strings use the Python strftime format syntax, # The *_INPUT_FORMATS strings use the Python strftime format syntax,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
# Kept ISO formats as they are in first position
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' '%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' # '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'

View File

@ -16,6 +16,7 @@ FIRST_DAY_OF_WEEK = 1 #Monday
# The *_INPUT_FORMATS strings use the Python strftime format syntax, # The *_INPUT_FORMATS strings use the Python strftime format syntax,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
# Kept ISO formats as they are in first position
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%Y-%m-%d', '%d.%m.%Y', '%d.%m.%y', # '2006-10-25', '25.10.2006', '25.10.06' '%Y-%m-%d', '%d.%m.%Y', '%d.%m.%y', # '2006-10-25', '25.10.2006', '25.10.06'
) )

View File

@ -18,12 +18,6 @@ FIRST_DAY_OF_WEEK = 1
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%d.%m.%Y.', '%d.%m.%y.', # '25.10.2006.', '25.10.06.' '%d.%m.%Y.', '%d.%m.%y.', # '25.10.2006.', '25.10.06.'
'%d. %m. %Y.', '%d. %m. %y.', # '25. 10. 2006.', '25. 10. 06.' '%d. %m. %Y.', '%d. %m. %y.', # '25. 10. 2006.', '25. 10. 06.'
'%Y-%m-%d', # '2006-10-25'
)
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
) )
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
@ -39,9 +33,6 @@ DATETIME_INPUT_FORMATS = (
'%d. %m. %y. %H:%M:%S', # '25. 10. 06. 14:30:59' '%d. %m. %y. %H:%M:%S', # '25. 10. 06. 14:30:59'
'%d. %m. %y. %H:%M', # '25. 10. 06. 14:30' '%d. %m. %y. %H:%M', # '25. 10. 06. 14:30'
'%d. %m. %y.', # '25. 10. 06.' '%d. %m. %y.', # '25. 10. 06.'
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
) )
DECIMAL_SEPARATOR = ',' DECIMAL_SEPARATOR = ','

View File

@ -15,6 +15,7 @@ FIRST_DAY_OF_WEEK = 0 # Sunday
# The *_INPUT_FORMATS strings use the Python strftime format syntax, # The *_INPUT_FORMATS strings use the Python strftime format syntax,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
# Kept ISO formats as they are in first position
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' '%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' # '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006'
@ -22,10 +23,6 @@ DATE_INPUT_FORMATS = (
# '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 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' # '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30' '%Y-%m-%d %H:%M', # '2006-10-25 14:30'

View File

@ -16,22 +16,17 @@ FIRST_DAY_OF_WEEK = 1 # Monday
# The *_INPUT_FORMATS strings use the Python strftime format syntax, # The *_INPUT_FORMATS strings use the Python strftime format syntax,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
# Kept ISO formats as they are in first position
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%Y-%m-%d', '%d.%m.%Y', '%d.%m.%y', # '2006-10-25', '25.10.2006', '25.10.06' '%Y-%m-%d', '%d.%m.%Y', '%d.%m.%y', # '2006-10-25', '25.10.2006', '25.10.06'
'%Y-%m-%d', # '2006-10-25',
# '%d. %b %Y', '%d %b %Y', # '25. okt 2006', '25 okt 2006' # '%d. %b %Y', '%d %b %Y', # '25. okt 2006', '25 okt 2006'
# '%d. %b. %Y', '%d %b. %Y', # '25. okt. 2006', '25 okt. 2006' # '%d. %b. %Y', '%d %b. %Y', # '25. okt. 2006', '25 okt. 2006'
# '%d. %B %Y', '%d %B %Y', # '25. oktober 2006', '25 oktober 2006' # '%d. %B %Y', '%d %B %Y', # '25. oktober 2006', '25 oktober 2006'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30' '%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25' '%Y-%m-%d', # '2006-10-25'
'%Y-%m-%d', # '2006-10-25'
'%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59' '%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59'
'%d.%m.%Y %H:%M', # '25.10.2006 14:30' '%d.%m.%Y %H:%M', # '25.10.2006 14:30'
'%d.%m.%Y', # '25.10.2006' '%d.%m.%Y', # '25.10.2006'

View File

@ -16,10 +16,11 @@ FIRST_DAY_OF_WEEK = 1 # Monday (in Dutch 'maandag')
# The *_INPUT_FORMATS strings use the Python strftime format syntax, # The *_INPUT_FORMATS strings use the Python strftime format syntax,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%d-%m-%Y', '%d-%m-%y', '%Y-%m-%d', # '20-01-2009', '20-01-09', '2009-01-20' '%d-%m-%Y', '%d-%m-%y', # '20-01-2009', '20-01-09'
# '%d %b %Y', '%d %b %y', # '20 jan 2009', '20 jan 09' # '%d %b %Y', '%d %b %y', # '20 jan 2009', '20 jan 09'
# '%d %B %Y', '%d %B %y', # '20 januari 2009', '20 januari 09' # '%d %B %Y', '%d %B %y', # '20 januari 2009', '20 januari 09'
) )
# Kept ISO formats as one is in first position
TIME_INPUT_FORMATS = ( TIME_INPUT_FORMATS = (
'%H:%M:%S', # '15:23:35' '%H:%M:%S', # '15:23:35'
'%H.%M:%S', # '15.23:35' '%H.%M:%S', # '15.23:35'

View File

@ -16,17 +16,13 @@ FIRST_DAY_OF_WEEK = 1 # Monday
# The *_INPUT_FORMATS strings use the Python strftime format syntax, # The *_INPUT_FORMATS strings use the Python strftime format syntax,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
# Kept ISO formats as they are in first position
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%Y-%m-%d', '%d.%m.%Y', '%d.%m.%y', # '2006-10-25', '25.10.2006', '25.10.06' '%Y-%m-%d', '%d.%m.%Y', '%d.%m.%y', # '2006-10-25', '25.10.2006', '25.10.06'
'%Y-%m-%d', # '2006-10-25',
# '%d. %b %Y', '%d %b %Y', # '25. okt 2006', '25 okt 2006' # '%d. %b %Y', '%d %b %Y', # '25. okt 2006', '25 okt 2006'
# '%d. %b. %Y', '%d %b. %Y', # '25. okt. 2006', '25 okt. 2006' # '%d. %b. %Y', '%d %b. %Y', # '25. okt. 2006', '25 okt. 2006'
# '%d. %B %Y', '%d %B %Y', # '25. oktober 2006', '25 oktober 2006' # '%d. %B %Y', '%d %B %Y', # '25. oktober 2006', '25 oktober 2006'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30' '%Y-%m-%d %H:%M', # '2006-10-25 14:30'

View File

@ -18,20 +18,13 @@ FIRST_DAY_OF_WEEK = 1 # Monday
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%d.%m.%Y', '%d.%m.%y', # '25.10.2006', '25.10.06' '%d.%m.%Y', '%d.%m.%y', # '25.10.2006', '25.10.06'
'%Y-%m-%d', '%y-%m-%d', # '2006-10-25', '06-10-25' '%y-%m-%d', # '06-10-25'
# '%d. %B %Y', '%d. %b. %Y', # '25. October 2006', '25. Oct. 2006' # '%d. %B %Y', '%d. %b. %Y', # '25. October 2006', '25. Oct. 2006'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59' '%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59'
'%d.%m.%Y %H:%M', # '25.10.2006 14:30' '%d.%m.%Y %H:%M', # '25.10.2006 14:30'
'%d.%m.%Y', # '25.10.2006' '%d.%m.%Y', # '25.10.2006'
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
) )
DECIMAL_SEPARATOR = ',' DECIMAL_SEPARATOR = ','
THOUSAND_SEPARATOR = ' ' THOUSAND_SEPARATOR = ' '

View File

@ -15,15 +15,12 @@ FIRST_DAY_OF_WEEK = 0 # Sunday
# The *_INPUT_FORMATS strings use the Python strftime format syntax, # The *_INPUT_FORMATS strings use the Python strftime format syntax,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
# Kept ISO formats as they are in first position
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%Y-%m-%d', '%d/%m/%Y', '%d/%m/%y', # '2006-10-25', '25/10/2006', '25/10/06' '%Y-%m-%d', '%d/%m/%Y', '%d/%m/%y', # '2006-10-25', '25/10/2006', '25/10/06'
# '%d de %b de %Y', '%d de %b, %Y', # '25 de Out de 2006', '25 Out, 2006' # '%d de %b de %Y', '%d de %b, %Y', # '25 de Out de 2006', '25 Out, 2006'
# '%d de %B de %Y', '%d de %B, %Y', # '25 de Outubro de 2006', '25 de Outubro, 2006' # '%d de %B de %Y', '%d de %B, %Y', # '25 de Outubro de 2006', '25 de Outubro, 2006'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30' '%Y-%m-%d %H:%M', # '2006-10-25 14:30'

View File

@ -17,14 +17,10 @@ FIRST_DAY_OF_WEEK = 0 # Sunday
# The *_INPUT_FORMATS strings use the Python strftime format syntax, # The *_INPUT_FORMATS strings use the Python strftime format syntax,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%d/%m/%Y', '%d/%m/%y', '%Y-%m-%d', # '25/10/2006', '25/10/06', '2006-10-25' '%d/%m/%Y', '%d/%m/%y', # '25/10/2006', '25/10/06'
# '%d de %b de %Y', '%d de %b, %Y', # '25 de Out de 2006', '25 Out, 2006' # '%d de %b de %Y', '%d de %b, %Y', # '25 de Out de 2006', '25 Out, 2006'
# '%d de %B de %Y', '%d de %B, %Y', # '25 de Outubro de 2006', '25 de Outubro, 2006' # '%d de %B de %Y', '%d de %B, %Y', # '25 de Outubro de 2006', '25 de Outubro, 2006'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d/%m/%Y %H:%M:%S', # '25/10/2006 14:30:59' '%d/%m/%Y %H:%M:%S', # '25/10/2006 14:30:59'
'%d/%m/%Y %H:%M', # '25/10/2006 14:30' '%d/%m/%Y %H:%M', # '25/10/2006 14:30'
@ -32,9 +28,6 @@ DATETIME_INPUT_FORMATS = (
'%d/%m/%y %H:%M:%S', # '25/10/06 14:30:59' '%d/%m/%y %H:%M:%S', # '25/10/06 14:30:59'
'%d/%m/%y %H:%M', # '25/10/06 14:30' '%d/%m/%y %H:%M', # '25/10/06 14:30'
'%d/%m/%y', # '25/10/06' '%d/%m/%y', # '25/10/06'
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
) )
DECIMAL_SEPARATOR = ',' DECIMAL_SEPARATOR = ','
THOUSAND_SEPARATOR = '.' THOUSAND_SEPARATOR = '.'

View File

@ -19,12 +19,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%d.%m.%Y', # '25.10.2006' '%d.%m.%Y', # '25.10.2006'
'%d.%m.%y', # '25.10.06' '%d.%m.%y', # '25.10.06'
'%Y-%m-%d', # '2006-10-25'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59' '%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59'
'%d.%m.%Y %H:%M', # '25.10.2006 14:30' '%d.%m.%Y %H:%M', # '25.10.2006 14:30'
@ -32,9 +27,6 @@ DATETIME_INPUT_FORMATS = (
'%d.%m.%y %H:%M:%S', # '25.10.06 14:30:59' '%d.%m.%y %H:%M:%S', # '25.10.06 14:30:59'
'%d.%m.%y %H:%M', # '25.10.06 14:30' '%d.%m.%y %H:%M', # '25.10.06 14:30'
'%d.%m.%y', # '25.10.06' '%d.%m.%y', # '25.10.06'
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
) )
DECIMAL_SEPARATOR = ',' DECIMAL_SEPARATOR = ','
THOUSAND_SEPARATOR = '\xa0' # non-breaking space THOUSAND_SEPARATOR = '\xa0' # non-breaking space

View File

@ -18,20 +18,13 @@ FIRST_DAY_OF_WEEK = 1 # Monday
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%d.%m.%Y', '%d.%m.%y', # '25.10.2006', '25.10.06' '%d.%m.%Y', '%d.%m.%y', # '25.10.2006', '25.10.06'
'%Y-%m-%d', '%y-%m-%d', # '2006-10-25', '06-10-25' '%y-%m-%d', # '06-10-25'
# '%d. %B %Y', '%d. %b. %Y', # '25. October 2006', '25. Oct. 2006' # '%d. %B %Y', '%d. %b. %Y', # '25. October 2006', '25. Oct. 2006'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59' '%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59'
'%d.%m.%Y %H:%M', # '25.10.2006 14:30' '%d.%m.%Y %H:%M', # '25.10.2006 14:30'
'%d.%m.%Y', # '25.10.2006' '%d.%m.%Y', # '25.10.2006'
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
) )
DECIMAL_SEPARATOR = ',' DECIMAL_SEPARATOR = ','
THOUSAND_SEPARATOR = '\xa0' # non-breaking space THOUSAND_SEPARATOR = '\xa0' # non-breaking space

View File

@ -21,11 +21,6 @@ DATE_INPUT_FORMATS = (
'%d. %m. %Y', '%d. %m. %y', # '25. 10. 2006', '25. 10. 06' '%d. %m. %Y', '%d. %m. %y', # '25. 10. 2006', '25. 10. 06'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59' '%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59'
'%d.%m.%Y %H:%M', # '25.10.2006 14:30' '%d.%m.%Y %H:%M', # '25.10.2006 14:30'

View File

@ -18,15 +18,10 @@ FIRST_DAY_OF_WEEK = 1
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%d.%m.%Y.', '%d.%m.%y.', # '25.10.2006.', '25.10.06.' '%d.%m.%Y.', '%d.%m.%y.', # '25.10.2006.', '25.10.06.'
'%d. %m. %Y.', '%d. %m. %y.', # '25. 10. 2006.', '25. 10. 06.' '%d. %m. %Y.', '%d. %m. %y.', # '25. 10. 2006.', '25. 10. 06.'
'%Y-%m-%d', # '2006-10-25'
# '%d. %b %y.', '%d. %B %y.', # '25. Oct 06.', '25. October 06.' # '%d. %b %y.', '%d. %B %y.', # '25. Oct 06.', '25. October 06.'
# '%d. %b \'%y.', '%d. %B \'%y.', # '25. Oct '06.', '25. October '06.' # '%d. %b \'%y.', '%d. %B \'%y.', # '25. Oct '06.', '25. October '06.'
# '%d. %b %Y.', '%d. %B %Y.', # '25. Oct 2006.', '25. October 2006.' # '%d. %b %Y.', '%d. %B %Y.', # '25. Oct 2006.', '25. October 2006.'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d.%m.%Y. %H:%M:%S', # '25.10.2006. 14:30:59' '%d.%m.%Y. %H:%M:%S', # '25.10.2006. 14:30:59'
'%d.%m.%Y. %H:%M', # '25.10.2006. 14:30' '%d.%m.%Y. %H:%M', # '25.10.2006. 14:30'
@ -40,9 +35,6 @@ DATETIME_INPUT_FORMATS = (
'%d. %m. %y. %H:%M:%S', # '25. 10. 06. 14:30:59' '%d. %m. %y. %H:%M:%S', # '25. 10. 06. 14:30:59'
'%d. %m. %y. %H:%M', # '25. 10. 06. 14:30' '%d. %m. %y. %H:%M', # '25. 10. 06. 14:30'
'%d. %m. %y.', # '25. 10. 06.' '%d. %m. %y.', # '25. 10. 06.'
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
) )
DECIMAL_SEPARATOR = ',' DECIMAL_SEPARATOR = ','
THOUSAND_SEPARATOR = '.' THOUSAND_SEPARATOR = '.'

View File

@ -18,15 +18,10 @@ FIRST_DAY_OF_WEEK = 1
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%d.%m.%Y.', '%d.%m.%y.', # '25.10.2006.', '25.10.06.' '%d.%m.%Y.', '%d.%m.%y.', # '25.10.2006.', '25.10.06.'
'%d. %m. %Y.', '%d. %m. %y.', # '25. 10. 2006.', '25. 10. 06.' '%d. %m. %Y.', '%d. %m. %y.', # '25. 10. 2006.', '25. 10. 06.'
'%Y-%m-%d', # '2006-10-25'
# '%d. %b %y.', '%d. %B %y.', # '25. Oct 06.', '25. October 06.' # '%d. %b %y.', '%d. %B %y.', # '25. Oct 06.', '25. October 06.'
# '%d. %b \'%y.', '%d. %B \'%y.', # '25. Oct '06.', '25. October '06.' # '%d. %b \'%y.', '%d. %B \'%y.', # '25. Oct '06.', '25. October '06.'
# '%d. %b %Y.', '%d. %B %Y.', # '25. Oct 2006.', '25. October 2006.' # '%d. %b %Y.', '%d. %B %Y.', # '25. Oct 2006.', '25. October 2006.'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d.%m.%Y. %H:%M:%S', # '25.10.2006. 14:30:59' '%d.%m.%Y. %H:%M:%S', # '25.10.2006. 14:30:59'
'%d.%m.%Y. %H:%M', # '25.10.2006. 14:30' '%d.%m.%Y. %H:%M', # '25.10.2006. 14:30'
@ -40,9 +35,6 @@ DATETIME_INPUT_FORMATS = (
'%d. %m. %y. %H:%M:%S', # '25. 10. 06. 14:30:59' '%d. %m. %y. %H:%M:%S', # '25. 10. 06. 14:30:59'
'%d. %m. %y. %H:%M', # '25. 10. 06. 14:30' '%d. %m. %y. %H:%M', # '25. 10. 06. 14:30'
'%d. %m. %y.', # '25. 10. 06.' '%d. %m. %y.', # '25. 10. 06.'
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
) )
DECIMAL_SEPARATOR = ',' DECIMAL_SEPARATOR = ','
THOUSAND_SEPARATOR = '.' THOUSAND_SEPARATOR = '.'

View File

@ -16,15 +16,12 @@ FIRST_DAY_OF_WEEK = 1
# The *_INPUT_FORMATS strings use the Python strftime format syntax, # The *_INPUT_FORMATS strings use the Python strftime format syntax,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
# Kept ISO formats as they are in first position
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%Y-%m-%d', # '2006-10-25' '%Y-%m-%d', # '2006-10-25'
'%m/%d/%Y', # '10/25/2006' '%m/%d/%Y', # '10/25/2006'
'%m/%d/%y', # '10/25/06' '%m/%d/%y', # '10/25/06'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30' '%Y-%m-%d %H:%M', # '2006-10-25 14:30'

View File

@ -17,20 +17,13 @@ FIRST_DAY_OF_WEEK = 1 # Pazartesi
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior # see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
DATE_INPUT_FORMATS = ( DATE_INPUT_FORMATS = (
'%d/%m/%Y', '%d/%m/%y', # '25/10/2006', '25/10/06' '%d/%m/%Y', '%d/%m/%y', # '25/10/2006', '25/10/06'
'%Y-%m-%d', '%y-%m-%d', # '2006-10-25', '06-10-25' '%y-%m-%d', # '06-10-25'
# '%d %B %Y', '%d %b. %Y', # '25 Ekim 2006', '25 Eki. 2006' # '%d %B %Y', '%d %b. %Y', # '25 Ekim 2006', '25 Eki. 2006'
) )
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = ( DATETIME_INPUT_FORMATS = (
'%d/%m/%Y %H:%M:%S', # '25/10/2006 14:30:59' '%d/%m/%Y %H:%M:%S', # '25/10/2006 14:30:59'
'%d/%m/%Y %H:%M', # '25/10/2006 14:30' '%d/%m/%Y %H:%M', # '25/10/2006 14:30'
'%d/%m/%Y', # '25/10/2006' '%d/%m/%Y', # '25/10/2006'
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%Y-%m-%d %H:%M', # '2006-10-25 14:30'
'%Y-%m-%d', # '2006-10-25'
) )
DECIMAL_SEPARATOR = ',' DECIMAL_SEPARATOR = ','
THOUSAND_SEPARATOR = '.' THOUSAND_SEPARATOR = '.'

View File

@ -75,7 +75,7 @@ STATICFILES_DIRS = (
STATICFILES_FINDERS = ( STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
# 'django.contrib.staticfiles.finders.DefaultStorageFinder', # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
) )
# Make this unique, and don't share it with anybody. # Make this unique, and don't share it with anybody.
@ -85,7 +85,7 @@ SECRET_KEY = '{{ secret_key }}'
TEMPLATE_LOADERS = ( TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader', 'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader', 'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader', # 'django.template.loaders.eggs.Loader',
) )
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (

View File

@ -9,7 +9,7 @@ import datetime
from django.db import models from django.db import models
from django.core.exceptions import ImproperlyConfigured, ValidationError from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.utils.encoding import smart_text from django.utils.encoding import smart_text, force_text
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
from django.contrib.admin.util import (get_model_from_relation, from django.contrib.admin.util import (get_model_from_relation,
@ -102,7 +102,7 @@ class SimpleListFilter(ListFilter):
} }
for lookup, title in self.lookup_choices: for lookup, title in self.lookup_choices:
yield { yield {
'selected': self.value() == lookup, 'selected': self.value() == force_text(lookup),
'query_string': cl.get_query_string({ 'query_string': cl.get_query_string({
self.parameter_name: lookup, self.parameter_name: lookup,
}, []), }, []),

View File

@ -6,8 +6,8 @@ from django.contrib.auth import authenticate
from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import AuthenticationForm
from django.utils.translation import ugettext_lazy from django.utils.translation import ugettext_lazy
ERROR_MESSAGE = ugettext_lazy("Please enter the correct username and password " ERROR_MESSAGE = ugettext_lazy("Please enter the correct %(username)s and password "
"for a staff account. Note that both fields are case-sensitive.") "for a staff account. Note that both fields may be case-sensitive.")
class AdminAuthenticationForm(AuthenticationForm): class AdminAuthenticationForm(AuthenticationForm):
@ -26,8 +26,12 @@ class AdminAuthenticationForm(AuthenticationForm):
if username and password: if username and password:
self.user_cache = authenticate(username=username, password=password) self.user_cache = authenticate(username=username, password=password)
if self.user_cache is None: if self.user_cache is None:
raise forms.ValidationError(message) raise forms.ValidationError(message % {
'username': self.username_field.verbose_name
})
elif not self.user_cache.is_active or not self.user_cache.is_staff: elif not self.user_cache.is_active or not self.user_cache.is_staff:
raise forms.ValidationError(message) raise forms.ValidationError(message % {
'username': self.username_field.verbose_name
})
self.check_for_test_cookie() self.check_for_test_cookie()
return self.cleaned_data return self.cleaned_data

View File

@ -186,9 +186,7 @@ class AdminReadonlyField(object):
if getattr(attr, "allow_tags", False): if getattr(attr, "allow_tags", False):
result_repr = mark_safe(result_repr) result_repr = mark_safe(result_repr)
else: else:
if value is None: if isinstance(f.rel, ManyToManyRel) and value is not None:
result_repr = EMPTY_CHANGELIST_VALUE
elif isinstance(f.rel, ManyToManyRel):
result_repr = ", ".join(map(six.text_type, value.all())) result_repr = ", ".join(map(six.text_type, value.all()))
else: else:
result_repr = display_for_field(value, f) result_repr = display_for_field(value, f)

View File

@ -4,7 +4,7 @@ from django.db import models
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.admin.util import quote from django.contrib.admin.util import quote
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext, ugettext_lazy as _
from django.utils.encoding import smart_text from django.utils.encoding import smart_text
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
@ -42,13 +42,16 @@ class LogEntry(models.Model):
def __str__(self): def __str__(self):
if self.action_flag == ADDITION: if self.action_flag == ADDITION:
return _('Added "%(object)s".') % {'object': self.object_repr} return ugettext('Added "%(object)s".') % {'object': self.object_repr}
elif self.action_flag == CHANGE: elif self.action_flag == CHANGE:
return _('Changed "%(object)s" - %(changes)s') % {'object': self.object_repr, 'changes': self.change_message} return ugettext('Changed "%(object)s" - %(changes)s') % {
'object': self.object_repr,
'changes': self.change_message,
}
elif self.action_flag == DELETION: elif self.action_flag == DELETION:
return _('Deleted "%(object)s."') % {'object': self.object_repr} return ugettext('Deleted "%(object)s."') % {'object': self.object_repr}
return _('LogEntry Object') return ugettext('LogEntry Object')
def is_addition(self): def is_addition(self):
return self.action_flag == ADDITION return self.action_flag == ADDITION

View File

@ -1,3 +1,4 @@
import copy
from functools import update_wrapper, partial from functools import update_wrapper, partial
import warnings import warnings
@ -130,7 +131,7 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
# passed to formfield_for_dbfield override the defaults. # passed to formfield_for_dbfield override the defaults.
for klass in db_field.__class__.mro(): for klass in db_field.__class__.mro():
if klass in self.formfield_overrides: if klass in self.formfield_overrides:
kwargs = dict(self.formfield_overrides[klass], **kwargs) kwargs = dict(copy.deepcopy(self.formfield_overrides[klass]), **kwargs)
return db_field.formfield(**kwargs) return db_field.formfield(**kwargs)
# For any other type of field, just call its formfield() method. # For any other type of field, just call its formfield() method.
@ -407,8 +408,6 @@ class ModelAdmin(BaseModelAdmin):
js.append('actions%s.js' % extra) js.append('actions%s.js' % extra)
if self.prepopulated_fields: if self.prepopulated_fields:
js.extend(['urlify.js', 'prepopulate%s.js' % extra]) js.extend(['urlify.js', 'prepopulate%s.js' % extra])
if self.opts.get_ordered_objects():
js.extend(['getElementsBySelector.js', 'dom-drag.js' , 'admin/ordering.js'])
return forms.Media(js=[static('admin/js/%s' % url) for url in js]) return forms.Media(js=[static('admin/js/%s' % url) for url in js])
def get_model_perms(self, request): def get_model_perms(self, request):
@ -552,7 +551,7 @@ class ModelAdmin(BaseModelAdmin):
""" """
from django.contrib.admin.models import LogEntry, DELETION from django.contrib.admin.models import LogEntry, DELETION
LogEntry.objects.log_action( LogEntry.objects.log_action(
user_id = request.user.id, user_id = request.user.pk,
content_type_id = ContentType.objects.get_for_model(self.model).pk, content_type_id = ContentType.objects.get_for_model(self.model).pk,
object_id = object.pk, object_id = object.pk,
object_repr = object_repr, object_repr = object_repr,
@ -665,6 +664,13 @@ class ModelAdmin(BaseModelAdmin):
# Use only the first item in list_display as link # Use only the first item in list_display as link
return list(list_display)[:1] return list(list_display)[:1]
def get_list_filter(self, request):
"""
Returns a sequence containing the fields to be displayed as filters in
the right sidebar of the changelist page.
"""
return self.list_filter
def construct_change_message(self, request, form, formsets): def construct_change_message(self, request, form, formsets):
""" """
Construct a change message from a changed object. Construct a change message from a changed object.
@ -691,12 +697,30 @@ class ModelAdmin(BaseModelAdmin):
change_message = ' '.join(change_message) change_message = ' '.join(change_message)
return change_message or _('No fields changed.') return change_message or _('No fields changed.')
def message_user(self, request, message): def message_user(self, request, message, level=messages.INFO, extra_tags='',
fail_silently=False):
""" """
Send a message to the user. The default implementation Send a message to the user. The default implementation
posts a message using the django.contrib.messages backend. posts a message using the django.contrib.messages backend.
Exposes almost the same API as messages.add_message(), but accepts the
positional arguments in a different order to maintain backwards
compatibility. For convenience, it accepts the `level` argument as
a string rather than the usual level number.
""" """
messages.info(request, message)
if not isinstance(level, int):
# attempt to get the level if passed a string
try:
level = getattr(messages.constants, level.upper())
except AttributeError:
levels = messages.constants.DEFAULT_TAGS.values()
levels_repr = ', '.join('`%s`' % l for l in levels)
raise ValueError('Bad message level string: `%s`. '
'Possible values are: %s' % (level, levels_repr))
messages.add_message(request, level, message, extra_tags=extra_tags,
fail_silently=fail_silently)
def save_form(self, request, form, change): def save_form(self, request, form, change):
""" """
@ -738,7 +762,6 @@ class ModelAdmin(BaseModelAdmin):
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None): def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
opts = self.model._meta opts = self.model._meta
app_label = opts.app_label app_label = opts.app_label
ordered_objects = opts.get_ordered_objects()
context.update({ context.update({
'add': add, 'add': add,
'change': change, 'change': change,
@ -747,7 +770,6 @@ class ModelAdmin(BaseModelAdmin):
'has_delete_permission': self.has_delete_permission(request, obj), 'has_delete_permission': self.has_delete_permission(request, obj),
'has_file_field': True, # FIXME - this should check if form or formsets have a FileField, 'has_file_field': True, # FIXME - this should check if form or formsets have a FileField,
'has_absolute_url': hasattr(self.model, 'get_absolute_url'), 'has_absolute_url': hasattr(self.model, 'get_absolute_url'),
'ordered_objects': ordered_objects,
'form_url': form_url, 'form_url': form_url,
'opts': opts, 'opts': opts,
'content_type_id': ContentType.objects.get_for_model(self.model).id, 'content_type_id': ContentType.objects.get_for_model(self.model).id,
@ -1174,6 +1196,7 @@ class ModelAdmin(BaseModelAdmin):
list_display = self.get_list_display(request) list_display = self.get_list_display(request)
list_display_links = self.get_list_display_links(request, list_display) list_display_links = self.get_list_display_links(request, list_display)
list_filter = self.get_list_filter(request)
# Check actions to see if any are available on this changelist # Check actions to see if any are available on this changelist
actions = self.get_actions(request) actions = self.get_actions(request)
@ -1184,7 +1207,7 @@ class ModelAdmin(BaseModelAdmin):
ChangeList = self.get_changelist(request) ChangeList = self.get_changelist(request)
try: try:
cl = ChangeList(request, self.model, list_display, cl = ChangeList(request, self.model, list_display,
list_display_links, self.list_filter, self.date_hierarchy, list_display_links, list_filter, self.date_hierarchy,
self.search_fields, self.list_select_related, self.search_fields, self.list_select_related,
self.list_per_page, self.list_max_show_all, self.list_editable, self.list_per_page, self.list_max_show_all, self.list_editable,
self) self)

View File

@ -354,6 +354,7 @@ class AdminSite(object):
info = (app_label, model._meta.module_name) info = (app_label, model._meta.module_name)
model_dict = { model_dict = {
'name': capfirst(model._meta.verbose_name_plural), 'name': capfirst(model._meta.verbose_name_plural),
'object_name': model._meta.object_name,
'perms': perms, 'perms': perms,
} }
if perms.get('change', False): if perms.get('change', False):
@ -371,6 +372,7 @@ class AdminSite(object):
else: else:
app_dict[app_label] = { app_dict[app_label] = {
'name': app_label.title(), 'name': app_label.title(),
'app_label': app_label,
'app_url': reverse('admin:app_list', kwargs={'app_label': app_label}, current_app=self.name), 'app_url': reverse('admin:app_list', kwargs={'app_label': app_label}, current_app=self.name),
'has_module_perms': has_module_perms, 'has_module_perms': has_module_perms,
'models': [model_dict], 'models': [model_dict],
@ -389,9 +391,9 @@ class AdminSite(object):
'app_list': app_list, 'app_list': app_list,
} }
context.update(extra_context or {}) context.update(extra_context or {})
return TemplateResponse(request, [ return TemplateResponse(request, self.index_template or
self.index_template or 'admin/index.html', 'admin/index.html', context,
], context, current_app=self.name) current_app=self.name)
def app_index(self, request, app_label, extra_context=None): def app_index(self, request, app_label, extra_context=None):
user = request.user user = request.user
@ -408,6 +410,7 @@ class AdminSite(object):
info = (app_label, model._meta.module_name) info = (app_label, model._meta.module_name)
model_dict = { model_dict = {
'name': capfirst(model._meta.verbose_name_plural), 'name': capfirst(model._meta.verbose_name_plural),
'object_name': model._meta.object_name,
'perms': perms, 'perms': perms,
} }
if perms.get('change', False): if perms.get('change', False):
@ -428,6 +431,7 @@ class AdminSite(object):
# information. # information.
app_dict = { app_dict = {
'name': app_label.title(), 'name': app_label.title(),
'app_label': app_label,
'app_url': '', 'app_url': '',
'has_module_perms': has_module_perms, 'has_module_perms': has_module_perms,
'models': [model_dict], 'models': [model_dict],

View File

@ -322,6 +322,10 @@ thead th.sorted {
background: #c5c5c5 url(../img/nav-bg-selected.gif) top left repeat-x; background: #c5c5c5 url(../img/nav-bg-selected.gif) top left repeat-x;
} }
thead th.sorted .text {
padding-right: 42px;
}
table thead th .text span { table thead th .text span {
padding: 2px 5px; padding: 2px 5px;
display:block; display:block;

View File

@ -84,6 +84,11 @@ table thead th.sorted .sortoptions {
float: left; float: left;
} }
thead th.sorted .text {
padding-right: 0;
padding-left: 42px;
}
/* dashboard styles */ /* dashboard styles */
.dashboard .module table td a { .dashboard .module table td a {

View File

@ -225,6 +225,21 @@ table p.datetime {
padding-left: 0; padding-left: 0;
} }
/* URL */
p.url {
line-height: 20px;
margin: 0;
padding: 0;
color: #666;
font-size: 11px;
font-weight: bold;
}
.url a {
font-weight: normal;
}
/* FILE UPLOADS */ /* FILE UPLOADS */
p.file-upload { p.file-upload {

View File

@ -1,137 +0,0 @@
addEvent(window, 'load', reorder_init);
var lis;
var top = 0;
var left = 0;
var height = 30;
function reorder_init() {
lis = document.getElementsBySelector('ul#orderthese li');
var input = document.getElementsBySelector('input[name=order_]')[0];
setOrder(input.value.split(','));
input.disabled = true;
draw();
// Now initialize the dragging behavior
var limit = (lis.length - 1) * height;
for (var i = 0; i < lis.length; i++) {
var li = lis[i];
var img = document.getElementById('handle'+li.id);
li.style.zIndex = 1;
Drag.init(img, li, left + 10, left + 10, top + 10, top + 10 + limit);
li.onDragStart = startDrag;
li.onDragEnd = endDrag;
img.style.cursor = 'move';
}
}
function submitOrderForm() {
var inputOrder = document.getElementsBySelector('input[name=order_]')[0];
inputOrder.value = getOrder();
inputOrder.disabled=false;
}
function startDrag() {
this.style.zIndex = '10';
this.className = 'dragging';
}
function endDrag(x, y) {
this.style.zIndex = '1';
this.className = '';
// Work out how far along it has been dropped, using x co-ordinate
var oldIndex = this.index;
var newIndex = Math.round((y - 10 - top) / height);
// 'Snap' to the correct position
this.style.top = (10 + top + newIndex * height) + 'px';
this.index = newIndex;
moveItem(oldIndex, newIndex);
}
function moveItem(oldIndex, newIndex) {
// Swaps two items, adjusts the index and left co-ord for all others
if (oldIndex == newIndex) {
return; // Nothing to swap;
}
var direction, lo, hi;
if (newIndex > oldIndex) {
lo = oldIndex;
hi = newIndex;
direction = -1;
} else {
direction = 1;
hi = oldIndex;
lo = newIndex;
}
var lis2 = new Array(); // We will build the new order in this array
for (var i = 0; i < lis.length; i++) {
if (i < lo || i > hi) {
// Position of items not between the indexes is unaffected
lis2[i] = lis[i];
continue;
} else if (i == newIndex) {
lis2[i] = lis[oldIndex];
continue;
} else {
// Item is between the two indexes - move it along 1
lis2[i] = lis[i - direction];
}
}
// Re-index everything
reIndex(lis2);
lis = lis2;
draw();
// document.getElementById('hiddenOrder').value = getOrder();
document.getElementsBySelector('input[name=order_]')[0].value = getOrder();
}
function reIndex(lis) {
for (var i = 0; i < lis.length; i++) {
lis[i].index = i;
}
}
function draw() {
for (var i = 0; i < lis.length; i++) {
var li = lis[i];
li.index = i;
li.style.position = 'absolute';
li.style.left = (10 + left) + 'px';
li.style.top = (10 + top + (i * height)) + 'px';
}
}
function getOrder() {
var order = new Array(lis.length);
for (var i = 0; i < lis.length; i++) {
order[i] = lis[i].id.substring(1, 100);
}
return order.join(',');
}
function setOrder(id_list) {
/* Set the current order to match the lsit of IDs */
var temp_lis = new Array();
for (var i = 0; i < id_list.length; i++) {
var id = 'p' + id_list[i];
temp_lis[temp_lis.length] = document.getElementById(id);
}
reIndex(temp_lis);
lis = temp_lis;
draw();
}
function addEvent(elm, evType, fn, useCapture)
// addEvent and removeEvent
// cross-browser event handling for IE5+, NS6 and Mozilla
// By Scott Andrew
{
if (elm.addEventListener){
elm.addEventListener(evType, fn, useCapture);
return true;
} else if (elm.attachEvent){
var r = elm.attachEvent("on"+evType, fn);
return r;
} else {
elm['on'+evType] = fn;
}
}

View File

@ -1,167 +0,0 @@
/* document.getElementsBySelector(selector)
- returns an array of element objects from the current document
matching the CSS selector. Selectors can contain element names,
class names and ids and can be nested. For example:
elements = document.getElementsBySelect('div#main p a.external')
Will return an array of all 'a' elements with 'external' in their
class attribute that are contained inside 'p' elements that are
contained inside the 'div' element which has id="main"
New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
See http://www.w3.org/TR/css3-selectors/#attribute-selectors
Version 0.4 - Simon Willison, March 25th 2003
-- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
-- Opera 7 fails
*/
function getAllChildren(e) {
// Returns all children of element. Workaround required for IE5/Windows. Ugh.
return e.all ? e.all : e.getElementsByTagName('*');
}
document.getElementsBySelector = function(selector) {
// Attempt to fail gracefully in lesser browsers
if (!document.getElementsByTagName) {
return new Array();
}
// Split selector in to tokens
var tokens = selector.split(' ');
var currentContext = new Array(document);
for (var i = 0; i < tokens.length; i++) {
token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');;
if (token.indexOf('#') > -1) {
// Token is an ID selector
var bits = token.split('#');
var tagName = bits[0];
var id = bits[1];
var element = document.getElementById(id);
if (!element || (tagName && element.nodeName.toLowerCase() != tagName)) {
// ID not found or tag with that ID not found, return false.
return new Array();
}
// Set currentContext to contain just this element
currentContext = new Array(element);
continue; // Skip to next token
}
if (token.indexOf('.') > -1) {
// Token contains a class selector
var bits = token.split('.');
var tagName = bits[0];
var className = bits[1];
if (!tagName) {
tagName = '*';
}
// Get elements matching tag, filter them for class selector
var found = new Array;
var foundCount = 0;
for (var h = 0; h < currentContext.length; h++) {
var elements;
if (tagName == '*') {
elements = getAllChildren(currentContext[h]);
} else {
try {
elements = currentContext[h].getElementsByTagName(tagName);
}
catch(e) {
elements = [];
}
}
for (var j = 0; j < elements.length; j++) {
found[foundCount++] = elements[j];
}
}
currentContext = new Array;
var currentContextIndex = 0;
for (var k = 0; k < found.length; k++) {
if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) {
currentContext[currentContextIndex++] = found[k];
}
}
continue; // Skip to next token
}
// Code to deal with attribute selectors
if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
var tagName = RegExp.$1;
var attrName = RegExp.$2;
var attrOperator = RegExp.$3;
var attrValue = RegExp.$4;
if (!tagName) {
tagName = '*';
}
// Grab all of the tagName elements within current context
var found = new Array;
var foundCount = 0;
for (var h = 0; h < currentContext.length; h++) {
var elements;
if (tagName == '*') {
elements = getAllChildren(currentContext[h]);
} else {
elements = currentContext[h].getElementsByTagName(tagName);
}
for (var j = 0; j < elements.length; j++) {
found[foundCount++] = elements[j];
}
}
currentContext = new Array;
var currentContextIndex = 0;
var checkFunction; // This function will be used to filter the elements
switch (attrOperator) {
case '=': // Equality
checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
break;
case '~': // Match one of space seperated words
checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
break;
case '|': // Match start with value followed by optional hyphen
checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
break;
case '^': // Match starts with value
checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
break;
case '$': // Match ends with value - fails with "Warning" in Opera 7
checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
break;
case '*': // Match ends with value
checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
break;
default :
// Just test for existence of attribute
checkFunction = function(e) { return e.getAttribute(attrName); };
}
currentContext = new Array;
var currentContextIndex = 0;
for (var k = 0; k < found.length; k++) {
if (checkFunction(found[k])) {
currentContext[currentContextIndex++] = found[k];
}
}
// alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
continue; // Skip to next token
}
// If we get here, token is JUST an element (not a class or ID selector)
tagName = token;
var found = new Array;
var foundCount = 0;
for (var h = 0; h < currentContext.length; h++) {
var elements = currentContext[h].getElementsByTagName(tagName);
for (var j = 0; j < elements.length; j++) {
found[foundCount++] = elements[j];
}
}
currentContext = found;
}
return currentContext;
}
/* That revolting regular expression explained
/^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/
\---/ \---/\-------------/ \-------/
| | | |
| | | The value
| | ~,|,^,$,* or =
| Attribute
Tag
*/

View File

@ -1,5 +1,5 @@
{% extends "admin/base_site.html" %} {% extends "admin/base_site.html" %}
{% load i18n admin_static admin_modify %} {% load i18n admin_static %}
{% load admin_urls %} {% load admin_urls %}
{% block extrahead %}{{ block.super }} {% block extrahead %}{{ block.super }}
@ -13,7 +13,7 @@
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a> <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_label|capfirst|escape }}</a> &rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_label|capfirst|escape }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}{{ original.pk }}">{{ original|truncatewords:"18" }}</a> &rsaquo; <a href="{% url opts|admin_urlname:'change' original.pk|admin_urlquote %}">{{ original|truncatewords:"18" }}</a>
&rsaquo; {% trans 'Change password' %} &rsaquo; {% trans 'Change password' %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -9,7 +9,7 @@
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />{% endblock %} {% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />{% endblock %}
{% block coltype %}{% if ordered_objects %}colMS{% else %}colM{% endif %}{% endblock %} {% block coltype %}colM{% endblock %}
{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %} {% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}

View File

@ -46,7 +46,7 @@
{% for field in line %} {% for field in line %}
<td{% if field.field.name %} class="field-{{ field.field.name }}"{% endif %}> <td{% if field.field.name %} class="field-{{ field.field.name }}"{% endif %}>
{% if field.is_readonly %} {% if field.is_readonly %}
<p>{{ field.contents }}</p> <p>{{ field.contents|linebreaksbr }}</p>
{% else %} {% else %}
{{ field.field.errors.as_ul }} {{ field.field.errors.as_ul }}
{{ field.field }} {{ field.field }}

View File

@ -14,7 +14,7 @@
{% else %} {% else %}
{{ field.label_tag }} {{ field.label_tag }}
{% if field.is_readonly %} {% if field.is_readonly %}
<p>{{ field.contents }}</p> <p>{{ field.contents|linebreaksbr }}</p>
{% else %} {% else %}
{{ field.field }} {{ field.field }}
{% endif %} {% endif %}

View File

@ -14,7 +14,7 @@
{% if app_list %} {% if app_list %}
{% for app in app_list %} {% for app in app_list %}
<div class="module"> <div class="app-{{ app.app_label }} module">
<table> <table>
<caption> <caption>
<a href="{{ app.app_url }}" class="section" title="{% blocktrans with name=app.name %}Models in the {{ name }} application{% endblocktrans %}"> <a href="{{ app.app_url }}" class="section" title="{% blocktrans with name=app.name %}Models in the {{ name }} application{% endblocktrans %}">
@ -22,7 +22,7 @@
</a> </a>
</caption> </caption>
{% for model in app.models %} {% for model in app.models %}
<tr> <tr class="model-{{ model.object_name|lower }}">
{% if model.admin_url %} {% if model.admin_url %}
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th> <th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
{% else %} {% else %}

View File

@ -1,8 +1,8 @@
{% load i18n admin_urls %} {% load i18n admin_urls %}
<div class="submit-row"> <div class="submit-row">
{% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" {{ onclick_attrib }}/>{% endif %} {% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" />{% endif %}
{% if show_delete_link %}<p class="deletelink-box"><a href="{% url opts|admin_urlname:'delete' original.pk|admin_urlquote %}" class="deletelink">{% trans "Delete" %}</a></p>{% endif %} {% if show_delete_link %}<p class="deletelink-box"><a href="{% url opts|admin_urlname:'delete' original.pk|admin_urlquote %}" class="deletelink">{% trans "Delete" %}</a></p>{% endif %}
{% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" {{ onclick_attrib }}/>{%endif%} {% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" />{%endif%}
{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" {{ onclick_attrib }}/>{% endif %} {% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" />{% endif %}
{% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" {{ onclick_attrib }}/>{% endif %} {% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" />{% endif %}
</div> </div>

View File

@ -30,8 +30,6 @@ def submit_row(context):
save_as = context['save_as'] save_as = context['save_as']
ctx = { ctx = {
'opts': opts, 'opts': opts,
'onclick_attrib': (opts.get_ordered_objects() and change
and 'onclick="submitOrderForm();"' or ''),
'show_delete_link': (not is_popup and context['has_delete_permission'] 'show_delete_link': (not is_popup and context['has_delete_permission']
and change and context.get('show_delete', True)), and change and context.get('show_delete', True)),
'show_save_as_new': not is_popup and change and save_as, 'show_save_as_new': not is_popup and change and save_as,

View File

@ -1,15 +0,0 @@
import warnings
from django.template import Library
from django.templatetags.static import PrefixNode
register = Library()
@register.simple_tag
def admin_media_prefix():
"""
Returns the string contained in the setting ADMIN_MEDIA_PREFIX.
"""
warnings.warn(
"The admin_media_prefix template tag is deprecated. "
"Use the static template tag instead.", DeprecationWarning)
return PrefixNode.handle_simple("ADMIN_MEDIA_PREFIX")

View File

@ -10,7 +10,7 @@ from django.contrib.admin.templatetags.admin_static import static
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.forms.widgets import RadioFieldRenderer from django.forms.widgets import RadioFieldRenderer
from django.forms.util import flatatt from django.forms.util import flatatt
from django.utils.html import escape, format_html, format_html_join from django.utils.html import escape, format_html, format_html_join, smart_urlquote
from django.utils.text import Truncator from django.utils.text import Truncator
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@ -306,6 +306,19 @@ class AdminURLFieldWidget(forms.TextInput):
final_attrs.update(attrs) final_attrs.update(attrs)
super(AdminURLFieldWidget, self).__init__(attrs=final_attrs) super(AdminURLFieldWidget, self).__init__(attrs=final_attrs)
def render(self, name, value, attrs=None):
html = super(AdminURLFieldWidget, self).render(name, value, attrs)
if value:
value = force_text(self._format_value(value))
final_attrs = {'href': mark_safe(smart_urlquote(value))}
html = format_html(
'<p class="url">{0} <a {1}>{2}</a><br />{3} {4}</p>',
_('Currently:'), flatatt(final_attrs), value,
_('Change:'), html
)
return html
class AdminIntegerFieldWidget(forms.TextInput): class AdminIntegerFieldWidget(forms.TextInput):
class_name = 'vIntegerField' class_name = 'vIntegerField'

View File

@ -14,6 +14,7 @@ from django.core import urlresolvers
from django.contrib.admindocs import utils from django.contrib.admindocs import utils
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.utils.importlib import import_module from django.utils.importlib import import_module
from django.utils._os import upath
from django.utils import six from django.utils import six
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@ -311,7 +312,7 @@ def load_all_installed_template_libraries():
try: try:
libraries = [ libraries = [
os.path.splitext(p)[0] os.path.splitext(p)[0]
for p in os.listdir(os.path.dirname(mod.__file__)) for p in os.listdir(os.path.dirname(upath(mod.__file__)))
if p.endswith('.py') and p[0].isalpha() if p.endswith('.py') and p[0].isalpha()
] ]
except OSError: except OSError:

View File

@ -1,6 +1,6 @@
import re import re
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.utils.importlib import import_module from django.utils.importlib import import_module
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
@ -60,6 +60,9 @@ def authenticate(**credentials):
except TypeError: except TypeError:
# This backend doesn't accept these credentials as arguments. Try the next one. # This backend doesn't accept these credentials as arguments. Try the next one.
continue continue
except PermissionDenied:
# This backend says to stop in our tracks - this user should not be allowed in at all.
return None
if user is None: if user is None:
continue continue
# Annotate the user object with the path of the backend. # Annotate the user object with the path of the backend.
@ -81,14 +84,14 @@ def login(request, user):
user = request.user user = request.user
# TODO: It would be nice to support different login methods, like signed cookies. # TODO: It would be nice to support different login methods, like signed cookies.
if SESSION_KEY in request.session: if SESSION_KEY in request.session:
if request.session[SESSION_KEY] != user.id: if request.session[SESSION_KEY] != user.pk:
# To avoid reusing another user's session, create a new, empty # To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different # session if the existing session corresponds to a different
# authenticated user. # authenticated user.
request.session.flush() request.session.flush()
else: else:
request.session.cycle_key() request.session.cycle_key()
request.session[SESSION_KEY] = user.id request.session[SESSION_KEY] = user.pk
request.session[BACKEND_SESSION_KEY] = user.backend request.session[BACKEND_SESSION_KEY] = user.backend
if hasattr(request, 'user'): if hasattr(request, 'user'):
request.user = user request.user = user

View File

@ -133,7 +133,7 @@ class UserAdmin(admin.ModelAdmin):
adminForm = admin.helpers.AdminForm(form, fieldsets, {}) adminForm = admin.helpers.AdminForm(form, fieldsets, {})
context = { context = {
'title': _('Change password: %s') % escape(user.username), 'title': _('Change password: %s') % escape(user.get_username()),
'adminForm': adminForm, 'adminForm': adminForm,
'form_url': form_url, 'form_url': form_url,
'form': form, 'form': form,
@ -148,10 +148,10 @@ class UserAdmin(admin.ModelAdmin):
'save_as': False, 'save_as': False,
'show_save': True, 'show_save': True,
} }
return TemplateResponse(request, [ return TemplateResponse(request,
self.change_user_password_template or self.change_user_password_template or
'admin/auth/user/change_password.html' 'admin/auth/user/change_password.html',
], context, current_app=self.admin_site.name) context, current_app=self.admin_site.name)
def response_add(self, request, obj, **kwargs): def response_add(self, request, obj, **kwargs):
""" """

View File

@ -18,7 +18,9 @@ class PermLookupDict(object):
def __bool__(self): def __bool__(self):
return self.user.has_module_perms(self.module_name) return self.user.has_module_perms(self.module_name)
__nonzero__ = __bool__ # Python 2
def __nonzero__(self): # Python 2 compatibility
return type(self).__bool__(self)
class PermWrapper(object): class PermWrapper(object):

View File

@ -27,7 +27,7 @@ class ReadOnlyPasswordHashWidget(forms.Widget):
encoded = value encoded = value
final_attrs = self.build_attrs(attrs) final_attrs = self.build_attrs(attrs)
if encoded == '' or encoded == UNUSABLE_PASSWORD: if not encoded or encoded == UNUSABLE_PASSWORD:
summary = mark_safe("<strong>%s</strong>" % ugettext("No password set.")) summary = mark_safe("<strong>%s</strong>" % ugettext("No password set."))
else: else:
try: try:
@ -52,6 +52,11 @@ class ReadOnlyPasswordHashField(forms.Field):
kwargs.setdefault("required", False) kwargs.setdefault("required", False)
super(ReadOnlyPasswordHashField, self).__init__(*args, **kwargs) super(ReadOnlyPasswordHashField, self).__init__(*args, **kwargs)
def bound_data(self, data, initial):
# Always return initial because the widget doesn't
# render an input field.
return initial
class UserCreationForm(forms.ModelForm): class UserCreationForm(forms.ModelForm):
""" """
@ -143,8 +148,8 @@ class AuthenticationForm(forms.Form):
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput) password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
error_messages = { error_messages = {
'invalid_login': _("Please enter a correct username and password. " 'invalid_login': _("Please enter a correct %(username)s and password. "
"Note that both fields are case-sensitive."), "Note that both fields may be case-sensitive."),
'no_cookies': _("Your Web browser doesn't appear to have cookies " 'no_cookies': _("Your Web browser doesn't appear to have cookies "
"enabled. Cookies are required for logging in."), "enabled. Cookies are required for logging in."),
'inactive': _("This account is inactive."), 'inactive': _("This account is inactive."),
@ -163,8 +168,8 @@ class AuthenticationForm(forms.Form):
# Set the label for the "username" field. # Set the label for the "username" field.
UserModel = get_user_model() UserModel = get_user_model()
username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD) self.username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
self.fields['username'].label = capfirst(username_field.verbose_name) self.fields['username'].label = capfirst(self.username_field.verbose_name)
def clean(self): def clean(self):
username = self.cleaned_data.get('username') username = self.cleaned_data.get('username')
@ -175,7 +180,9 @@ class AuthenticationForm(forms.Form):
password=password) password=password)
if self.user_cache is None: if self.user_cache is None:
raise forms.ValidationError( raise forms.ValidationError(
self.error_messages['invalid_login']) self.error_messages['invalid_login'] % {
'username': self.username_field.verbose_name
})
elif not self.user_cache.is_active: elif not self.user_cache.is_active:
raise forms.ValidationError(self.error_messages['inactive']) raise forms.ValidationError(self.error_messages['inactive'])
self.check_for_test_cookie() self.check_for_test_cookie()
@ -209,10 +216,12 @@ class PasswordResetForm(forms.Form):
""" """
UserModel = get_user_model() UserModel = get_user_model()
email = self.cleaned_data["email"] email = self.cleaned_data["email"]
self.users_cache = UserModel.objects.filter(email__iexact=email, self.users_cache = UserModel.objects.filter(email__iexact=email)
is_active=True)
if not len(self.users_cache): if not len(self.users_cache):
raise forms.ValidationError(self.error_messages['unknown']) raise forms.ValidationError(self.error_messages['unknown'])
if not any(user.is_active for user in self.users_cache):
# none of the filtered users are active
raise forms.ValidationError(self.error_messages['unknown'])
if any((user.password == UNUSABLE_PASSWORD) if any((user.password == UNUSABLE_PASSWORD)
for user in self.users_cache): for user in self.users_cache):
raise forms.ValidationError(self.error_messages['unusable']) raise forms.ValidationError(self.error_messages['unusable'])
@ -239,7 +248,7 @@ class PasswordResetForm(forms.Form):
'email': user.email, 'email': user.email,
'domain': domain, 'domain': domain,
'site_name': site_name, 'site_name': site_name,
'uid': int_to_base36(user.id), 'uid': int_to_base36(user.pk),
'user': user, 'user': user,
'token': token_generator.make_token(user), 'token': token_generator.make_token(user),
'protocol': use_https and 'https' or 'http', 'protocol': use_https and 'https' or 'http',

View File

@ -21,17 +21,12 @@ def check_password(environ, username, password):
user = UserModel.objects.get_by_natural_key(username) user = UserModel.objects.get_by_natural_key(username)
except UserModel.DoesNotExist: except UserModel.DoesNotExist:
return None return None
try: if not user.is_active:
if not user.is_active:
return None
except AttributeError as e:
# a custom user may not support is_active
return None return None
return user.check_password(password) return user.check_password(password)
finally: finally:
db.close_connection() db.close_connection()
def groups_for_user(environ, username): def groups_for_user(environ, username):
""" """
Authorizes a user based on groups Authorizes a user based on groups

View File

@ -10,6 +10,7 @@ import unicodedata
from django.contrib.auth import models as auth_app, get_user_model from django.contrib.auth import models as auth_app, get_user_model
from django.core import exceptions from django.core import exceptions
from django.core.management.base import CommandError from django.core.management.base import CommandError
from django.db import DEFAULT_DB_ALIAS, router
from django.db.models import get_models, signals from django.db.models import get_models, signals
from django.utils import six from django.utils import six
from django.utils.six.moves import input from django.utils.six.moves import input
@ -57,7 +58,10 @@ def _check_permission_clashing(custom, builtin, ctype):
(codename, ctype.app_label, ctype.model_class().__name__)) (codename, ctype.app_label, ctype.model_class().__name__))
pool.add(codename) pool.add(codename)
def create_permissions(app, created_models, verbosity, **kwargs): def create_permissions(app, created_models, verbosity, db=DEFAULT_DB_ALIAS, **kwargs):
if not router.allow_syncdb(db, auth_app.Permission):
return
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
app_models = get_models(app) app_models = get_models(app)
@ -68,7 +72,9 @@ def create_permissions(app, created_models, verbosity, **kwargs):
# The codenames and ctypes that should exist. # The codenames and ctypes that should exist.
ctypes = set() ctypes = set()
for klass in app_models: for klass in app_models:
ctype = ContentType.objects.get_for_model(klass) # Force looking up the content types in the current database
# before creating foreign keys to them.
ctype = ContentType.objects.db_manager(db).get_for_model(klass)
ctypes.add(ctype) ctypes.add(ctype)
for perm in _get_all_permissions(klass._meta, ctype): for perm in _get_all_permissions(klass._meta, ctype):
searched_perms.append((ctype, perm)) searched_perms.append((ctype, perm))
@ -76,21 +82,21 @@ def create_permissions(app, created_models, verbosity, **kwargs):
# Find all the Permissions that have a context_type for a model we're # Find all the Permissions that have a context_type for a model we're
# looking for. We don't need to check for codenames since we already have # looking for. We don't need to check for codenames since we already have
# a list of the ones we're going to create. # a list of the ones we're going to create.
all_perms = set(auth_app.Permission.objects.filter( all_perms = set(auth_app.Permission.objects.using(db).filter(
content_type__in=ctypes, content_type__in=ctypes,
).values_list( ).values_list(
"content_type", "codename" "content_type", "codename"
)) ))
objs = [ perms = [
auth_app.Permission(codename=codename, name=name, content_type=ctype) auth_app.Permission(codename=codename, name=name, content_type=ctype)
for ctype, (codename, name) in searched_perms for ctype, (codename, name) in searched_perms
if (ctype.pk, codename) not in all_perms if (ctype.pk, codename) not in all_perms
] ]
auth_app.Permission.objects.bulk_create(objs) auth_app.Permission.objects.using(db).bulk_create(perms)
if verbosity >= 2: if verbosity >= 2:
for obj in objs: for perm in perms:
print("Adding permission '%s'" % obj) print("Adding permission '%s'" % perm)
def create_superuser(app, created_models, verbosity, db, **kwargs): def create_superuser(app, created_models, verbosity, db, **kwargs):

View File

@ -1,4 +1,6 @@
from django.contrib import auth from django.contrib import auth
from django.contrib.auth import load_backend
from django.contrib.auth.backends import RemoteUserBackend
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
@ -47,9 +49,18 @@ class RemoteUserMiddleware(object):
try: try:
username = request.META[self.header] username = request.META[self.header]
except KeyError: except KeyError:
# If specified header doesn't exist then return (leaving # If specified header doesn't exist then remove any existing
# request.user set to AnonymousUser by the # authenticated remote-user, or return (leaving request.user set to
# AuthenticationMiddleware). # AnonymousUser by the AuthenticationMiddleware).
if request.user.is_authenticated():
try:
stored_backend = load_backend(request.session.get(
auth.BACKEND_SESSION_KEY, ''))
if isinstance(stored_backend, RemoteUserBackend):
auth.logout(request)
except ImproperlyConfigured as e:
# backend failed to load
auth.logout(request)
return return
# If the user is already authenticated and that user is the user we are # If the user is already authenticated and that user is the user we are
# getting passed in the headers, then the correct user is already # getting passed in the headers, then the correct user is already

View File

@ -195,43 +195,13 @@ class UserManager(BaseUserManager):
return u return u
# A few helper functions for common logic between User and AnonymousUser.
def _user_get_all_permissions(user, obj):
permissions = set()
for backend in auth.get_backends():
if hasattr(backend, "get_all_permissions"):
if obj is not None:
permissions.update(backend.get_all_permissions(user, obj))
else:
permissions.update(backend.get_all_permissions(user))
return permissions
def _user_has_perm(user, perm, obj):
for backend in auth.get_backends():
if hasattr(backend, "has_perm"):
if obj is not None:
if backend.has_perm(user, perm, obj):
return True
else:
if backend.has_perm(user, perm):
return True
return False
def _user_has_module_perms(user, app_label):
for backend in auth.get_backends():
if hasattr(backend, "has_module_perms"):
if backend.has_module_perms(user, app_label):
return True
return False
@python_2_unicode_compatible @python_2_unicode_compatible
class AbstractBaseUser(models.Model): class AbstractBaseUser(models.Model):
password = models.CharField(_('password'), max_length=128) password = models.CharField(_('password'), max_length=128)
last_login = models.DateTimeField(_('last login'), default=timezone.now) last_login = models.DateTimeField(_('last login'), default=timezone.now)
is_active = True
REQUIRED_FIELDS = [] REQUIRED_FIELDS = []
class Meta: class Meta:
@ -288,32 +258,46 @@ class AbstractBaseUser(models.Model):
raise NotImplementedError() raise NotImplementedError()
class AbstractUser(AbstractBaseUser): # A few helper functions for common logic between User and AnonymousUser.
""" def _user_get_all_permissions(user, obj):
An abstract base class implementing a fully featured User model with permissions = set()
admin-compliant permissions. for backend in auth.get_backends():
if hasattr(backend, "get_all_permissions"):
if obj is not None:
permissions.update(backend.get_all_permissions(user, obj))
else:
permissions.update(backend.get_all_permissions(user))
return permissions
Username, password and email are required. Other fields are optional.
def _user_has_perm(user, perm, obj):
for backend in auth.get_backends():
if hasattr(backend, "has_perm"):
if obj is not None:
if backend.has_perm(user, perm, obj):
return True
else:
if backend.has_perm(user, perm):
return True
return False
def _user_has_module_perms(user, app_label):
for backend in auth.get_backends():
if hasattr(backend, "has_module_perms"):
if backend.has_module_perms(user, app_label):
return True
return False
class PermissionsMixin(models.Model):
"""
A mixin class that adds the fields and methods necessary to support
Django's Group and Permission model using the ModelBackend.
""" """
username = models.CharField(_('username'), max_length=30, unique=True,
help_text=_('Required. 30 characters or fewer. Letters, numbers and '
'@/./+/-/_ characters'),
validators=[
validators.RegexValidator(re.compile('^[\w.@+-]+$'), _('Enter a valid username.'), 'invalid')
])
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
email = models.EmailField(_('email address'), blank=True)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin '
'site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
is_superuser = models.BooleanField(_('superuser status'), default=False, is_superuser = models.BooleanField(_('superuser status'), default=False,
help_text=_('Designates that this user has all permissions without ' help_text=_('Designates that this user has all permissions without '
'explicitly assigning them.')) 'explicitly assigning them.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
groups = models.ManyToManyField(Group, verbose_name=_('groups'), groups = models.ManyToManyField(Group, verbose_name=_('groups'),
blank=True, help_text=_('The groups this user belongs to. A user will ' blank=True, help_text=_('The groups this user belongs to. A user will '
'get all permissions granted to each of ' 'get all permissions granted to each of '
@ -322,30 +306,9 @@ class AbstractUser(AbstractBaseUser):
verbose_name=_('user permissions'), blank=True, verbose_name=_('user permissions'), blank=True,
help_text='Specific permissions for this user.') help_text='Specific permissions for this user.')
objects = UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta: class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
abstract = True abstract = True
def get_absolute_url(self):
return "/users/%s/" % urlquote(self.username)
def get_full_name(self):
"""
Returns the first_name plus the last_name, with a space in between.
"""
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
"Returns the short name for the user."
return self.first_name
def get_group_permissions(self, obj=None): def get_group_permissions(self, obj=None):
""" """
Returns a list of permission strings that this user has through his/her Returns a list of permission strings that this user has through his/her
@ -403,6 +366,55 @@ class AbstractUser(AbstractBaseUser):
return _user_has_module_perms(self, app_label) return _user_has_module_perms(self, app_label)
class AbstractUser(AbstractBaseUser, PermissionsMixin):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
Username, password and email are required. Other fields are optional.
"""
username = models.CharField(_('username'), max_length=30, unique=True,
help_text=_('Required. 30 characters or fewer. Letters, numbers and '
'@/./+/-/_ characters'),
validators=[
validators.RegexValidator(re.compile('^[\w.@+-]+$'), _('Enter a valid username.'), 'invalid')
])
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
email = models.EmailField(_('email address'), blank=True)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin '
'site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
objects = UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
abstract = True
def get_absolute_url(self):
return "/users/%s/" % urlquote(self.username)
def get_full_name(self):
"""
Returns the first_name plus the last_name, with a space in between.
"""
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
"Returns the short name for the user."
return self.first_name
def email_user(self, subject, message, from_email=None): def email_user(self, subject, message, from_email=None):
""" """
Sends an email to this User. Sends an email to this User.

View File

@ -14,3 +14,16 @@ from django.contrib.auth.tests.tokens import *
from django.contrib.auth.tests.views import * from django.contrib.auth.tests.views import *
# The password for the fixture data users is 'password' # The password for the fixture data users is 'password'
from django.dispatch import receiver
from django.test.signals import setting_changed
@receiver(setting_changed)
def user_model_swapped(**kwargs):
if kwargs['setting'] == 'AUTH_USER_MODEL':
from django.db.models.manager import ensure_default_manager
from django.contrib.auth.models import User
# Reset User manager
setattr(User, 'objects', User._default_manager)
ensure_default_manager(User)

View File

@ -4,9 +4,10 @@ from datetime import date
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User, Group, Permission, AnonymousUser from django.contrib.auth.models import User, Group, Permission, AnonymousUser
from django.contrib.auth.tests.utils import skipIfCustomUser from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.auth.tests.custom_user import ExtensionUser from django.contrib.auth.tests.custom_user import ExtensionUser, CustomPermissionsUser
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.contrib.auth import authenticate
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
@ -33,7 +34,7 @@ class BaseModelBackendTest(object):
ContentType.objects.clear_cache() ContentType.objects.clear_cache()
def test_has_perm(self): def test_has_perm(self):
user = self.UserModel.objects.get(username='test') user = self.UserModel.objects.get(pk=self.user.pk)
self.assertEqual(user.has_perm('auth.test'), False) self.assertEqual(user.has_perm('auth.test'), False)
user.is_staff = True user.is_staff = True
user.save() user.save()
@ -52,14 +53,14 @@ class BaseModelBackendTest(object):
self.assertEqual(user.has_perm('auth.test'), False) self.assertEqual(user.has_perm('auth.test'), False)
def test_custom_perms(self): def test_custom_perms(self):
user = self.UserModel.objects.get(username='test') user = self.UserModel.objects.get(pk=self.user.pk)
content_type = ContentType.objects.get_for_model(Group) content_type = ContentType.objects.get_for_model(Group)
perm = Permission.objects.create(name='test', content_type=content_type, codename='test') perm = Permission.objects.create(name='test', content_type=content_type, codename='test')
user.user_permissions.add(perm) user.user_permissions.add(perm)
user.save() user.save()
# reloading user to purge the _perm_cache # reloading user to purge the _perm_cache
user = self.UserModel.objects.get(username='test') user = self.UserModel.objects.get(pk=self.user.pk)
self.assertEqual(user.get_all_permissions() == set(['auth.test']), True) self.assertEqual(user.get_all_permissions() == set(['auth.test']), True)
self.assertEqual(user.get_group_permissions(), set([])) self.assertEqual(user.get_group_permissions(), set([]))
self.assertEqual(user.has_module_perms('Group'), False) self.assertEqual(user.has_module_perms('Group'), False)
@ -70,7 +71,7 @@ class BaseModelBackendTest(object):
perm = Permission.objects.create(name='test3', content_type=content_type, codename='test3') perm = Permission.objects.create(name='test3', content_type=content_type, codename='test3')
user.user_permissions.add(perm) user.user_permissions.add(perm)
user.save() user.save()
user = self.UserModel.objects.get(username='test') user = self.UserModel.objects.get(pk=self.user.pk)
self.assertEqual(user.get_all_permissions(), set(['auth.test2', 'auth.test', 'auth.test3'])) self.assertEqual(user.get_all_permissions(), set(['auth.test2', 'auth.test', 'auth.test3']))
self.assertEqual(user.has_perm('test'), False) self.assertEqual(user.has_perm('test'), False)
self.assertEqual(user.has_perm('auth.test'), True) self.assertEqual(user.has_perm('auth.test'), True)
@ -80,7 +81,7 @@ class BaseModelBackendTest(object):
group.permissions.add(perm) group.permissions.add(perm)
group.save() group.save()
user.groups.add(group) user.groups.add(group)
user = self.UserModel.objects.get(username='test') user = self.UserModel.objects.get(pk=self.user.pk)
exp = set(['auth.test2', 'auth.test', 'auth.test3', 'auth.test_group']) exp = set(['auth.test2', 'auth.test', 'auth.test3', 'auth.test_group'])
self.assertEqual(user.get_all_permissions(), exp) self.assertEqual(user.get_all_permissions(), exp)
self.assertEqual(user.get_group_permissions(), set(['auth.test_group'])) self.assertEqual(user.get_group_permissions(), set(['auth.test_group']))
@ -92,7 +93,7 @@ class BaseModelBackendTest(object):
def test_has_no_object_perm(self): def test_has_no_object_perm(self):
"""Regressiontest for #12462""" """Regressiontest for #12462"""
user = self.UserModel.objects.get(username='test') user = self.UserModel.objects.get(pk=self.user.pk)
content_type = ContentType.objects.get_for_model(Group) content_type = ContentType.objects.get_for_model(Group)
perm = Permission.objects.create(name='test', content_type=content_type, codename='test') perm = Permission.objects.create(name='test', content_type=content_type, codename='test')
user.user_permissions.add(perm) user.user_permissions.add(perm)
@ -105,7 +106,7 @@ class BaseModelBackendTest(object):
def test_get_all_superuser_permissions(self): def test_get_all_superuser_permissions(self):
"A superuser has all permissions. Refs #14795" "A superuser has all permissions. Refs #14795"
user = self.UserModel.objects.get(username='test2') user = self.UserModel.objects.get(pk=self.superuser.pk)
self.assertEqual(len(user.get_all_permissions()), len(Permission.objects.all())) self.assertEqual(len(user.get_all_permissions()), len(Permission.objects.all()))
@ -117,12 +118,12 @@ class ModelBackendTest(BaseModelBackendTest, TestCase):
UserModel = User UserModel = User
def create_users(self): def create_users(self):
User.objects.create_user( self.user = User.objects.create_user(
username='test', username='test',
email='test@example.com', email='test@example.com',
password='test', password='test',
) )
User.objects.create_superuser( self.superuser = User.objects.create_superuser(
username='test2', username='test2',
email='test2@example.com', email='test2@example.com',
password='test', password='test',
@ -150,13 +151,13 @@ class ExtensionUserModelBackendTest(BaseModelBackendTest, TestCase):
UserModel = ExtensionUser UserModel = ExtensionUser
def create_users(self): def create_users(self):
ExtensionUser.objects.create_user( self.user = ExtensionUser.objects.create_user(
username='test', username='test',
email='test@example.com', email='test@example.com',
password='test', password='test',
date_of_birth=date(2006, 4, 25) date_of_birth=date(2006, 4, 25)
) )
ExtensionUser.objects.create_superuser( self.superuser = ExtensionUser.objects.create_superuser(
username='test2', username='test2',
email='test2@example.com', email='test2@example.com',
password='test', password='test',
@ -164,6 +165,31 @@ class ExtensionUserModelBackendTest(BaseModelBackendTest, TestCase):
) )
@override_settings(AUTH_USER_MODEL='auth.CustomPermissionsUser')
class CustomPermissionsUserModelBackendTest(BaseModelBackendTest, TestCase):
"""
Tests for the ModelBackend using the CustomPermissionsUser model.
As with the ExtensionUser test, this isn't a perfect test, because both
the User and CustomPermissionsUser are synchronized to the database,
which wouldn't ordinary happen in production.
"""
UserModel = CustomPermissionsUser
def create_users(self):
self.user = CustomPermissionsUser.objects.create_user(
email='test@example.com',
password='test',
date_of_birth=date(2006, 4, 25)
)
self.superuser = CustomPermissionsUser.objects.create_superuser(
email='test2@example.com',
password='test',
date_of_birth=date(1976, 11, 8)
)
class TestObj(object): class TestObj(object):
pass pass
@ -323,3 +349,38 @@ class InActiveUserBackendTest(TestCase):
def test_has_module_perms(self): def test_has_module_perms(self):
self.assertEqual(self.user1.has_module_perms("app1"), False) self.assertEqual(self.user1.has_module_perms("app1"), False)
self.assertEqual(self.user1.has_module_perms("app2"), False) self.assertEqual(self.user1.has_module_perms("app2"), False)
class PermissionDeniedBackend(object):
"""
Always raises PermissionDenied.
"""
supports_object_permissions = True
supports_anonymous_user = True
supports_inactive_user = True
def authenticate(self, username=None, password=None):
raise PermissionDenied
@skipIfCustomUser
class PermissionDeniedBackendTest(TestCase):
"""
Tests that other backends are not checked once a backend raises PermissionDenied
"""
backend = 'django.contrib.auth.tests.auth_backends.PermissionDeniedBackend'
def setUp(self):
self.user1 = User.objects.create_user('test', 'test@example.com', 'test')
self.user1.save()
@override_settings(AUTHENTICATION_BACKENDS=(backend, ) +
tuple(settings.AUTHENTICATION_BACKENDS))
def test_permission_denied(self):
"user is not authenticated after a backend raises permission denied #2550"
self.assertEqual(authenticate(username='test', password='test'), None)
@override_settings(AUTHENTICATION_BACKENDS=tuple(
settings.AUTHENTICATION_BACKENDS) + (backend, ))
def test_authenticates(self):
self.assertEqual(authenticate(username='test', password='test'), self.user1)

View File

@ -162,6 +162,8 @@ class BasicTestCase(TestCase):
def test_swappable_user(self): def test_swappable_user(self):
"The current user model can be swapped out for another" "The current user model can be swapped out for another"
self.assertEqual(get_user_model(), CustomUser) self.assertEqual(get_user_model(), CustomUser)
with self.assertRaises(AttributeError):
User.objects.all()
@override_settings(AUTH_USER_MODEL='badsetting') @override_settings(AUTH_USER_MODEL='badsetting')
def test_swappable_user_bad_setting(self): def test_swappable_user_bad_setting(self):

View File

@ -9,6 +9,7 @@ from django.contrib.auth.context_processors import PermWrapper, PermLookupDict
from django.db.models import Q from django.db.models import Q
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils._os import upath
class MockUser(object): class MockUser(object):
@ -63,7 +64,7 @@ class PermWrapperTests(TestCase):
@skipIfCustomUser @skipIfCustomUser
@override_settings( @override_settings(
TEMPLATE_DIRS=( TEMPLATE_DIRS=(
os.path.join(os.path.dirname(__file__), 'templates'), os.path.join(os.path.dirname(upath(__file__)), 'templates'),
), ),
USE_TZ=False, # required for loading the fixture USE_TZ=False, # required for loading the fixture
PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',), PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),

View File

@ -1,5 +1,11 @@
from django.db import models from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, AbstractUser, UserManager from django.contrib.auth.models import (
BaseUserManager,
AbstractBaseUser,
AbstractUser,
UserManager,
PermissionsMixin
)
# The custom User uses email as the unique identifier, and requires # The custom User uses email as the unique identifier, and requires
@ -88,3 +94,53 @@ class ExtensionUser(AbstractUser):
class Meta: class Meta:
app_label = 'auth' app_label = 'auth'
# The CustomPermissionsUser users email as the identifier, but uses the normal
# Django permissions model. This allows us to check that the PermissionsMixin
# includes everything that is needed to interact with the ModelBackend.
class CustomPermissionsUserManager(CustomUserManager):
def create_superuser(self, email, password, date_of_birth):
u = self.create_user(email, password=password, date_of_birth=date_of_birth)
u.is_superuser = True
u.save(using=self._db)
return u
class CustomPermissionsUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
date_of_birth = models.DateField()
objects = CustomPermissionsUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['date_of_birth']
class Meta:
app_label = 'auth'
def get_full_name(self):
return self.email
def get_short_name(self):
return self.email
def __unicode__(self):
return self.email
class IsActiveTestUser1(AbstractBaseUser):
"""
This test user class and derivatives test the default is_active behavior
"""
username = models.CharField(max_length=30, unique=True)
objects = BaseUserManager()
USERNAME_FIELD = 'username'
class Meta:
app_label = 'auth'
# the is_active attr is provided by AbstractBaseUser

View File

@ -3,13 +3,15 @@ from __future__ import unicode_literals
import os import os
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm, from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm,
PasswordChangeForm, SetPasswordForm, UserChangeForm, PasswordResetForm) PasswordChangeForm, SetPasswordForm, UserChangeForm, PasswordResetForm,
ReadOnlyPasswordHashWidget)
from django.contrib.auth.tests.utils import skipIfCustomUser from django.contrib.auth.tests.utils import skipIfCustomUser
from django.core import mail from django.core import mail
from django.forms.fields import Field, EmailField from django.forms.fields import Field, EmailField
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils._os import upath
from django.utils import translation from django.utils import translation
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@ -98,7 +100,9 @@ class AuthenticationFormTest(TestCase):
form = AuthenticationForm(None, data) form = AuthenticationForm(None, data)
self.assertFalse(form.is_valid()) self.assertFalse(form.is_valid())
self.assertEqual(form.non_field_errors(), self.assertEqual(form.non_field_errors(),
[force_text(form.error_messages['invalid_login'])]) [force_text(form.error_messages['invalid_login'] % {
'username': User._meta.get_field('username').verbose_name
})])
def test_inactive_user(self): def test_inactive_user(self):
# The user is inactive. # The user is inactive.
@ -282,6 +286,14 @@ class UserChangeFormTest(TestCase):
self.assertTrue(form.is_valid()) self.assertTrue(form.is_valid())
self.assertEqual(form.cleaned_data['password'], 'sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161') self.assertEqual(form.cleaned_data['password'], 'sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161')
def test_bug_19349_bound_password_field(self):
user = User.objects.get(username='testclient')
form = UserChangeForm(data={}, instance=user)
# When rendering the bound password field,
# ReadOnlyPasswordHashWidget needs the initial
# value to render correctly
self.assertEqual(form.initial['password'], form['password'].value())
@skipIfCustomUser @skipIfCustomUser
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) @override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
@ -322,7 +334,7 @@ class PasswordResetFormTest(TestCase):
self.assertEqual(form.cleaned_data['email'], email) self.assertEqual(form.cleaned_data['email'], email)
def test_custom_email_subject(self): def test_custom_email_subject(self):
template_path = os.path.join(os.path.dirname(__file__), 'templates') template_path = os.path.join(os.path.dirname(upath(__file__)), 'templates')
with self.settings(TEMPLATE_DIRS=(template_path,)): with self.settings(TEMPLATE_DIRS=(template_path,)):
data = {'email': 'testclient@example.com'} data = {'email': 'testclient@example.com'}
form = PasswordResetForm(data) form = PasswordResetForm(data)
@ -362,3 +374,13 @@ class PasswordResetFormTest(TestCase):
self.assertFalse(form.is_valid()) self.assertFalse(form.is_valid())
self.assertEqual(form["email"].errors, self.assertEqual(form["email"].errors,
[_("The user account associated with this email address cannot reset the password.")]) [_("The user account associated with this email address cannot reset the password.")])
class ReadOnlyPasswordHashWidgetTest(TestCase):
def test_bug_19349_render_with_none_value(self):
# Rendering the widget with value set to None
# mustn't raise an exception.
widget = ReadOnlyPasswordHashWidget()
html = widget.render(name='password', value=None, attrs={})
self.assertIn(_("No password set."), html)

View File

@ -2,30 +2,23 @@ from __future__ import unicode_literals
from django.contrib.auth.handlers.modwsgi import check_password, groups_for_user from django.contrib.auth.handlers.modwsgi import check_password, groups_for_user
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from django.contrib.auth.tests import CustomUser
from django.contrib.auth.tests.utils import skipIfCustomUser from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TransactionTestCase from django.test import TransactionTestCase
from django.test.utils import override_settings
class ModWsgiHandlerTestCase(TransactionTestCase): class ModWsgiHandlerTestCase(TransactionTestCase):
""" """
Tests for the mod_wsgi authentication handler Tests for the mod_wsgi authentication handler
""" """
@skipIfCustomUser
def setUp(self):
user1 = User.objects.create_user('test', 'test@example.com', 'test')
User.objects.create_user('test1', 'test1@example.com', 'test1')
group = Group.objects.create(name='test_group')
user1.groups.add(group)
def test_check_password(self): def test_check_password(self):
""" """
Verify that check_password returns the correct values as per Verify that check_password returns the correct values as per
http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Authentication_Provider http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Authentication_Provider
because the custom user available in the test framework does not
support the is_active attribute, we can't test this with a custom
user.
""" """
User.objects.create_user('test', 'test@example.com', 'test')
# User not in database # User not in database
self.assertTrue(check_password({}, 'unknown', '') is None) self.assertTrue(check_password({}, 'unknown', '') is None)
@ -33,15 +26,43 @@ class ModWsgiHandlerTestCase(TransactionTestCase):
# Valid user with correct password # Valid user with correct password
self.assertTrue(check_password({}, 'test', 'test')) self.assertTrue(check_password({}, 'test', 'test'))
# correct password, but user is inactive
User.objects.filter(username='test').update(is_active=False)
self.assertFalse(check_password({}, 'test', 'test'))
# Valid user with incorrect password # Valid user with incorrect password
self.assertFalse(check_password({}, 'test', 'incorrect')) self.assertFalse(check_password({}, 'test', 'incorrect'))
@override_settings(AUTH_USER_MODEL='auth.CustomUser')
def test_check_password_custom_user(self):
"""
Verify that check_password returns the correct values as per
http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Authentication_Provider
with custom user installed
"""
CustomUser.objects.create_user('test@example.com', '1990-01-01', 'test')
# User not in database
self.assertTrue(check_password({}, 'unknown', '') is None)
# Valid user with correct password'
self.assertTrue(check_password({}, 'test@example.com', 'test'))
# Valid user with incorrect password
self.assertFalse(check_password({}, 'test@example.com', 'incorrect'))
@skipIfCustomUser @skipIfCustomUser
def test_groups_for_user(self): def test_groups_for_user(self):
""" """
Check that groups_for_user returns correct values as per Check that groups_for_user returns correct values as per
http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Group_Authorisation http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Group_Authorisation
""" """
user1 = User.objects.create_user('test', 'test@example.com', 'test')
User.objects.create_user('test1', 'test1@example.com', 'test1')
group = Group.objects.create(name='test_group')
user1.groups.add(group)
# User not in database # User not in database
self.assertEqual(groups_for_user({}, 'unknown'), []) self.assertEqual(groups_for_user({}, 'unknown'), [])

View File

@ -1,4 +1,5 @@
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import (Group, User, SiteProfileNotAvailable, from django.contrib.auth.models import (Group, User, SiteProfileNotAvailable,
UserManager) UserManager)
from django.contrib.auth.tests.utils import skipIfCustomUser from django.contrib.auth.tests.utils import skipIfCustomUser
@ -98,3 +99,36 @@ class UserManagerTestCase(TestCase):
self.assertRaisesMessage(ValueError, self.assertRaisesMessage(ValueError,
'The given username must be set', 'The given username must be set',
User.objects.create_user, username='') User.objects.create_user, username='')
class IsActiveTestCase(TestCase):
"""
Tests the behavior of the guaranteed is_active attribute
"""
@skipIfCustomUser
def test_builtin_user_isactive(self):
user = User.objects.create(username='foo', email='foo@bar.com')
# is_active is true by default
self.assertEqual(user.is_active, True)
user.is_active = False
user.save()
user_fetched = User.objects.get(pk=user.pk)
# the is_active flag is saved
self.assertFalse(user_fetched.is_active)
@override_settings(AUTH_USER_MODEL='auth.IsActiveTestUser1')
def test_is_active_field_default(self):
"""
tests that the default value for is_active is provided
"""
UserModel = get_user_model()
user = UserModel(username='foo')
self.assertEqual(user.is_active, True)
# you can set the attribute - but it will not save
user.is_active = False
# there should be no problem saving - but the attribute is not saved
user.save()
user_fetched = UserModel.objects.get(pk=user.pk)
# the attribute is always true for newly retrieved instance
self.assertEqual(user_fetched.is_active, True)

View File

@ -1,8 +1,9 @@
from datetime import datetime from datetime import datetime
from django.conf import settings from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth.backends import RemoteUserBackend from django.contrib.auth.backends import RemoteUserBackend
from django.contrib.auth.models import User from django.contrib.auth.models import User, AnonymousUser
from django.contrib.auth.tests.utils import skipIfCustomUser from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TestCase from django.test import TestCase
from django.utils import timezone from django.utils import timezone
@ -23,7 +24,7 @@ class RemoteUserTest(TestCase):
self.curr_middleware = settings.MIDDLEWARE_CLASSES self.curr_middleware = settings.MIDDLEWARE_CLASSES
self.curr_auth = settings.AUTHENTICATION_BACKENDS self.curr_auth = settings.AUTHENTICATION_BACKENDS
settings.MIDDLEWARE_CLASSES += (self.middleware,) settings.MIDDLEWARE_CLASSES += (self.middleware,)
settings.AUTHENTICATION_BACKENDS = (self.backend,) settings.AUTHENTICATION_BACKENDS += (self.backend,)
def test_no_remote_user(self): def test_no_remote_user(self):
""" """
@ -97,6 +98,26 @@ class RemoteUserTest(TestCase):
response = self.client.get('/remote_user/', REMOTE_USER=self.known_user) response = self.client.get('/remote_user/', REMOTE_USER=self.known_user)
self.assertEqual(default_login, response.context['user'].last_login) self.assertEqual(default_login, response.context['user'].last_login)
def test_header_disappears(self):
"""
Tests that a logged in user is logged out automatically when
the REMOTE_USER header disappears during the same browser session.
"""
User.objects.create(username='knownuser')
# Known user authenticates
response = self.client.get('/remote_user/', REMOTE_USER=self.known_user)
self.assertEqual(response.context['user'].username, 'knownuser')
# During the session, the REMOTE_USER header disappears. Should trigger logout.
response = self.client.get('/remote_user/')
self.assertEqual(response.context['user'].is_anonymous(), True)
# verify the remoteuser middleware will not remove a user
# authenticated via another backend
User.objects.create_user(username='modeluser', password='foo')
self.client.login(username='modeluser', password='foo')
authenticate(username='modeluser', password='foo')
response = self.client.get('/remote_user/')
self.assertEqual(response.context['user'].username, 'modeluser')
def tearDown(self): def tearDown(self):
"""Restores settings to avoid breaking other tests.""" """Restores settings to avoid breaking other tests."""
settings.MIDDLEWARE_CLASSES = self.curr_middleware settings.MIDDLEWARE_CLASSES = self.curr_middleware

View File

@ -1,4 +1,4 @@
unicode: {{ user }} unicode: {{ user }}
id: {{ user.id }} id: {{ user.pk }}
username: {{ user.username }} username: {{ user.username }}
url: {% url 'userpage' user %} url: {% url 'userpage' user %}

View File

@ -11,6 +11,7 @@ from django.http import QueryDict
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.html import escape from django.utils.html import escape
from django.utils.http import urlquote from django.utils.http import urlquote
from django.utils._os import upath
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
@ -27,7 +28,7 @@ from django.contrib.auth.tests.utils import skipIfCustomUser
LANGUAGE_CODE='en', LANGUAGE_CODE='en',
TEMPLATE_LOADERS=global_settings.TEMPLATE_LOADERS, TEMPLATE_LOADERS=global_settings.TEMPLATE_LOADERS,
TEMPLATE_DIRS=( TEMPLATE_DIRS=(
os.path.join(os.path.dirname(__file__), 'templates'), os.path.join(os.path.dirname(upath(__file__)), 'templates'),
), ),
USE_TZ=False, USE_TZ=False,
PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',), PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
@ -115,6 +116,8 @@ class PasswordResetTest(AuthViewsTestCase):
self.assertTrue("http://adminsite.com" in mail.outbox[0].body) self.assertTrue("http://adminsite.com" in mail.outbox[0].body)
self.assertEqual(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email) self.assertEqual(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email)
# Skip any 500 handler action (like sending more mail...)
@override_settings(DEBUG_PROPAGATE_EXCEPTIONS=True)
def test_poisoned_http_host(self): def test_poisoned_http_host(self):
"Poisoned HTTP_HOST headers can't be used for reset emails" "Poisoned HTTP_HOST headers can't be used for reset emails"
# This attack is based on the way browsers handle URLs. The colon # This attack is based on the way browsers handle URLs. The colon
@ -131,6 +134,8 @@ class PasswordResetTest(AuthViewsTestCase):
) )
self.assertEqual(len(mail.outbox), 0) self.assertEqual(len(mail.outbox), 0)
# Skip any 500 handler action (like sending more mail...)
@override_settings(DEBUG_PROPAGATE_EXCEPTIONS=True)
def test_poisoned_http_host_admin_site(self): def test_poisoned_http_host_admin_site(self):
"Poisoned HTTP_HOST headers can't be used for reset emails on admin views" "Poisoned HTTP_HOST headers can't be used for reset emails on admin views"
with self.assertRaises(SuspiciousOperation): with self.assertRaises(SuspiciousOperation):
@ -243,7 +248,9 @@ class ChangePasswordTest(AuthViewsTestCase):
'username': 'testclient', 'username': 'testclient',
'password': password, 'password': password,
}) })
self.assertContainsEscaped(response, AuthenticationForm.error_messages['invalid_login']) self.assertContainsEscaped(response, AuthenticationForm.error_messages['invalid_login'] % {
'username': User._meta.get_field('username').verbose_name
})
def logout(self): def logout(self):
response = self.client.get('/logout/') response = self.client.get('/logout/')

View File

@ -58,7 +58,7 @@ class PasswordResetTokenGenerator(object):
# Ensure results are consistent across DB backends # Ensure results are consistent across DB backends
login_timestamp = user.last_login.replace(microsecond=0, tzinfo=None) login_timestamp = user.last_login.replace(microsecond=0, tzinfo=None)
value = (six.text_type(user.id) + user.password + value = (six.text_type(user.pk) + user.password +
six.text_type(login_timestamp) + six.text_type(timestamp)) six.text_type(login_timestamp) + six.text_type(timestamp))
hash = salted_hmac(key_salt, value).hexdigest()[::2] hash = salted_hmac(key_salt, value).hexdigest()[::2]
return "%s-%s" % (ts_b36, hash) return "%s-%s" % (ts_b36, hash)

View File

@ -7,7 +7,7 @@ from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect, QueryDict from django.http import HttpResponseRedirect, QueryDict
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils.http import base36_to_int from django.utils.http import base36_to_int, is_safe_url
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.shortcuts import resolve_url from django.shortcuts import resolve_url
from django.views.decorators.debug import sensitive_post_parameters from django.views.decorators.debug import sensitive_post_parameters
@ -37,18 +37,12 @@ def login(request, template_name='registration/login.html',
if request.method == "POST": if request.method == "POST":
form = authentication_form(data=request.POST) form = authentication_form(data=request.POST)
if form.is_valid(): if form.is_valid():
# 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] # Ensure the user-originating redirection url is safe.
# Heavier security check -- don't allow redirection to a different if not is_safe_url(url=redirect_to, host=request.get_host()):
# host.
if netloc and netloc != request.get_host():
redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL) redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
# Okay, security checks complete. Log the user in. # Okay, security check complete. Log the user in.
auth_login(request, form.get_user()) auth_login(request, form.get_user())
if request.session.test_cookie_worked(): if request.session.test_cookie_worked():
@ -82,27 +76,27 @@ def logout(request, next_page=None,
Logs out the user and displays 'You are logged out' message. Logs out the user and displays 'You are logged out' message.
""" """
auth_logout(request) auth_logout(request)
redirect_to = request.REQUEST.get(redirect_field_name, '')
if redirect_to:
netloc = urlparse(redirect_to)[1]
# Security check -- don't allow redirection to a different host.
if not (netloc and netloc != request.get_host()):
return HttpResponseRedirect(redirect_to)
if next_page is None: if redirect_field_name in request.REQUEST:
current_site = get_current_site(request) next_page = request.REQUEST[redirect_field_name]
context = { # Security check -- don't allow redirection to a different host.
'site': current_site, if not is_safe_url(url=next_page, host=request.get_host()):
'site_name': current_site.name, next_page = request.path
'title': _('Logged out')
} if next_page:
if extra_context is not None:
context.update(extra_context)
return TemplateResponse(request, template_name, context,
current_app=current_app)
else:
# Redirect to this page until the session has been cleared. # Redirect to this page until the session has been cleared.
return HttpResponseRedirect(next_page or request.path) return HttpResponseRedirect(next_page)
current_site = get_current_site(request)
context = {
'site': current_site,
'site_name': current_site.name,
'title': _('Logged out')
}
if extra_context is not None:
context.update(extra_context)
return TemplateResponse(request, template_name, context,
current_app=current_app)
def logout_then_login(request, login_url=None, current_app=None, extra_context=None): def logout_then_login(request, login_url=None, current_app=None, extra_context=None):
@ -206,7 +200,7 @@ def password_reset_confirm(request, uidb36=None, token=None,
post_reset_redirect = reverse('django.contrib.auth.views.password_reset_complete') post_reset_redirect = reverse('django.contrib.auth.views.password_reset_complete')
try: try:
uid_int = base36_to_int(uidb36) uid_int = base36_to_int(uidb36)
user = UserModel.objects.get(id=uid_int) user = UserModel.objects.get(pk=uid_int)
except (ValueError, OverflowError, UserModel.DoesNotExist): except (ValueError, OverflowError, UserModel.DoesNotExist):
user = None user = None

View File

@ -20,9 +20,9 @@ def get_comment_app():
# Try to import the package # Try to import the package
try: try:
package = import_module(comments_app) package = import_module(comments_app)
except ImportError: except ImportError as e:
raise ImproperlyConfigured("The COMMENTS_APP setting refers to "\ raise ImproperlyConfigured("The COMMENTS_APP setting refers to "\
"a non-existing package.") "a non-existing package. (%s)" % e)
return package return package

View File

@ -6,7 +6,7 @@ from django.dispatch import Signal
# Sent just before a comment will be posted (after it's been approved and # Sent just before a comment will be posted (after it's been approved and
# moderated; this can be used to modify the comment (in place) with posting # moderated; this can be used to modify the comment (in place) with posting
# details or other such actions. If any receiver returns False the comment will be # details or other such actions. If any receiver returns False the comment will be
# discarded and a 403 (not allowed) response. This signal is sent at more or less # discarded and a 400 response. This signal is sent at more or less
# the same time (just before, actually) as the Comment object's pre-save signal, # the same time (just before, actually) as the Comment object's pre-save signal,
# except that the HTTP request is sent along with this signal. # except that the HTTP request is sent along with this signal.
comment_will_be_posted = Signal(providing_args=["comment", "request"]) comment_will_be_posted = Signal(providing_args=["comment", "request"])

View File

@ -44,9 +44,6 @@ def post_comment(request, next=None, using=None):
if not data.get('email', ''): if not data.get('email', ''):
data["email"] = request.user.email data["email"] = request.user.email
# Check to see if the POST data overrides the view's next argument.
next = data.get("next", next)
# Look up the object we're trying to comment about # Look up the object we're trying to comment about
ctype = data.get("content_type") ctype = data.get("content_type")
object_pk = data.get("object_pk") object_pk = data.get("object_pk")
@ -100,7 +97,7 @@ def post_comment(request, next=None, using=None):
template_list, { template_list, {
"comment": form.data.get("comment", ""), "comment": form.data.get("comment", ""),
"form": form, "form": form,
"next": next, "next": data.get("next", next),
}, },
RequestContext(request, {}) RequestContext(request, {})
) )
@ -131,7 +128,8 @@ def post_comment(request, next=None, using=None):
request=request request=request
) )
return next_redirect(data, next, comment_done, c=comment._get_pk_val()) return next_redirect(request, fallback=next or 'comments-comment-done',
c=comment._get_pk_val())
comment_done = confirmation_view( comment_done = confirmation_view(
template="comments/posted.html", template="comments/posted.html",

View File

@ -10,7 +10,6 @@ from django.shortcuts import get_object_or_404, render_to_response
from django.views.decorators.csrf import csrf_protect from django.views.decorators.csrf import csrf_protect
@csrf_protect @csrf_protect
@login_required @login_required
def flag(request, comment_id, next=None): def flag(request, comment_id, next=None):
@ -27,7 +26,8 @@ def flag(request, comment_id, next=None):
# Flag on POST # Flag on POST
if request.method == 'POST': if request.method == 'POST':
perform_flag(request, comment) perform_flag(request, comment)
return next_redirect(request.POST.copy(), next, flag_done, c=comment.pk) return next_redirect(request, fallback=next or 'comments-flag-done',
c=comment.pk)
# Render a form on GET # Render a form on GET
else: else:
@ -54,7 +54,8 @@ def delete(request, comment_id, next=None):
if request.method == 'POST': if request.method == 'POST':
# Flag the comment as deleted instead of actually deleting it. # Flag the comment as deleted instead of actually deleting it.
perform_delete(request, comment) perform_delete(request, comment)
return next_redirect(request.POST.copy(), next, delete_done, c=comment.pk) return next_redirect(request, fallback=next or 'comments-delete-done',
c=comment.pk)
# Render a form on GET # Render a form on GET
else: else:
@ -81,7 +82,8 @@ def approve(request, comment_id, next=None):
if request.method == 'POST': if request.method == 'POST':
# Flag the comment as approved. # Flag the comment as approved.
perform_approve(request, comment) perform_approve(request, comment)
return next_redirect(request.POST.copy(), next, approve_done, c=comment.pk) return next_redirect(request, fallback=next or 'comments-approve-done',
c=comment.pk)
# Render a form on GET # Render a form on GET
else: else:

View File

@ -9,25 +9,26 @@ except ImportError: # Python 2
from urllib import urlencode from urllib import urlencode
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.core import urlresolvers from django.shortcuts import render_to_response, resolve_url
from django.shortcuts import render_to_response
from django.template import RequestContext from django.template import RequestContext
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.contrib import comments from django.contrib import comments
from django.utils.http import is_safe_url
def next_redirect(data, default, default_view, **get_kwargs): def next_redirect(request, fallback, **get_kwargs):
""" """
Handle the "where should I go next?" part of comment views. Handle the "where should I go next?" part of comment views.
The next value could be a kwarg to the function (``default``), or a The next value could be a
``?next=...`` GET arg, or the URL of a given view (``default_view``). See ``?next=...`` GET arg or the URL of a given view (``fallback``). See
the view modules for examples. the view modules for examples.
Returns an ``HttpResponseRedirect``. Returns an ``HttpResponseRedirect``.
""" """
next = data.get("next", default) next = request.POST.get('next')
if next is None: if not is_safe_url(url=next, host=request.get_host()):
next = urlresolvers.reverse(default_view) next = resolve_url(fallback)
if get_kwargs: if get_kwargs:
if '#' in next: if '#' in next:
tmp = next.rsplit('#', 1) tmp = next.rsplit('#', 1)

View File

@ -5,20 +5,19 @@ from __future__ import unicode_literals
from collections import defaultdict from collections import defaultdict
from functools import partial from functools import partial
from operator import attrgetter
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import connection from django.db import connection
from django.db.models import signals from django.db.models import signals
from django.db import models, router, DEFAULT_DB_ALIAS from django.db import models, router, DEFAULT_DB_ALIAS
from django.db.models.fields.related import RelatedField, Field, ManyToManyRel from django.db.models.fields.related import RelatedField, Field, ManyToManyRel
from django.db.models.loading import get_model
from django.forms import ModelForm from django.forms import ModelForm
from django.forms.models import BaseModelFormSet, modelformset_factory, save_instance from django.forms.models import BaseModelFormSet, modelformset_factory, save_instance
from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils.encoding import smart_text from django.utils.encoding import smart_text
class GenericForeignKey(object): class GenericForeignKey(object):
""" """
Provides a generic relation to any object through content-type/object-id Provides a generic relation to any object through content-type/object-id
@ -52,9 +51,6 @@ class GenericForeignKey(object):
kwargs[self.fk_field] = value._get_pk_val() kwargs[self.fk_field] = value._get_pk_val()
def get_content_type(self, obj=None, id=None, using=None): def get_content_type(self, obj=None, id=None, using=None):
# Convenience function using get_model avoids a circular import when
# using this model
ContentType = get_model("contenttypes", "contenttype")
if obj: if obj:
return ContentType.objects.db_manager(obj._state.db).get_for_model(obj) return ContentType.objects.db_manager(obj._state.db).get_for_model(obj)
elif id: elif id:
@ -209,18 +205,16 @@ class GenericRelation(RelatedField, Field):
# same db_type as well. # same db_type as well.
return None return None
def extra_filters(self, pieces, pos, negate): def get_content_type(self):
""" """
Return an extra filter to the queryset so that the results are filtered Returns the content type associated with this field's model.
on the appropriate content type.
""" """
if negate: return ContentType.objects.get_for_model(self.model)
return []
ContentType = get_model("contenttypes", "contenttype") def get_extra_join_sql(self, connection, qn, lhs_alias, rhs_alias):
content_type = ContentType.objects.get_for_model(self.model) extra_col = self.rel.to._meta.get_field_by_name(self.content_type_field_name)[0].column
prefix = "__".join(pieces[:pos + 1]) contenttype = self.get_content_type().pk
return [("%s__%s" % (prefix, self.content_type_field_name), return " AND %s.%s = %%s" % (qn(rhs_alias), qn(extra_col)), [contenttype]
content_type)]
def bulk_related_objects(self, objs, using=DEFAULT_DB_ALIAS): def bulk_related_objects(self, objs, using=DEFAULT_DB_ALIAS):
""" """
@ -251,9 +245,6 @@ class ReverseGenericRelatedObjectsDescriptor(object):
if instance is None: if instance is None:
return self return self
# This import is done here to avoid circular import importing this module
from django.contrib.contenttypes.models import ContentType
# Dynamically create a class that subclasses the related model's # Dynamically create a class that subclasses the related model's
# default manager. # default manager.
rel_model = self.field.rel.to rel_model = self.field.rel.to
@ -329,8 +320,11 @@ def create_generic_related_manager(superclass):
set(obj._get_pk_val() for obj in instances) set(obj._get_pk_val() for obj in instances)
} }
qs = super(GenericRelatedObjectManager, self).get_query_set().using(db).filter(**query) qs = super(GenericRelatedObjectManager, self).get_query_set().using(db).filter(**query)
# We (possibly) need to convert object IDs to the type of the
# instances' PK in order to match up instances:
object_id_converter = instances[0]._meta.pk.to_python
return (qs, return (qs,
attrgetter(self.object_id_field_name), lambda relobj: object_id_converter(getattr(relobj, self.object_id_field_name)),
lambda obj: obj._get_pk_val(), lambda obj: obj._get_pk_val(),
False, False,
self.prefetch_cache_name) self.prefetch_cache_name)
@ -381,8 +375,6 @@ class BaseGenericInlineFormSet(BaseModelFormSet):
def __init__(self, data=None, files=None, instance=None, save_as_new=None, def __init__(self, data=None, files=None, instance=None, save_as_new=None,
prefix=None, queryset=None): prefix=None, queryset=None):
# Avoid a circular import.
from django.contrib.contenttypes.models import ContentType
opts = self.model._meta opts = self.model._meta
self.instance = instance self.instance = instance
self.rel_name = '-'.join(( self.rel_name = '-'.join((
@ -411,8 +403,6 @@ class BaseGenericInlineFormSet(BaseModelFormSet):
)) ))
def save_new(self, form, commit=True): def save_new(self, form, commit=True):
# Avoid a circular import.
from django.contrib.contenttypes.models import ContentType
kwargs = { kwargs = {
self.ct_field.get_attname(): ContentType.objects.get_for_model(self.instance).pk, self.ct_field.get_attname(): ContentType.objects.get_for_model(self.instance).pk,
self.ct_fk_field.get_attname(): self.instance.pk, self.ct_fk_field.get_attname(): self.instance.pk,
@ -434,8 +424,6 @@ def generic_inlineformset_factory(model, form=ModelForm,
defaults ``content_type`` and ``object_id`` respectively. defaults ``content_type`` and ``object_id`` respectively.
""" """
opts = model._meta opts = model._meta
# Avoid a circular import.
from django.contrib.contenttypes.models import ContentType
# if there is no field called `ct_field` let the exception propagate # if there is no field called `ct_field` let the exception propagate
ct_field = opts.get_field(ct_field) ct_field = opts.get_field(ct_field)
if not isinstance(ct_field, models.ForeignKey) or ct_field.rel.to != ContentType: if not isinstance(ct_field, models.ForeignKey) or ct_field.rel.to != ContentType:

View File

@ -1,14 +1,19 @@
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import DEFAULT_DB_ALIAS, router
from django.db.models import get_apps, get_models, signals from django.db.models import get_apps, get_models, signals
from django.utils.encoding import smart_text from django.utils.encoding import smart_text
from django.utils import six from django.utils import six
from django.utils.six.moves import input from django.utils.six.moves import input
def update_contenttypes(app, created_models, verbosity=2, **kwargs):
def update_contenttypes(app, created_models, verbosity=2, db=DEFAULT_DB_ALIAS, **kwargs):
""" """
Creates content types for models in the given app, removing any model Creates content types for models in the given app, removing any model
entries that no longer have a matching model class. entries that no longer have a matching model class.
""" """
if not router.allow_syncdb(db, ContentType):
return
ContentType.objects.clear_cache() ContentType.objects.clear_cache()
app_models = get_models(app) app_models = get_models(app)
if not app_models: if not app_models:
@ -19,10 +24,11 @@ def update_contenttypes(app, created_models, verbosity=2, **kwargs):
(model._meta.object_name.lower(), model) (model._meta.object_name.lower(), model)
for model in app_models for model in app_models
) )
# Get all the content types # Get all the content types
content_types = dict( content_types = dict(
(ct.model, ct) (ct.model, ct)
for ct in ContentType.objects.filter(app_label=app_label) for ct in ContentType.objects.using(db).filter(app_label=app_label)
) )
to_remove = [ to_remove = [
ct ct
@ -30,7 +36,7 @@ def update_contenttypes(app, created_models, verbosity=2, **kwargs):
if model_name not in app_models if model_name not in app_models
] ]
cts = ContentType.objects.bulk_create([ cts = [
ContentType( ContentType(
name=smart_text(model._meta.verbose_name_raw), name=smart_text(model._meta.verbose_name_raw),
app_label=app_label, app_label=app_label,
@ -38,7 +44,8 @@ def update_contenttypes(app, created_models, verbosity=2, **kwargs):
) )
for (model_name, model) in six.iteritems(app_models) for (model_name, model) in six.iteritems(app_models)
if model_name not in content_types if model_name not in content_types
]) ]
ContentType.objects.using(db).bulk_create(cts)
if verbosity >= 2: if verbosity >= 2:
for ct in cts: for ct in cts:
print("Adding content type '%s | %s'" % (ct.app_label, ct.model)) print("Adding content type '%s | %s'" % (ct.app_label, ct.model))
@ -71,6 +78,7 @@ If you're unsure, answer 'no'.
if verbosity >= 2: if verbosity >= 2:
print("Stale content types remain.") print("Stale content types remain.")
def update_all_contenttypes(verbosity=2, **kwargs): def update_all_contenttypes(verbosity=2, **kwargs):
for app in get_apps(): for app in get_apps():
update_contenttypes(app, None, verbosity, **kwargs) update_contenttypes(app, None, verbosity, **kwargs)

View File

@ -35,7 +35,7 @@ class FlatpageForm(forms.ModelForm):
for site in sites: for site in sites:
if same_url.filter(sites=site).exists(): if same_url.filter(sites=site).exists():
raise forms.ValidationError( raise forms.ValidationError(
_('Flatpage with url %(url)s already exists for site %(site)s' % _('Flatpage with url %(url)s already exists for site %(site)s') %
{'url': url, 'site': site})) {'url': url, 'site': site})
return super(FlatpageForm, self).clean() return super(FlatpageForm, self).clean()

View File

@ -14,6 +14,7 @@ from django.contrib.formtools.wizard import FormWizard
from django.test import TestCase from django.test import TestCase
from django.test.html import parse_html from django.test.html import parse_html
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils._os import upath
from django.utils import unittest from django.utils import unittest
from django.contrib.formtools.tests.wizard import * from django.contrib.formtools.tests.wizard import *
@ -36,7 +37,7 @@ class TestFormPreview(preview.FormPreview):
@override_settings( @override_settings(
TEMPLATE_DIRS=( TEMPLATE_DIRS=(
os.path.join(os.path.dirname(__file__), 'templates'), os.path.join(os.path.dirname(upath(__file__)), 'templates'),
), ),
) )
class PreviewTests(TestCase): class PreviewTests(TestCase):
@ -214,7 +215,7 @@ class DummyRequest(http.HttpRequest):
@override_settings( @override_settings(
SECRET_KEY="123", SECRET_KEY="123",
TEMPLATE_DIRS=( TEMPLATE_DIRS=(
os.path.join(os.path.dirname(__file__), 'templates'), os.path.join(os.path.dirname(upath(__file__)), 'templates'),
), ),
) )
class WizardTests(TestCase): class WizardTests(TestCase):

View File

@ -9,6 +9,7 @@ from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.formtools.wizard.views import CookieWizardView from django.contrib.formtools.wizard.views import CookieWizardView
from django.contrib.formtools.tests.wizard.forms import UserForm, UserFormSet from django.contrib.formtools.tests.wizard.forms import UserForm, UserFormSet
from django.utils._os import upath
class WizardTests(object): class WizardTests(object):
@ -72,6 +73,10 @@ class WizardTests(object):
self.assertEqual(response.context['wizard']['steps'].current, 'form2') self.assertEqual(response.context['wizard']['steps'].current, 'form2')
self.assertEqual(response.context.get('another_var', None), True) self.assertEqual(response.context.get('another_var', None), True)
# ticket #19025: `form` should be included in context
form = response.context_data['wizard']['form']
self.assertEqual(response.context_data['form'], form)
def test_form_finish(self): def test_form_finish(self):
response = self.client.get(self.wizard_url) response = self.client.get(self.wizard_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -82,7 +87,7 @@ class WizardTests(object):
self.assertEqual(response.context['wizard']['steps'].current, 'form2') self.assertEqual(response.context['wizard']['steps'].current, 'form2')
post_data = self.wizard_step_data[1] post_data = self.wizard_step_data[1]
post_data['form2-file1'] = open(__file__, 'rb') post_data['form2-file1'] = open(upath(__file__), 'rb')
response = self.client.post(self.wizard_url, post_data) response = self.client.post(self.wizard_url, post_data)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['wizard']['steps'].current, 'form3') self.assertEqual(response.context['wizard']['steps'].current, 'form3')
@ -95,7 +100,7 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
all_data = response.context['form_list'] all_data = response.context['form_list']
with open(__file__, 'rb') as f: with open(upath(__file__), 'rb') as f:
self.assertEqual(all_data[1]['file1'].read(), f.read()) self.assertEqual(all_data[1]['file1'].read(), f.read())
all_data[1]['file1'].close() all_data[1]['file1'].close()
del all_data[1]['file1'] del all_data[1]['file1']
@ -114,7 +119,7 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
post_data = self.wizard_step_data[1] post_data = self.wizard_step_data[1]
with open(__file__, 'rb') as post_file: with open(upath(__file__), 'rb') as post_file:
post_data['form2-file1'] = post_file post_data['form2-file1'] = post_file
response = self.client.post(self.wizard_url, post_data) response = self.client.post(self.wizard_url, post_data)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -126,7 +131,7 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
all_data = response.context['all_cleaned_data'] all_data = response.context['all_cleaned_data']
with open(__file__, 'rb') as f: with open(upath(__file__), 'rb') as f:
self.assertEqual(all_data['file1'].read(), f.read()) self.assertEqual(all_data['file1'].read(), f.read())
all_data['file1'].close() all_data['file1'].close()
del all_data['file1'] del all_data['file1']
@ -146,7 +151,7 @@ class WizardTests(object):
post_data = self.wizard_step_data[1] post_data = self.wizard_step_data[1]
post_data['form2-file1'].close() post_data['form2-file1'].close()
post_data['form2-file1'] = open(__file__, 'rb') post_data['form2-file1'] = open(upath(__file__), 'rb')
response = self.client.post(self.wizard_url, post_data) response = self.client.post(self.wizard_url, post_data)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -174,7 +179,7 @@ class WizardTests(object):
post_data = self.wizard_step_data[1] post_data = self.wizard_step_data[1]
post_data['form2-file1'].close() post_data['form2-file1'].close()
post_data['form2-file1'] = open(__file__, 'rb') post_data['form2-file1'] = open(upath(__file__), 'rb')
response = self.client.post(self.wizard_url, post_data) response = self.client.post(self.wizard_url, post_data)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['wizard']['steps'].current, 'form3') self.assertEqual(response.context['wizard']['steps'].current, 'form3')
@ -287,7 +292,7 @@ class WizardTestKwargs(TestCase):
self.wizard_step_data[0]['form1-user'] = self.testuser.pk self.wizard_step_data[0]['form1-user'] = self.testuser.pk
def test_template(self): def test_template(self):
templates = os.path.join(os.path.dirname(__file__), 'templates') templates = os.path.join(os.path.dirname(upath(__file__)), 'templates')
with self.settings( with self.settings(
TEMPLATE_DIRS=list(settings.TEMPLATE_DIRS) + [templates]): TEMPLATE_DIRS=list(settings.TEMPLATE_DIRS) + [templates]):
response = self.client.get(self.wizard_url) response = self.client.get(self.wizard_url)

View File

@ -69,7 +69,9 @@ class BaseStorage(object):
wizard_files = self.data[self.step_files_key].get(step, {}) wizard_files = self.data[self.step_files_key].get(step, {})
if wizard_files and not self.file_storage: if wizard_files and not self.file_storage:
raise NoFileStorageConfigured raise NoFileStorageConfigured(
"You need to define 'file_storage' in your "
"wizard view in order to handle file uploads.")
files = {} files = {}
for field, field_dict in six.iteritems(wizard_files): for field, field_dict in six.iteritems(wizard_files):
@ -81,7 +83,9 @@ class BaseStorage(object):
def set_step_files(self, step, files): def set_step_files(self, step, files):
if files and not self.file_storage: if files and not self.file_storage:
raise NoFileStorageConfigured raise NoFileStorageConfigured(
"You need to define 'file_storage' in your "
"wizard view in order to handle file uploads.")
if step not in self.data[self.step_files_key]: if step not in self.data[self.step_files_key]:
self.data[self.step_files_key][step] = {} self.data[self.step_files_key][step] = {}

View File

@ -174,7 +174,9 @@ class WizardView(TemplateView):
for field in six.itervalues(form.base_fields): for field in six.itervalues(form.base_fields):
if (isinstance(field, forms.FileField) and if (isinstance(field, forms.FileField) and
not hasattr(cls, 'file_storage')): not hasattr(cls, 'file_storage')):
raise NoFileStorageConfigured raise NoFileStorageConfigured(
"You need to define 'file_storage' in your "
"wizard view in order to handle file uploads.")
# build the kwargs for the wizardview instances # build the kwargs for the wizardview instances
kwargs['form_list'] = init_form_list kwargs['form_list'] = init_form_list
@ -436,8 +438,8 @@ class WizardView(TemplateView):
def get_all_cleaned_data(self): def get_all_cleaned_data(self):
""" """
Returns a merged dictionary of all step cleaned_data dictionaries. Returns a merged dictionary of all step cleaned_data dictionaries.
If a step contains a `FormSet`, the key will be prefixed with formset If a step contains a `FormSet`, the key will be prefixed with
and contain a list of the formset cleaned_data dictionaries. 'formset-' and contain a list of the formset cleaned_data dictionaries.
""" """
cleaned_data = {} cleaned_data = {}
for form_key in self.get_form_list(): for form_key in self.get_form_list():
@ -458,8 +460,8 @@ class WizardView(TemplateView):
def get_cleaned_data_for_step(self, step): def get_cleaned_data_for_step(self, step):
""" """
Returns the cleaned data for a given `step`. Before returning the Returns the cleaned data for a given `step`. Before returning the
cleaned data, the stored values are being revalidated through the cleaned data, the stored values are revalidated through the form.
form. If the data doesn't validate, None will be returned. If the data doesn't validate, None will be returned.
""" """
if step in self.form_list: if step in self.form_list:
form_obj = self.get_form(step=step, form_obj = self.get_form(step=step,
@ -528,7 +530,7 @@ class WizardView(TemplateView):
context.update({'another_var': True}) context.update({'another_var': True})
return context return context
""" """
context = super(WizardView, self).get_context_data(**kwargs) context = super(WizardView, self).get_context_data(form=form, **kwargs)
context.update(self.storage.extra_data) context.update(self.storage.extra_data)
context['wizard'] = { context['wizard'] = {
'form': form, 'form': form,

View File

@ -7,29 +7,7 @@ class GeoSQLCompiler(BaseGeoSQLCompiler, SQLCompiler):
pass pass
class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler): class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler):
def placeholder(self, field, val): pass
if field is None:
# A field value of None means the value is raw.
return val
elif hasattr(field, 'get_placeholder'):
# Some fields (e.g. geo fields) need special munging before
# they can be inserted.
ph = field.get_placeholder(val, self.connection)
if ph == 'NULL':
# If the placeholder returned is 'NULL', then we need to
# to remove None from the Query parameters. Specifically,
# cx_Oracle will assume a CHAR type when a placeholder ('%s')
# is used for columns of MDSYS.SDO_GEOMETRY. Thus, we use
# 'NULL' for the value, and remove None from the query params.
# See also #10888.
param_idx = self.query.columns.index(field.column)
params = list(self.query.params)
params.pop(param_idx)
self.query.params = tuple(params)
return ph
else:
# Return the common case for the placeholder
return '%s'
class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler): class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler):
pass pass

View File

@ -288,3 +288,12 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
def spatial_ref_sys(self): def spatial_ref_sys(self):
from django.contrib.gis.db.backends.oracle.models import SpatialRefSys from django.contrib.gis.db.backends.oracle.models import SpatialRefSys
return SpatialRefSys return SpatialRefSys
def modify_insert_params(self, placeholders, params):
"""Drop out insert parameters for NULL placeholder. Needed for Oracle Spatial
backend due to #10888
"""
# This code doesn't work for bulk insert cases.
assert len(placeholders) == 1
return [[param for pholder,param
in six.moves.zip(placeholders[0], params[0]) if pholder != 'NULL'], ]

View File

@ -1,12 +1,23 @@
from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation
from django.utils.functional import cached_property
class PostGISCreation(DatabaseCreation): class PostGISCreation(DatabaseCreation):
geom_index_type = 'GIST' geom_index_type = 'GIST'
geom_index_ops = 'GIST_GEOMETRY_OPS' geom_index_ops = 'GIST_GEOMETRY_OPS'
geom_index_ops_nd = 'GIST_GEOMETRY_OPS_ND' geom_index_ops_nd = 'GIST_GEOMETRY_OPS_ND'
@cached_property
def template_postgis(self):
template_postgis = getattr(settings, 'POSTGIS_TEMPLATE', 'template_postgis')
cursor = self.connection.cursor()
cursor.execute('SELECT 1 FROM pg_database WHERE datname = %s LIMIT 1;', (template_postgis,))
if cursor.fetchone():
return template_postgis
return None
def sql_indexes_for_field(self, model, f, style): def sql_indexes_for_field(self, model, f, style):
"Return any spatial index creation SQL for the field." "Return any spatial index creation SQL for the field."
from django.contrib.gis.db.models.fields import GeometryField from django.contrib.gis.db.models.fields import GeometryField
@ -67,5 +78,19 @@ class PostGISCreation(DatabaseCreation):
return output return output
def sql_table_creation_suffix(self): def sql_table_creation_suffix(self):
postgis_template = getattr(settings, 'POSTGIS_TEMPLATE', 'template_postgis') if self.template_postgis is not None:
return ' TEMPLATE %s' % self.connection.ops.quote_name(postgis_template) return ' TEMPLATE %s' % (
self.connection.ops.quote_name(self.template_postgis),)
return ''
def _create_test_db(self, verbosity, autoclobber):
test_database_name = super(PostGISCreation, self)._create_test_db(verbosity, autoclobber)
if self.template_postgis is None:
# Connect to the test database in order to create the postgis extension
self.connection.close()
self.connection.settings_dict["NAME"] = test_database_name
cursor = self.connection.cursor()
cursor.execute("CREATE EXTENSION postgis")
cursor.connection.commit()
return test_database_name

View File

@ -36,29 +36,23 @@ class DatabaseWrapper(SQLiteDatabaseWrapper):
self.creation = SpatiaLiteCreation(self) self.creation = SpatiaLiteCreation(self)
self.introspection = SpatiaLiteIntrospection(self) self.introspection = SpatiaLiteIntrospection(self)
def _cursor(self): def get_new_connection(self, conn_params):
if self.connection is None: conn = super(DatabaseWrapper, self).get_new_connection(conn_params)
self._sqlite_create_connection() # Enabling extension loading on the SQLite connection.
try:
## From here on, customized for GeoDjango ## conn.enable_load_extension(True)
except AttributeError:
# Enabling extension loading on the SQLite connection. raise ImproperlyConfigured(
try: 'The pysqlite library does not support C extension loading. '
self.connection.enable_load_extension(True) 'Both SQLite and pysqlite must be configured to allow '
except AttributeError: 'the loading of extensions to use SpatiaLite.')
raise ImproperlyConfigured('The pysqlite library does not support C extension loading. ' # Loading the SpatiaLite library extension on the connection, and returning
'Both SQLite and pysqlite must be configured to allow ' # the created cursor.
'the loading of extensions to use SpatiaLite.' cur = conn.cursor(factory=SQLiteCursorWrapper)
) try:
cur.execute("SELECT load_extension(%s)", (self.spatialite_lib,))
# Loading the SpatiaLite library extension on the connection, and returning except Exception as msg:
# the created cursor. raise ImproperlyConfigured('Unable to load the SpatiaLite library extension '
cur = self.connection.cursor(factory=SQLiteCursorWrapper) '"%s" because: %s' % (self.spatialite_lib, msg))
try: cur.close()
cur.execute("SELECT load_extension(%s)", (self.spatialite_lib,)) return conn
except Exception as msg:
raise ImproperlyConfigured('Unable to load the SpatiaLite library extension '
'"%s" because: %s' % (self.spatialite_lib, msg))
return cur
else:
return self.connection.cursor(factory=SQLiteCursorWrapper)

View File

@ -760,8 +760,10 @@ class GeoQuerySet(QuerySet):
self.query.add_select_related([field_name]) self.query.add_select_related([field_name])
compiler = self.query.get_compiler(self.db) compiler = self.query.get_compiler(self.db)
compiler.pre_sql_setup() compiler.pre_sql_setup()
rel_table, rel_col = self.query.related_select_cols[self.query.related_select_fields.index(geo_field)] for (rel_table, rel_col), field in self.query.related_select_cols:
return compiler._field_column(geo_field, rel_table) if field == geo_field:
return compiler._field_column(geo_field, rel_table)
raise ValueError("%r not in self.query.related_select_cols" % geo_field)
elif not geo_field in opts.local_fields: elif not geo_field in opts.local_fields:
# This geographic field is inherited from another model, so we have to # This geographic field is inherited from another model, so we have to
# use the db table for the _parent_ model instead. # use the db table for the _parent_ model instead.

View File

@ -39,7 +39,7 @@ class GeoSQLCompiler(compiler.SQLCompiler):
if self.query.select: if self.query.select:
only_load = self.deferred_to_columns() only_load = self.deferred_to_columns()
# This loop customized for GeoQuery. # This loop customized for GeoQuery.
for col, field in zip(self.query.select, self.query.select_fields): for col, field in self.query.select:
if isinstance(col, (list, tuple)): if isinstance(col, (list, tuple)):
alias, column = col alias, column = col
table = self.query.alias_map[alias].table_name table = self.query.alias_map[alias].table_name
@ -85,7 +85,7 @@ class GeoSQLCompiler(compiler.SQLCompiler):
]) ])
# This loop customized for GeoQuery. # This loop customized for GeoQuery.
for (table, col), field in zip(self.query.related_select_cols, self.query.related_select_fields): for (table, col), field in self.query.related_select_cols:
r = self.get_field_select(field, table, col) r = self.get_field_select(field, table, col)
if with_aliases and col in col_aliases: if with_aliases and col in col_aliases:
c_alias = 'Col%d' % len(col_aliases) c_alias = 'Col%d' % len(col_aliases)
@ -101,7 +101,7 @@ class GeoSQLCompiler(compiler.SQLCompiler):
return result return result
def get_default_columns(self, with_aliases=False, col_aliases=None, def get_default_columns(self, with_aliases=False, col_aliases=None,
start_alias=None, opts=None, as_pairs=False, local_only=False): start_alias=None, opts=None, as_pairs=False, from_parent=None):
""" """
Computes the default columns for selecting every field in the base Computes the default columns for selecting every field in the base
model. Will sometimes be called to pull in related models (e.g. via model. Will sometimes be called to pull in related models (e.g. via
@ -127,7 +127,7 @@ class GeoSQLCompiler(compiler.SQLCompiler):
if start_alias: if start_alias:
seen = {None: start_alias} seen = {None: start_alias}
for field, model in opts.get_fields_with_model(): for field, model in opts.get_fields_with_model():
if local_only and model is not None: if from_parent and model is not None and issubclass(from_parent, model):
continue continue
if start_alias: if start_alias:
try: try:

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