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
Danilo Bargen
Shai Berger <shai@platonix.com>
berto
Julian Bez
Arvis Bickovskis <viestards.lists@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>
Owen Griffiths
Espen Grindhaug <http://grindhaug.org/>
Mike Grouchy <http://mikegrouchy.com/>
Janos Guljas
Thomas Güttler <hv@tbz-pariv.de>
Horst Gutmann <zerok@zerokspot.com>
@ -380,6 +382,7 @@ answer newbie questions, and generally made Django that much better:
Christian Metts
michal@plovarna.cz
Slawek Mikula <slawek dot mikula at gmail dot com>
Katie Miller <katie@sub50.com>
Shawn Milochik <shawn@milochik.com>
mitakummaa@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>
Nowell Strite <http://nowell.strite.org/>
Thomas Stromberg <tstromberg@google.com>
Ben Sturmfels <ben@sturm.com.au>
Travis Swicegood <travis@domain51.com>
Pascal Varet
SuperJared
@ -528,6 +532,7 @@ answer newbie questions, and generally made Django that much better:
Terry Huang <terryh.tp@gmail.com>
Travis Terry <tdterry7@gmail.com>
thebjorn <bp@datakortet.no>
Lowe Thiderman <lowe.thiderman@gmail.com>
Zach Thompson <zthompson47@gmail.com>
Michael Thornhill <michael.thornhill@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>
Jarek Zgoda <jarek.zgoda@gmail.com>
Cheng Zhang
Hannes Struß <x@hannesstruss.de>
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):
# 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).
"""
import warnings
from django.core import management
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.
"""
import logging
import os
import time # Needed for Windows
import warnings
@ -55,6 +56,15 @@ class LazySettings(LazyObject):
"""
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:
from django.utils.log import DEFAULT_LOGGING
# First find the logging configuration function ...
@ -83,6 +93,7 @@ class LazySettings(LazyObject):
for name, value in options.items():
setattr(holder, name, value)
self._wrapped = holder
self._configure_logging()
@property
def configured(self):
@ -99,9 +110,6 @@ class BaseSettings(object):
def __setattr__(self, name, value):
if name in ("MEDIA_URL", "STATIC_URL") and value and not value.endswith('/'):
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):
raise ValueError("The ALLOWED_INCLUDE_ROOTS setting must be set "
"to a tuple, not a string.")

View File

@ -150,12 +150,8 @@ SERVER_EMAIL = 'root@localhost'
# Whether to send broken-link emails.
SEND_BROKEN_LINK_EMAILS = False
# Database connection info.
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.dummy',
},
}
# Database connection info. If left empty, will default to the dummy backend.
DATABASES = {}
# Classes used to implement DB routing behavior.
DATABASE_ROUTERS = []
@ -449,6 +445,7 @@ MIDDLEWARE_CLASSES = (
# 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_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.

View File

@ -1,5 +1,8 @@
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 = {
'ar': {
'bidi': True,
@ -53,7 +56,7 @@ LANG_INFO = {
'bidi': False,
'code': 'da',
'name': 'Danish',
'name_local': 'Dansk',
'name_local': 'dansk',
},
'de': {
'bidi': False,
@ -137,7 +140,7 @@ LANG_INFO = {
'bidi': False,
'code': 'fr',
'name': 'French',
'name_local': 'Fran\xe7ais',
'name_local': 'fran\xe7ais',
},
'fy-nl': {
'bidi': False,
@ -269,7 +272,7 @@ LANG_INFO = {
'bidi': False,
'code': 'nb',
'name': 'Norwegian Bokmal',
'name_local': 'Norsk (bokm\xe5l)',
'name_local': 'norsk (bokm\xe5l)',
},
'ne': {
'bidi': False,
@ -287,13 +290,13 @@ LANG_INFO = {
'bidi': False,
'code': 'nn',
'name': 'Norwegian Nynorsk',
'name_local': 'Norsk (nynorsk)',
'name_local': 'norsk (nynorsk)',
},
'no': {
'bidi': False,
'code': 'no',
'name': 'Norwegian',
'name_local': 'Norsk',
'name_local': 'norsk',
},
'pa': {
'bidi': False,
@ -365,7 +368,7 @@ LANG_INFO = {
'bidi': False,
'code': 'sv',
'name': 'Swedish',
'name_local': 'Svenska',
'name_local': 'svenska',
},
'sw': {
'bidi': False,

View File

@ -19,10 +19,6 @@ DATE_INPUT_FORMATS = (
# '31/12/2009', '31/12/09'
'%d/%m/%Y', '%d/%m/%y'
)
TIME_INPUT_FORMATS = (
# '14:30:59', '14:30'
'%H:%M:%S', '%H:%M'
)
DATETIME_INPUT_FORMATS = (
'%d/%m/%Y %H:%M:%S',
'%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,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
DATE_INPUT_FORMATS = (
'%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.%m.%Y', '%d.%m.%y', # '05.01.2006', '05.01.06'
'%d. %m. %Y', '%d. %m. %y', # '5. 1. 2006', '5. 1. 06'
# '%d. %B %Y', '%d. %b. %Y', # '25. October 2006', '25. Oct. 2006'
)
# Kept ISO formats as one is in first position
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
'%H:%M:%S', # '04:30:59'
'%H.%M', # '04.30'
'%H:%M', # '04:30'
)
DATETIME_INPUT_FORMATS = (
'%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', # '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'
'%d.%m.%Y %H:%M:%S', # '05.01.2006 04:30:59'
'%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'
'%d. %m. %Y %H:%M:%S', # '05. 01. 2006 04:30:59'
'%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 = ','
THOUSAND_SEPARATOR = '\xa0' # non-breaking space

View File

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

View File

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

View File

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

View File

@ -19,13 +19,8 @@ FIRST_DAY_OF_WEEK = 1 # Monday
DATE_INPUT_FORMATS = (
'%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'
'%Y-%m-%d', '%y-%m-%d', # '2006-10-25', '06-10-25'
# '%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 = (
'%d/%m/%Y %H:%M:%S', # '25/10/2006 14:30:59'
'%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', # Swiss (fr_CH), '25.10.2006 14:30'
'%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 = ','
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,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
# Kept ISO formats as they are in first position
DATE_INPUT_FORMATS = (
'%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.'
)
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%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', '%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 = (
'%d/%m/%Y %H:%M:%S', # '25/10/2006 14:30:59'
'%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,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
# Kept ISO formats as they are in first position
DATE_INPUT_FORMATS = (
'%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', # '25 October 2006', '25 October, 2006'
# '%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 = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
'%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,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
# Kept ISO formats as they are in first position
DATE_INPUT_FORMATS = (
'%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'

View File

@ -16,6 +16,7 @@ FIRST_DAY_OF_WEEK = 1 #Monday
# The *_INPUT_FORMATS strings use the Python strftime format syntax,
# see http://docs.python.org/library/datetime.html#strftime-strptime-behavior
# Kept ISO formats as they are in first position
DATE_INPUT_FORMATS = (
'%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 = (
'%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 = (
@ -39,9 +33,6 @@ DATETIME_INPUT_FORMATS = (
'%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.', # '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 = ','

View File

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

View File

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

View File

@ -19,12 +19,7 @@ FIRST_DAY_OF_WEEK = 1 # Monday
DATE_INPUT_FORMATS = (
'%d.%m.%Y', # '25.10.2006'
'%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 = (
'%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59'
'%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', # '25.10.06 14:30'
'%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 = ','
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
DATE_INPUT_FORMATS = (
'%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'
)
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = (
'%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', # '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 = ','
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'
)
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = (
'%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59'
'%d.%m.%Y %H:%M', # '25.10.2006 14:30'

View File

@ -18,15 +18,10 @@ FIRST_DAY_OF_WEEK = 1
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'
# '%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.'
)
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = (
'%d.%m.%Y. %H:%M:%S', # '25.10.2006. 14:30:59'
'%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', # '25. 10. 06. 14:30'
'%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 = ','
THOUSAND_SEPARATOR = '.'

View File

@ -18,15 +18,10 @@ FIRST_DAY_OF_WEEK = 1
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'
# '%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.'
)
TIME_INPUT_FORMATS = (
'%H:%M:%S', # '14:30:59'
'%H:%M', # '14:30'
)
DATETIME_INPUT_FORMATS = (
'%d.%m.%Y. %H:%M:%S', # '25.10.2006. 14:30:59'
'%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', # '25. 10. 06. 14:30'
'%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 = ','
THOUSAND_SEPARATOR = '.'

View File

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

View File

@ -75,7 +75,7 @@ STATICFILES_DIRS = (
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'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.
@ -85,7 +85,7 @@ SECRET_KEY = '{{ secret_key }}'
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
# 'django.template.loaders.eggs.Loader',
)
MIDDLEWARE_CLASSES = (

View File

@ -9,7 +9,7 @@ import datetime
from django.db import models
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 import timezone
from django.contrib.admin.util import (get_model_from_relation,
@ -102,7 +102,7 @@ class SimpleListFilter(ListFilter):
}
for lookup, title in self.lookup_choices:
yield {
'selected': self.value() == lookup,
'selected': self.value() == force_text(lookup),
'query_string': cl.get_query_string({
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.utils.translation import ugettext_lazy
ERROR_MESSAGE = ugettext_lazy("Please enter the correct username and password "
"for a staff account. Note that both fields are case-sensitive.")
ERROR_MESSAGE = ugettext_lazy("Please enter the correct %(username)s and password "
"for a staff account. Note that both fields may be case-sensitive.")
class AdminAuthenticationForm(AuthenticationForm):
@ -26,8 +26,12 @@ class AdminAuthenticationForm(AuthenticationForm):
if username and password:
self.user_cache = authenticate(username=username, password=password)
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:
raise forms.ValidationError(message)
raise forms.ValidationError(message % {
'username': self.username_field.verbose_name
})
self.check_for_test_cookie()
return self.cleaned_data

View File

@ -186,9 +186,7 @@ class AdminReadonlyField(object):
if getattr(attr, "allow_tags", False):
result_repr = mark_safe(result_repr)
else:
if value is None:
result_repr = EMPTY_CHANGELIST_VALUE
elif isinstance(f.rel, ManyToManyRel):
if isinstance(f.rel, ManyToManyRel) and value is not None:
result_repr = ", ".join(map(six.text_type, value.all()))
else:
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.contrib.contenttypes.models import ContentType
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 python_2_unicode_compatible
@ -42,13 +42,16 @@ class LogEntry(models.Model):
def __str__(self):
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:
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:
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):
return self.action_flag == ADDITION

View File

@ -1,3 +1,4 @@
import copy
from functools import update_wrapper, partial
import warnings
@ -130,7 +131,7 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
# passed to formfield_for_dbfield override the defaults.
for klass in db_field.__class__.mro():
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)
# For any other type of field, just call its formfield() method.
@ -407,8 +408,6 @@ class ModelAdmin(BaseModelAdmin):
js.append('actions%s.js' % extra)
if self.prepopulated_fields:
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])
def get_model_perms(self, request):
@ -552,7 +551,7 @@ class ModelAdmin(BaseModelAdmin):
"""
from django.contrib.admin.models import LogEntry, DELETION
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,
object_id = object.pk,
object_repr = object_repr,
@ -665,6 +664,13 @@ class ModelAdmin(BaseModelAdmin):
# Use only the first item in list_display as link
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):
"""
Construct a change message from a changed object.
@ -691,12 +697,30 @@ class ModelAdmin(BaseModelAdmin):
change_message = ' '.join(change_message)
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
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):
"""
@ -738,7 +762,6 @@ class ModelAdmin(BaseModelAdmin):
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
opts = self.model._meta
app_label = opts.app_label
ordered_objects = opts.get_ordered_objects()
context.update({
'add': add,
'change': change,
@ -747,7 +770,6 @@ class ModelAdmin(BaseModelAdmin):
'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_absolute_url': hasattr(self.model, 'get_absolute_url'),
'ordered_objects': ordered_objects,
'form_url': form_url,
'opts': opts,
'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_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
actions = self.get_actions(request)
@ -1184,7 +1207,7 @@ class ModelAdmin(BaseModelAdmin):
ChangeList = self.get_changelist(request)
try:
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.list_per_page, self.list_max_show_all, self.list_editable,
self)

View File

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

View File

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

View File

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

View File

@ -225,6 +225,21 @@ table p.datetime {
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 */
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" %}
{% load i18n admin_static admin_modify %}
{% load i18n admin_static %}
{% load admin_urls %}
{% block extrahead %}{{ block.super }}
@ -13,7 +13,7 @@
<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 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' %}
</div>
{% 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 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 %}

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
{% load i18n admin_urls %}
<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_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" {{ onclick_attrib }}/>{%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_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" {{ 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" />{% endif %}
{% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" />{% endif %}
</div>

View File

@ -30,8 +30,6 @@ def submit_row(context):
save_as = context['save_as']
ctx = {
'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']
and change and context.get('show_delete', True)),
'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.forms.widgets import RadioFieldRenderer
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.translation import ugettext as _
from django.utils.safestring import mark_safe
@ -306,6 +306,19 @@ class AdminURLFieldWidget(forms.TextInput):
final_attrs.update(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_name = 'vIntegerField'

View File

@ -14,6 +14,7 @@ from django.core import urlresolvers
from django.contrib.admindocs import utils
from django.contrib.sites.models import Site
from django.utils.importlib import import_module
from django.utils._os import upath
from django.utils import six
from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
@ -311,7 +312,7 @@ def load_all_installed_template_libraries():
try:
libraries = [
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()
]
except OSError:

View File

@ -1,6 +1,6 @@
import re
from django.core.exceptions import ImproperlyConfigured
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.utils.importlib import import_module
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
@ -60,6 +60,9 @@ def authenticate(**credentials):
except TypeError:
# This backend doesn't accept these credentials as arguments. Try the next one.
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:
continue
# Annotate the user object with the path of the backend.
@ -81,14 +84,14 @@ def login(request, user):
user = request.user
# TODO: It would be nice to support different login methods, like signed cookies.
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
# session if the existing session corresponds to a different
# authenticated user.
request.session.flush()
else:
request.session.cycle_key()
request.session[SESSION_KEY] = user.id
request.session[SESSION_KEY] = user.pk
request.session[BACKEND_SESSION_KEY] = user.backend
if hasattr(request, 'user'):
request.user = user

View File

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

View File

@ -18,7 +18,9 @@ class PermLookupDict(object):
def __bool__(self):
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):

View File

@ -27,7 +27,7 @@ class ReadOnlyPasswordHashWidget(forms.Widget):
encoded = value
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."))
else:
try:
@ -52,6 +52,11 @@ class ReadOnlyPasswordHashField(forms.Field):
kwargs.setdefault("required", False)
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):
"""
@ -143,8 +148,8 @@ class AuthenticationForm(forms.Form):
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
error_messages = {
'invalid_login': _("Please enter a correct username and password. "
"Note that both fields are case-sensitive."),
'invalid_login': _("Please enter a correct %(username)s and password. "
"Note that both fields may be case-sensitive."),
'no_cookies': _("Your Web browser doesn't appear to have cookies "
"enabled. Cookies are required for logging in."),
'inactive': _("This account is inactive."),
@ -163,8 +168,8 @@ class AuthenticationForm(forms.Form):
# Set the label for the "username" field.
UserModel = get_user_model()
username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
self.fields['username'].label = capfirst(username_field.verbose_name)
self.username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
self.fields['username'].label = capfirst(self.username_field.verbose_name)
def clean(self):
username = self.cleaned_data.get('username')
@ -175,7 +180,9 @@ class AuthenticationForm(forms.Form):
password=password)
if self.user_cache is None:
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:
raise forms.ValidationError(self.error_messages['inactive'])
self.check_for_test_cookie()
@ -209,10 +216,12 @@ class PasswordResetForm(forms.Form):
"""
UserModel = get_user_model()
email = self.cleaned_data["email"]
self.users_cache = UserModel.objects.filter(email__iexact=email,
is_active=True)
self.users_cache = UserModel.objects.filter(email__iexact=email)
if not len(self.users_cache):
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)
for user in self.users_cache):
raise forms.ValidationError(self.error_messages['unusable'])
@ -239,7 +248,7 @@ class PasswordResetForm(forms.Form):
'email': user.email,
'domain': domain,
'site_name': site_name,
'uid': int_to_base36(user.id),
'uid': int_to_base36(user.pk),
'user': user,
'token': token_generator.make_token(user),
'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)
except UserModel.DoesNotExist:
return None
try:
if not user.is_active:
return None
except AttributeError as e:
# a custom user may not support is_active
if not user.is_active:
return None
return user.check_password(password)
finally:
db.close_connection()
def groups_for_user(environ, username):
"""
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.core import exceptions
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.utils import six
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__))
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
app_models = get_models(app)
@ -68,7 +72,9 @@ def create_permissions(app, created_models, verbosity, **kwargs):
# The codenames and ctypes that should exist.
ctypes = set()
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)
for perm in _get_all_permissions(klass._meta, ctype):
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
# looking for. We don't need to check for codenames since we already have
# 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,
).values_list(
"content_type", "codename"
))
objs = [
perms = [
auth_app.Permission(codename=codename, name=name, content_type=ctype)
for ctype, (codename, name) in searched_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:
for obj in objs:
print("Adding permission '%s'" % obj)
for perm in perms:
print("Adding permission '%s'" % perm)
def create_superuser(app, created_models, verbosity, db, **kwargs):

View File

@ -1,4 +1,6 @@
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.utils.functional import SimpleLazyObject
@ -47,9 +49,18 @@ class RemoteUserMiddleware(object):
try:
username = request.META[self.header]
except KeyError:
# If specified header doesn't exist then return (leaving
# request.user set to AnonymousUser by the
# AuthenticationMiddleware).
# If specified header doesn't exist then remove any existing
# authenticated remote-user, or return (leaving request.user set to
# 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
# 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

View File

@ -195,43 +195,13 @@ class UserManager(BaseUserManager):
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
class AbstractBaseUser(models.Model):
password = models.CharField(_('password'), max_length=128)
last_login = models.DateTimeField(_('last login'), default=timezone.now)
is_active = True
REQUIRED_FIELDS = []
class Meta:
@ -288,32 +258,46 @@ class AbstractBaseUser(models.Model):
raise NotImplementedError()
class AbstractUser(AbstractBaseUser):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
# 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
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,
help_text=_('Designates that this user has all permissions without '
'explicitly assigning them.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
groups = models.ManyToManyField(Group, verbose_name=_('groups'),
blank=True, help_text=_('The groups this user belongs to. A user will '
'get all permissions granted to each of '
@ -322,30 +306,9 @@ class AbstractUser(AbstractBaseUser):
verbose_name=_('user permissions'), blank=True,
help_text='Specific permissions for this user.')
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 get_group_permissions(self, obj=None):
"""
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)
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):
"""
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 *
# 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.contrib.auth.models import User, Group, Permission, AnonymousUser
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.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.utils import override_settings
@ -33,7 +34,7 @@ class BaseModelBackendTest(object):
ContentType.objects.clear_cache()
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)
user.is_staff = True
user.save()
@ -52,14 +53,14 @@ class BaseModelBackendTest(object):
self.assertEqual(user.has_perm('auth.test'), False)
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)
perm = Permission.objects.create(name='test', content_type=content_type, codename='test')
user.user_permissions.add(perm)
user.save()
# 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_group_permissions(), set([]))
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')
user.user_permissions.add(perm)
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.has_perm('test'), False)
self.assertEqual(user.has_perm('auth.test'), True)
@ -80,7 +81,7 @@ class BaseModelBackendTest(object):
group.permissions.add(perm)
group.save()
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'])
self.assertEqual(user.get_all_permissions(), exp)
self.assertEqual(user.get_group_permissions(), set(['auth.test_group']))
@ -92,7 +93,7 @@ class BaseModelBackendTest(object):
def test_has_no_object_perm(self):
"""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)
perm = Permission.objects.create(name='test', content_type=content_type, codename='test')
user.user_permissions.add(perm)
@ -105,7 +106,7 @@ class BaseModelBackendTest(object):
def test_get_all_superuser_permissions(self):
"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()))
@ -117,12 +118,12 @@ class ModelBackendTest(BaseModelBackendTest, TestCase):
UserModel = User
def create_users(self):
User.objects.create_user(
self.user = User.objects.create_user(
username='test',
email='test@example.com',
password='test',
)
User.objects.create_superuser(
self.superuser = User.objects.create_superuser(
username='test2',
email='test2@example.com',
password='test',
@ -150,13 +151,13 @@ class ExtensionUserModelBackendTest(BaseModelBackendTest, TestCase):
UserModel = ExtensionUser
def create_users(self):
ExtensionUser.objects.create_user(
self.user = ExtensionUser.objects.create_user(
username='test',
email='test@example.com',
password='test',
date_of_birth=date(2006, 4, 25)
)
ExtensionUser.objects.create_superuser(
self.superuser = ExtensionUser.objects.create_superuser(
username='test2',
email='test2@example.com',
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):
pass
@ -323,3 +349,38 @@ class InActiveUserBackendTest(TestCase):
def test_has_module_perms(self):
self.assertEqual(self.user1.has_module_perms("app1"), 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):
"The current user model can be swapped out for another"
self.assertEqual(get_user_model(), CustomUser)
with self.assertRaises(AttributeError):
User.objects.all()
@override_settings(AUTH_USER_MODEL='badsetting')
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.test import TestCase
from django.test.utils import override_settings
from django.utils._os import upath
class MockUser(object):
@ -63,7 +64,7 @@ class PermWrapperTests(TestCase):
@skipIfCustomUser
@override_settings(
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
PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),

View File

@ -1,5 +1,11 @@
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
@ -88,3 +94,53 @@ class ExtensionUser(AbstractUser):
class Meta:
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
from django.contrib.auth.models import User
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.core import mail
from django.forms.fields import Field, EmailField
from django.test import TestCase
from django.test.utils import override_settings
from django.utils.encoding import force_text
from django.utils._os import upath
from django.utils import translation
from django.utils.translation import ugettext as _
@ -98,7 +100,9 @@ class AuthenticationFormTest(TestCase):
form = AuthenticationForm(None, data)
self.assertFalse(form.is_valid())
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):
# The user is inactive.
@ -282,6 +286,14 @@ class UserChangeFormTest(TestCase):
self.assertTrue(form.is_valid())
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
@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)
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,)):
data = {'email': 'testclient@example.com'}
form = PasswordResetForm(data)
@ -362,3 +374,13 @@ class PasswordResetFormTest(TestCase):
self.assertFalse(form.is_valid())
self.assertEqual(form["email"].errors,
[_("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.models import User, Group
from django.contrib.auth.tests import CustomUser
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TransactionTestCase
from django.test.utils import override_settings
class ModWsgiHandlerTestCase(TransactionTestCase):
"""
Tests for the mod_wsgi authentication handler
"""
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)
@skipIfCustomUser
def test_check_password(self):
"""
Verify that check_password returns the correct values as per
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
self.assertTrue(check_password({}, 'unknown', '') is None)
@ -33,15 +26,43 @@ class ModWsgiHandlerTestCase(TransactionTestCase):
# Valid user with correct password
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
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
def test_groups_for_user(self):
"""
Check that groups_for_user returns correct values as per
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
self.assertEqual(groups_for_user({}, 'unknown'), [])

View File

@ -1,4 +1,5 @@
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import (Group, User, SiteProfileNotAvailable,
UserManager)
from django.contrib.auth.tests.utils import skipIfCustomUser
@ -98,3 +99,36 @@ class UserManagerTestCase(TestCase):
self.assertRaisesMessage(ValueError,
'The given username must be set',
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 django.conf import settings
from django.contrib.auth import authenticate
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.test import TestCase
from django.utils import timezone
@ -23,7 +24,7 @@ class RemoteUserTest(TestCase):
self.curr_middleware = settings.MIDDLEWARE_CLASSES
self.curr_auth = settings.AUTHENTICATION_BACKENDS
settings.MIDDLEWARE_CLASSES += (self.middleware,)
settings.AUTHENTICATION_BACKENDS = (self.backend,)
settings.AUTHENTICATION_BACKENDS += (self.backend,)
def test_no_remote_user(self):
"""
@ -97,6 +98,26 @@ class RemoteUserTest(TestCase):
response = self.client.get('/remote_user/', REMOTE_USER=self.known_user)
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):
"""Restores settings to avoid breaking other tests."""
settings.MIDDLEWARE_CLASSES = self.curr_middleware

View File

@ -1,4 +1,4 @@
unicode: {{ user }}
id: {{ user.id }}
id: {{ user.pk }}
username: {{ user.username }}
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.html import escape
from django.utils.http import urlquote
from django.utils._os import upath
from django.test import TestCase
from django.test.utils import override_settings
@ -27,7 +28,7 @@ from django.contrib.auth.tests.utils import skipIfCustomUser
LANGUAGE_CODE='en',
TEMPLATE_LOADERS=global_settings.TEMPLATE_LOADERS,
TEMPLATE_DIRS=(
os.path.join(os.path.dirname(__file__), 'templates'),
os.path.join(os.path.dirname(upath(__file__)), 'templates'),
),
USE_TZ=False,
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.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):
"Poisoned HTTP_HOST headers can't be used for reset emails"
# 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)
# Skip any 500 handler action (like sending more mail...)
@override_settings(DEBUG_PROPAGATE_EXCEPTIONS=True)
def test_poisoned_http_host_admin_site(self):
"Poisoned HTTP_HOST headers can't be used for reset emails on admin views"
with self.assertRaises(SuspiciousOperation):
@ -243,7 +248,9 @@ class ChangePasswordTest(AuthViewsTestCase):
'username': 'testclient',
'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):
response = self.client.get('/logout/')

View File

@ -58,7 +58,7 @@ class PasswordResetTokenGenerator(object):
# Ensure results are consistent across DB backends
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))
hash = salted_hmac(key_salt, value).hexdigest()[::2]
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.http import HttpResponseRedirect, QueryDict
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.shortcuts import resolve_url
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":
form = authentication_form(data=request.POST)
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]
# Heavier security check -- don't allow redirection to a different
# host.
if netloc and netloc != request.get_host():
# Ensure the user-originating redirection url is safe.
if not is_safe_url(url=redirect_to, host=request.get_host()):
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())
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.
"""
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:
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)
else:
if redirect_field_name in request.REQUEST:
next_page = request.REQUEST[redirect_field_name]
# Security check -- don't allow redirection to a different host.
if not is_safe_url(url=next_page, host=request.get_host()):
next_page = request.path
if next_page:
# 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):
@ -206,7 +200,7 @@ def password_reset_confirm(request, uidb36=None, token=None,
post_reset_redirect = reverse('django.contrib.auth.views.password_reset_complete')
try:
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):
user = None

View File

@ -20,9 +20,9 @@ def get_comment_app():
# Try to import the package
try:
package = import_module(comments_app)
except ImportError:
except ImportError as e:
raise ImproperlyConfigured("The COMMENTS_APP setting refers to "\
"a non-existing package.")
"a non-existing package. (%s)" % e)
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
# 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
# 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,
# except that the HTTP request is sent along with this signal.
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', ''):
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
ctype = data.get("content_type")
object_pk = data.get("object_pk")
@ -100,7 +97,7 @@ def post_comment(request, next=None, using=None):
template_list, {
"comment": form.data.get("comment", ""),
"form": form,
"next": next,
"next": data.get("next", next),
},
RequestContext(request, {})
)
@ -131,7 +128,8 @@ def post_comment(request, next=None, using=None):
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(
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
@csrf_protect
@login_required
def flag(request, comment_id, next=None):
@ -27,7 +26,8 @@ def flag(request, comment_id, next=None):
# Flag on POST
if request.method == 'POST':
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
else:
@ -54,7 +54,8 @@ def delete(request, comment_id, next=None):
if request.method == 'POST':
# Flag the comment as deleted instead of actually deleting it.
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
else:
@ -81,7 +82,8 @@ def approve(request, comment_id, next=None):
if request.method == 'POST':
# Flag the comment as approved.
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
else:

View File

@ -9,25 +9,26 @@ except ImportError: # Python 2
from urllib import urlencode
from django.http import HttpResponseRedirect
from django.core import urlresolvers
from django.shortcuts import render_to_response
from django.shortcuts import render_to_response, resolve_url
from django.template import RequestContext
from django.core.exceptions import ObjectDoesNotExist
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.
The next value could be a kwarg to the function (``default``), or a
``?next=...`` GET arg, or the URL of a given view (``default_view``). See
The next value could be a
``?next=...`` GET arg or the URL of a given view (``fallback``). See
the view modules for examples.
Returns an ``HttpResponseRedirect``.
"""
next = data.get("next", default)
if next is None:
next = urlresolvers.reverse(default_view)
next = request.POST.get('next')
if not is_safe_url(url=next, host=request.get_host()):
next = resolve_url(fallback)
if get_kwargs:
if '#' in next:
tmp = next.rsplit('#', 1)

View File

@ -5,20 +5,19 @@ from __future__ import unicode_literals
from collections import defaultdict
from functools import partial
from operator import attrgetter
from django.core.exceptions import ObjectDoesNotExist
from django.db import connection
from django.db.models import signals
from django.db import models, router, DEFAULT_DB_ALIAS
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.models import BaseModelFormSet, modelformset_factory, save_instance
from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets
from django.contrib.contenttypes.models import ContentType
from django.utils.encoding import smart_text
class GenericForeignKey(object):
"""
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()
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:
return ContentType.objects.db_manager(obj._state.db).get_for_model(obj)
elif id:
@ -209,18 +205,16 @@ class GenericRelation(RelatedField, Field):
# same db_type as well.
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
on the appropriate content type.
Returns the content type associated with this field's model.
"""
if negate:
return []
ContentType = get_model("contenttypes", "contenttype")
content_type = ContentType.objects.get_for_model(self.model)
prefix = "__".join(pieces[:pos + 1])
return [("%s__%s" % (prefix, self.content_type_field_name),
content_type)]
return ContentType.objects.get_for_model(self.model)
def get_extra_join_sql(self, connection, qn, lhs_alias, rhs_alias):
extra_col = self.rel.to._meta.get_field_by_name(self.content_type_field_name)[0].column
contenttype = self.get_content_type().pk
return " AND %s.%s = %%s" % (qn(rhs_alias), qn(extra_col)), [contenttype]
def bulk_related_objects(self, objs, using=DEFAULT_DB_ALIAS):
"""
@ -251,9 +245,6 @@ class ReverseGenericRelatedObjectsDescriptor(object):
if instance is None:
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
# default manager.
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)
}
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,
attrgetter(self.object_id_field_name),
lambda relobj: object_id_converter(getattr(relobj, self.object_id_field_name)),
lambda obj: obj._get_pk_val(),
False,
self.prefetch_cache_name)
@ -381,8 +375,6 @@ class BaseGenericInlineFormSet(BaseModelFormSet):
def __init__(self, data=None, files=None, instance=None, save_as_new=None,
prefix=None, queryset=None):
# Avoid a circular import.
from django.contrib.contenttypes.models import ContentType
opts = self.model._meta
self.instance = instance
self.rel_name = '-'.join((
@ -411,8 +403,6 @@ class BaseGenericInlineFormSet(BaseModelFormSet):
))
def save_new(self, form, commit=True):
# Avoid a circular import.
from django.contrib.contenttypes.models import ContentType
kwargs = {
self.ct_field.get_attname(): ContentType.objects.get_for_model(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.
"""
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
ct_field = opts.get_field(ct_field)
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.db import DEFAULT_DB_ALIAS, router
from django.db.models import get_apps, get_models, signals
from django.utils.encoding import smart_text
from django.utils import six
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
entries that no longer have a matching model class.
"""
if not router.allow_syncdb(db, ContentType):
return
ContentType.objects.clear_cache()
app_models = get_models(app)
if not app_models:
@ -19,10 +24,11 @@ def update_contenttypes(app, created_models, verbosity=2, **kwargs):
(model._meta.object_name.lower(), model)
for model in app_models
)
# Get all the content types
content_types = dict(
(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 = [
ct
@ -30,7 +36,7 @@ def update_contenttypes(app, created_models, verbosity=2, **kwargs):
if model_name not in app_models
]
cts = ContentType.objects.bulk_create([
cts = [
ContentType(
name=smart_text(model._meta.verbose_name_raw),
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)
if model_name not in content_types
])
]
ContentType.objects.using(db).bulk_create(cts)
if verbosity >= 2:
for ct in cts:
print("Adding content type '%s | %s'" % (ct.app_label, ct.model))
@ -71,6 +78,7 @@ If you're unsure, answer 'no'.
if verbosity >= 2:
print("Stale content types remain.")
def update_all_contenttypes(verbosity=2, **kwargs):
for app in get_apps():
update_contenttypes(app, None, verbosity, **kwargs)

View File

@ -35,7 +35,7 @@ class FlatpageForm(forms.ModelForm):
for site in sites:
if same_url.filter(sites=site).exists():
raise forms.ValidationError(
_('Flatpage with url %(url)s already exists for site %(site)s' %
{'url': url, 'site': site}))
_('Flatpage with url %(url)s already exists for site %(site)s') %
{'url': url, 'site': site})
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.html import parse_html
from django.test.utils import override_settings
from django.utils._os import upath
from django.utils import unittest
from django.contrib.formtools.tests.wizard import *
@ -36,7 +37,7 @@ class TestFormPreview(preview.FormPreview):
@override_settings(
TEMPLATE_DIRS=(
os.path.join(os.path.dirname(__file__), 'templates'),
os.path.join(os.path.dirname(upath(__file__)), 'templates'),
),
)
class PreviewTests(TestCase):
@ -214,7 +215,7 @@ class DummyRequest(http.HttpRequest):
@override_settings(
SECRET_KEY="123",
TEMPLATE_DIRS=(
os.path.join(os.path.dirname(__file__), 'templates'),
os.path.join(os.path.dirname(upath(__file__)), 'templates'),
),
)
class WizardTests(TestCase):

View File

@ -9,6 +9,7 @@ from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.formtools.wizard.views import CookieWizardView
from django.contrib.formtools.tests.wizard.forms import UserForm, UserFormSet
from django.utils._os import upath
class WizardTests(object):
@ -72,6 +73,10 @@ class WizardTests(object):
self.assertEqual(response.context['wizard']['steps'].current, 'form2')
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):
response = self.client.get(self.wizard_url)
self.assertEqual(response.status_code, 200)
@ -82,7 +87,7 @@ class WizardTests(object):
self.assertEqual(response.context['wizard']['steps'].current, 'form2')
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)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['wizard']['steps'].current, 'form3')
@ -95,7 +100,7 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200)
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())
all_data[1]['file1'].close()
del all_data[1]['file1']
@ -114,7 +119,7 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200)
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
response = self.client.post(self.wizard_url, post_data)
self.assertEqual(response.status_code, 200)
@ -126,7 +131,7 @@ class WizardTests(object):
self.assertEqual(response.status_code, 200)
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())
all_data['file1'].close()
del all_data['file1']
@ -146,7 +151,7 @@ class WizardTests(object):
post_data = self.wizard_step_data[1]
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)
self.assertEqual(response.status_code, 200)
@ -174,7 +179,7 @@ class WizardTests(object):
post_data = self.wizard_step_data[1]
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)
self.assertEqual(response.status_code, 200)
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
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(
TEMPLATE_DIRS=list(settings.TEMPLATE_DIRS) + [templates]):
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, {})
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 = {}
for field, field_dict in six.iteritems(wizard_files):
@ -81,7 +83,9 @@ class BaseStorage(object):
def set_step_files(self, step, files):
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]:
self.data[self.step_files_key][step] = {}

View File

@ -174,7 +174,9 @@ class WizardView(TemplateView):
for field in six.itervalues(form.base_fields):
if (isinstance(field, forms.FileField) and
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
kwargs['form_list'] = init_form_list
@ -436,8 +438,8 @@ class WizardView(TemplateView):
def get_all_cleaned_data(self):
"""
Returns a merged dictionary of all step cleaned_data dictionaries.
If a step contains a `FormSet`, the key will be prefixed with formset
and contain a list of the formset cleaned_data dictionaries.
If a step contains a `FormSet`, the key will be prefixed with
'formset-' and contain a list of the formset cleaned_data dictionaries.
"""
cleaned_data = {}
for form_key in self.get_form_list():
@ -458,8 +460,8 @@ class WizardView(TemplateView):
def get_cleaned_data_for_step(self, step):
"""
Returns the cleaned data for a given `step`. Before returning the
cleaned data, the stored values are being revalidated through the
form. If the data doesn't validate, None will be returned.
cleaned data, the stored values are revalidated through the form.
If the data doesn't validate, None will be returned.
"""
if step in self.form_list:
form_obj = self.get_form(step=step,
@ -528,7 +530,7 @@ class WizardView(TemplateView):
context.update({'another_var': True})
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['wizard'] = {
'form': form,

View File

@ -7,29 +7,7 @@ class GeoSQLCompiler(BaseGeoSQLCompiler, SQLCompiler):
pass
class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler):
def placeholder(self, field, val):
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'
pass
class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler):
pass

View File

@ -288,3 +288,12 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
def spatial_ref_sys(self):
from django.contrib.gis.db.backends.oracle.models import 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.core.exceptions import ImproperlyConfigured
from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation
from django.utils.functional import cached_property
class PostGISCreation(DatabaseCreation):
geom_index_type = 'GIST'
geom_index_ops = 'GIST_GEOMETRY_OPS'
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):
"Return any spatial index creation SQL for the field."
from django.contrib.gis.db.models.fields import GeometryField
@ -67,5 +78,19 @@ class PostGISCreation(DatabaseCreation):
return output
def sql_table_creation_suffix(self):
postgis_template = getattr(settings, 'POSTGIS_TEMPLATE', 'template_postgis')
return ' TEMPLATE %s' % self.connection.ops.quote_name(postgis_template)
if self.template_postgis is not None:
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.introspection = SpatiaLiteIntrospection(self)
def _cursor(self):
if self.connection is None:
self._sqlite_create_connection()
## From here on, customized for GeoDjango ##
# Enabling extension loading on the SQLite connection.
try:
self.connection.enable_load_extension(True)
except AttributeError:
raise ImproperlyConfigured('The pysqlite library does not support C extension loading. '
'Both SQLite and pysqlite must be configured to allow '
'the loading of extensions to use SpatiaLite.'
)
# Loading the SpatiaLite library extension on the connection, and returning
# the created cursor.
cur = self.connection.cursor(factory=SQLiteCursorWrapper)
try:
cur.execute("SELECT load_extension(%s)", (self.spatialite_lib,))
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)
def get_new_connection(self, conn_params):
conn = super(DatabaseWrapper, self).get_new_connection(conn_params)
# Enabling extension loading on the SQLite connection.
try:
conn.enable_load_extension(True)
except AttributeError:
raise ImproperlyConfigured(
'The pysqlite library does not support C extension loading. '
'Both SQLite and pysqlite must be configured to allow '
'the loading of extensions to use SpatiaLite.')
# Loading the SpatiaLite library extension on the connection, and returning
# the created cursor.
cur = conn.cursor(factory=SQLiteCursorWrapper)
try:
cur.execute("SELECT load_extension(%s)", (self.spatialite_lib,))
except Exception as msg:
raise ImproperlyConfigured('Unable to load the SpatiaLite library extension '
'"%s" because: %s' % (self.spatialite_lib, msg))
cur.close()
return conn

View File

@ -760,8 +760,10 @@ class GeoQuerySet(QuerySet):
self.query.add_select_related([field_name])
compiler = self.query.get_compiler(self.db)
compiler.pre_sql_setup()
rel_table, rel_col = self.query.related_select_cols[self.query.related_select_fields.index(geo_field)]
return compiler._field_column(geo_field, rel_table)
for (rel_table, rel_col), field in self.query.related_select_cols:
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:
# This geographic field is inherited from another model, so we have to
# use the db table for the _parent_ model instead.

View File

@ -39,7 +39,7 @@ class GeoSQLCompiler(compiler.SQLCompiler):
if self.query.select:
only_load = self.deferred_to_columns()
# 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)):
alias, column = col
table = self.query.alias_map[alias].table_name
@ -85,7 +85,7 @@ class GeoSQLCompiler(compiler.SQLCompiler):
])
# 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)
if with_aliases and col in col_aliases:
c_alias = 'Col%d' % len(col_aliases)
@ -101,7 +101,7 @@ class GeoSQLCompiler(compiler.SQLCompiler):
return result
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
model. Will sometimes be called to pull in related models (e.g. via
@ -127,7 +127,7 @@ class GeoSQLCompiler(compiler.SQLCompiler):
if start_alias:
seen = {None: start_alias}
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
if start_alias:
try:

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