mirror of https://github.com/django/django.git
Merge branch 'master' into schema-alteration
Conflicts: django/db/backends/__init__.py django/db/models/fields/related.py django/db/models/options.py
This commit is contained in:
commit
6a632e0457
|
@ -1,4 +1,8 @@
|
|||
*.egg-info
|
||||
*.pot
|
||||
*.py[co]
|
||||
MANIFEST
|
||||
dist/
|
||||
docs/_build/
|
||||
tests/coverage_html/
|
||||
tests/.coverage
|
|
@ -4,3 +4,5 @@ syntax:glob
|
|||
*.pot
|
||||
*.py[co]
|
||||
docs/_build/
|
||||
tests/coverage_html/
|
||||
tests/.coverage
|
|
@ -1,5 +1,5 @@
|
|||
[main]
|
||||
host = https://www.transifex.net
|
||||
host = https://www.transifex.com
|
||||
lang_map = sr@latin:sr_Latn
|
||||
|
||||
[django.core]
|
||||
|
|
3
AUTHORS
3
AUTHORS
|
@ -33,6 +33,7 @@ The PRIMARY AUTHORS are (and/or have been):
|
|||
* Florian Apolloner
|
||||
* Jeremy Dunck
|
||||
* Bryan Veloso
|
||||
* Preston Holmes
|
||||
|
||||
More information on the main contributors to Django can be found in
|
||||
docs/internals/committers.txt.
|
||||
|
@ -424,6 +425,7 @@ answer newbie questions, and generally made Django that much better:
|
|||
phil@produxion.net
|
||||
phil.h.smith@gmail.com
|
||||
Gustavo Picon
|
||||
Travis Pinney
|
||||
Michael Placentra II <someone@michaelplacentra2.net>
|
||||
plisk
|
||||
Daniel Poelzleithner <http://poelzi.org/>
|
||||
|
@ -499,6 +501,7 @@ answer newbie questions, and generally made Django that much better:
|
|||
Wiliam Alves de Souza <wiliamsouza83@gmail.com>
|
||||
Don Spaulding <donspauldingii@gmail.com>
|
||||
Calvin Spealman <ironfroggy@gmail.com>
|
||||
Dane Springmeyer
|
||||
Bjørn Stabell <bjorn@exoweb.net>
|
||||
Georgi Stanojevski <glisha@gmail.com>
|
||||
starrynight <cmorgh@gmail.com>
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
======================
|
||||
Contributing to Django
|
||||
======================
|
||||
|
||||
As an open source project, Django welcomes contributions of many forms.
|
||||
|
||||
Examples of contributions include:
|
||||
|
||||
* Code patches
|
||||
* Documentation improvements
|
||||
* Bug reports and patch reviews
|
||||
|
||||
Extensive contribution guidelines are available in the repository at
|
||||
``docs/internals/contributing/``, or online at:
|
||||
|
||||
https://docs.djangoproject.com/en/dev/internals/contributing/
|
|
@ -32,3 +32,5 @@ recursive-include django/contrib/gis/tests/geogapp/fixtures *
|
|||
recursive-include django/contrib/gis/tests/relatedapp/fixtures *
|
||||
recursive-include django/contrib/sitemaps/templates *
|
||||
recursive-include django/contrib/sitemaps/tests/templates *
|
||||
recursive-exclude * __pycache__
|
||||
recursive-exclude * *.py[co]
|
||||
|
|
|
@ -25,7 +25,7 @@ class LazySettings(LazyObject):
|
|||
The user can manually configure settings prior to using them. Otherwise,
|
||||
Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE.
|
||||
"""
|
||||
def _setup(self, name):
|
||||
def _setup(self, name=None):
|
||||
"""
|
||||
Load the settings module pointed to by the environment variable. This
|
||||
is used the first time we need any settings at all, if the user has not
|
||||
|
@ -36,20 +36,40 @@ class LazySettings(LazyObject):
|
|||
if not settings_module: # If it's set but is an empty string.
|
||||
raise KeyError
|
||||
except KeyError:
|
||||
desc = ("setting %s" % name) if name else "settings"
|
||||
raise ImproperlyConfigured(
|
||||
"Requested setting %s, but settings are not configured. "
|
||||
"Requested %s, but settings are not configured. "
|
||||
"You must either define the environment variable %s "
|
||||
"or call settings.configure() before accessing settings."
|
||||
% (name, ENVIRONMENT_VARIABLE))
|
||||
% (desc, ENVIRONMENT_VARIABLE))
|
||||
|
||||
self._wrapped = Settings(settings_module)
|
||||
|
||||
self._configure_logging()
|
||||
|
||||
def __getattr__(self, name):
|
||||
if self._wrapped is empty:
|
||||
self._setup(name)
|
||||
return getattr(self._wrapped, name)
|
||||
|
||||
def _configure_logging(self):
|
||||
"""
|
||||
Setup logging from LOGGING_CONFIG and LOGGING settings.
|
||||
"""
|
||||
if self.LOGGING_CONFIG:
|
||||
from django.utils.log import DEFAULT_LOGGING
|
||||
# First find the logging configuration function ...
|
||||
logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1)
|
||||
logging_config_module = importlib.import_module(logging_config_path)
|
||||
logging_config_func = getattr(logging_config_module, logging_config_func_name)
|
||||
|
||||
logging_config_func(DEFAULT_LOGGING)
|
||||
|
||||
if self.LOGGING:
|
||||
# Backwards-compatibility shim for #16288 fix
|
||||
compat_patch_logging_config(self.LOGGING)
|
||||
|
||||
# ... then invoke it with the logging settings
|
||||
logging_config_func(self.LOGGING)
|
||||
|
||||
def configure(self, default_settings=global_settings, **options):
|
||||
"""
|
||||
|
@ -133,19 +153,6 @@ class Settings(BaseSettings):
|
|||
os.environ['TZ'] = self.TIME_ZONE
|
||||
time.tzset()
|
||||
|
||||
# Settings are configured, so we can set up the logger if required
|
||||
if self.LOGGING_CONFIG:
|
||||
# First find the logging configuration function ...
|
||||
logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1)
|
||||
logging_config_module = importlib.import_module(logging_config_path)
|
||||
logging_config_func = getattr(logging_config_module, logging_config_func_name)
|
||||
|
||||
# Backwards-compatibility shim for #16288 fix
|
||||
compat_patch_logging_config(self.LOGGING)
|
||||
|
||||
# ... then invoke it with the logging settings
|
||||
logging_config_func(self.LOGGING)
|
||||
|
||||
|
||||
class UserSettingsHolder(BaseSettings):
|
||||
"""
|
||||
|
|
|
@ -144,7 +144,7 @@ DEFAULT_CHARSET = 'utf-8'
|
|||
# Encoding of files read from disk (template and initial SQL files).
|
||||
FILE_CHARSET = 'utf-8'
|
||||
|
||||
# E-mail address that error messages come from.
|
||||
# Email address that error messages come from.
|
||||
SERVER_EMAIL = 'root@localhost'
|
||||
|
||||
# Whether to send broken-link emails.
|
||||
|
@ -488,6 +488,8 @@ PROFANITIES_LIST = ()
|
|||
# AUTHENTICATION #
|
||||
##################
|
||||
|
||||
AUTH_USER_MODEL = 'auth.User'
|
||||
|
||||
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
|
||||
|
||||
LOGIN_URL = '/accounts/login/'
|
||||
|
@ -549,33 +551,8 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.fallback.FallbackStorage'
|
|||
# The callable to use to configure logging
|
||||
LOGGING_CONFIG = 'django.utils.log.dictConfig'
|
||||
|
||||
# The default logging configuration. This sends an email to
|
||||
# the site admins on every HTTP 500 error. All other log
|
||||
# records are sent to the bit bucket.
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'filters': {
|
||||
'require_debug_false': {
|
||||
'()': 'django.utils.log.RequireDebugFalse',
|
||||
}
|
||||
},
|
||||
'handlers': {
|
||||
'mail_admins': {
|
||||
'level': 'ERROR',
|
||||
'filters': ['require_debug_false'],
|
||||
'class': 'django.utils.log.AdminEmailHandler'
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
'django.request': {
|
||||
'handlers': ['mail_admins'],
|
||||
'level': 'ERROR',
|
||||
'propagate': True,
|
||||
},
|
||||
}
|
||||
}
|
||||
# Custom logging configuration.
|
||||
LOGGING = {}
|
||||
|
||||
# Default exception reporter filter class used in case none has been
|
||||
# specifically assigned to the HttpRequest instance.
|
||||
|
|
|
@ -35,7 +35,7 @@ DATETIME_INPUT_FORMATS = (
|
|||
'%Y-%m-%d', # '2006-10-25'
|
||||
)
|
||||
|
||||
# these are the separators for non-monetary numbers. For monetary numbers,
|
||||
# these are the separators for non-monetary numbers. For monetary numbers,
|
||||
# the DECIMAL_SEPARATOR is a . (decimal point) and the THOUSAND_SEPARATOR is a
|
||||
# ' (single quote).
|
||||
# For details, please refer to http://www.bk.admin.ch/dokumentation/sprachen/04915/05016/index.html?lang=de
|
|
@ -4,7 +4,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2012-03-23 02:29+0100\n"
|
||||
"POT-Creation-Date: 2012-10-15 10:55+0200\n"
|
||||
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
|
||||
"Last-Translator: Django team\n"
|
||||
"Language-Team: English <en@li.org>\n"
|
||||
|
@ -297,385 +297,386 @@ msgstr ""
|
|||
msgid "Traditional Chinese"
|
||||
msgstr ""
|
||||
|
||||
#: core/validators.py:24 forms/fields.py:51
|
||||
#: core/validators.py:21 forms/fields.py:52
|
||||
msgid "Enter a valid value."
|
||||
msgstr ""
|
||||
|
||||
#: core/validators.py:99 forms/fields.py:601
|
||||
msgid "This URL appears to be a broken link."
|
||||
#: core/validators.py:104 forms/fields.py:464
|
||||
msgid "Enter a valid email address."
|
||||
msgstr ""
|
||||
|
||||
#: core/validators.py:131 forms/fields.py:600
|
||||
msgid "Enter a valid URL."
|
||||
msgstr ""
|
||||
|
||||
#: core/validators.py:165 forms/fields.py:474
|
||||
msgid "Enter a valid e-mail address."
|
||||
msgstr ""
|
||||
|
||||
#: core/validators.py:168 forms/fields.py:1023
|
||||
#: core/validators.py:107 forms/fields.py:1013
|
||||
msgid ""
|
||||
"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."
|
||||
msgstr ""
|
||||
|
||||
#: core/validators.py:171 core/validators.py:188 forms/fields.py:997
|
||||
#: core/validators.py:110 core/validators.py:129 forms/fields.py:987
|
||||
msgid "Enter a valid IPv4 address."
|
||||
msgstr ""
|
||||
|
||||
#: core/validators.py:175 core/validators.py:189
|
||||
#: core/validators.py:115 core/validators.py:130
|
||||
msgid "Enter a valid IPv6 address."
|
||||
msgstr ""
|
||||
|
||||
#: core/validators.py:184 core/validators.py:187
|
||||
#: core/validators.py:125 core/validators.py:128
|
||||
msgid "Enter a valid IPv4 or IPv6 address."
|
||||
msgstr ""
|
||||
|
||||
#: core/validators.py:209 db/models/fields/__init__.py:638
|
||||
#: core/validators.py:151 db/models/fields/__init__.py:655
|
||||
msgid "Enter only digits separated by commas."
|
||||
msgstr ""
|
||||
|
||||
#: core/validators.py:215
|
||||
#: core/validators.py:157
|
||||
#, python-format
|
||||
msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)."
|
||||
msgstr ""
|
||||
|
||||
#: core/validators.py:233 forms/fields.py:209 forms/fields.py:262
|
||||
#: core/validators.py:176 forms/fields.py:210 forms/fields.py:263
|
||||
#, python-format
|
||||
msgid "Ensure this value is less than or equal to %(limit_value)s."
|
||||
msgstr ""
|
||||
|
||||
#: core/validators.py:238 forms/fields.py:210 forms/fields.py:263
|
||||
#: core/validators.py:182 forms/fields.py:211 forms/fields.py:264
|
||||
#, python-format
|
||||
msgid "Ensure this value is greater than or equal to %(limit_value)s."
|
||||
msgstr ""
|
||||
|
||||
#: core/validators.py:244
|
||||
#: core/validators.py:189
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Ensure this value has at least %(limit_value)d characters (it has "
|
||||
"%(show_value)d)."
|
||||
msgstr ""
|
||||
|
||||
#: core/validators.py:250
|
||||
#: core/validators.py:196
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Ensure this value has at most %(limit_value)d characters (it has "
|
||||
"%(show_value)d)."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/base.py:764
|
||||
#: db/models/base.py:843
|
||||
#, python-format
|
||||
msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/base.py:787 forms/models.py:577
|
||||
#: db/models/base.py:866 forms/models.py:573
|
||||
msgid "and"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/base.py:788 db/models/fields/__init__.py:65
|
||||
#: db/models/base.py:867 db/models/fields/__init__.py:70
|
||||
#, python-format
|
||||
msgid "%(model_name)s with this %(field_label)s already exists."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:62
|
||||
#: db/models/fields/__init__.py:67
|
||||
#, python-format
|
||||
msgid "Value %r is not a valid choice."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:63
|
||||
#: db/models/fields/__init__.py:68
|
||||
msgid "This field cannot be null."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:64
|
||||
#: db/models/fields/__init__.py:69
|
||||
msgid "This field cannot be blank."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:71
|
||||
#: db/models/fields/__init__.py:76
|
||||
#, python-format
|
||||
msgid "Field of type: %(field_type)s"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:506 db/models/fields/__init__.py:961
|
||||
#: db/models/fields/__init__.py:517 db/models/fields/__init__.py:985
|
||||
msgid "Integer"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:510 db/models/fields/__init__.py:959
|
||||
#: db/models/fields/__init__.py:521 db/models/fields/__init__.py:983
|
||||
#, python-format
|
||||
msgid "'%s' value must be an integer."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:552
|
||||
#: db/models/fields/__init__.py:569
|
||||
#, python-format
|
||||
msgid "'%s' value must be either True or False."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:554
|
||||
#: db/models/fields/__init__.py:571
|
||||
msgid "Boolean (Either True or False)"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:605
|
||||
#: db/models/fields/__init__.py:622
|
||||
#, python-format
|
||||
msgid "String (up to %(max_length)s)"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:633
|
||||
#: db/models/fields/__init__.py:650
|
||||
msgid "Comma-separated integers"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:647
|
||||
#: db/models/fields/__init__.py:664
|
||||
#, python-format
|
||||
msgid "'%s' value has an invalid date format. It must be in YYYY-MM-DD format."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:649 db/models/fields/__init__.py:734
|
||||
#: db/models/fields/__init__.py:666 db/models/fields/__init__.py:754
|
||||
#, python-format
|
||||
msgid ""
|
||||
"'%s' value has the correct format (YYYY-MM-DD) but it is an invalid date."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:652
|
||||
#: db/models/fields/__init__.py:669
|
||||
msgid "Date (without time)"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:732
|
||||
#: db/models/fields/__init__.py:752
|
||||
#, python-format
|
||||
msgid ""
|
||||
"'%s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[."
|
||||
"uuuuuu]][TZ] format."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:736
|
||||
#: db/models/fields/__init__.py:756
|
||||
#, python-format
|
||||
msgid ""
|
||||
"'%s' value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) but "
|
||||
"it is an invalid date/time."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:740
|
||||
#: db/models/fields/__init__.py:760
|
||||
msgid "Date (with time)"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:831
|
||||
#: db/models/fields/__init__.py:849
|
||||
#, python-format
|
||||
msgid "'%s' value must be a decimal number."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:833
|
||||
#: db/models/fields/__init__.py:851
|
||||
msgid "Decimal number"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:890
|
||||
msgid "E-mail address"
|
||||
#: db/models/fields/__init__.py:908
|
||||
msgid "Email address"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:906
|
||||
#: db/models/fields/__init__.py:927
|
||||
msgid "File path"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:930
|
||||
#: db/models/fields/__init__.py:954
|
||||
#, python-format
|
||||
msgid "'%s' value must be a float."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:932
|
||||
#: db/models/fields/__init__.py:956
|
||||
msgid "Floating point number"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:993
|
||||
#: db/models/fields/__init__.py:1017
|
||||
msgid "Big (8 byte) integer"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:1007
|
||||
#: db/models/fields/__init__.py:1031
|
||||
msgid "IPv4 address"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:1023
|
||||
#: db/models/fields/__init__.py:1047
|
||||
msgid "IP address"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:1065
|
||||
#: db/models/fields/__init__.py:1090
|
||||
#, python-format
|
||||
msgid "'%s' value must be either None, True or False."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:1067
|
||||
#: db/models/fields/__init__.py:1092
|
||||
msgid "Boolean (Either True, False or None)"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:1116
|
||||
#: db/models/fields/__init__.py:1141
|
||||
msgid "Positive integer"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:1127
|
||||
#: db/models/fields/__init__.py:1152
|
||||
msgid "Positive small integer"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:1138
|
||||
#: db/models/fields/__init__.py:1163
|
||||
#, python-format
|
||||
msgid "Slug (up to %(max_length)s)"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:1156
|
||||
#: db/models/fields/__init__.py:1181
|
||||
msgid "Small integer"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:1162
|
||||
#: db/models/fields/__init__.py:1187
|
||||
msgid "Text"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:1180
|
||||
#: db/models/fields/__init__.py:1205
|
||||
#, python-format
|
||||
msgid ""
|
||||
"'%s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] format."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:1182
|
||||
#: db/models/fields/__init__.py:1207
|
||||
#, python-format
|
||||
msgid ""
|
||||
"'%s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an invalid "
|
||||
"time."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:1185
|
||||
#: db/models/fields/__init__.py:1210
|
||||
msgid "Time"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/__init__.py:1249
|
||||
#: db/models/fields/__init__.py:1272
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/files.py:214
|
||||
#: db/models/fields/files.py:211
|
||||
#, python-format
|
||||
msgid "Filename is %(extra)d characters too long."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/files.py:221
|
||||
msgid "File"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/files.py:321
|
||||
#: db/models/fields/files.py:347
|
||||
msgid "Image"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/related.py:903
|
||||
#: db/models/fields/related.py:950
|
||||
#, python-format
|
||||
msgid "Model %(model)s with pk %(pk)r does not exist."
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/related.py:905
|
||||
#: db/models/fields/related.py:952
|
||||
msgid "Foreign Key (type determined by related field)"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/related.py:1033
|
||||
#: db/models/fields/related.py:1082
|
||||
msgid "One-to-one relationship"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/related.py:1096
|
||||
#: db/models/fields/related.py:1149
|
||||
msgid "Many-to-many relationship"
|
||||
msgstr ""
|
||||
|
||||
#: db/models/fields/related.py:1120
|
||||
#: db/models/fields/related.py:1174
|
||||
msgid ""
|
||||
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:50
|
||||
#: forms/fields.py:51
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:208
|
||||
#: forms/fields.py:209
|
||||
msgid "Enter a whole number."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:240 forms/fields.py:261
|
||||
#: forms/fields.py:241 forms/fields.py:262
|
||||
msgid "Enter a number."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:264
|
||||
#, python-format
|
||||
msgid "Ensure that there are no more than %s digits in total."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:265
|
||||
#, python-format
|
||||
msgid "Ensure that there are no more than %s decimal places."
|
||||
msgid "Ensure that there are no more than %s digits in total."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:266
|
||||
#, python-format
|
||||
msgid "Ensure that there are no more than %s decimal places."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:267
|
||||
#, python-format
|
||||
msgid "Ensure that there are no more than %s digits before the decimal point."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:365 forms/fields.py:963
|
||||
#: forms/fields.py:355 forms/fields.py:953
|
||||
msgid "Enter a valid date."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:388 forms/fields.py:964
|
||||
#: forms/fields.py:378 forms/fields.py:954
|
||||
msgid "Enter a valid time."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:409
|
||||
#: forms/fields.py:399
|
||||
msgid "Enter a valid date/time."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:485
|
||||
#: forms/fields.py:475
|
||||
msgid "No file was submitted. Check the encoding type on the form."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:486
|
||||
#: forms/fields.py:476
|
||||
msgid "No file was submitted."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:487
|
||||
#: forms/fields.py:477
|
||||
msgid "The submitted file is empty."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:488
|
||||
#: forms/fields.py:478
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Ensure this filename has at most %(max)d characters (it has %(length)d)."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:489
|
||||
#: forms/fields.py:479
|
||||
msgid "Please either submit a file or check the clear checkbox, not both."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:544
|
||||
#: forms/fields.py:534
|
||||
msgid ""
|
||||
"Upload a valid image. The file you uploaded was either not an image or a "
|
||||
"corrupted image."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:689 forms/fields.py:769
|
||||
#: forms/fields.py:580
|
||||
msgid "Enter a valid URL."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:666 forms/fields.py:746
|
||||
#, python-format
|
||||
msgid "Select a valid choice. %(value)s is not one of the available choices."
|
||||
msgstr ""
|
||||
|
||||
#: forms/fields.py:770 forms/fields.py:858 forms/models.py:999
|
||||
#: forms/fields.py:747 forms/fields.py:835 forms/models.py:999
|
||||
msgid "Enter a list of values."
|
||||
msgstr ""
|
||||
|
||||
#: forms/formsets.py:317 forms/formsets.py:319
|
||||
#: forms/formsets.py:323 forms/formsets.py:325
|
||||
msgid "Order"
|
||||
msgstr ""
|
||||
|
||||
#: forms/formsets.py:321
|
||||
#: forms/formsets.py:327
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
#: forms/models.py:571
|
||||
#: forms/models.py:567
|
||||
#, python-format
|
||||
msgid "Please correct the duplicate data for %(field)s."
|
||||
msgstr ""
|
||||
|
||||
#: forms/models.py:575
|
||||
#: forms/models.py:571
|
||||
#, python-format
|
||||
msgid "Please correct the duplicate data for %(field)s, which must be unique."
|
||||
msgstr ""
|
||||
|
||||
#: forms/models.py:581
|
||||
#: forms/models.py:577
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Please correct the duplicate data for %(field_name)s which must be unique "
|
||||
"for the %(lookup)s in %(date_field)s."
|
||||
msgstr ""
|
||||
|
||||
#: forms/models.py:589
|
||||
#: forms/models.py:585
|
||||
msgid "Please correct the duplicate values below."
|
||||
msgstr ""
|
||||
|
||||
|
@ -697,94 +698,94 @@ msgstr ""
|
|||
msgid "\"%s\" is not a valid value for a primary key."
|
||||
msgstr ""
|
||||
|
||||
#: forms/util.py:70
|
||||
#: forms/util.py:81
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%(datetime)s couldn't be interpreted in time zone %(current_timezone)s; it "
|
||||
"may be ambiguous or it may not exist."
|
||||
msgstr ""
|
||||
|
||||
#: forms/widgets.py:325
|
||||
#: forms/widgets.py:336
|
||||
msgid "Currently"
|
||||
msgstr ""
|
||||
|
||||
#: forms/widgets.py:326
|
||||
#: forms/widgets.py:337
|
||||
msgid "Change"
|
||||
msgstr ""
|
||||
|
||||
#: forms/widgets.py:327
|
||||
#: forms/widgets.py:338
|
||||
msgid "Clear"
|
||||
msgstr ""
|
||||
|
||||
#: forms/widgets.py:582
|
||||
#: forms/widgets.py:591
|
||||
msgid "Unknown"
|
||||
msgstr ""
|
||||
|
||||
#: forms/widgets.py:583
|
||||
#: forms/widgets.py:592
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#: forms/widgets.py:584
|
||||
#: forms/widgets.py:593
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
#: template/defaultfilters.py:797
|
||||
#: template/defaultfilters.py:794
|
||||
msgid "yes,no,maybe"
|
||||
msgstr ""
|
||||
|
||||
#: template/defaultfilters.py:825 template/defaultfilters.py:830
|
||||
#: template/defaultfilters.py:822 template/defaultfilters.py:833
|
||||
#, python-format
|
||||
msgid "%(size)d byte"
|
||||
msgid_plural "%(size)d bytes"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: template/defaultfilters.py:832
|
||||
#: template/defaultfilters.py:835
|
||||
#, python-format
|
||||
msgid "%s KB"
|
||||
msgstr ""
|
||||
|
||||
#: template/defaultfilters.py:834
|
||||
#: template/defaultfilters.py:837
|
||||
#, python-format
|
||||
msgid "%s MB"
|
||||
msgstr ""
|
||||
|
||||
#: template/defaultfilters.py:836
|
||||
#: template/defaultfilters.py:839
|
||||
#, python-format
|
||||
msgid "%s GB"
|
||||
msgstr ""
|
||||
|
||||
#: template/defaultfilters.py:838
|
||||
#: template/defaultfilters.py:841
|
||||
#, python-format
|
||||
msgid "%s TB"
|
||||
msgstr ""
|
||||
|
||||
#: template/defaultfilters.py:839
|
||||
#: template/defaultfilters.py:842
|
||||
#, python-format
|
||||
msgid "%s PB"
|
||||
msgstr ""
|
||||
|
||||
#: utils/dateformat.py:45
|
||||
#: utils/dateformat.py:47
|
||||
msgid "p.m."
|
||||
msgstr ""
|
||||
|
||||
#: utils/dateformat.py:46
|
||||
#: utils/dateformat.py:48
|
||||
msgid "a.m."
|
||||
msgstr ""
|
||||
|
||||
#: utils/dateformat.py:51
|
||||
#: utils/dateformat.py:53
|
||||
msgid "PM"
|
||||
msgstr ""
|
||||
|
||||
#: utils/dateformat.py:52
|
||||
#: utils/dateformat.py:54
|
||||
msgid "AM"
|
||||
msgstr ""
|
||||
|
||||
#: utils/dateformat.py:101
|
||||
#: utils/dateformat.py:103
|
||||
msgid "midnight"
|
||||
msgstr ""
|
||||
|
||||
#: utils/dateformat.py:103
|
||||
#: utils/dateformat.py:105
|
||||
msgid "noon"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1060,148 +1061,133 @@ msgctxt "alt. month"
|
|||
msgid "December"
|
||||
msgstr ""
|
||||
|
||||
#: utils/text.py:65
|
||||
#: utils/text.py:70
|
||||
#, python-format
|
||||
msgctxt "String to return when truncating text"
|
||||
msgid "%(truncated_text)s..."
|
||||
msgstr ""
|
||||
|
||||
#: utils/text.py:234
|
||||
#: utils/text.py:239
|
||||
msgid "or"
|
||||
msgstr ""
|
||||
|
||||
#. Translators: This string is used as a separator between list elements
|
||||
#: utils/text.py:251
|
||||
#: utils/text.py:256
|
||||
msgid ", "
|
||||
msgstr ""
|
||||
|
||||
#: utils/timesince.py:20
|
||||
#: utils/timesince.py:22
|
||||
msgid "year"
|
||||
msgid_plural "years"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: utils/timesince.py:21
|
||||
#: utils/timesince.py:23
|
||||
msgid "month"
|
||||
msgid_plural "months"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: utils/timesince.py:22
|
||||
#: utils/timesince.py:24
|
||||
msgid "week"
|
||||
msgid_plural "weeks"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: utils/timesince.py:23
|
||||
#: utils/timesince.py:25
|
||||
msgid "day"
|
||||
msgid_plural "days"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: utils/timesince.py:24
|
||||
#: utils/timesince.py:26
|
||||
msgid "hour"
|
||||
msgid_plural "hours"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: utils/timesince.py:25
|
||||
#: utils/timesince.py:27
|
||||
msgid "minute"
|
||||
msgid_plural "minutes"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: utils/timesince.py:41
|
||||
#: utils/timesince.py:43
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
#: utils/timesince.py:46
|
||||
#: utils/timesince.py:48
|
||||
#, python-format
|
||||
msgid "%(number)d %(type)s"
|
||||
msgstr ""
|
||||
|
||||
#: utils/timesince.py:52
|
||||
#: utils/timesince.py:54
|
||||
#, python-format
|
||||
msgid ", %(number)d %(type)s"
|
||||
msgstr ""
|
||||
|
||||
#: views/static.py:52
|
||||
#: views/static.py:55
|
||||
msgid "Directory indexes are not allowed here."
|
||||
msgstr ""
|
||||
|
||||
#: views/static.py:54
|
||||
#: views/static.py:57
|
||||
#, python-format
|
||||
msgid "\"%(path)s\" does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: views/static.py:95
|
||||
#: views/static.py:98
|
||||
#, python-format
|
||||
msgid "Index of %(directory)s"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/create_update.py:121
|
||||
#, python-format
|
||||
msgid "The %(verbose_name)s was created successfully."
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/create_update.py:164
|
||||
#, python-format
|
||||
msgid "The %(verbose_name)s was updated successfully."
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/create_update.py:207
|
||||
#, python-format
|
||||
msgid "The %(verbose_name)s was deleted."
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/dates.py:33
|
||||
#: views/generic/dates.py:42
|
||||
msgid "No year specified"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/dates.py:58
|
||||
#: views/generic/dates.py:98
|
||||
msgid "No month specified"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/dates.py:99
|
||||
#: views/generic/dates.py:157
|
||||
msgid "No day specified"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/dates.py:138
|
||||
#: views/generic/dates.py:213
|
||||
msgid "No week specified"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/dates.py:198 views/generic/dates.py:215
|
||||
#: views/generic/dates.py:368 views/generic/dates.py:393
|
||||
#, python-format
|
||||
msgid "No %(verbose_name_plural)s available"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/dates.py:467
|
||||
#: views/generic/dates.py:646
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Future %(verbose_name_plural)s not available because %(class_name)s."
|
||||
"allow_future is False."
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/dates.py:501
|
||||
#: views/generic/dates.py:678
|
||||
#, python-format
|
||||
msgid "Invalid date string '%(datestr)s' given format '%(format)s'"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/detail.py:51
|
||||
#: views/generic/detail.py:54
|
||||
#, python-format
|
||||
msgid "No %(verbose_name)s found matching the query"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/list.py:45
|
||||
#: views/generic/list.py:49
|
||||
msgid "Page is not 'last', nor can it be converted to an int."
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/list.py:50
|
||||
#: views/generic/list.py:54
|
||||
#, python-format
|
||||
msgid "Invalid page (%(page_number)s)"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/list.py:117
|
||||
#: views/generic/list.py:134
|
||||
#, python-format
|
||||
msgid "Empty list and '%(class_name)s.allow_empty' is False."
|
||||
msgstr ""
|
||||
|
|
|
@ -15,6 +15,10 @@ framework.
|
|||
"""
|
||||
import os
|
||||
|
||||
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
|
||||
# if running multiple sites in the same mod_wsgi process. To fix this, use
|
||||
# mod_wsgi daemon mode with each site in its own daemon process, or use
|
||||
# os.environ["DJANGO_SETTINGS_MODULE"] = "{{ project_name }}.settings"
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings")
|
||||
|
||||
# This application object is used by any WSGI server configured to use this
|
||||
|
|
|
@ -4,12 +4,12 @@ from django import forms
|
|||
|
||||
from django.contrib.auth import authenticate
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext_lazy, ugettext as _
|
||||
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.")
|
||||
|
||||
|
||||
class AdminAuthenticationForm(AuthenticationForm):
|
||||
"""
|
||||
A custom authentication form used in the admin app.
|
||||
|
@ -26,17 +26,6 @@ class AdminAuthenticationForm(AuthenticationForm):
|
|||
if username and password:
|
||||
self.user_cache = authenticate(username=username, password=password)
|
||||
if self.user_cache is None:
|
||||
if '@' in username:
|
||||
# Mistakenly entered e-mail address instead of username? Look it up.
|
||||
try:
|
||||
user = User.objects.get(email=username)
|
||||
except (User.DoesNotExist, User.MultipleObjectsReturned):
|
||||
# Nothing to do here, moving along.
|
||||
pass
|
||||
else:
|
||||
if user.check_password(password):
|
||||
message = _("Your e-mail address is not your username."
|
||||
" Try '%s' instead.") % user.username
|
||||
raise forms.ValidationError(message)
|
||||
elif not self.user_cache.is_active or not self.user_cache.is_staff:
|
||||
raise forms.ValidationError(message)
|
||||
|
|
|
@ -4,7 +4,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2012-03-23 02:34+0100\n"
|
||||
"POT-Creation-Date: 2012-10-15 10:56+0200\n"
|
||||
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
|
||||
"Last-Translator: Django team\n"
|
||||
"Language-Team: English <en@li.org>\n"
|
||||
|
@ -32,39 +32,39 @@ msgstr ""
|
|||
msgid "Delete selected %(verbose_name_plural)s"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:101 filters.py:191 filters.py:231 filters.py:268 filters.py:378
|
||||
#: filters.py:101 filters.py:197 filters.py:237 filters.py:274 filters.py:380
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:232
|
||||
#: filters.py:238
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:233
|
||||
#: filters.py:239
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:247
|
||||
#: filters.py:253
|
||||
msgid "Unknown"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:306
|
||||
#: filters.py:308
|
||||
msgid "Any date"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:307
|
||||
#: filters.py:309
|
||||
msgid "Today"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:311
|
||||
#: filters.py:313
|
||||
msgid "Past 7 days"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:315
|
||||
#: filters.py:317
|
||||
msgid "This month"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:319
|
||||
#: filters.py:321
|
||||
msgid "This year"
|
||||
msgstr ""
|
||||
|
||||
|
@ -74,134 +74,129 @@ msgid ""
|
|||
"that both fields are case-sensitive."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:18
|
||||
#: forms.py:19
|
||||
msgid "Please log in again, because your session has expired."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:37
|
||||
#, python-format
|
||||
msgid "Your e-mail address is not your username. Try '%s' instead."
|
||||
msgstr ""
|
||||
|
||||
#: helpers.py:20
|
||||
#: helpers.py:23
|
||||
msgid "Action:"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:19
|
||||
#: models.py:24
|
||||
msgid "action time"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:22
|
||||
#: models.py:27
|
||||
msgid "object id"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:23
|
||||
#: models.py:28
|
||||
msgid "object repr"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:24
|
||||
#: models.py:29
|
||||
msgid "action flag"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:25
|
||||
#: models.py:30
|
||||
msgid "change message"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:30
|
||||
#: models.py:35
|
||||
msgid "log entry"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:31
|
||||
#: models.py:36
|
||||
msgid "log entries"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:40
|
||||
#: models.py:45
|
||||
#, python-format
|
||||
msgid "Added \"%(object)s\"."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:42
|
||||
#: models.py:47
|
||||
#, python-format
|
||||
msgid "Changed \"%(object)s\" - %(changes)s"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:44
|
||||
#: models.py:49
|
||||
#, python-format
|
||||
msgid "Deleted \"%(object)s.\""
|
||||
msgstr ""
|
||||
|
||||
#: models.py:46
|
||||
#: models.py:51
|
||||
msgid "LogEntry Object"
|
||||
msgstr ""
|
||||
|
||||
#: options.py:150 options.py:166
|
||||
#: options.py:151 options.py:167
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: options.py:671
|
||||
#: options.py:672
|
||||
#, python-format
|
||||
msgid "Changed %s."
|
||||
msgstr ""
|
||||
|
||||
#: options.py:671 options.py:681
|
||||
#: options.py:672 options.py:682
|
||||
msgid "and"
|
||||
msgstr ""
|
||||
|
||||
#: options.py:676
|
||||
#: options.py:677
|
||||
#, python-format
|
||||
msgid "Added %(name)s \"%(object)s\"."
|
||||
msgstr ""
|
||||
|
||||
#: options.py:680
|
||||
#: options.py:681
|
||||
#, python-format
|
||||
msgid "Changed %(list)s for %(name)s \"%(object)s\"."
|
||||
msgstr ""
|
||||
|
||||
#: options.py:685
|
||||
#: options.py:686
|
||||
#, python-format
|
||||
msgid "Deleted %(name)s \"%(object)s\"."
|
||||
msgstr ""
|
||||
|
||||
#: options.py:689
|
||||
#: options.py:690
|
||||
msgid "No fields changed."
|
||||
msgstr ""
|
||||
|
||||
#: options.py:772
|
||||
#: options.py:773
|
||||
#, python-format
|
||||
msgid "The %(name)s \"%(obj)s\" was added successfully."
|
||||
msgstr ""
|
||||
|
||||
#: options.py:776 options.py:824
|
||||
#: options.py:777 options.py:825
|
||||
msgid "You may edit it again below."
|
||||
msgstr ""
|
||||
|
||||
#: options.py:788 options.py:837
|
||||
#: options.py:789 options.py:838
|
||||
#, python-format
|
||||
msgid "You may add another %s below."
|
||||
msgstr ""
|
||||
|
||||
#: options.py:822
|
||||
#: options.py:823
|
||||
#, python-format
|
||||
msgid "The %(name)s \"%(obj)s\" was changed successfully."
|
||||
msgstr ""
|
||||
|
||||
#: options.py:830
|
||||
#: options.py:831
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
|
||||
msgstr ""
|
||||
|
||||
#: options.py:899 options.py:1159
|
||||
#: options.py:900 options.py:1159
|
||||
msgid ""
|
||||
"Items must be selected in order to perform actions on them. No items have "
|
||||
"been changed."
|
||||
msgstr ""
|
||||
|
||||
#: options.py:918
|
||||
#: options.py:919
|
||||
msgid "No action selected."
|
||||
msgstr ""
|
||||
|
||||
#: options.py:998
|
||||
#: options.py:999
|
||||
#, python-format
|
||||
msgid "Add %s"
|
||||
msgstr ""
|
||||
|
@ -249,34 +244,34 @@ msgstr ""
|
|||
msgid "Change history: %s"
|
||||
msgstr ""
|
||||
|
||||
#: sites.py:315 tests.py:61 templates/admin/login.html:49
|
||||
#: templates/registration/password_reset_complete.html:20
|
||||
#: views/decorators.py:23
|
||||
#: sites.py:322 tests.py:57 templates/admin/login.html:48
|
||||
#: templates/registration/password_reset_complete.html:19
|
||||
#: views/decorators.py:24
|
||||
msgid "Log in"
|
||||
msgstr ""
|
||||
|
||||
#: sites.py:380
|
||||
#: sites.py:388
|
||||
msgid "Site administration"
|
||||
msgstr ""
|
||||
|
||||
#: sites.py:432
|
||||
#: sites.py:440
|
||||
#, python-format
|
||||
msgid "%s administration"
|
||||
msgstr ""
|
||||
|
||||
#: widgets.py:87
|
||||
#: widgets.py:90
|
||||
msgid "Date:"
|
||||
msgstr ""
|
||||
|
||||
#: widgets.py:87
|
||||
#: widgets.py:91
|
||||
msgid "Time:"
|
||||
msgstr ""
|
||||
|
||||
#: widgets.py:161
|
||||
#: widgets.py:165
|
||||
msgid "Lookup"
|
||||
msgstr ""
|
||||
|
||||
#: widgets.py:267
|
||||
#: widgets.py:271
|
||||
msgid "Add Another"
|
||||
msgstr ""
|
||||
|
||||
|
@ -288,39 +283,39 @@ msgstr ""
|
|||
msgid "We're sorry, but the requested page could not be found."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/500.html:7 templates/admin/app_index.html:8
|
||||
#: templates/admin/base.html:45 templates/admin/change_form.html:21
|
||||
#: templates/admin/change_list.html:43
|
||||
#: templates/admin/delete_confirmation.html:8
|
||||
#: templates/admin/delete_selected_confirmation.html:8
|
||||
#: templates/admin/invalid_setup.html:7 templates/admin/object_history.html:8
|
||||
#: templates/admin/auth/user/change_password.html:15
|
||||
#: templates/registration/logged_out.html:5
|
||||
#: templates/registration/password_change_done.html:7
|
||||
#: templates/registration/password_change_form.html:8
|
||||
#: templates/registration/password_reset_complete.html:7
|
||||
#: templates/registration/password_reset_confirm.html:7
|
||||
#: templates/registration/password_reset_done.html:7
|
||||
#: templates/registration/password_reset_form.html:7
|
||||
#: templates/admin/500.html:6 templates/admin/app_index.html:7
|
||||
#: templates/admin/base.html:47 templates/admin/change_form.html:19
|
||||
#: templates/admin/change_list.html:41
|
||||
#: templates/admin/delete_confirmation.html:7
|
||||
#: templates/admin/delete_selected_confirmation.html:7
|
||||
#: templates/admin/invalid_setup.html:6 templates/admin/object_history.html:7
|
||||
#: templates/admin/auth/user/change_password.html:13
|
||||
#: templates/registration/logged_out.html:4
|
||||
#: templates/registration/password_change_done.html:6
|
||||
#: templates/registration/password_change_form.html:7
|
||||
#: templates/registration/password_reset_complete.html:6
|
||||
#: templates/registration/password_reset_confirm.html:6
|
||||
#: templates/registration/password_reset_done.html:6
|
||||
#: templates/registration/password_reset_form.html:6
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/500.html:8
|
||||
#: templates/admin/500.html:7
|
||||
msgid "Server error"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/500.html:12
|
||||
#: templates/admin/500.html:11
|
||||
msgid "Server error (500)"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/500.html:15
|
||||
#: templates/admin/500.html:14
|
||||
msgid "Server Error <em>(500)</em>"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/500.html:16
|
||||
#: templates/admin/500.html:15
|
||||
msgid ""
|
||||
"There's been an error. It's been reported to the site administrators via e-"
|
||||
"mail and should be fixed shortly. Thanks for your patience."
|
||||
"There's been an error. It's been reported to the site administrators via "
|
||||
"email and should be fixed shortly. Thanks for your patience."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/actions.html:4
|
||||
|
@ -344,7 +339,7 @@ msgstr ""
|
|||
msgid "Clear selection"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/app_index.html:11 templates/admin/index.html:19
|
||||
#: templates/admin/app_index.html:10 templates/admin/index.html:21
|
||||
#, python-format
|
||||
msgid "%(name)s"
|
||||
msgstr ""
|
||||
|
@ -354,22 +349,22 @@ msgid "Welcome,"
|
|||
msgstr ""
|
||||
|
||||
#: templates/admin/base.html:33
|
||||
#: templates/registration/password_change_done.html:4
|
||||
#: templates/registration/password_change_form.html:5
|
||||
#: templates/registration/password_change_done.html:3
|
||||
#: templates/registration/password_change_form.html:4
|
||||
msgid "Documentation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/base.html:35
|
||||
#: templates/admin/auth/user/change_password.html:19
|
||||
#: templates/admin/auth/user/change_password.html:53
|
||||
#: templates/registration/password_change_done.html:4
|
||||
#: templates/registration/password_change_form.html:5
|
||||
#: templates/admin/base.html:36
|
||||
#: templates/admin/auth/user/change_password.html:17
|
||||
#: templates/admin/auth/user/change_password.html:51
|
||||
#: templates/registration/password_change_done.html:3
|
||||
#: templates/registration/password_change_form.html:4
|
||||
msgid "Change password"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/base.html:36
|
||||
#: templates/registration/password_change_done.html:4
|
||||
#: templates/registration/password_change_form.html:5
|
||||
#: templates/admin/base.html:38
|
||||
#: templates/registration/password_change_done.html:3
|
||||
#: templates/registration/password_change_form.html:4
|
||||
msgid "Log out"
|
||||
msgstr ""
|
||||
|
||||
|
@ -381,35 +376,35 @@ msgstr ""
|
|||
msgid "Django administration"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/change_form.html:24 templates/admin/index.html:29
|
||||
#: templates/admin/change_form.html:22 templates/admin/index.html:33
|
||||
msgid "Add"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/change_form.html:34 templates/admin/object_history.html:12
|
||||
#: templates/admin/change_form.html:32 templates/admin/object_history.html:11
|
||||
msgid "History"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/change_form.html:35
|
||||
#: templates/admin/change_form.html:33
|
||||
#: templates/admin/edit_inline/stacked.html:9
|
||||
#: templates/admin/edit_inline/tabular.html:30
|
||||
msgid "View on site"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/change_form.html:46 templates/admin/change_list.html:69
|
||||
#: templates/admin/login.html:18
|
||||
#: templates/admin/auth/user/change_password.html:29
|
||||
#: templates/registration/password_change_form.html:21
|
||||
#: templates/admin/change_form.html:44 templates/admin/change_list.html:67
|
||||
#: templates/admin/login.html:17
|
||||
#: templates/admin/auth/user/change_password.html:27
|
||||
#: templates/registration/password_change_form.html:20
|
||||
msgid "Please correct the error below."
|
||||
msgid_plural "Please correct the errors below."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: templates/admin/change_list.html:60
|
||||
#: templates/admin/change_list.html:58
|
||||
#, python-format
|
||||
msgid "Add %(name)s"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/change_list.html:80
|
||||
#: templates/admin/change_list.html:78
|
||||
msgid "Filter"
|
||||
msgstr ""
|
||||
|
||||
|
@ -426,12 +421,12 @@ msgstr ""
|
|||
msgid "Toggle sorting"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/delete_confirmation.html:12
|
||||
#: templates/admin/delete_confirmation.html:11
|
||||
#: templates/admin/submit_line.html:4
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/delete_confirmation.html:19
|
||||
#: templates/admin/delete_confirmation.html:18
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting "
|
||||
|
@ -439,30 +434,30 @@ msgid ""
|
|||
"following types of objects:"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/delete_confirmation.html:27
|
||||
#: templates/admin/delete_confirmation.html:26
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Deleting the %(object_name)s '%(escaped_object)s' would require deleting the "
|
||||
"following protected related objects:"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/delete_confirmation.html:35
|
||||
#: templates/admin/delete_confirmation.html:34
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? "
|
||||
"All of the following related items will be deleted:"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/delete_confirmation.html:40
|
||||
#: templates/admin/delete_selected_confirmation.html:45
|
||||
#: templates/admin/delete_confirmation.html:39
|
||||
#: templates/admin/delete_selected_confirmation.html:44
|
||||
msgid "Yes, I'm sure"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/delete_selected_confirmation.html:11
|
||||
#: templates/admin/delete_selected_confirmation.html:10
|
||||
msgid "Delete multiple objects"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/delete_selected_confirmation.html:18
|
||||
#: templates/admin/delete_selected_confirmation.html:17
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Deleting the selected %(objects_name)s would result in deleting related "
|
||||
|
@ -470,14 +465,14 @@ msgid ""
|
|||
"types of objects:"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/delete_selected_confirmation.html:26
|
||||
#: templates/admin/delete_selected_confirmation.html:25
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Deleting the selected %(objects_name)s would require deleting the following "
|
||||
"protected related objects:"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/delete_selected_confirmation.html:34
|
||||
#: templates/admin/delete_selected_confirmation.html:33
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Are you sure you want to delete the selected %(objects_name)s? All of the "
|
||||
|
@ -489,67 +484,63 @@ msgstr ""
|
|||
msgid " By %(filter_title)s "
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/index.html:18
|
||||
#: templates/admin/index.html:20
|
||||
#, python-format
|
||||
msgid "Models available in the %(name)s application."
|
||||
msgid "Models in the %(name)s application"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/index.html:35
|
||||
#: templates/admin/index.html:39
|
||||
msgid "Change"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/index.html:45
|
||||
#: templates/admin/index.html:49
|
||||
msgid "You don't have permission to edit anything."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/index.html:53
|
||||
#: templates/admin/index.html:57
|
||||
msgid "Recent Actions"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/index.html:54
|
||||
#: templates/admin/index.html:58
|
||||
msgid "My Actions"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/index.html:58
|
||||
#: templates/admin/index.html:62
|
||||
msgid "None available"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/index.html:72
|
||||
#: templates/admin/index.html:76
|
||||
msgid "Unknown content"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/invalid_setup.html:13
|
||||
#: templates/admin/invalid_setup.html:12
|
||||
msgid ""
|
||||
"Something's wrong with your database installation. Make sure the appropriate "
|
||||
"database tables have been created, and make sure the database is readable by "
|
||||
"the appropriate user."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/login.html:34
|
||||
msgid "Username:"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/login.html:38
|
||||
#: templates/admin/login.html:37
|
||||
msgid "Password:"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/login.html:45
|
||||
#: templates/admin/login.html:44
|
||||
msgid "Forgotten your password or username?"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/object_history.html:24
|
||||
#: templates/admin/object_history.html:23
|
||||
msgid "Date/time"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/object_history.html:25
|
||||
#: templates/admin/object_history.html:24
|
||||
msgid "User"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/object_history.html:26
|
||||
#: templates/admin/object_history.html:25
|
||||
msgid "Action"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/object_history.html:40
|
||||
#: templates/admin/object_history.html:39
|
||||
msgid ""
|
||||
"This object doesn't have a change history. It probably wasn't added via this "
|
||||
"admin site."
|
||||
|
@ -601,147 +592,147 @@ msgstr ""
|
|||
msgid "Enter a username and password."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/auth/user/change_password.html:33
|
||||
#: templates/admin/auth/user/change_password.html:31
|
||||
#, python-format
|
||||
msgid "Enter a new password for the user <strong>%(username)s</strong>."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/auth/user/change_password.html:40
|
||||
#: templates/admin/auth/user/change_password.html:38
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/auth/user/change_password.html:46
|
||||
#: templates/registration/password_change_form.html:43
|
||||
#: templates/admin/auth/user/change_password.html:44
|
||||
#: templates/registration/password_change_form.html:42
|
||||
msgid "Password (again)"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/auth/user/change_password.html:47
|
||||
#: templates/admin/auth/user/change_password.html:45
|
||||
msgid "Enter the same password as above, for verification."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/edit_inline/stacked.html:67
|
||||
#: templates/admin/edit_inline/tabular.html:115
|
||||
#, python-format
|
||||
msgid "Add another %(verbose_name)s"
|
||||
#: templates/admin/edit_inline/stacked.html:26
|
||||
#: templates/admin/edit_inline/tabular.html:76
|
||||
msgid "Remove"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/edit_inline/stacked.html:70
|
||||
#: templates/admin/edit_inline/tabular.html:118
|
||||
msgid "Remove"
|
||||
#: templates/admin/edit_inline/stacked.html:27
|
||||
#: templates/admin/edit_inline/tabular.html:75
|
||||
#, python-format
|
||||
msgid "Add another %(verbose_name)s"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/edit_inline/tabular.html:17
|
||||
msgid "Delete?"
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/logged_out.html:9
|
||||
#: templates/registration/logged_out.html:8
|
||||
msgid "Thanks for spending some quality time with the Web site today."
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/logged_out.html:11
|
||||
#: templates/registration/logged_out.html:10
|
||||
msgid "Log in again"
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_change_done.html:8
|
||||
#: templates/registration/password_change_form.html:9
|
||||
#: templates/registration/password_change_form.html:13
|
||||
#: templates/registration/password_change_form.html:25
|
||||
#: templates/registration/password_change_done.html:7
|
||||
#: templates/registration/password_change_form.html:8
|
||||
#: templates/registration/password_change_form.html:12
|
||||
#: templates/registration/password_change_form.html:24
|
||||
msgid "Password change"
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_change_done.html:12
|
||||
#: templates/registration/password_change_done.html:16
|
||||
#: templates/registration/password_change_done.html:11
|
||||
#: templates/registration/password_change_done.html:15
|
||||
msgid "Password change successful"
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_change_done.html:18
|
||||
#: templates/registration/password_change_done.html:17
|
||||
msgid "Your password was changed."
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_change_form.html:27
|
||||
#: templates/registration/password_change_form.html:26
|
||||
msgid ""
|
||||
"Please enter your old password, for security's sake, and then enter your new "
|
||||
"password twice so we can verify you typed it in correctly."
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_change_form.html:33
|
||||
#: templates/registration/password_change_form.html:32
|
||||
msgid "Old password"
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_change_form.html:38
|
||||
#: templates/registration/password_change_form.html:37
|
||||
msgid "New password"
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_change_form.html:49
|
||||
#: templates/registration/password_reset_confirm.html:27
|
||||
#: templates/registration/password_change_form.html:48
|
||||
#: templates/registration/password_reset_confirm.html:26
|
||||
msgid "Change my password"
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_reset_complete.html:8
|
||||
#: templates/registration/password_reset_confirm.html:12
|
||||
#: templates/registration/password_reset_done.html:8
|
||||
#: templates/registration/password_reset_form.html:8
|
||||
#: templates/registration/password_reset_form.html:12
|
||||
#: templates/registration/password_reset_form.html:16
|
||||
#: templates/registration/password_reset_complete.html:7
|
||||
#: templates/registration/password_reset_confirm.html:11
|
||||
#: templates/registration/password_reset_done.html:7
|
||||
#: templates/registration/password_reset_form.html:7
|
||||
#: templates/registration/password_reset_form.html:11
|
||||
#: templates/registration/password_reset_form.html:15
|
||||
msgid "Password reset"
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_reset_complete.html:12
|
||||
#: templates/registration/password_reset_complete.html:16
|
||||
#: templates/registration/password_reset_complete.html:11
|
||||
#: templates/registration/password_reset_complete.html:15
|
||||
msgid "Password reset complete"
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_reset_complete.html:18
|
||||
#: templates/registration/password_reset_complete.html:17
|
||||
msgid "Your password has been set. You may go ahead and log in now."
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_reset_confirm.html:8
|
||||
#: templates/registration/password_reset_confirm.html:7
|
||||
msgid "Password reset confirmation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_reset_confirm.html:18
|
||||
#: templates/registration/password_reset_confirm.html:17
|
||||
msgid "Enter new password"
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_reset_confirm.html:20
|
||||
#: templates/registration/password_reset_confirm.html:19
|
||||
msgid ""
|
||||
"Please enter your new password twice so we can verify you typed it in "
|
||||
"correctly."
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_reset_confirm.html:24
|
||||
#: templates/registration/password_reset_confirm.html:23
|
||||
msgid "New password:"
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_reset_confirm.html:26
|
||||
#: templates/registration/password_reset_confirm.html:25
|
||||
msgid "Confirm password:"
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_reset_confirm.html:32
|
||||
#: templates/registration/password_reset_confirm.html:31
|
||||
msgid "Password reset unsuccessful"
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_reset_confirm.html:34
|
||||
#: templates/registration/password_reset_confirm.html:33
|
||||
msgid ""
|
||||
"The password reset link was invalid, possibly because it has already been "
|
||||
"used. Please request a new password reset."
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_reset_done.html:12
|
||||
#: templates/registration/password_reset_done.html:16
|
||||
#: templates/registration/password_reset_done.html:11
|
||||
#: templates/registration/password_reset_done.html:15
|
||||
msgid "Password reset successful"
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_reset_done.html:18
|
||||
#: templates/registration/password_reset_done.html:17
|
||||
msgid ""
|
||||
"We've e-mailed you instructions for setting your password to the e-mail "
|
||||
"We've emailed you instructions for setting your password to the email "
|
||||
"address you submitted. You should be receiving it shortly."
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_reset_email.html:2
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You're receiving this e-mail because you requested a password reset for your "
|
||||
"You're receiving this email because you requested a password reset for your "
|
||||
"user account at %(site_name)s."
|
||||
msgstr ""
|
||||
|
||||
|
@ -762,34 +753,34 @@ msgstr ""
|
|||
msgid "The %(site_name)s team"
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_reset_form.html:18
|
||||
#: templates/registration/password_reset_form.html:17
|
||||
msgid ""
|
||||
"Forgotten your password? Enter your e-mail address below, and we'll e-mail "
|
||||
"Forgotten your password? Enter your email address below, and we'll email "
|
||||
"instructions for setting a new one."
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_reset_form.html:22
|
||||
msgid "E-mail address:"
|
||||
#: templates/registration/password_reset_form.html:21
|
||||
msgid "Email address:"
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_reset_form.html:22
|
||||
#: templates/registration/password_reset_form.html:21
|
||||
msgid "Reset my password"
|
||||
msgstr ""
|
||||
|
||||
#: templatetags/admin_list.py:336
|
||||
#: templatetags/admin_list.py:344
|
||||
msgid "All dates"
|
||||
msgstr ""
|
||||
|
||||
#: views/main.py:31
|
||||
#: views/main.py:33
|
||||
msgid "(None)"
|
||||
msgstr ""
|
||||
|
||||
#: views/main.py:74
|
||||
#, python-format
|
||||
msgid "Select %s"
|
||||
msgstr ""
|
||||
|
||||
#: views/main.py:76
|
||||
#, python-format
|
||||
msgid "Select %s"
|
||||
msgstr ""
|
||||
|
||||
#: views/main.py:78
|
||||
#, python-format
|
||||
msgid "Select %s to change"
|
||||
msgstr ""
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.admin.util import quote
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.encoding import smart_text
|
||||
|
@ -12,15 +12,17 @@ ADDITION = 1
|
|||
CHANGE = 2
|
||||
DELETION = 3
|
||||
|
||||
|
||||
class LogEntryManager(models.Manager):
|
||||
def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
|
||||
e = self.model(None, None, user_id, content_type_id, smart_text(object_id), object_repr[:200], action_flag, change_message)
|
||||
e.save()
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class LogEntry(models.Model):
|
||||
action_time = models.DateTimeField(_('action time'), auto_now=True)
|
||||
user = models.ForeignKey(User)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL)
|
||||
content_type = models.ForeignKey(ContentType, blank=True, null=True)
|
||||
object_id = models.TextField(_('object id'), blank=True, null=True)
|
||||
object_repr = models.CharField(_('object repr'), max_length=200)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
from functools import update_wrapper, partial
|
||||
import warnings
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.forms.formsets import all_valid
|
||||
|
@ -6,7 +8,7 @@ from django.forms.models import (modelform_factory, modelformset_factory,
|
|||
inlineformset_factory, BaseInlineFormSet)
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.admin import widgets, helpers
|
||||
from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict
|
||||
from django.contrib.admin.util import quote, unquote, flatten_fieldsets, get_deleted_objects, model_format_dict
|
||||
from django.contrib.admin.templatetags.admin_static import static
|
||||
from django.contrib import messages
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
|
@ -344,14 +346,14 @@ class ModelAdmin(BaseModelAdmin):
|
|||
self.admin_site = admin_site
|
||||
super(ModelAdmin, self).__init__()
|
||||
|
||||
def get_inline_instances(self, request):
|
||||
def get_inline_instances(self, request, obj=None):
|
||||
inline_instances = []
|
||||
for inline_class in self.inlines:
|
||||
inline = inline_class(self.model, self.admin_site)
|
||||
if request:
|
||||
if not (inline.has_add_permission(request) or
|
||||
inline.has_change_permission(request) or
|
||||
inline.has_delete_permission(request)):
|
||||
inline.has_change_permission(request, obj) or
|
||||
inline.has_delete_permission(request, obj)):
|
||||
continue
|
||||
if not inline.has_add_permission(request):
|
||||
inline.max_num = 0
|
||||
|
@ -504,7 +506,7 @@ class ModelAdmin(BaseModelAdmin):
|
|||
fields=self.list_editable, **defaults)
|
||||
|
||||
def get_formsets(self, request, obj=None):
|
||||
for inline in self.get_inline_instances(request):
|
||||
for inline in self.get_inline_instances(request, obj):
|
||||
yield inline.get_formset(request, obj)
|
||||
|
||||
def get_paginator(self, request, queryset, per_page, orphans=0, allow_empty_first_page=True):
|
||||
|
@ -763,21 +765,49 @@ class ModelAdmin(BaseModelAdmin):
|
|||
"admin/change_form.html"
|
||||
], context, current_app=self.admin_site.name)
|
||||
|
||||
def response_add(self, request, obj, post_url_continue='../%s/'):
|
||||
def response_add(self, request, obj, post_url_continue='../%s/',
|
||||
continue_editing_url=None, add_another_url=None,
|
||||
hasperm_url=None, noperm_url=None):
|
||||
"""
|
||||
Determines the HttpResponse for the add_view stage.
|
||||
"""
|
||||
opts = obj._meta
|
||||
pk_value = obj._get_pk_val()
|
||||
|
||||
msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
|
||||
:param request: HttpRequest instance.
|
||||
:param obj: Object just added.
|
||||
:param post_url_continue: Deprecated/undocumented.
|
||||
:param continue_editing_url: URL where user will be redirected after
|
||||
pressing 'Save and continue editing'.
|
||||
:param add_another_url: URL where user will be redirected after
|
||||
pressing 'Save and add another'.
|
||||
:param hasperm_url: URL to redirect after a successful object creation
|
||||
when the user has change permissions.
|
||||
:param noperm_url: URL to redirect after a successful object creation
|
||||
when the user has no change permissions.
|
||||
"""
|
||||
if post_url_continue != '../%s/':
|
||||
warnings.warn("The undocumented 'post_url_continue' argument to "
|
||||
"ModelAdmin.response_add() is deprecated, use the new "
|
||||
"*_url arguments instead.", DeprecationWarning,
|
||||
stacklevel=2)
|
||||
opts = obj._meta
|
||||
pk_value = obj.pk
|
||||
app_label = opts.app_label
|
||||
model_name = opts.module_name
|
||||
site_name = self.admin_site.name
|
||||
|
||||
msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
|
||||
|
||||
# Here, we distinguish between different save types by checking for
|
||||
# the presence of keys in request.POST.
|
||||
if "_continue" in request.POST:
|
||||
self.message_user(request, msg + ' ' + _("You may edit it again below."))
|
||||
msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
|
||||
self.message_user(request, msg)
|
||||
if continue_editing_url is None:
|
||||
continue_editing_url = 'admin:%s_%s_change' % (app_label, model_name)
|
||||
url = reverse(continue_editing_url, args=(quote(pk_value),),
|
||||
current_app=site_name)
|
||||
if "_popup" in request.POST:
|
||||
post_url_continue += "?_popup=1"
|
||||
return HttpResponseRedirect(post_url_continue % pk_value)
|
||||
url += "?_popup=1"
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
if "_popup" in request.POST:
|
||||
return HttpResponse(
|
||||
|
@ -786,72 +816,104 @@ class ModelAdmin(BaseModelAdmin):
|
|||
# escape() calls force_text.
|
||||
(escape(pk_value), escapejs(obj)))
|
||||
elif "_addanother" in request.POST:
|
||||
self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_text(opts.verbose_name)))
|
||||
return HttpResponseRedirect(request.path)
|
||||
msg = _('The %(name)s "%(obj)s" was added successfully. You may add another %(name)s below.') % msg_dict
|
||||
self.message_user(request, msg)
|
||||
if add_another_url is None:
|
||||
add_another_url = 'admin:%s_%s_add' % (app_label, model_name)
|
||||
url = reverse(add_another_url, current_app=site_name)
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
msg = _('The %(name)s "%(obj)s" was added successfully.') % msg_dict
|
||||
self.message_user(request, msg)
|
||||
|
||||
# Figure out where to redirect. If the user has change permission,
|
||||
# redirect to the change-list page for this object. Otherwise,
|
||||
# redirect to the admin index.
|
||||
if self.has_change_permission(request, None):
|
||||
post_url = reverse('admin:%s_%s_changelist' %
|
||||
(opts.app_label, opts.module_name),
|
||||
current_app=self.admin_site.name)
|
||||
if hasperm_url is None:
|
||||
hasperm_url = 'admin:%s_%s_changelist' % (app_label, model_name)
|
||||
url = reverse(hasperm_url, current_app=site_name)
|
||||
else:
|
||||
post_url = reverse('admin:index',
|
||||
current_app=self.admin_site.name)
|
||||
return HttpResponseRedirect(post_url)
|
||||
if noperm_url is None:
|
||||
noperm_url = 'admin:index'
|
||||
url = reverse(noperm_url, current_app=site_name)
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
def response_change(self, request, obj):
|
||||
def response_change(self, request, obj, continue_editing_url=None,
|
||||
save_as_new_url=None, add_another_url=None,
|
||||
hasperm_url=None, noperm_url=None):
|
||||
"""
|
||||
Determines the HttpResponse for the change_view stage.
|
||||
|
||||
:param request: HttpRequest instance.
|
||||
:param obj: Object just modified.
|
||||
:param continue_editing_url: URL where user will be redirected after
|
||||
pressing 'Save and continue editing'.
|
||||
:param save_as_new_url: URL where user will be redirected after pressing
|
||||
'Save as new' (when applicable).
|
||||
:param add_another_url: URL where user will be redirected after pressing
|
||||
'Save and add another'.
|
||||
:param hasperm_url: URL to redirect after a successful object edition when
|
||||
the user has change permissions.
|
||||
:param noperm_url: URL to redirect after a successful object edition when
|
||||
the user has no change permissions.
|
||||
"""
|
||||
opts = obj._meta
|
||||
|
||||
app_label = opts.app_label
|
||||
model_name = opts.module_name
|
||||
site_name = self.admin_site.name
|
||||
verbose_name = opts.verbose_name
|
||||
# Handle proxy models automatically created by .only() or .defer().
|
||||
# Refs #14529
|
||||
verbose_name = opts.verbose_name
|
||||
module_name = opts.module_name
|
||||
if obj._deferred:
|
||||
opts_ = opts.proxy_for_model._meta
|
||||
verbose_name = opts_.verbose_name
|
||||
module_name = opts_.module_name
|
||||
model_name = opts_.module_name
|
||||
|
||||
pk_value = obj._get_pk_val()
|
||||
msg_dict = {'name': force_text(verbose_name), 'obj': force_text(obj)}
|
||||
|
||||
msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_text(verbose_name), 'obj': force_text(obj)}
|
||||
if "_continue" in request.POST:
|
||||
self.message_user(request, msg + ' ' + _("You may edit it again below."))
|
||||
if "_popup" in request.REQUEST:
|
||||
return HttpResponseRedirect(request.path + "?_popup=1")
|
||||
else:
|
||||
return HttpResponseRedirect(request.path)
|
||||
elif "_saveasnew" in request.POST:
|
||||
msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_text(verbose_name), 'obj': obj}
|
||||
msg = _('The %(name)s "%(obj)s" was changed successfully. You may edit it again below.') % msg_dict
|
||||
self.message_user(request, msg)
|
||||
return HttpResponseRedirect(reverse('admin:%s_%s_change' %
|
||||
(opts.app_label, module_name),
|
||||
args=(pk_value,),
|
||||
current_app=self.admin_site.name))
|
||||
if continue_editing_url is None:
|
||||
continue_editing_url = 'admin:%s_%s_change' % (app_label, model_name)
|
||||
url = reverse(continue_editing_url, args=(quote(obj.pk),),
|
||||
current_app=site_name)
|
||||
if "_popup" in request.POST:
|
||||
url += "?_popup=1"
|
||||
return HttpResponseRedirect(url)
|
||||
elif "_saveasnew" in request.POST:
|
||||
msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
|
||||
self.message_user(request, msg)
|
||||
if save_as_new_url is None:
|
||||
save_as_new_url = 'admin:%s_%s_change' % (app_label, model_name)
|
||||
url = reverse(save_as_new_url, args=(quote(obj.pk),),
|
||||
current_app=site_name)
|
||||
return HttpResponseRedirect(url)
|
||||
elif "_addanother" in request.POST:
|
||||
self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_text(verbose_name)))
|
||||
return HttpResponseRedirect(reverse('admin:%s_%s_add' %
|
||||
(opts.app_label, module_name),
|
||||
current_app=self.admin_site.name))
|
||||
msg = _('The %(name)s "%(obj)s" was changed successfully. You may add another %(name)s below.') % msg_dict
|
||||
self.message_user(request, msg)
|
||||
if add_another_url is None:
|
||||
add_another_url = 'admin:%s_%s_add' % (app_label, model_name)
|
||||
url = reverse(add_another_url, current_app=site_name)
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
msg = _('The %(name)s "%(obj)s" was changed successfully.') % msg_dict
|
||||
self.message_user(request, msg)
|
||||
# Figure out where to redirect. If the user has change permission,
|
||||
# redirect to the change-list page for this object. Otherwise,
|
||||
# redirect to the admin index.
|
||||
if self.has_change_permission(request, None):
|
||||
post_url = reverse('admin:%s_%s_changelist' %
|
||||
(opts.app_label, module_name),
|
||||
current_app=self.admin_site.name)
|
||||
if hasperm_url is None:
|
||||
hasperm_url = 'admin:%s_%s_changelist' % (app_label,
|
||||
model_name)
|
||||
url = reverse(hasperm_url, current_app=site_name)
|
||||
else:
|
||||
post_url = reverse('admin:index',
|
||||
current_app=self.admin_site.name)
|
||||
return HttpResponseRedirect(post_url)
|
||||
if noperm_url is None:
|
||||
noperm_url = 'admin:index'
|
||||
url = reverse(noperm_url, current_app=site_name)
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
def response_action(self, request, queryset):
|
||||
"""
|
||||
|
@ -932,7 +994,7 @@ class ModelAdmin(BaseModelAdmin):
|
|||
|
||||
ModelForm = self.get_form(request)
|
||||
formsets = []
|
||||
inline_instances = self.get_inline_instances(request)
|
||||
inline_instances = self.get_inline_instances(request, None)
|
||||
if request.method == 'POST':
|
||||
form = ModelForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
|
@ -1029,7 +1091,7 @@ class ModelAdmin(BaseModelAdmin):
|
|||
|
||||
ModelForm = self.get_form(request, obj)
|
||||
formsets = []
|
||||
inline_instances = self.get_inline_instances(request)
|
||||
inline_instances = self.get_inline_instances(request, obj)
|
||||
if request.method == 'POST':
|
||||
form = ModelForm(request.POST, request.FILES, instance=obj)
|
||||
if form.is_valid():
|
||||
|
|
|
@ -9,7 +9,6 @@ from django.db.models.base import ModelBase
|
|||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils import six
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.translation import ugettext as _
|
||||
|
@ -18,12 +17,15 @@ from django.conf import settings
|
|||
|
||||
LOGIN_FORM_KEY = 'this_is_the_login_form'
|
||||
|
||||
|
||||
class AlreadyRegistered(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NotRegistered(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AdminSite(object):
|
||||
"""
|
||||
An AdminSite object encapsulates an instance of the Django admin application, ready
|
||||
|
@ -41,7 +43,7 @@ class AdminSite(object):
|
|||
password_change_done_template = None
|
||||
|
||||
def __init__(self, name='admin', app_name='admin'):
|
||||
self._registry = {} # model_class class -> admin_class instance
|
||||
self._registry = {} # model_class class -> admin_class instance
|
||||
self.name = name
|
||||
self.app_name = app_name
|
||||
self._actions = {'delete_selected': actions.delete_selected}
|
||||
|
@ -80,20 +82,23 @@ class AdminSite(object):
|
|||
if model in self._registry:
|
||||
raise AlreadyRegistered('The model %s is already registered' % model.__name__)
|
||||
|
||||
# If we got **options then dynamically construct a subclass of
|
||||
# admin_class with those **options.
|
||||
if options:
|
||||
# For reasons I don't quite understand, without a __module__
|
||||
# the created class appears to "live" in the wrong place,
|
||||
# which causes issues later on.
|
||||
options['__module__'] = __name__
|
||||
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
|
||||
# Ignore the registration if the model has been
|
||||
# swapped out.
|
||||
if not model._meta.swapped:
|
||||
# If we got **options then dynamically construct a subclass of
|
||||
# admin_class with those **options.
|
||||
if options:
|
||||
# For reasons I don't quite understand, without a __module__
|
||||
# the created class appears to "live" in the wrong place,
|
||||
# which causes issues later on.
|
||||
options['__module__'] = __name__
|
||||
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
|
||||
|
||||
# Validate (which might be a no-op)
|
||||
validate(admin_class, model)
|
||||
# Validate (which might be a no-op)
|
||||
validate(admin_class, model)
|
||||
|
||||
# Instantiate the admin class to save in the registry
|
||||
self._registry[model] = admin_class(model, self)
|
||||
# Instantiate the admin class to save in the registry
|
||||
self._registry[model] = admin_class(model, self)
|
||||
|
||||
def unregister(self, model_or_iterable):
|
||||
"""
|
||||
|
@ -319,6 +324,7 @@ class AdminSite(object):
|
|||
REDIRECT_FIELD_NAME: request.get_full_path(),
|
||||
}
|
||||
context.update(extra_context or {})
|
||||
|
||||
defaults = {
|
||||
'extra_context': context,
|
||||
'current_app': self.name,
|
||||
|
|
|
@ -12,6 +12,6 @@
|
|||
|
||||
{% block content %}
|
||||
<h1>{% trans 'Server Error <em>(500)</em>' %}</h1>
|
||||
<p>{% trans "There's been an error. It's been reported to the site administrators via e-mail and should be fixed shortly. Thanks for your patience." %}</p>
|
||||
<p>{% trans "There's been an error. It's been reported to the site administrators via email and should be fixed shortly. Thanks for your patience." %}</p>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
</p>
|
||||
{% endif %}
|
||||
|
||||
<p>{% blocktrans with username=original.username %}Enter a new password for the user <strong>{{ username }}</strong>.{% endblocktrans %}</p>
|
||||
<p>{% blocktrans with username=original %}Enter a new password for the user <strong>{{ username }}</strong>.{% endblocktrans %}</p>
|
||||
|
||||
<fieldset class="module aligned">
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
{% if user.is_active and user.is_staff %}
|
||||
<div id="user-tools">
|
||||
{% trans 'Welcome,' %}
|
||||
<strong>{% filter force_escape %}{% firstof user.first_name user.username %}{% endfilter %}</strong>.
|
||||
<strong>{% filter force_escape %}{% firstof user.get_short_name user.get_username %}{% endfilter %}</strong>.
|
||||
{% block userlinks %}
|
||||
{% url 'django-admindocs-docroot' as docsroot %}
|
||||
{% if docsroot %}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
{% if change %}{% if not is_popup %}
|
||||
<ul class="object-tools">
|
||||
{% block object-tools-items %}
|
||||
<li><a href="{% url opts|admin_urlname:'history' original.pk %}" class="historylink">{% trans "History" %}</a></li>
|
||||
<li><a href="{% url opts|admin_urlname:'history' original.pk|admin_urlquote %}" class="historylink">{% trans "History" %}</a></li>
|
||||
{% if has_absolute_url %}<li><a href="{% url 'admin:view_on_site' content_type_id original.pk %}" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
|
||||
<div class="form-row">
|
||||
{% if not form.this_is_the_login_form.errors %}{{ form.username.errors }}{% endif %}
|
||||
<label for="id_username" class="required">{% trans 'Username:' %}</label> {{ form.username }}
|
||||
<label for="id_username" class="required">{{ form.username.label }}:</label> {{ form.username }}
|
||||
</div>
|
||||
<div class="form-row">
|
||||
{% if not form.this_is_the_login_form.errors %}{{ form.password.errors }}{% endif %}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
{% for action in action_list %}
|
||||
<tr>
|
||||
<th scope="row">{{ action.action_time|date:"DATETIME_FORMAT" }}</th>
|
||||
<td>{{ action.user.username }}{% if action.user.get_full_name %} ({{ action.user.get_full_name }}){% endif %}</td>
|
||||
<td>{{ action.user.get_username }}{% if action.user.get_full_name %} ({{ action.user.get_full_name }}){% endif %}</td>
|
||||
<td>{{ action.change_message }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{% load i18n %}
|
||||
{% 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_delete_link %}<p class="deletelink-box"><a href="delete/" class="deletelink">{% trans "Delete" %}</a></p>{% endif %}
|
||||
{% if show_delete_link %}<p class="deletelink-box"><a href="{% url opts|admin_urlname:'delete' original.pk|admin_urlquote %}" class="deletelink">{% trans "Delete" %}</a></p>{% endif %}
|
||||
{% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" {{ onclick_attrib }}/>{%endif%}
|
||||
{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" {{ onclick_attrib }} />{% endif %}
|
||||
{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" {{ onclick_attrib }}/>{% endif %}
|
||||
{% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" {{ onclick_attrib }}/>{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -14,6 +14,6 @@
|
|||
|
||||
<h1>{% trans 'Password reset successful' %}</h1>
|
||||
|
||||
<p>{% trans "We've e-mailed you instructions for setting your password to the e-mail address you submitted. You should be receiving it shortly." %}</p>
|
||||
<p>{% trans "We've emailed you instructions for setting your password to the email address you submitted. You should be receiving it shortly." %}</p>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{% load i18n %}{% autoescape off %}
|
||||
{% blocktrans %}You're receiving this e-mail because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
|
||||
{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
|
||||
|
||||
{% trans "Please go to the following page and choose a new password:" %}
|
||||
{% block reset_link %}
|
||||
{{ protocol }}://{{ domain }}{% url 'django.contrib.auth.views.password_reset_confirm' uidb36=uid token=token %}
|
||||
{% endblock %}
|
||||
{% trans "Your username, in case you've forgotten:" %} {{ user.username }}
|
||||
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
|
||||
|
||||
{% trans "Thanks for using our site!" %}
|
||||
|
||||
|
|
|
@ -14,11 +14,11 @@
|
|||
|
||||
<h1>{% trans "Password reset" %}</h1>
|
||||
|
||||
<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll e-mail instructions for setting a new one." %}</p>
|
||||
<p>{% trans "Forgotten your password? Enter your email address below, and we'll email instructions for setting a new one." %}</p>
|
||||
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
{{ form.email.errors }}
|
||||
<p><label for="id_email">{% trans 'E-mail address:' %}</label> {{ form.email }} <input type="submit" value="{% trans 'Reset my password' %}" /></p>
|
||||
<p><label for="id_email">{% trans 'Email address:' %}</label> {{ form.email }} <input type="submit" value="{% trans 'Reset my password' %}" /></p>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -28,7 +28,8 @@ def submit_row(context):
|
|||
change = context['change']
|
||||
is_popup = context['is_popup']
|
||||
save_as = context['save_as']
|
||||
return {
|
||||
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']
|
||||
|
@ -40,6 +41,9 @@ def submit_row(context):
|
|||
'is_popup': is_popup,
|
||||
'show_save': True
|
||||
}
|
||||
if context.get('original') is not None:
|
||||
ctx['original'] = context['original']
|
||||
return ctx
|
||||
|
||||
@register.filter
|
||||
def cell_count(inline_admin_form):
|
||||
|
|
|
@ -21,9 +21,9 @@ class AdminSeleniumWebDriverTestCase(LiveServerTestCase):
|
|||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
super(AdminSeleniumWebDriverTestCase, cls).tearDownClass()
|
||||
if hasattr(cls, 'selenium'):
|
||||
cls.selenium.quit()
|
||||
super(AdminSeleniumWebDriverTestCase, cls).tearDownClass()
|
||||
|
||||
def wait_until(self, callback, timeout=10):
|
||||
"""
|
||||
|
@ -98,4 +98,4 @@ class AdminSeleniumWebDriverTestCase(LiveServerTestCase):
|
|||
`klass`.
|
||||
"""
|
||||
return (self.selenium.find_element_by_css_selector(selector)
|
||||
.get_attribute('class').find(klass) != -1)
|
||||
.get_attribute('class').find(klass) != -1)
|
||||
|
|
|
@ -48,9 +48,9 @@ def prepare_lookup_value(key, value):
|
|||
def quote(s):
|
||||
"""
|
||||
Ensure that primary key values do not confuse the admin URLs by escaping
|
||||
any '/', '_' and ':' characters. Similar to urllib.quote, except that the
|
||||
quoting is slightly different so that it doesn't get automatically
|
||||
unquoted by the Web browser.
|
||||
any '/', '_' and ':' and similarly problematic characters.
|
||||
Similar to urllib.quote, except that the quoting is slightly different so
|
||||
that it doesn't get automatically unquoted by the Web browser.
|
||||
"""
|
||||
if not isinstance(s, six.string_types):
|
||||
return s
|
||||
|
@ -191,6 +191,13 @@ class NestedObjects(Collector):
|
|||
roots.extend(self._nested(root, seen, format_callback))
|
||||
return roots
|
||||
|
||||
def can_fast_delete(self, *args, **kwargs):
|
||||
"""
|
||||
We always want to load the objects into memory so that we can display
|
||||
them to the user in confirm page.
|
||||
"""
|
||||
return False
|
||||
|
||||
|
||||
def model_format_dict(obj):
|
||||
"""
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.contrib.admin.forms import AdminAuthenticationForm
|
|||
from django.contrib.auth.views import login
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||
|
||||
|
||||
def staff_member_required(view_func):
|
||||
"""
|
||||
Decorator for views that checks that the user is logged in and is a staff
|
||||
|
|
|
@ -3,6 +3,7 @@ from functools import reduce
|
|||
|
||||
from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured
|
||||
from django.core.paginator import InvalidPage
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
@ -376,4 +377,8 @@ class ChangeList(object):
|
|||
return qs
|
||||
|
||||
def url_for_result(self, result):
|
||||
return "%s/" % quote(getattr(result, self.pk_attname))
|
||||
pk = getattr(result, self.pk_attname)
|
||||
return reverse('admin:%s_%s_change' % (self.opts.app_label,
|
||||
self.opts.module_name),
|
||||
args=(quote(pk),),
|
||||
current_app=self.model_admin.admin_site.name)
|
||||
|
|
|
@ -4,7 +4,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2012-03-23 02:36+0100\n"
|
||||
"POT-Creation-Date: 2012-10-22 09:28+0200\n"
|
||||
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
|
||||
"Last-Translator: Django team\n"
|
||||
"Language-Team: English <en@li.org>\n"
|
||||
|
@ -13,63 +13,75 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: views.py:57 views.py:59 views.py:61
|
||||
#: views.py:58 views.py:60 views.py:62
|
||||
msgid "tag:"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:92 views.py:94 views.py:96
|
||||
#: views.py:93 views.py:95 views.py:97
|
||||
msgid "filter:"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:155 views.py:157 views.py:159
|
||||
#: views.py:156 views.py:158 views.py:160
|
||||
msgid "view:"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:187
|
||||
#: views.py:188
|
||||
#, python-format
|
||||
msgid "App %r not found"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:194
|
||||
#: views.py:195
|
||||
#, python-format
|
||||
msgid "Model %(model_name)r not found in app %(app_label)r"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:206
|
||||
#: views.py:207
|
||||
#, python-format
|
||||
msgid "the related `%(app_label)s.%(data_type)s` object"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:206 views.py:225 views.py:230 views.py:244 views.py:258
|
||||
#: views.py:263
|
||||
#: views.py:207 views.py:226 views.py:231 views.py:245 views.py:259
|
||||
#: views.py:264
|
||||
msgid "model:"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:221 views.py:253
|
||||
#: views.py:222 views.py:254
|
||||
#, python-format
|
||||
msgid "related `%(app_label)s.%(object_name)s` objects"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:225 views.py:258
|
||||
#: views.py:226 views.py:259
|
||||
#, python-format
|
||||
msgid "all %s"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:230 views.py:263
|
||||
#: views.py:231 views.py:264
|
||||
#, python-format
|
||||
msgid "number of %s"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:268
|
||||
#: views.py:269
|
||||
#, python-format
|
||||
msgid "Fields on %s objects"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:360
|
||||
#: views.py:361
|
||||
#, python-format
|
||||
msgid "%s does not appear to be a urlpattern object"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/bookmarklets.html:6 templates/admin_doc/index.html:6
|
||||
#: templates/admin_doc/missing_docutils.html:6
|
||||
#: templates/admin_doc/model_detail.html:14
|
||||
#: templates/admin_doc/model_index.html:8
|
||||
#: templates/admin_doc/template_detail.html:6
|
||||
#: templates/admin_doc/template_filter_index.html:7
|
||||
#: templates/admin_doc/template_tag_index.html:7
|
||||
#: templates/admin_doc/view_detail.html:6
|
||||
#: templates/admin_doc/view_index.html:7
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/bookmarklets.html:7 templates/admin_doc/index.html:7
|
||||
#: templates/admin_doc/missing_docutils.html:7
|
||||
#: templates/admin_doc/model_detail.html:15
|
||||
|
@ -79,30 +91,18 @@ msgstr ""
|
|||
#: templates/admin_doc/template_tag_index.html:8
|
||||
#: templates/admin_doc/view_detail.html:7
|
||||
#: templates/admin_doc/view_index.html:8
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/bookmarklets.html:8 templates/admin_doc/index.html:8
|
||||
#: templates/admin_doc/missing_docutils.html:8
|
||||
#: templates/admin_doc/model_detail.html:16
|
||||
#: templates/admin_doc/model_index.html:10
|
||||
#: templates/admin_doc/template_detail.html:8
|
||||
#: templates/admin_doc/template_filter_index.html:9
|
||||
#: templates/admin_doc/template_tag_index.html:9
|
||||
#: templates/admin_doc/view_detail.html:8
|
||||
#: templates/admin_doc/view_index.html:9
|
||||
msgid "Documentation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/bookmarklets.html:9
|
||||
#: templates/admin_doc/bookmarklets.html:8
|
||||
msgid "Bookmarklets"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/bookmarklets.html:12
|
||||
#: templates/admin_doc/bookmarklets.html:11
|
||||
msgid "Documentation bookmarklets"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/bookmarklets.html:16
|
||||
#: templates/admin_doc/bookmarklets.html:15
|
||||
msgid ""
|
||||
"\n"
|
||||
"<p class=\"help\">To install bookmarklets, drag the link to your bookmarks\n"
|
||||
|
@ -113,60 +113,69 @@ msgid ""
|
|||
"your computer is \"internal\").</p>\n"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/bookmarklets.html:26
|
||||
#: templates/admin_doc/bookmarklets.html:25
|
||||
msgid "Documentation for this page"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/bookmarklets.html:27
|
||||
#: templates/admin_doc/bookmarklets.html:26
|
||||
msgid ""
|
||||
"Jumps you from any page to the documentation for the view that generates "
|
||||
"that page."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/bookmarklets.html:29
|
||||
#: templates/admin_doc/bookmarklets.html:28
|
||||
msgid "Show object ID"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/bookmarklets.html:30
|
||||
#: templates/admin_doc/bookmarklets.html:29
|
||||
msgid ""
|
||||
"Shows the content-type and unique ID for pages that represent a single "
|
||||
"object."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/bookmarklets.html:32
|
||||
#: templates/admin_doc/bookmarklets.html:31
|
||||
msgid "Edit this object (current window)"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/bookmarklets.html:33
|
||||
#: templates/admin_doc/bookmarklets.html:32
|
||||
msgid "Jumps to the admin page for pages that represent a single object."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/bookmarklets.html:35
|
||||
#: templates/admin_doc/bookmarklets.html:34
|
||||
msgid "Edit this object (new window)"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/bookmarklets.html:36
|
||||
#: templates/admin_doc/bookmarklets.html:35
|
||||
msgid "As above, but opens the admin page in a new window."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/model_detail.html:17
|
||||
#: templates/admin_doc/model_index.html:11
|
||||
#: templates/admin_doc/model_detail.html:16
|
||||
#: templates/admin_doc/model_index.html:10
|
||||
msgid "Models"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/template_detail.html:9
|
||||
#: templates/admin_doc/template_detail.html:8
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/template_filter_index.html:10
|
||||
#: templates/admin_doc/template_filter_index.html:9
|
||||
msgid "Filters"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/template_tag_index.html:10
|
||||
#: templates/admin_doc/template_tag_index.html:9
|
||||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin_doc/view_detail.html:9
|
||||
#: templates/admin_doc/view_index.html:10
|
||||
#: templates/admin_doc/view_detail.html:8
|
||||
#: templates/admin_doc/view_index.html:9
|
||||
msgid "Views"
|
||||
msgstr ""
|
||||
|
||||
#: tests/__init__.py:23
|
||||
msgid "Boolean (Either True or False)"
|
||||
msgstr ""
|
||||
|
||||
#: tests/__init__.py:33
|
||||
#, python-format
|
||||
msgid "Field of type: %(field_type)s"
|
||||
msgstr ""
|
||||
|
|
|
@ -26,7 +26,7 @@ class TestFieldType(unittest.TestCase):
|
|||
def test_custom_fields(self):
|
||||
self.assertEqual(
|
||||
views.get_readable_field_data_type(fields.CustomField()),
|
||||
_('A custom field type')
|
||||
'A custom field type'
|
||||
)
|
||||
self.assertEqual(
|
||||
views.get_readable_field_data_type(fields.DescriptionLackingField()),
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import re
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils.importlib import import_module
|
||||
from django.contrib.auth.signals import user_logged_in, user_logged_out
|
||||
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
|
||||
|
||||
SESSION_KEY = '_auth_user_id'
|
||||
BACKEND_SESSION_KEY = '_auth_user_backend'
|
||||
REDIRECT_FIELD_NAME = 'next'
|
||||
|
||||
|
||||
def load_backend(path):
|
||||
i = path.rfind('.')
|
||||
module, attr = path[:i], path[i+1:]
|
||||
module, attr = path[:i], path[i + 1:]
|
||||
try:
|
||||
mod = import_module(module)
|
||||
except ImportError as e:
|
||||
|
@ -21,6 +24,7 @@ def load_backend(path):
|
|||
raise ImproperlyConfigured('Module "%s" does not define a "%s" authentication backend' % (module, attr))
|
||||
return cls()
|
||||
|
||||
|
||||
def get_backends():
|
||||
from django.conf import settings
|
||||
backends = []
|
||||
|
@ -30,6 +34,22 @@ def get_backends():
|
|||
raise ImproperlyConfigured('No authentication backends have been defined. Does AUTHENTICATION_BACKENDS contain anything?')
|
||||
return backends
|
||||
|
||||
|
||||
def _clean_credentials(credentials):
|
||||
"""
|
||||
Cleans a dictionary of credentials of potentially sensitive info before
|
||||
sending to less secure functions.
|
||||
|
||||
Not comprehensive - intended for user_login_failed signal
|
||||
"""
|
||||
SENSITIVE_CREDENTIALS = re.compile('api|token|key|secret|password|signature', re.I)
|
||||
CLEANSED_SUBSTITUTE = '********************'
|
||||
for key in credentials:
|
||||
if SENSITIVE_CREDENTIALS.search(key):
|
||||
credentials[key] = CLEANSED_SUBSTITUTE
|
||||
return credentials
|
||||
|
||||
|
||||
def authenticate(**credentials):
|
||||
"""
|
||||
If the given credentials are valid, return a User object.
|
||||
|
@ -46,6 +66,11 @@ def authenticate(**credentials):
|
|||
user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__)
|
||||
return user
|
||||
|
||||
# The credentials supplied are invalid to all backends, fire signal
|
||||
user_login_failed.send(sender=__name__,
|
||||
credentials=_clean_credentials(credentials))
|
||||
|
||||
|
||||
def login(request, user):
|
||||
"""
|
||||
Persist a user id and a backend in the request. This way a user doesn't
|
||||
|
@ -69,6 +94,7 @@ def login(request, user):
|
|||
request.user = user
|
||||
user_logged_in.send(sender=user.__class__, request=request, user=user)
|
||||
|
||||
|
||||
def logout(request):
|
||||
"""
|
||||
Removes the authenticated user's ID from the request and flushes their
|
||||
|
@ -86,6 +112,22 @@ def logout(request):
|
|||
from django.contrib.auth.models import AnonymousUser
|
||||
request.user = AnonymousUser()
|
||||
|
||||
|
||||
def get_user_model():
|
||||
"Return the User model that is active in this project"
|
||||
from django.conf import settings
|
||||
from django.db.models import get_model
|
||||
|
||||
try:
|
||||
app_label, model_name = settings.AUTH_USER_MODEL.split('.')
|
||||
except ValueError:
|
||||
raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'")
|
||||
user_model = get_model(app_label, model_name)
|
||||
if user_model is None:
|
||||
raise ImproperlyConfigured("AUTH_USER_MODEL refers to model '%s' that has not been installed" % settings.AUTH_USER_MODEL)
|
||||
return user_model
|
||||
|
||||
|
||||
def get_user(request):
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
try:
|
||||
|
|
|
@ -11,14 +11,13 @@ from django.shortcuts import get_object_or_404
|
|||
from django.template.response import TemplateResponse
|
||||
from django.utils.html import escape
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils import six
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
|
||||
csrf_protect_m = method_decorator(csrf_protect)
|
||||
|
||||
|
||||
class GroupAdmin(admin.ModelAdmin):
|
||||
search_fields = ('name',)
|
||||
ordering = ('name',)
|
||||
|
@ -54,10 +53,10 @@ class UserAdmin(admin.ModelAdmin):
|
|||
add_form = UserCreationForm
|
||||
change_password_form = AdminPasswordChangeForm
|
||||
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
|
||||
list_filter = ('is_staff', 'is_superuser', 'is_active')
|
||||
list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups')
|
||||
search_fields = ('username', 'first_name', 'last_name', 'email')
|
||||
ordering = ('username',)
|
||||
filter_horizontal = ('user_permissions',)
|
||||
filter_horizontal = ('groups', 'user_permissions',)
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
if not obj:
|
||||
|
@ -106,9 +105,10 @@ class UserAdmin(admin.ModelAdmin):
|
|||
raise PermissionDenied
|
||||
if extra_context is None:
|
||||
extra_context = {}
|
||||
username_field = self.model._meta.get_field(self.model.USERNAME_FIELD)
|
||||
defaults = {
|
||||
'auto_populated_fields': (),
|
||||
'username_help_text': self.model._meta.get_field('username').help_text,
|
||||
'username_help_text': username_field.help_text,
|
||||
}
|
||||
extra_context.update(defaults)
|
||||
return super(UserAdmin, self).add_view(request, form_url,
|
||||
|
@ -153,7 +153,7 @@ class UserAdmin(admin.ModelAdmin):
|
|||
'admin/auth/user/change_password.html'
|
||||
], context, current_app=self.admin_site.name)
|
||||
|
||||
def response_add(self, request, obj, post_url_continue='../%s/'):
|
||||
def response_add(self, request, obj, **kwargs):
|
||||
"""
|
||||
Determines the HttpResponse for the add_view stage. It mostly defers to
|
||||
its superclass implementation but is customized because the User model
|
||||
|
@ -166,9 +166,7 @@ class UserAdmin(admin.ModelAdmin):
|
|||
# * We are adding a user in a popup
|
||||
if '_addanother' not in request.POST and '_popup' not in request.POST:
|
||||
request.POST['_continue'] = 1
|
||||
return super(UserAdmin, self).response_add(request, obj,
|
||||
post_url_continue)
|
||||
return super(UserAdmin, self).response_add(request, obj, **kwargs)
|
||||
|
||||
admin.site.register(Group, GroupAdmin)
|
||||
admin.site.register(User, UserAdmin)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.auth.models import User, Permission
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Permission
|
||||
|
||||
|
||||
class ModelBackend(object):
|
||||
|
@ -12,10 +12,11 @@ class ModelBackend(object):
|
|||
# configurable.
|
||||
def authenticate(self, username=None, password=None):
|
||||
try:
|
||||
user = User.objects.get(username=username)
|
||||
UserModel = get_user_model()
|
||||
user = UserModel.objects.get_by_natural_key(username)
|
||||
if user.check_password(password):
|
||||
return user
|
||||
except User.DoesNotExist:
|
||||
except UserModel.DoesNotExist:
|
||||
return None
|
||||
|
||||
def get_group_permissions(self, user_obj, obj=None):
|
||||
|
@ -29,7 +30,9 @@ class ModelBackend(object):
|
|||
if user_obj.is_superuser:
|
||||
perms = Permission.objects.all()
|
||||
else:
|
||||
perms = Permission.objects.filter(group__user=user_obj)
|
||||
user_groups_field = get_user_model()._meta.get_field('groups')
|
||||
user_groups_query = 'group__%s' % user_groups_field.related_query_name()
|
||||
perms = Permission.objects.filter(**{user_groups_query: user_obj})
|
||||
perms = perms.values_list('content_type__app_label', 'codename').order_by()
|
||||
user_obj._group_perm_cache = set(["%s.%s" % (ct, name) for ct, name in perms])
|
||||
return user_obj._group_perm_cache
|
||||
|
@ -60,8 +63,9 @@ class ModelBackend(object):
|
|||
|
||||
def get_user(self, user_id):
|
||||
try:
|
||||
return User.objects.get(pk=user_id)
|
||||
except User.DoesNotExist:
|
||||
UserModel = get_user_model()
|
||||
return UserModel.objects.get(pk=user_id)
|
||||
except UserModel.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
|
@ -94,17 +98,21 @@ class RemoteUserBackend(ModelBackend):
|
|||
user = None
|
||||
username = self.clean_username(remote_user)
|
||||
|
||||
UserModel = get_user_model()
|
||||
|
||||
# Note that this could be accomplished in one try-except clause, but
|
||||
# instead we use get_or_create when creating unknown users since it has
|
||||
# built-in safeguards for multiple threads.
|
||||
if self.create_unknown_user:
|
||||
user, created = User.objects.get_or_create(username=username)
|
||||
user, created = UserModel.objects.get_or_create(**{
|
||||
UserModel.USERNAME_FIELD: username
|
||||
})
|
||||
if created:
|
||||
user = self.configure_user(user)
|
||||
else:
|
||||
try:
|
||||
user = User.objects.get(username=username)
|
||||
except User.DoesNotExist:
|
||||
user = UserModel.objects.get_by_natural_key(username)
|
||||
except UserModel.DoesNotExist:
|
||||
pass
|
||||
return user
|
||||
|
||||
|
|
|
@ -11,6 +11,11 @@ class PermLookupDict(object):
|
|||
def __getitem__(self, perm_name):
|
||||
return self.user.has_perm("%s.%s" % (self.module_name, perm_name))
|
||||
|
||||
def __iter__(self):
|
||||
# To fix 'item in perms.someapp' and __getitem__ iteraction we need to
|
||||
# define __iter__. See #18979 for details.
|
||||
raise TypeError("PermLookupDict is not iterable.")
|
||||
|
||||
def __bool__(self):
|
||||
return self.user.has_module_perms(self.module_name)
|
||||
__nonzero__ = __bool__ # Python 2
|
||||
|
@ -27,6 +32,17 @@ class PermWrapper(object):
|
|||
# I am large, I contain multitudes.
|
||||
raise TypeError("PermWrapper is not iterable.")
|
||||
|
||||
def __contains__(self, perm_name):
|
||||
"""
|
||||
Lookup by "someapp" or "someapp.someperm" in perms.
|
||||
"""
|
||||
if '.' not in perm_name:
|
||||
# The name refers to module.
|
||||
return bool(self[perm_name])
|
||||
module_name, perm_name = perm_name.split('.', 1)
|
||||
return self[module_name][perm_name]
|
||||
|
||||
|
||||
def auth(request):
|
||||
"""
|
||||
Returns context variables required by apps that use Django's authentication
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
[
|
||||
{
|
||||
"pk": "1",
|
||||
"model": "auth.customuser",
|
||||
"fields": {
|
||||
"password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
|
||||
"last_login": "2006-12-17 07:03:31",
|
||||
"email": "staffmember@example.com",
|
||||
"is_active": true,
|
||||
"is_admin": false,
|
||||
"date_of_birth": "1976-11-08"
|
||||
}
|
||||
}
|
||||
]
|
|
@ -7,9 +7,10 @@ from django.utils.datastructures import SortedDict
|
|||
from django.utils.html import format_html, format_html_join
|
||||
from django.utils.http import int_to_base36
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
|
||||
from django.contrib.auth import authenticate
|
||||
from django.contrib.auth import authenticate, get_user_model
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.hashers import UNUSABLE_PASSWORD, identify_hasher
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
|
@ -117,9 +118,6 @@ class UserChangeForm(forms.ModelForm):
|
|||
"this user's password, but you can change the password "
|
||||
"using <a href=\"password/\">this form</a>."))
|
||||
|
||||
def clean_password(self):
|
||||
return self.initial["password"]
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
|
||||
|
@ -129,13 +127,19 @@ class UserChangeForm(forms.ModelForm):
|
|||
if f is not None:
|
||||
f.queryset = f.queryset.select_related('content_type')
|
||||
|
||||
def clean_password(self):
|
||||
# Regardless of what the user provides, return the initial value.
|
||||
# This is done here, rather than on the field, because the
|
||||
# field does not have access to the initial value
|
||||
return self.initial["password"]
|
||||
|
||||
|
||||
class AuthenticationForm(forms.Form):
|
||||
"""
|
||||
Base class for authenticating users. Extend this to get a form that accepts
|
||||
username/password logins.
|
||||
"""
|
||||
username = forms.CharField(label=_("Username"), max_length=30)
|
||||
username = forms.CharField(max_length=254)
|
||||
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
|
||||
|
||||
error_messages = {
|
||||
|
@ -157,6 +161,11 @@ class AuthenticationForm(forms.Form):
|
|||
self.user_cache = None
|
||||
super(AuthenticationForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# 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)
|
||||
|
||||
def clean(self):
|
||||
username = self.cleaned_data.get('username')
|
||||
password = self.cleaned_data.get('password')
|
||||
|
@ -187,20 +196,21 @@ class AuthenticationForm(forms.Form):
|
|||
|
||||
class PasswordResetForm(forms.Form):
|
||||
error_messages = {
|
||||
'unknown': _("That e-mail address doesn't have an associated "
|
||||
'unknown': _("That email address doesn't have an associated "
|
||||
"user account. Are you sure you've registered?"),
|
||||
'unusable': _("The user account associated with this e-mail "
|
||||
'unusable': _("The user account associated with this email "
|
||||
"address cannot reset the password."),
|
||||
}
|
||||
email = forms.EmailField(label=_("E-mail"), max_length=75)
|
||||
email = forms.EmailField(label=_("Email"), max_length=254)
|
||||
|
||||
def clean_email(self):
|
||||
"""
|
||||
Validates that an active user exists with the given email address.
|
||||
"""
|
||||
UserModel = get_user_model()
|
||||
email = self.cleaned_data["email"]
|
||||
self.users_cache = User.objects.filter(email__iexact=email,
|
||||
is_active=True)
|
||||
self.users_cache = UserModel.objects.filter(email__iexact=email,
|
||||
is_active=True)
|
||||
if not len(self.users_cache):
|
||||
raise forms.ValidationError(self.error_messages['unknown'])
|
||||
if any((user.password == UNUSABLE_PASSWORD)
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
from django.contrib import auth
|
||||
from django import db
|
||||
from django.utils.encoding import force_bytes
|
||||
|
||||
|
||||
def check_password(environ, username, password):
|
||||
"""
|
||||
Authenticates against Django's auth database
|
||||
|
||||
mod_wsgi docs specify None, True, False as return value depending
|
||||
on whether the user exists and authenticates.
|
||||
"""
|
||||
|
||||
UserModel = auth.get_user_model()
|
||||
# db connection state is managed similarly to the wsgi handler
|
||||
# as mod_wsgi may call these functions outside of a request/response cycle
|
||||
db.reset_queries()
|
||||
|
||||
try:
|
||||
try:
|
||||
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
|
||||
return None
|
||||
return user.check_password(password)
|
||||
finally:
|
||||
db.close_connection()
|
||||
|
||||
|
||||
def groups_for_user(environ, username):
|
||||
"""
|
||||
Authorizes a user based on groups
|
||||
"""
|
||||
|
||||
UserModel = auth.get_user_model()
|
||||
db.reset_queries()
|
||||
|
||||
try:
|
||||
try:
|
||||
user = UserModel.objects.get_by_natural_key(username)
|
||||
except UserModel.DoesNotExist:
|
||||
return []
|
||||
try:
|
||||
if not user.is_active:
|
||||
return []
|
||||
except AttributeError as e:
|
||||
# a custom user may not support is_active
|
||||
return []
|
||||
return [force_bytes(group.name) for group in user.groups.all()]
|
||||
finally:
|
||||
db.close_connection()
|
|
@ -4,7 +4,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2012-03-23 02:36+0100\n"
|
||||
"POT-Creation-Date: 2012-10-15 10:56+0200\n"
|
||||
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
|
||||
"Last-Translator: Django team\n"
|
||||
"Language-Team: English <en@li.org>\n"
|
||||
|
@ -25,48 +25,56 @@ msgstr ""
|
|||
msgid "Important dates"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:125
|
||||
#: admin.py:126
|
||||
msgid "Password changed successfully."
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:135
|
||||
#: admin.py:136
|
||||
#, python-format
|
||||
msgid "Change password: %s"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:62
|
||||
#: forms.py:31 tests/forms.py:249 tests/forms.py:254
|
||||
msgid "No password set."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:37 tests/forms.py:259 tests/forms.py:265
|
||||
msgid "Invalid password format or unknown hashing algorithm."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:65
|
||||
msgid "A user with that username already exists."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:63 forms.py:251 forms.py:308
|
||||
#: forms.py:66 forms.py:257 forms.py:317
|
||||
msgid "The two password fields didn't match."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:65 forms.py:110 forms.py:139
|
||||
#: forms.py:68 forms.py:113
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:67 forms.py:111
|
||||
#: forms.py:70 forms.py:114
|
||||
msgid "Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:70 forms.py:114
|
||||
#: forms.py:73 forms.py:117
|
||||
msgid "This value may contain only letters, numbers and @/./+/-/_ characters."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:72 forms.py:116 forms.py:140 forms.py:310
|
||||
#: forms.py:75 forms.py:119 forms.py:140 forms.py:319
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:74
|
||||
#: forms.py:77
|
||||
msgid "Password confirmation"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:76
|
||||
#: forms.py:79
|
||||
msgid "Enter the same password as above, for verification."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:117
|
||||
#: forms.py:120
|
||||
msgid ""
|
||||
"Raw passwords are not stored, so there is no way to see this user's "
|
||||
"password, but you can change the password using <a href=\"password/\">this "
|
||||
|
@ -89,178 +97,178 @@ msgstr ""
|
|||
msgid "This account is inactive."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:191
|
||||
#: forms.py:196
|
||||
msgid ""
|
||||
"That e-mail address doesn't have an associated user account. Are you sure "
|
||||
"That email address doesn't have an associated user account. Are you sure "
|
||||
"you've registered?"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:193
|
||||
#: forms.py:198 tests/forms.py:347
|
||||
msgid ""
|
||||
"The user account associated with this e-mail address cannot reset the "
|
||||
"The user account associated with this email address cannot reset the "
|
||||
"password."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:196
|
||||
msgid "E-mail"
|
||||
#: forms.py:201
|
||||
msgid "Email"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:253
|
||||
#: forms.py:259
|
||||
msgid "New password"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:255
|
||||
#: forms.py:261
|
||||
msgid "New password confirmation"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:284
|
||||
#: forms.py:290
|
||||
msgid "Your old password was entered incorrectly. Please enter it again."
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:287
|
||||
#: forms.py:293
|
||||
msgid "Old password"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:312
|
||||
#: forms.py:321
|
||||
msgid "Password (again)"
|
||||
msgstr ""
|
||||
|
||||
#: hashers.py:218 hashers.py:269 hashers.py:298 hashers.py:326 hashers.py:355
|
||||
#: hashers.py:389
|
||||
#: hashers.py:241 hashers.py:292 hashers.py:321 hashers.py:349 hashers.py:378
|
||||
#: hashers.py:412
|
||||
msgid "algorithm"
|
||||
msgstr ""
|
||||
|
||||
#: hashers.py:219
|
||||
#: hashers.py:242
|
||||
msgid "iterations"
|
||||
msgstr ""
|
||||
|
||||
#: hashers.py:220 hashers.py:271 hashers.py:299 hashers.py:327 hashers.py:390
|
||||
#: hashers.py:243 hashers.py:294 hashers.py:322 hashers.py:350 hashers.py:413
|
||||
msgid "salt"
|
||||
msgstr ""
|
||||
|
||||
#: hashers.py:221 hashers.py:300 hashers.py:328 hashers.py:356 hashers.py:391
|
||||
#: hashers.py:244 hashers.py:323 hashers.py:351 hashers.py:379 hashers.py:414
|
||||
msgid "hash"
|
||||
msgstr ""
|
||||
|
||||
#: hashers.py:270
|
||||
#: hashers.py:293
|
||||
msgid "work factor"
|
||||
msgstr ""
|
||||
|
||||
#: hashers.py:272
|
||||
#: hashers.py:295
|
||||
msgid "checksum"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:66 models.py:113
|
||||
#: models.py:72 models.py:121
|
||||
msgid "name"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:68
|
||||
#: models.py:74
|
||||
msgid "codename"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:72
|
||||
#: models.py:78
|
||||
msgid "permission"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:73 models.py:115
|
||||
#: models.py:79 models.py:123
|
||||
msgid "permissions"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:120
|
||||
#: models.py:128
|
||||
msgid "group"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:121 models.py:250
|
||||
#: models.py:129 models.py:317
|
||||
msgid "groups"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:232
|
||||
msgid "username"
|
||||
msgid "password"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:233
|
||||
msgid "last login"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:298
|
||||
msgid "username"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:299
|
||||
msgid ""
|
||||
"Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:235
|
||||
#: models.py:302
|
||||
msgid "Enter a valid username."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:304
|
||||
msgid "first name"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:236
|
||||
#: models.py:305
|
||||
msgid "last name"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:237
|
||||
msgid "e-mail address"
|
||||
#: models.py:306
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:238
|
||||
msgid "password"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:239
|
||||
#: models.py:307
|
||||
msgid "staff status"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:240
|
||||
#: models.py:308
|
||||
msgid "Designates whether the user can log into this admin site."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:242
|
||||
#: models.py:310
|
||||
msgid "active"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:243
|
||||
#: models.py:311
|
||||
msgid ""
|
||||
"Designates whether this user should be treated as active. Unselect this "
|
||||
"instead of deleting accounts."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:245
|
||||
#: models.py:313
|
||||
msgid "superuser status"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:246
|
||||
#: models.py:314
|
||||
msgid ""
|
||||
"Designates that this user has all permissions without explicitly assigning "
|
||||
"them."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:248
|
||||
msgid "last login"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:249
|
||||
#: models.py:316
|
||||
msgid "date joined"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:251
|
||||
#: models.py:318
|
||||
msgid ""
|
||||
"The groups this user belongs to. A user will get all permissions granted to "
|
||||
"each of his/her group."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:255
|
||||
#: models.py:322
|
||||
msgid "user permissions"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:260
|
||||
#: models.py:331
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:261
|
||||
#: models.py:332
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:93
|
||||
#: views.py:97
|
||||
msgid "Logged out"
|
||||
msgstr ""
|
||||
|
||||
#: management/commands/createsuperuser.py:27
|
||||
msgid "Enter a valid e-mail address."
|
||||
msgstr ""
|
||||
|
||||
#: templates/registration/password_reset_subject.txt:2
|
||||
#, python-format
|
||||
msgid "Password reset on %(site_name)s"
|
||||
|
|
|
@ -6,9 +6,11 @@ from __future__ import unicode_literals
|
|||
import getpass
|
||||
import locale
|
||||
import unicodedata
|
||||
from django.contrib.auth import models as auth_app
|
||||
|
||||
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.models import get_models, signals
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils import six
|
||||
from django.utils.six.moves import input
|
||||
|
||||
|
@ -17,13 +19,43 @@ def _get_permission_codename(action, opts):
|
|||
return '%s_%s' % (action, opts.object_name.lower())
|
||||
|
||||
|
||||
def _get_all_permissions(opts):
|
||||
"Returns (codename, name) for all permissions in the given opts."
|
||||
def _get_all_permissions(opts, ctype):
|
||||
"""
|
||||
Returns (codename, name) for all permissions in the given opts.
|
||||
"""
|
||||
builtin = _get_builtin_permissions(opts)
|
||||
custom = list(opts.permissions)
|
||||
_check_permission_clashing(custom, builtin, ctype)
|
||||
return builtin + custom
|
||||
|
||||
def _get_builtin_permissions(opts):
|
||||
"""
|
||||
Returns (codename, name) for all autogenerated permissions.
|
||||
"""
|
||||
perms = []
|
||||
for action in ('add', 'change', 'delete'):
|
||||
perms.append((_get_permission_codename(action, opts), 'Can %s %s' % (action, opts.verbose_name_raw)))
|
||||
return perms + list(opts.permissions)
|
||||
perms.append((_get_permission_codename(action, opts),
|
||||
'Can %s %s' % (action, opts.verbose_name_raw)))
|
||||
return perms
|
||||
|
||||
def _check_permission_clashing(custom, builtin, ctype):
|
||||
"""
|
||||
Check that permissions for a model do not clash. Raises CommandError if
|
||||
there are duplicate permissions.
|
||||
"""
|
||||
pool = set()
|
||||
builtin_codenames = set(p[0] for p in builtin)
|
||||
for codename, _name in custom:
|
||||
if codename in pool:
|
||||
raise CommandError(
|
||||
"The permission codename '%s' is duplicated for model '%s.%s'." %
|
||||
(codename, ctype.app_label, ctype.model_class().__name__))
|
||||
elif codename in builtin_codenames:
|
||||
raise CommandError(
|
||||
"The permission codename '%s' clashes with a builtin permission "
|
||||
"for model '%s.%s'." %
|
||||
(codename, ctype.app_label, ctype.model_class().__name__))
|
||||
pool.add(codename)
|
||||
|
||||
def create_permissions(app, created_models, verbosity, **kwargs):
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
@ -38,7 +70,7 @@ def create_permissions(app, created_models, verbosity, **kwargs):
|
|||
for klass in app_models:
|
||||
ctype = ContentType.objects.get_for_model(klass)
|
||||
ctypes.add(ctype)
|
||||
for perm in _get_all_permissions(klass._meta):
|
||||
for perm in _get_all_permissions(klass._meta, ctype):
|
||||
searched_perms.append((ctype, perm))
|
||||
|
||||
# Find all the Permissions that have a context_type for a model we're
|
||||
|
@ -64,7 +96,9 @@ def create_permissions(app, created_models, verbosity, **kwargs):
|
|||
def create_superuser(app, created_models, verbosity, db, **kwargs):
|
||||
from django.core.management import call_command
|
||||
|
||||
if auth_app.User in created_models and kwargs.get('interactive', True):
|
||||
UserModel = get_user_model()
|
||||
|
||||
if UserModel in created_models and kwargs.get('interactive', True):
|
||||
msg = ("\nYou just installed Django's auth system, which means you "
|
||||
"don't have any superusers defined.\nWould you like to create one "
|
||||
"now? (yes/no): ")
|
||||
|
@ -113,28 +147,35 @@ def get_default_username(check_db=True):
|
|||
:returns: The username, or an empty string if no username can be
|
||||
determined.
|
||||
"""
|
||||
from django.contrib.auth.management.commands.createsuperuser import (
|
||||
RE_VALID_USERNAME)
|
||||
# If the User model has been swapped out, we can't make any assumptions
|
||||
# about the default user name.
|
||||
if auth_app.User._meta.swapped:
|
||||
return ''
|
||||
|
||||
default_username = get_system_username()
|
||||
try:
|
||||
default_username = unicodedata.normalize('NFKD', default_username)\
|
||||
.encode('ascii', 'ignore').decode('ascii').replace(' ', '').lower()
|
||||
except UnicodeDecodeError:
|
||||
return ''
|
||||
if not RE_VALID_USERNAME.match(default_username):
|
||||
|
||||
# Run the username validator
|
||||
try:
|
||||
auth_app.User._meta.get_field('username').run_validators(default_username)
|
||||
except exceptions.ValidationError:
|
||||
return ''
|
||||
|
||||
# Don't return the default username if it is already taken.
|
||||
if check_db and default_username:
|
||||
try:
|
||||
User.objects.get(username=default_username)
|
||||
except User.DoesNotExist:
|
||||
auth_app.User.objects.get(username=default_username)
|
||||
except auth_app.User.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
return ''
|
||||
return default_username
|
||||
|
||||
|
||||
signals.post_syncdb.connect(create_permissions,
|
||||
dispatch_uid = "django.contrib.auth.management.create_permissions")
|
||||
dispatch_uid="django.contrib.auth.management.create_permissions")
|
||||
signals.post_syncdb.connect(create_superuser,
|
||||
sender=auth_app, dispatch_uid = "django.contrib.auth.management.create_superuser")
|
||||
sender=auth_app, dispatch_uid="django.contrib.auth.management.create_superuser")
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import getpass
|
||||
from optparse import make_option
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import DEFAULT_DB_ALIAS
|
||||
|
||||
|
||||
|
@ -30,12 +30,16 @@ class Command(BaseCommand):
|
|||
else:
|
||||
username = getpass.getuser()
|
||||
|
||||
UserModel = get_user_model()
|
||||
|
||||
try:
|
||||
u = User.objects.using(options.get('database')).get(username=username)
|
||||
except User.DoesNotExist:
|
||||
u = UserModel.objects.using(options.get('database')).get(**{
|
||||
UserModel.USERNAME_FIELD: username
|
||||
})
|
||||
except UserModel.DoesNotExist:
|
||||
raise CommandError("user '%s' does not exist" % username)
|
||||
|
||||
self.stdout.write("Changing password for user '%s'\n" % u.username)
|
||||
self.stdout.write("Changing password for user '%s'\n" % u)
|
||||
|
||||
MAX_TRIES = 3
|
||||
count = 0
|
||||
|
@ -48,9 +52,9 @@ class Command(BaseCommand):
|
|||
count = count + 1
|
||||
|
||||
if count == MAX_TRIES:
|
||||
raise CommandError("Aborting password change for user '%s' after %s attempts" % (username, count))
|
||||
raise CommandError("Aborting password change for user '%s' after %s attempts" % (u, count))
|
||||
|
||||
u.set_password(p1)
|
||||
u.save()
|
||||
|
||||
return "Password changed successfully for user '%s'" % u.username
|
||||
return "Password changed successfully for user '%s'" % u
|
||||
|
|
|
@ -3,109 +3,119 @@ Management utility to create superusers.
|
|||
"""
|
||||
|
||||
import getpass
|
||||
import re
|
||||
import sys
|
||||
from optparse import make_option
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.management import get_default_username
|
||||
from django.core import exceptions
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import DEFAULT_DB_ALIAS
|
||||
from django.utils.six.moves import input
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
RE_VALID_USERNAME = re.compile('[\w.@+-]+$')
|
||||
|
||||
EMAIL_RE = re.compile(
|
||||
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
|
||||
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' # quoted-string
|
||||
r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
|
||||
|
||||
|
||||
def is_valid_email(value):
|
||||
if not EMAIL_RE.search(value):
|
||||
raise exceptions.ValidationError(_('Enter a valid e-mail address.'))
|
||||
from django.utils.text import capfirst
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--username', dest='username', default=None,
|
||||
help='Specifies the username for the superuser.'),
|
||||
make_option('--email', dest='email', default=None,
|
||||
help='Specifies the email address for the superuser.'),
|
||||
make_option('--noinput', action='store_false', dest='interactive', default=True,
|
||||
help=('Tells Django to NOT prompt the user for input of any kind. '
|
||||
'You must use --username and --email with --noinput, and '
|
||||
'superusers created with --noinput will not be able to log '
|
||||
'in until they\'re given a valid password.')),
|
||||
make_option('--database', action='store', dest='database',
|
||||
default=DEFAULT_DB_ALIAS, help='Specifies the database to use. Default is "default".'),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Options are defined in an __init__ method to support swapping out
|
||||
# custom user models in tests.
|
||||
super(Command, self).__init__(*args, **kwargs)
|
||||
self.UserModel = get_user_model()
|
||||
self.username_field = self.UserModel._meta.get_field(self.UserModel.USERNAME_FIELD)
|
||||
|
||||
self.option_list = BaseCommand.option_list + (
|
||||
make_option('--%s' % self.UserModel.USERNAME_FIELD, dest=self.UserModel.USERNAME_FIELD, default=None,
|
||||
help='Specifies the login for the superuser.'),
|
||||
make_option('--noinput', action='store_false', dest='interactive', default=True,
|
||||
help=('Tells Django to NOT prompt the user for input of any kind. '
|
||||
'You must use --%s with --noinput, along with an option for '
|
||||
'any other required field. Superusers created with --noinput will '
|
||||
' not be able to log in until they\'re given a valid password.' %
|
||||
self.UserModel.USERNAME_FIELD)),
|
||||
make_option('--database', action='store', dest='database',
|
||||
default=DEFAULT_DB_ALIAS, help='Specifies the database to use. Default is "default".'),
|
||||
) + tuple(
|
||||
make_option('--%s' % field, dest=field, default=None,
|
||||
help='Specifies the %s for the superuser.' % field)
|
||||
for field in self.UserModel.REQUIRED_FIELDS
|
||||
)
|
||||
|
||||
option_list = BaseCommand.option_list
|
||||
help = 'Used to create a superuser.'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
username = options.get('username', None)
|
||||
email = options.get('email', None)
|
||||
username = options.get(self.UserModel.USERNAME_FIELD, None)
|
||||
interactive = options.get('interactive')
|
||||
verbosity = int(options.get('verbosity', 1))
|
||||
database = options.get('database')
|
||||
|
||||
# Do quick and dirty validation if --noinput
|
||||
if not interactive:
|
||||
if not username or not email:
|
||||
raise CommandError("You must use --username and --email with --noinput.")
|
||||
if not RE_VALID_USERNAME.match(username):
|
||||
raise CommandError("Invalid username. Use only letters, digits, and underscores")
|
||||
try:
|
||||
is_valid_email(email)
|
||||
except exceptions.ValidationError:
|
||||
raise CommandError("Invalid email address.")
|
||||
|
||||
# If not provided, create the user with an unusable password
|
||||
password = None
|
||||
user_data = {}
|
||||
|
||||
# Prompt for username/email/password. Enclose this whole thing in a
|
||||
# try/except to trap for a keyboard interrupt and exit gracefully.
|
||||
if interactive:
|
||||
# Do quick and dirty validation if --noinput
|
||||
if not interactive:
|
||||
try:
|
||||
if not username:
|
||||
raise CommandError("You must use --%s with --noinput." %
|
||||
self.UserModel.USERNAME_FIELD)
|
||||
username = self.username_field.clean(username, None)
|
||||
|
||||
for field_name in self.UserModel.REQUIRED_FIELDS:
|
||||
if options.get(field_name):
|
||||
field = self.UserModel._meta.get_field(field_name)
|
||||
user_data[field_name] = field.clean(options[field_name], None)
|
||||
else:
|
||||
raise CommandError("You must use --%s with --noinput." % field_name)
|
||||
except exceptions.ValidationError as e:
|
||||
raise CommandError('; '.join(e.messages))
|
||||
|
||||
else:
|
||||
# Prompt for username/password, and any other required fields.
|
||||
# Enclose this whole thing in a try/except to trap for a
|
||||
# keyboard interrupt and exit gracefully.
|
||||
default_username = get_default_username()
|
||||
try:
|
||||
|
||||
# Get a username
|
||||
while 1:
|
||||
while username is None:
|
||||
if not username:
|
||||
input_msg = 'Username'
|
||||
input_msg = capfirst(self.username_field.verbose_name)
|
||||
if default_username:
|
||||
input_msg += ' (leave blank to use %r)' % default_username
|
||||
username = input(input_msg + ': ')
|
||||
if default_username and username == '':
|
||||
username = default_username
|
||||
if not RE_VALID_USERNAME.match(username):
|
||||
self.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.")
|
||||
input_msg += " (leave blank to use '%s')" % default_username
|
||||
raw_value = input(input_msg + ': ')
|
||||
|
||||
if default_username and raw_value == '':
|
||||
raw_value = default_username
|
||||
try:
|
||||
username = self.username_field.clean(raw_value, None)
|
||||
except exceptions.ValidationError as e:
|
||||
self.stderr.write("Error: %s" % '; '.join(e.messages))
|
||||
username = None
|
||||
continue
|
||||
try:
|
||||
User.objects.using(database).get(username=username)
|
||||
except User.DoesNotExist:
|
||||
break
|
||||
self.UserModel.objects.db_manager(database).get_by_natural_key(username)
|
||||
except self.UserModel.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
self.stderr.write("Error: That username is already taken.")
|
||||
self.stderr.write("Error: That %s is already taken." %
|
||||
self.username_field.verbose_name)
|
||||
username = None
|
||||
|
||||
# Get an email
|
||||
while 1:
|
||||
if not email:
|
||||
email = input('E-mail address: ')
|
||||
try:
|
||||
is_valid_email(email)
|
||||
except exceptions.ValidationError:
|
||||
self.stderr.write("Error: That e-mail address is invalid.")
|
||||
email = None
|
||||
else:
|
||||
break
|
||||
for field_name in self.UserModel.REQUIRED_FIELDS:
|
||||
field = self.UserModel._meta.get_field(field_name)
|
||||
user_data[field_name] = options.get(field_name)
|
||||
while user_data[field_name] is None:
|
||||
raw_value = input(capfirst(field.verbose_name + ': '))
|
||||
try:
|
||||
user_data[field_name] = field.clean(raw_value, None)
|
||||
except exceptions.ValidationError as e:
|
||||
self.stderr.write("Error: %s" % '; '.join(e.messages))
|
||||
user_data[field_name] = None
|
||||
|
||||
# Get a password
|
||||
while 1:
|
||||
while password is None:
|
||||
if not password:
|
||||
password = getpass.getpass()
|
||||
password2 = getpass.getpass('Password (again): ')
|
||||
|
@ -117,12 +127,13 @@ class Command(BaseCommand):
|
|||
self.stderr.write("Error: Blank passwords aren't allowed.")
|
||||
password = None
|
||||
continue
|
||||
break
|
||||
|
||||
except KeyboardInterrupt:
|
||||
self.stderr.write("\nOperation cancelled.")
|
||||
sys.exit(1)
|
||||
|
||||
User.objects.db_manager(database).create_superuser(username, email, password)
|
||||
user_data[self.UserModel.USERNAME_FIELD] = username
|
||||
user_data['password'] = password
|
||||
self.UserModel.objects.db_manager(database).create_superuser(**user_data)
|
||||
if verbosity >= 1:
|
||||
self.stdout.write("Superuser created successfully.")
|
||||
|
||||
self.stdout.write("Superuser created successfully.")
|
||||
|
|
|
@ -55,7 +55,7 @@ class RemoteUserMiddleware(object):
|
|||
# getting passed in the headers, then the correct user is already
|
||||
# persisted in the session and we don't need to continue.
|
||||
if request.user.is_authenticated():
|
||||
if request.user.username == self.clean_username(username, request):
|
||||
if request.user.get_username() == self.clean_username(username, request):
|
||||
return
|
||||
# We are seeing this user for the first time in this session, attempt
|
||||
# to authenticate the user.
|
||||
|
@ -75,6 +75,6 @@ class RemoteUserMiddleware(object):
|
|||
backend = auth.load_backend(backend_str)
|
||||
try:
|
||||
username = backend.clean_username(username)
|
||||
except AttributeError: # Backend has no clean_username method.
|
||||
except AttributeError: # Backend has no clean_username method.
|
||||
pass
|
||||
return username
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
from __future__ import unicode_literals
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.mail import send_mail
|
||||
from django.core import validators
|
||||
from django.db import models
|
||||
from django.db.models.manager import EmptyManager
|
||||
from django.utils.crypto import get_random_string
|
||||
|
@ -96,6 +99,7 @@ class GroupManager(models.Manager):
|
|||
def get_by_natural_key(self, name):
|
||||
return self.get(name=name)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Group(models.Model):
|
||||
"""
|
||||
|
@ -131,7 +135,7 @@ class Group(models.Model):
|
|||
return (self.name,)
|
||||
|
||||
|
||||
class UserManager(models.Manager):
|
||||
class BaseUserManager(models.Manager):
|
||||
|
||||
@classmethod
|
||||
def normalize_email(cls, email):
|
||||
|
@ -148,30 +152,6 @@ class UserManager(models.Manager):
|
|||
email = '@'.join([email_name, domain_part.lower()])
|
||||
return email
|
||||
|
||||
def create_user(self, username, email=None, password=None):
|
||||
"""
|
||||
Creates and saves a User with the given username, email and password.
|
||||
"""
|
||||
now = timezone.now()
|
||||
if not username:
|
||||
raise ValueError('The given username must be set')
|
||||
email = UserManager.normalize_email(email)
|
||||
user = self.model(username=username, email=email,
|
||||
is_staff=False, is_active=True, is_superuser=False,
|
||||
last_login=now, date_joined=now)
|
||||
|
||||
user.set_password(password)
|
||||
user.save(using=self._db)
|
||||
return user
|
||||
|
||||
def create_superuser(self, username, email, password):
|
||||
u = self.create_user(username, email, password)
|
||||
u.is_staff = True
|
||||
u.is_active = True
|
||||
u.is_superuser = True
|
||||
u.save(using=self._db)
|
||||
return u
|
||||
|
||||
def make_random_password(self, length=10,
|
||||
allowed_chars='abcdefghjkmnpqrstuvwxyz'
|
||||
'ABCDEFGHJKLMNPQRSTUVWXYZ'
|
||||
|
@ -185,7 +165,34 @@ class UserManager(models.Manager):
|
|||
return get_random_string(length, allowed_chars)
|
||||
|
||||
def get_by_natural_key(self, username):
|
||||
return self.get(username=username)
|
||||
return self.get(**{self.model.USERNAME_FIELD: username})
|
||||
|
||||
|
||||
class UserManager(BaseUserManager):
|
||||
|
||||
def create_user(self, username, email=None, password=None, **extra_fields):
|
||||
"""
|
||||
Creates and saves a User with the given username, email and password.
|
||||
"""
|
||||
now = timezone.now()
|
||||
if not username:
|
||||
raise ValueError('The given username must be set')
|
||||
email = UserManager.normalize_email(email)
|
||||
user = self.model(username=username, email=email,
|
||||
is_staff=False, is_active=True, is_superuser=False,
|
||||
last_login=now, date_joined=now, **extra_fields)
|
||||
|
||||
user.set_password(password)
|
||||
user.save(using=self._db)
|
||||
return user
|
||||
|
||||
def create_superuser(self, username, email, password, **extra_fields):
|
||||
u = self.create_user(username, email, password, **extra_fields)
|
||||
u.is_staff = True
|
||||
u.is_active = True
|
||||
u.is_superuser = True
|
||||
u.save(using=self._db)
|
||||
return u
|
||||
|
||||
|
||||
# A few helper functions for common logic between User and AnonymousUser.
|
||||
|
@ -201,8 +208,6 @@ def _user_get_all_permissions(user, obj):
|
|||
|
||||
|
||||
def _user_has_perm(user, perm, obj):
|
||||
anon = user.is_anonymous()
|
||||
active = user.is_active
|
||||
for backend in auth.get_backends():
|
||||
if hasattr(backend, "has_perm"):
|
||||
if obj is not None:
|
||||
|
@ -215,8 +220,6 @@ def _user_has_perm(user, perm, obj):
|
|||
|
||||
|
||||
def _user_has_module_perms(user, app_label):
|
||||
anon = user.is_anonymous()
|
||||
active = user.is_active
|
||||
for backend in auth.get_backends():
|
||||
if hasattr(backend, "has_module_perms"):
|
||||
if backend.has_module_perms(user, app_label):
|
||||
|
@ -225,52 +228,24 @@ def _user_has_module_perms(user, app_label):
|
|||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class User(models.Model):
|
||||
"""
|
||||
Users within the Django authentication system are represented by this
|
||||
model.
|
||||
|
||||
Username and password 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'))
|
||||
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(_('e-mail address'), blank=True)
|
||||
class AbstractBaseUser(models.Model):
|
||||
password = models.CharField(_('password'), max_length=128)
|
||||
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.'))
|
||||
last_login = models.DateTimeField(_('last login'), default=timezone.now)
|
||||
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 '
|
||||
'his/her group.'))
|
||||
user_permissions = models.ManyToManyField(Permission,
|
||||
verbose_name=_('user permissions'), blank=True,
|
||||
help_text='Specific permissions for this user.')
|
||||
objects = UserManager()
|
||||
|
||||
REQUIRED_FIELDS = []
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('user')
|
||||
verbose_name_plural = _('users')
|
||||
abstract = True
|
||||
|
||||
def get_username(self):
|
||||
"Return the identifying username for this User"
|
||||
return getattr(self, self.USERNAME_FIELD)
|
||||
|
||||
def __str__(self):
|
||||
return self.username
|
||||
return self.get_username()
|
||||
|
||||
def natural_key(self):
|
||||
return (self.username,)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return "/users/%s/" % urlquote(self.username)
|
||||
return (self.get_username(),)
|
||||
|
||||
def is_anonymous(self):
|
||||
"""
|
||||
|
@ -286,13 +261,6 @@ class User(models.Model):
|
|||
"""
|
||||
return True
|
||||
|
||||
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 set_password(self, raw_password):
|
||||
self.password = make_password(raw_password)
|
||||
|
||||
|
@ -313,6 +281,71 @@ class User(models.Model):
|
|||
def has_usable_password(self):
|
||||
return is_password_usable(self.password)
|
||||
|
||||
def get_full_name(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_short_name(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class AbstractUser(AbstractBaseUser):
|
||||
"""
|
||||
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.'))
|
||||
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 '
|
||||
'his/her group.'))
|
||||
user_permissions = models.ManyToManyField(Permission,
|
||||
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
|
||||
|
@ -381,6 +414,8 @@ class User(models.Model):
|
|||
Returns site-specific profile for this user. Raises
|
||||
SiteProfileNotAvailable if this site does not allow profiles.
|
||||
"""
|
||||
warnings.warn("The use of AUTH_PROFILE_MODULE to define user profiles has been deprecated.",
|
||||
PendingDeprecationWarning)
|
||||
if not hasattr(self, '_profile_cache'):
|
||||
from django.conf import settings
|
||||
if not getattr(settings, 'AUTH_PROFILE_MODULE', False):
|
||||
|
@ -407,6 +442,17 @@ class User(models.Model):
|
|||
return self._profile_cache
|
||||
|
||||
|
||||
class User(AbstractUser):
|
||||
"""
|
||||
Users within the Django authentication system are represented by this
|
||||
model.
|
||||
|
||||
Username, password and email are required. Other fields are optional.
|
||||
"""
|
||||
class Meta:
|
||||
swappable = 'AUTH_USER_MODEL'
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class AnonymousUser(object):
|
||||
id = None
|
||||
|
@ -431,7 +477,7 @@ class AnonymousUser(object):
|
|||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self):
|
||||
return 1 # instances always return the same hash value
|
||||
return 1 # instances always return the same hash value
|
||||
|
||||
def save(self):
|
||||
raise NotImplementedError
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from django.dispatch import Signal
|
||||
|
||||
user_logged_in = Signal(providing_args=['request', 'user'])
|
||||
user_login_failed = Signal(providing_args=['credentials'])
|
||||
user_logged_out = Signal(providing_args=['request', 'user'])
|
||||
|
|
|
@ -1,26 +1,16 @@
|
|||
from django.contrib.auth.tests.auth_backends import (BackendTest,
|
||||
RowlevelBackendTest, AnonymousUserBackendTest, NoBackendsTest,
|
||||
InActiveUserBackendTest)
|
||||
from django.contrib.auth.tests.basic import BasicTestCase
|
||||
from django.contrib.auth.tests.context_processors import AuthContextProcessorTests
|
||||
from django.contrib.auth.tests.decorators import LoginRequiredTestCase
|
||||
from django.contrib.auth.tests.forms import (UserCreationFormTest,
|
||||
AuthenticationFormTest, SetPasswordFormTest, PasswordChangeFormTest,
|
||||
UserChangeFormTest, PasswordResetFormTest)
|
||||
from django.contrib.auth.tests.remote_user import (RemoteUserTest,
|
||||
RemoteUserNoCreateTest, RemoteUserCustomTest)
|
||||
from django.contrib.auth.tests.management import (
|
||||
GetDefaultUsernameTestCase,
|
||||
ChangepasswordManagementCommandTestCase,
|
||||
)
|
||||
from django.contrib.auth.tests.models import (ProfileTestCase, NaturalKeysTestCase,
|
||||
LoadDataWithoutNaturalKeysTestCase, LoadDataWithNaturalKeysTestCase,
|
||||
UserManagerTestCase)
|
||||
from django.contrib.auth.tests.hashers import TestUtilsHashPass
|
||||
from django.contrib.auth.tests.signals import SignalTestCase
|
||||
from django.contrib.auth.tests.tokens import TokenGeneratorTest
|
||||
from django.contrib.auth.tests.views import (AuthViewNamedURLTests,
|
||||
PasswordResetTest, ChangePasswordTest, LoginTest, LogoutTest,
|
||||
LoginURLSettings)
|
||||
from django.contrib.auth.tests.custom_user import *
|
||||
from django.contrib.auth.tests.auth_backends import *
|
||||
from django.contrib.auth.tests.basic import *
|
||||
from django.contrib.auth.tests.context_processors import *
|
||||
from django.contrib.auth.tests.decorators import *
|
||||
from django.contrib.auth.tests.forms import *
|
||||
from django.contrib.auth.tests.remote_user import *
|
||||
from django.contrib.auth.tests.management import *
|
||||
from django.contrib.auth.tests.models import *
|
||||
from django.contrib.auth.tests.handlers import *
|
||||
from django.contrib.auth.tests.hashers import *
|
||||
from django.contrib.auth.tests.signals import *
|
||||
from django.contrib.auth.tests.tokens import *
|
||||
from django.contrib.auth.tests.views import *
|
||||
|
||||
# The password for the fixture data users is 'password'
|
||||
|
|
|
@ -1,22 +1,29 @@
|
|||
from __future__ import unicode_literals
|
||||
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.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
|
||||
|
||||
class BackendTest(TestCase):
|
||||
|
||||
class BaseModelBackendTest(object):
|
||||
"""
|
||||
A base class for tests that need to validate the ModelBackend
|
||||
with different User models. Subclasses should define a class
|
||||
level UserModel attribute, and a create_users() method to
|
||||
construct two users for test purposes.
|
||||
"""
|
||||
backend = 'django.contrib.auth.backends.ModelBackend'
|
||||
|
||||
def setUp(self):
|
||||
self.curr_auth = settings.AUTHENTICATION_BACKENDS
|
||||
settings.AUTHENTICATION_BACKENDS = (self.backend,)
|
||||
User.objects.create_user('test', 'test@example.com', 'test')
|
||||
User.objects.create_superuser('test2', 'test2@example.com', 'test')
|
||||
self.create_users()
|
||||
|
||||
def tearDown(self):
|
||||
settings.AUTHENTICATION_BACKENDS = self.curr_auth
|
||||
|
@ -26,7 +33,7 @@ class BackendTest(TestCase):
|
|||
ContentType.objects.clear_cache()
|
||||
|
||||
def test_has_perm(self):
|
||||
user = User.objects.get(username='test')
|
||||
user = self.UserModel.objects.get(username='test')
|
||||
self.assertEqual(user.has_perm('auth.test'), False)
|
||||
user.is_staff = True
|
||||
user.save()
|
||||
|
@ -45,14 +52,14 @@ class BackendTest(TestCase):
|
|||
self.assertEqual(user.has_perm('auth.test'), False)
|
||||
|
||||
def test_custom_perms(self):
|
||||
user = User.objects.get(username='test')
|
||||
content_type=ContentType.objects.get_for_model(Group)
|
||||
user = self.UserModel.objects.get(username='test')
|
||||
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 = User.objects.get(username='test')
|
||||
user = self.UserModel.objects.get(username='test')
|
||||
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)
|
||||
|
@ -63,7 +70,7 @@ class BackendTest(TestCase):
|
|||
perm = Permission.objects.create(name='test3', content_type=content_type, codename='test3')
|
||||
user.user_permissions.add(perm)
|
||||
user.save()
|
||||
user = User.objects.get(username='test')
|
||||
user = self.UserModel.objects.get(username='test')
|
||||
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)
|
||||
|
@ -73,7 +80,7 @@ class BackendTest(TestCase):
|
|||
group.permissions.add(perm)
|
||||
group.save()
|
||||
user.groups.add(group)
|
||||
user = User.objects.get(username='test')
|
||||
user = self.UserModel.objects.get(username='test')
|
||||
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']))
|
||||
|
@ -85,8 +92,8 @@ class BackendTest(TestCase):
|
|||
|
||||
def test_has_no_object_perm(self):
|
||||
"""Regressiontest for #12462"""
|
||||
user = User.objects.get(username='test')
|
||||
content_type=ContentType.objects.get_for_model(Group)
|
||||
user = self.UserModel.objects.get(username='test')
|
||||
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()
|
||||
|
@ -98,9 +105,65 @@ class BackendTest(TestCase):
|
|||
|
||||
def test_get_all_superuser_permissions(self):
|
||||
"A superuser has all permissions. Refs #14795"
|
||||
user = User.objects.get(username='test2')
|
||||
user = self.UserModel.objects.get(username='test2')
|
||||
self.assertEqual(len(user.get_all_permissions()), len(Permission.objects.all()))
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class ModelBackendTest(BaseModelBackendTest, TestCase):
|
||||
"""
|
||||
Tests for the ModelBackend using the default User model.
|
||||
"""
|
||||
UserModel = User
|
||||
|
||||
def create_users(self):
|
||||
User.objects.create_user(
|
||||
username='test',
|
||||
email='test@example.com',
|
||||
password='test',
|
||||
)
|
||||
User.objects.create_superuser(
|
||||
username='test2',
|
||||
email='test2@example.com',
|
||||
password='test',
|
||||
)
|
||||
|
||||
|
||||
@override_settings(AUTH_USER_MODEL='auth.ExtensionUser')
|
||||
class ExtensionUserModelBackendTest(BaseModelBackendTest, TestCase):
|
||||
"""
|
||||
Tests for the ModelBackend using the custom ExtensionUser model.
|
||||
|
||||
This isn't a perfect test, because both the User and ExtensionUser are
|
||||
synchronized to the database, which wouldn't ordinary happen in
|
||||
production. As a result, it doesn't catch errors caused by the non-
|
||||
existence of the User table.
|
||||
|
||||
The specific problem is queries on .filter(groups__user) et al, which
|
||||
makes an implicit assumption that the user model is called 'User'. In
|
||||
production, the auth.User table won't exist, so the requested join
|
||||
won't exist either; in testing, the auth.User *does* exist, and
|
||||
so does the join. However, the join table won't contain any useful
|
||||
data; for testing, we check that the data we expect actually does exist.
|
||||
"""
|
||||
|
||||
UserModel = ExtensionUser
|
||||
|
||||
def create_users(self):
|
||||
ExtensionUser.objects.create_user(
|
||||
username='test',
|
||||
email='test@example.com',
|
||||
password='test',
|
||||
date_of_birth=date(2006, 4, 25)
|
||||
)
|
||||
ExtensionUser.objects.create_superuser(
|
||||
username='test2',
|
||||
email='test2@example.com',
|
||||
password='test',
|
||||
date_of_birth=date(1976, 11, 8)
|
||||
)
|
||||
|
||||
|
||||
class TestObj(object):
|
||||
pass
|
||||
|
||||
|
@ -108,7 +171,7 @@ class TestObj(object):
|
|||
class SimpleRowlevelBackend(object):
|
||||
def has_perm(self, user, perm, obj=None):
|
||||
if not obj:
|
||||
return # We only support row level perms
|
||||
return # We only support row level perms
|
||||
|
||||
if isinstance(obj, TestObj):
|
||||
if user.username == 'test2':
|
||||
|
@ -126,7 +189,7 @@ class SimpleRowlevelBackend(object):
|
|||
|
||||
def get_all_permissions(self, user, obj=None):
|
||||
if not obj:
|
||||
return [] # We only support row level perms
|
||||
return [] # We only support row level perms
|
||||
|
||||
if not isinstance(obj, TestObj):
|
||||
return ['none']
|
||||
|
@ -140,7 +203,7 @@ class SimpleRowlevelBackend(object):
|
|||
|
||||
def get_group_permissions(self, user, obj=None):
|
||||
if not obj:
|
||||
return # We only support row level perms
|
||||
return # We only support row level perms
|
||||
|
||||
if not isinstance(obj, TestObj):
|
||||
return ['none']
|
||||
|
@ -151,6 +214,7 @@ class SimpleRowlevelBackend(object):
|
|||
return ['none']
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class RowlevelBackendTest(TestCase):
|
||||
"""
|
||||
Tests for auth backend that supports object level permissions
|
||||
|
@ -186,7 +250,6 @@ class RowlevelBackendTest(TestCase):
|
|||
self.assertEqual(self.user2.get_all_permissions(), set([]))
|
||||
|
||||
def test_get_group_permissions(self):
|
||||
content_type=ContentType.objects.get_for_model(Group)
|
||||
group = Group.objects.create(name='test_group')
|
||||
self.user3.groups.add(group)
|
||||
self.assertEqual(self.user3.get_group_permissions(TestObj()), set(['group_perm']))
|
||||
|
@ -223,6 +286,7 @@ class AnonymousUserBackendTest(TestCase):
|
|||
self.assertEqual(self.user1.get_all_permissions(TestObj()), set(['anon']))
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
@override_settings(AUTHENTICATION_BACKENDS=[])
|
||||
class NoBackendsTest(TestCase):
|
||||
"""
|
||||
|
@ -235,6 +299,7 @@ class NoBackendsTest(TestCase):
|
|||
self.assertRaises(ImproperlyConfigured, self.user.has_perm, ('perm', TestObj(),))
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class InActiveUserBackendTest(TestCase):
|
||||
"""
|
||||
Tests for a inactive user
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
import locale
|
||||
import traceback
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.management.commands import createsuperuser
|
||||
from django.contrib.auth.models import User, AnonymousUser
|
||||
from django.contrib.auth.tests.custom_user import CustomUser
|
||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.utils.six import StringIO
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class BasicTestCase(TestCase):
|
||||
def test_user(self):
|
||||
"Check that users can be created and can set their password"
|
||||
|
@ -34,7 +39,7 @@ class BasicTestCase(TestCase):
|
|||
|
||||
# Check API-based user creation with no password
|
||||
u2 = User.objects.create_user('testuser2', 'test2@example.com')
|
||||
self.assertFalse(u.has_usable_password())
|
||||
self.assertFalse(u2.has_usable_password())
|
||||
|
||||
def test_user_no_email(self):
|
||||
"Check that users can be created without an email"
|
||||
|
@ -98,7 +103,6 @@ class BasicTestCase(TestCase):
|
|||
self.assertEqual(u.email, 'joe2@somewhere.org')
|
||||
self.assertFalse(u.has_usable_password())
|
||||
|
||||
|
||||
new_io = StringIO()
|
||||
call_command("createsuperuser",
|
||||
interactive=False,
|
||||
|
@ -124,15 +128,21 @@ class BasicTestCase(TestCase):
|
|||
|
||||
# Temporarily replace getpass to allow interactive code to be used
|
||||
# non-interactively
|
||||
class mock_getpass: pass
|
||||
class mock_getpass:
|
||||
pass
|
||||
mock_getpass.getpass = staticmethod(lambda p=None: "nopasswd")
|
||||
createsuperuser.getpass = mock_getpass
|
||||
|
||||
# Call the command in this new environment
|
||||
new_io = StringIO()
|
||||
call_command("createsuperuser", interactive=True, username="nolocale@somewhere.org", email="nolocale@somewhere.org", stdout=new_io)
|
||||
call_command("createsuperuser",
|
||||
interactive=True,
|
||||
username="nolocale@somewhere.org",
|
||||
email="nolocale@somewhere.org",
|
||||
stdout=new_io
|
||||
)
|
||||
|
||||
except TypeError as e:
|
||||
except TypeError:
|
||||
self.fail("createsuperuser fails if the OS provides no information about the current locale")
|
||||
|
||||
finally:
|
||||
|
@ -143,3 +153,24 @@ class BasicTestCase(TestCase):
|
|||
# If we were successful, a user should have been created
|
||||
u = User.objects.get(username="nolocale@somewhere.org")
|
||||
self.assertEqual(u.email, 'nolocale@somewhere.org')
|
||||
|
||||
def test_get_user_model(self):
|
||||
"The current user model can be retrieved"
|
||||
self.assertEqual(get_user_model(), User)
|
||||
|
||||
@override_settings(AUTH_USER_MODEL='auth.CustomUser')
|
||||
def test_swappable_user(self):
|
||||
"The current user model can be swapped out for another"
|
||||
self.assertEqual(get_user_model(), CustomUser)
|
||||
|
||||
@override_settings(AUTH_USER_MODEL='badsetting')
|
||||
def test_swappable_user_bad_setting(self):
|
||||
"The alternate user setting must point to something in the format app.model"
|
||||
with self.assertRaises(ImproperlyConfigured):
|
||||
get_user_model()
|
||||
|
||||
@override_settings(AUTH_USER_MODEL='thismodel.doesntexist')
|
||||
def test_swappable_user_nonexistent_model(self):
|
||||
"The current user model must point to an installed model"
|
||||
with self.assertRaises(ImproperlyConfigured):
|
||||
get_user_model()
|
||||
|
|
|
@ -2,12 +2,65 @@ import os
|
|||
|
||||
from django.conf import global_settings
|
||||
from django.contrib.auth import authenticate
|
||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||
from django.contrib.auth.models import User, Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.auth.context_processors import PermWrapper, PermLookupDict
|
||||
from django.db.models import Q
|
||||
from django.template import context
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
|
||||
|
||||
class MockUser(object):
|
||||
def has_module_perms(self, perm):
|
||||
if perm == 'mockapp':
|
||||
return True
|
||||
return False
|
||||
|
||||
def has_perm(self, perm):
|
||||
if perm == 'mockapp.someperm':
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class PermWrapperTests(TestCase):
|
||||
"""
|
||||
Test some details of the PermWrapper implementation.
|
||||
"""
|
||||
class EQLimiterObject(object):
|
||||
"""
|
||||
This object makes sure __eq__ will not be called endlessly.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.eq_calls = 0
|
||||
|
||||
def __eq__(self, other):
|
||||
if self.eq_calls > 0:
|
||||
return True
|
||||
self.eq_calls += 1
|
||||
return False
|
||||
|
||||
def test_permwrapper_in(self):
|
||||
"""
|
||||
Test that 'something' in PermWrapper works as expected.
|
||||
"""
|
||||
perms = PermWrapper(MockUser())
|
||||
# Works for modules and full permissions.
|
||||
self.assertTrue('mockapp' in perms)
|
||||
self.assertFalse('nonexisting' in perms)
|
||||
self.assertTrue('mockapp.someperm' in perms)
|
||||
self.assertFalse('mockapp.nonexisting' in perms)
|
||||
|
||||
def test_permlookupdict_in(self):
|
||||
"""
|
||||
No endless loops if accessed with 'in' - refs #18979.
|
||||
"""
|
||||
pldict = PermLookupDict(MockUser(), 'mockapp')
|
||||
with self.assertRaises(TypeError):
|
||||
self.EQLimiterObject() in pldict
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
@override_settings(
|
||||
TEMPLATE_DIRS=(
|
||||
os.path.join(os.path.dirname(__file__), 'templates'),
|
||||
|
@ -47,9 +100,28 @@ class AuthContextProcessorTests(TestCase):
|
|||
self.assertContains(response, "Session accessed")
|
||||
|
||||
def test_perms_attrs(self):
|
||||
self.client.login(username='super', password='secret')
|
||||
u = User.objects.create_user(username='normal', password='secret')
|
||||
u.user_permissions.add(
|
||||
Permission.objects.get(
|
||||
content_type=ContentType.objects.get_for_model(Permission),
|
||||
codename='add_permission'))
|
||||
self.client.login(username='normal', password='secret')
|
||||
response = self.client.get('/auth_processor_perms/')
|
||||
self.assertContains(response, "Has auth permissions")
|
||||
self.assertContains(response, "Has auth.add_permission permissions")
|
||||
self.assertNotContains(response, "nonexisting")
|
||||
|
||||
def test_perm_in_perms_attrs(self):
|
||||
u = User.objects.create_user(username='normal', password='secret')
|
||||
u.user_permissions.add(
|
||||
Permission.objects.get(
|
||||
content_type=ContentType.objects.get_for_model(Permission),
|
||||
codename='add_permission'))
|
||||
self.client.login(username='normal', password='secret')
|
||||
response = self.client.get('/auth_processor_perm_in_perms/')
|
||||
self.assertContains(response, "Has auth permissions")
|
||||
self.assertContains(response, "Has auth.add_permission permissions")
|
||||
self.assertNotContains(response, "nonexisting")
|
||||
|
||||
def test_message_attrs(self):
|
||||
self.client.login(username='super', password='secret')
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
from django.db import models
|
||||
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, AbstractUser, UserManager
|
||||
|
||||
|
||||
# The custom User uses email as the unique identifier, and requires
|
||||
# that every user provide a date of birth. This lets us test
|
||||
# changes in username datatype, and non-text required fields.
|
||||
|
||||
class CustomUserManager(BaseUserManager):
|
||||
def create_user(self, email, date_of_birth, password=None):
|
||||
"""
|
||||
Creates and saves a User with the given email and password.
|
||||
"""
|
||||
if not email:
|
||||
raise ValueError('Users must have an email address')
|
||||
|
||||
user = self.model(
|
||||
email=CustomUserManager.normalize_email(email),
|
||||
date_of_birth=date_of_birth,
|
||||
)
|
||||
|
||||
user.set_password(password)
|
||||
user.save(using=self._db)
|
||||
return user
|
||||
|
||||
def create_superuser(self, email, password, date_of_birth):
|
||||
u = self.create_user(email, password=password, date_of_birth=date_of_birth)
|
||||
u.is_admin = True
|
||||
u.save(using=self._db)
|
||||
return u
|
||||
|
||||
|
||||
class CustomUser(AbstractBaseUser):
|
||||
email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
|
||||
is_active = models.BooleanField(default=True)
|
||||
is_admin = models.BooleanField(default=False)
|
||||
date_of_birth = models.DateField()
|
||||
|
||||
objects = CustomUserManager()
|
||||
|
||||
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
|
||||
|
||||
# Maybe required?
|
||||
def get_group_permissions(self, obj=None):
|
||||
return set()
|
||||
|
||||
def get_all_permissions(self, obj=None):
|
||||
return set()
|
||||
|
||||
def has_perm(self, perm, obj=None):
|
||||
return True
|
||||
|
||||
def has_perms(self, perm_list, obj=None):
|
||||
return True
|
||||
|
||||
def has_module_perms(self, app_label):
|
||||
return True
|
||||
|
||||
# Admin required fields
|
||||
@property
|
||||
def is_staff(self):
|
||||
return self.is_admin
|
||||
|
||||
|
||||
# The extension user is a simple extension of the built-in user class,
|
||||
# adding a required date_of_birth field. This allows us to check for
|
||||
# any hard references to the name "User" in forms/handlers etc.
|
||||
|
||||
class ExtensionUser(AbstractUser):
|
||||
date_of_birth = models.DateField()
|
||||
|
||||
objects = UserManager()
|
||||
|
||||
REQUIRED_FIELDS = AbstractUser.REQUIRED_FIELDS + ['date_of_birth']
|
||||
|
||||
class Meta:
|
||||
app_label = 'auth'
|
|
@ -1,7 +1,9 @@
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.tests.views import AuthViewsTestCase
|
||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class LoginRequiredTestCase(AuthViewsTestCase):
|
||||
"""
|
||||
Tests the login_required decorators
|
||||
|
|
|
@ -4,16 +4,17 @@ import os
|
|||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm,
|
||||
PasswordChangeForm, SetPasswordForm, UserChangeForm, PasswordResetForm)
|
||||
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 import six
|
||||
from django.utils import translation
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
||||
class UserCreationFormTest(TestCase):
|
||||
|
||||
|
@ -81,6 +82,7 @@ class UserCreationFormTest(TestCase):
|
|||
self.assertEqual(repr(u), '<User: jsmith@example.com>')
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
||||
class AuthenticationFormTest(TestCase):
|
||||
|
||||
|
@ -133,6 +135,7 @@ class AuthenticationFormTest(TestCase):
|
|||
self.assertEqual(form.non_field_errors(), [])
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
||||
class SetPasswordFormTest(TestCase):
|
||||
|
||||
|
@ -160,6 +163,7 @@ class SetPasswordFormTest(TestCase):
|
|||
self.assertTrue(form.is_valid())
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
||||
class PasswordChangeFormTest(TestCase):
|
||||
|
||||
|
@ -208,6 +212,7 @@ class PasswordChangeFormTest(TestCase):
|
|||
['old_password', 'new_password1', 'new_password2'])
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
||||
class UserChangeFormTest(TestCase):
|
||||
|
||||
|
@ -260,7 +265,25 @@ class UserChangeFormTest(TestCase):
|
|||
self.assertIn(_("Invalid password format or unknown hashing algorithm."),
|
||||
form.as_table())
|
||||
|
||||
def test_bug_19133(self):
|
||||
"The change form does not return the password value"
|
||||
# Use the form to construct the POST data
|
||||
user = User.objects.get(username='testclient')
|
||||
form_for_data = UserChangeForm(instance=user)
|
||||
post_data = form_for_data.initial
|
||||
|
||||
# The password field should be readonly, so anything
|
||||
# posted here should be ignored; the form will be
|
||||
# valid, and give back the 'initial' value for the
|
||||
# password field.
|
||||
post_data['password'] = 'new password'
|
||||
form = UserChangeForm(instance=user, data=post_data)
|
||||
|
||||
self.assertTrue(form.is_valid())
|
||||
self.assertEqual(form.cleaned_data['password'], 'sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161')
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
||||
class PasswordResetFormTest(TestCase):
|
||||
|
||||
|
@ -338,4 +361,4 @@ class PasswordResetFormTest(TestCase):
|
|||
form = PasswordResetForm(data)
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertEqual(form["email"].errors,
|
||||
[_("The user account associated with this e-mail address cannot reset the password.")])
|
||||
[_("The user account associated with this email address cannot reset the password.")])
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
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.utils import skipIfCustomUser
|
||||
from django.test import TransactionTestCase
|
||||
|
||||
|
||||
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)
|
||||
|
||||
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 not in database
|
||||
self.assertTrue(check_password({}, 'unknown', '') is None)
|
||||
|
||||
# Valid user with correct password
|
||||
self.assertTrue(check_password({}, 'test', 'test'))
|
||||
|
||||
# Valid user with incorrect password
|
||||
self.assertFalse(check_password({}, 'test', '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
|
||||
"""
|
||||
|
||||
# User not in database
|
||||
self.assertEqual(groups_for_user({}, 'unknown'), [])
|
||||
|
||||
self.assertEqual(groups_for_user({}, 'test'), [b'test_group'])
|
||||
self.assertEqual(groups_for_user({}, 'test1'), [])
|
|
@ -1,13 +1,21 @@
|
|||
from __future__ import unicode_literals
|
||||
from datetime import date
|
||||
|
||||
from django.contrib.auth import models, management
|
||||
from django.contrib.auth.management import create_permissions
|
||||
from django.contrib.auth.management.commands import changepassword
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.tests import CustomUser
|
||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import CommandError
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.utils import six
|
||||
from django.utils.six import StringIO
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class GetDefaultUsernameTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
@ -36,6 +44,7 @@ class GetDefaultUsernameTestCase(TestCase):
|
|||
self.assertEqual(management.get_default_username(), 'julia')
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class ChangepasswordManagementCommandTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
@ -48,7 +57,7 @@ class ChangepasswordManagementCommandTestCase(TestCase):
|
|||
self.stderr.close()
|
||||
|
||||
def test_that_changepassword_command_changes_joes_password(self):
|
||||
" Executing the changepassword management command should change joe's password "
|
||||
"Executing the changepassword management command should change joe's password"
|
||||
self.assertTrue(self.user.check_password('qwerty'))
|
||||
command = changepassword.Command()
|
||||
command._get_pass = lambda *args: 'not qwerty'
|
||||
|
@ -69,3 +78,133 @@ class ChangepasswordManagementCommandTestCase(TestCase):
|
|||
|
||||
with self.assertRaises(CommandError):
|
||||
command.execute("joe", stdout=self.stdout, stderr=self.stderr)
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class CreatesuperuserManagementCommandTestCase(TestCase):
|
||||
|
||||
def test_createsuperuser(self):
|
||||
"Check the operation of the createsuperuser management command"
|
||||
# We can use the management command to create a superuser
|
||||
new_io = StringIO()
|
||||
call_command("createsuperuser",
|
||||
interactive=False,
|
||||
username="joe",
|
||||
email="joe@somewhere.org",
|
||||
stdout=new_io
|
||||
)
|
||||
command_output = new_io.getvalue().strip()
|
||||
self.assertEqual(command_output, 'Superuser created successfully.')
|
||||
u = User.objects.get(username="joe")
|
||||
self.assertEqual(u.email, 'joe@somewhere.org')
|
||||
|
||||
# created password should be unusable
|
||||
self.assertFalse(u.has_usable_password())
|
||||
|
||||
def test_verbosity_zero(self):
|
||||
# We can supress output on the management command
|
||||
new_io = StringIO()
|
||||
call_command("createsuperuser",
|
||||
interactive=False,
|
||||
username="joe2",
|
||||
email="joe2@somewhere.org",
|
||||
verbosity=0,
|
||||
stdout=new_io
|
||||
)
|
||||
command_output = new_io.getvalue().strip()
|
||||
self.assertEqual(command_output, '')
|
||||
u = User.objects.get(username="joe2")
|
||||
self.assertEqual(u.email, 'joe2@somewhere.org')
|
||||
self.assertFalse(u.has_usable_password())
|
||||
|
||||
def test_email_in_username(self):
|
||||
new_io = StringIO()
|
||||
call_command("createsuperuser",
|
||||
interactive=False,
|
||||
username="joe+admin@somewhere.org",
|
||||
email="joe@somewhere.org",
|
||||
stdout=new_io
|
||||
)
|
||||
u = User.objects.get(username="joe+admin@somewhere.org")
|
||||
self.assertEqual(u.email, 'joe@somewhere.org')
|
||||
self.assertFalse(u.has_usable_password())
|
||||
|
||||
@override_settings(AUTH_USER_MODEL='auth.CustomUser')
|
||||
def test_swappable_user(self):
|
||||
"A superuser can be created when a custom User model is in use"
|
||||
# We can use the management command to create a superuser
|
||||
# We skip validation because the temporary substitution of the
|
||||
# swappable User model messes with validation.
|
||||
new_io = StringIO()
|
||||
call_command("createsuperuser",
|
||||
interactive=False,
|
||||
email="joe@somewhere.org",
|
||||
date_of_birth="1976-04-01",
|
||||
stdout=new_io,
|
||||
skip_validation=True
|
||||
)
|
||||
command_output = new_io.getvalue().strip()
|
||||
self.assertEqual(command_output, 'Superuser created successfully.')
|
||||
u = CustomUser.objects.get(email="joe@somewhere.org")
|
||||
self.assertEqual(u.date_of_birth, date(1976, 4, 1))
|
||||
|
||||
# created password should be unusable
|
||||
self.assertFalse(u.has_usable_password())
|
||||
|
||||
@override_settings(AUTH_USER_MODEL='auth.CustomUser')
|
||||
def test_swappable_user_missing_required_field(self):
|
||||
"A Custom superuser won't be created when a required field isn't provided"
|
||||
# We can use the management command to create a superuser
|
||||
# We skip validation because the temporary substitution of the
|
||||
# swappable User model messes with validation.
|
||||
new_io = StringIO()
|
||||
with self.assertRaises(CommandError):
|
||||
call_command("createsuperuser",
|
||||
interactive=False,
|
||||
username="joe@somewhere.org",
|
||||
stdout=new_io,
|
||||
stderr=new_io,
|
||||
skip_validation=True
|
||||
)
|
||||
|
||||
self.assertEqual(CustomUser.objects.count(), 0)
|
||||
|
||||
|
||||
class PermissionDuplicationTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self._original_permissions = models.Permission._meta.permissions[:]
|
||||
|
||||
def tearDown(self):
|
||||
models.Permission._meta.permissions = self._original_permissions
|
||||
|
||||
def test_duplicated_permissions(self):
|
||||
"""
|
||||
Test that we show proper error message if we are trying to create
|
||||
duplicate permissions.
|
||||
"""
|
||||
# check duplicated default permission
|
||||
models.Permission._meta.permissions = [
|
||||
('change_permission', 'Can edit permission (duplicate)')]
|
||||
self.assertRaisesRegexp(CommandError,
|
||||
"The permission codename 'change_permission' clashes with a "
|
||||
"builtin permission for model 'auth.Permission'.",
|
||||
create_permissions, models, [], verbosity=0)
|
||||
|
||||
# check duplicated custom permissions
|
||||
models.Permission._meta.permissions = [
|
||||
('my_custom_permission', 'Some permission'),
|
||||
('other_one', 'Some other permission'),
|
||||
('my_custom_permission', 'Some permission with duplicate permission code'),
|
||||
]
|
||||
self.assertRaisesRegexp(CommandError,
|
||||
"The permission codename 'my_custom_permission' is duplicated for model "
|
||||
"'auth.Permission'.",
|
||||
create_permissions, models, [], verbosity=0)
|
||||
|
||||
# should not raise anything
|
||||
models.Permission._meta.permissions = [
|
||||
('my_custom_permission', 'Some permission'),
|
||||
('other_one', 'Some other permission'),
|
||||
]
|
||||
create_permissions(models, [], verbosity=0)
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.models import (Group, User, SiteProfileNotAvailable,
|
||||
UserManager)
|
||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.utils import six
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
@override_settings(USE_TZ=False, AUTH_PROFILE_MODULE='')
|
||||
class ProfileTestCase(TestCase):
|
||||
|
||||
|
@ -31,6 +33,7 @@ class ProfileTestCase(TestCase):
|
|||
user.get_profile()
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
@override_settings(USE_TZ=False)
|
||||
class NaturalKeysTestCase(TestCase):
|
||||
fixtures = ['authtestdata.json']
|
||||
|
@ -45,6 +48,7 @@ class NaturalKeysTestCase(TestCase):
|
|||
self.assertEqual(Group.objects.get_by_natural_key('users'), users_group)
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
@override_settings(USE_TZ=False)
|
||||
class LoadDataWithoutNaturalKeysTestCase(TestCase):
|
||||
fixtures = ['regular.json']
|
||||
|
@ -55,6 +59,7 @@ class LoadDataWithoutNaturalKeysTestCase(TestCase):
|
|||
self.assertEqual(group, user.groups.get())
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
@override_settings(USE_TZ=False)
|
||||
class LoadDataWithNaturalKeysTestCase(TestCase):
|
||||
fixtures = ['natural.json']
|
||||
|
@ -65,6 +70,7 @@ class LoadDataWithNaturalKeysTestCase(TestCase):
|
|||
self.assertEqual(group, user.groups.get())
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class UserManagerTestCase(TestCase):
|
||||
|
||||
def test_create_user(self):
|
||||
|
|
|
@ -3,10 +3,12 @@ from datetime import datetime
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.backends import RemoteUserBackend
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class RemoteUserTest(TestCase):
|
||||
|
||||
urls = 'django.contrib.auth.tests.urls'
|
||||
|
@ -106,6 +108,7 @@ class RemoteUserNoCreateBackend(RemoteUserBackend):
|
|||
create_unknown_user = False
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class RemoteUserNoCreateTest(RemoteUserTest):
|
||||
"""
|
||||
Contains the same tests as RemoteUserTest, but using a custom auth backend
|
||||
|
@ -142,6 +145,7 @@ class CustomRemoteUserBackend(RemoteUserBackend):
|
|||
return user
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class RemoteUserCustomTest(RemoteUserTest):
|
||||
"""
|
||||
Tests a custom RemoteUserBackend subclass that overrides the clean_username
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.contrib.auth import signals
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
|
||||
class SignalTestCase(TestCase):
|
||||
urls = 'django.contrib.auth.tests.urls'
|
||||
|
@ -14,27 +18,41 @@ class SignalTestCase(TestCase):
|
|||
def listener_logout(self, user, **kwargs):
|
||||
self.logged_out.append(user)
|
||||
|
||||
def listener_login_failed(self, sender, credentials, **kwargs):
|
||||
self.login_failed.append(credentials)
|
||||
|
||||
def setUp(self):
|
||||
"""Set up the listeners and reset the logged in/logged out counters"""
|
||||
self.logged_in = []
|
||||
self.logged_out = []
|
||||
self.login_failed = []
|
||||
signals.user_logged_in.connect(self.listener_login)
|
||||
signals.user_logged_out.connect(self.listener_logout)
|
||||
signals.user_login_failed.connect(self.listener_login_failed)
|
||||
|
||||
def tearDown(self):
|
||||
"""Disconnect the listeners"""
|
||||
signals.user_logged_in.disconnect(self.listener_login)
|
||||
signals.user_logged_out.disconnect(self.listener_logout)
|
||||
signals.user_login_failed.disconnect(self.listener_login_failed)
|
||||
|
||||
def test_login(self):
|
||||
# Only a successful login will trigger the signal.
|
||||
# Only a successful login will trigger the success signal.
|
||||
self.client.login(username='testclient', password='bad')
|
||||
self.assertEqual(len(self.logged_in), 0)
|
||||
self.assertEqual(len(self.login_failed), 1)
|
||||
self.assertEqual(self.login_failed[0]['username'], 'testclient')
|
||||
# verify the password is cleansed
|
||||
self.assertTrue('***' in self.login_failed[0]['password'])
|
||||
|
||||
# Like this:
|
||||
self.client.login(username='testclient', password='password')
|
||||
self.assertEqual(len(self.logged_in), 1)
|
||||
self.assertEqual(self.logged_in[0].username, 'testclient')
|
||||
|
||||
# Ensure there were no more failures.
|
||||
self.assertEqual(len(self.login_failed), 1)
|
||||
|
||||
def test_logout_anonymous(self):
|
||||
# The log_out function will still trigger the signal for anonymous
|
||||
# users.
|
||||
|
@ -47,3 +65,16 @@ class SignalTestCase(TestCase):
|
|||
self.client.get('/logout/next_page/')
|
||||
self.assertEqual(len(self.logged_out), 1)
|
||||
self.assertEqual(self.logged_out[0].username, 'testclient')
|
||||
|
||||
def test_update_last_login(self):
|
||||
"""Ensure that only `last_login` is updated in `update_last_login`"""
|
||||
user = User.objects.get(pk=3)
|
||||
old_last_login = user.last_login
|
||||
|
||||
user.username = "This username shouldn't get saved"
|
||||
request = RequestFactory().get('/login')
|
||||
signals.user_logged_in.send(sender=user.__class__, request=request,
|
||||
user=user)
|
||||
user = User.objects.get(pk=3)
|
||||
self.assertEqual(user.username, 'staff')
|
||||
self.assertNotEqual(user.last_login, old_last_login)
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{% if 'auth' in perms %}Has auth permissions{% endif %}
|
||||
{% if 'auth.add_permission' in perms %}Has auth.add_permission permissions{% endif %}
|
||||
{% if 'nonexisting' in perms %}nonexisting perm found{% endif %}
|
||||
{% if 'auth.nonexisting' in perms %}auth.nonexisting perm found{% endif %}
|
|
@ -1 +1,4 @@
|
|||
{% if perms.auth %}Has auth permissions{% endif %}
|
||||
{% if perms.auth.add_permission %}Has auth.add_permission permissions{% endif %}
|
||||
{% if perms.nonexisting %}nonexisting perm found{% endif %}
|
||||
{% if perms.auth.nonexisting in perms %}auth.nonexisting perm found{% endif %}
|
||||
|
|
|
@ -1 +1 @@
|
|||
E-mail sent
|
||||
Email sent
|
|
@ -4,10 +4,12 @@ from datetime import date, timedelta
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.tokens import PasswordResetTokenGenerator
|
||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||
from django.test import TestCase
|
||||
from django.utils import unittest
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class TokenGeneratorTest(TestCase):
|
||||
|
||||
def test_make_token(self):
|
||||
|
|
|
@ -37,6 +37,10 @@ def auth_processor_perms(request):
|
|||
return render_to_response('context_processors/auth_attrs_perms.html',
|
||||
RequestContext(request, {}, processors=[context_processors.auth]))
|
||||
|
||||
def auth_processor_perm_in_perms(request):
|
||||
return render_to_response('context_processors/auth_attrs_perm_in_perms.html',
|
||||
RequestContext(request, {}, processors=[context_processors.auth]))
|
||||
|
||||
def auth_processor_messages(request):
|
||||
info(request, "Message 1")
|
||||
return render_to_response('context_processors/auth_attrs_messages.html',
|
||||
|
@ -51,6 +55,7 @@ urlpatterns = urlpatterns + patterns('',
|
|||
(r'^logout/next_page/$', 'django.contrib.auth.views.logout', dict(next_page='/somewhere/')),
|
||||
(r'^remote_user/$', remote_user_auth_view),
|
||||
(r'^password_reset_from_email/$', 'django.contrib.auth.views.password_reset', dict(from_email='staffmember@example.com')),
|
||||
(r'^admin_password_reset/$', 'django.contrib.auth.views.password_reset', dict(is_admin_site=True)),
|
||||
(r'^login_required/$', login_required(password_reset)),
|
||||
(r'^login_required_login_url/$', login_required(password_reset, login_url='/somewhere/')),
|
||||
|
||||
|
@ -58,6 +63,7 @@ urlpatterns = urlpatterns + patterns('',
|
|||
(r'^auth_processor_attr_access/$', auth_processor_attr_access),
|
||||
(r'^auth_processor_user/$', auth_processor_user),
|
||||
(r'^auth_processor_perms/$', auth_processor_perms),
|
||||
(r'^auth_processor_perm_in_perms/$', auth_processor_perm_in_perms),
|
||||
(r'^auth_processor_messages/$', auth_processor_messages),
|
||||
url(r'^userpage/(.+)/$', userpage, name="userpage"),
|
||||
)
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
from django.conf import settings
|
||||
from django.utils.unittest import skipIf
|
||||
|
||||
|
||||
def skipIfCustomUser(test_func):
|
||||
"""
|
||||
Skip a test if a custom user model is in use.
|
||||
"""
|
||||
return skipIf(settings.AUTH_USER_MODEL != 'auth.User', 'Custom user model in use')(test_func)
|
|
@ -5,6 +5,7 @@ from django.conf import global_settings, settings
|
|||
from django.contrib.sites.models import Site, RequestSite
|
||||
from django.contrib.auth.models import User
|
||||
from django.core import mail
|
||||
from django.core.exceptions import SuspiciousOperation
|
||||
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||
from django.http import QueryDict
|
||||
from django.utils.encoding import force_text
|
||||
|
@ -16,6 +17,7 @@ from django.test.utils import override_settings
|
|||
from django.contrib.auth import SESSION_KEY, REDIRECT_FIELD_NAME
|
||||
from django.contrib.auth.forms import (AuthenticationForm, PasswordChangeForm,
|
||||
SetPasswordForm, PasswordResetForm)
|
||||
from django.contrib.auth.tests.utils import skipIfCustomUser
|
||||
|
||||
|
||||
@override_settings(
|
||||
|
@ -50,6 +52,7 @@ class AuthViewsTestCase(TestCase):
|
|||
return self.assertContains(response, escape(force_text(text)), **kwargs)
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class AuthViewNamedURLTests(AuthViewsTestCase):
|
||||
urls = 'django.contrib.auth.urls'
|
||||
|
||||
|
@ -75,6 +78,7 @@ class AuthViewNamedURLTests(AuthViewsTestCase):
|
|||
self.fail("Reversal of url named '%s' failed with NoReverseMatch" % name)
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class PasswordResetTest(AuthViewsTestCase):
|
||||
|
||||
def test_email_not_found(self):
|
||||
|
@ -100,6 +104,42 @@ class PasswordResetTest(AuthViewsTestCase):
|
|||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertEqual("staffmember@example.com", mail.outbox[0].from_email)
|
||||
|
||||
def test_admin_reset(self):
|
||||
"If the reset view is marked as being for admin, the HTTP_HOST header is used for a domain override."
|
||||
response = self.client.post('/admin_password_reset/',
|
||||
{'email': 'staffmember@example.com'},
|
||||
HTTP_HOST='adminsite.com'
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertTrue("http://adminsite.com" in mail.outbox[0].body)
|
||||
self.assertEqual(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email)
|
||||
|
||||
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
|
||||
# should be used to separate the port, but if the URL contains an @,
|
||||
# the colon is interpreted as part of a username for login purposes,
|
||||
# making 'evil.com' the request domain. Since HTTP_HOST is used to
|
||||
# produce a meaningful reset URL, we need to be certain that the
|
||||
# HTTP_HOST header isn't poisoned. This is done as a check when get_host()
|
||||
# is invoked, but we check here as a practical consequence.
|
||||
with self.assertRaises(SuspiciousOperation):
|
||||
self.client.post('/password_reset/',
|
||||
{'email': 'staffmember@example.com'},
|
||||
HTTP_HOST='www.example:dr.frankenstein@evil.tld'
|
||||
)
|
||||
self.assertEqual(len(mail.outbox), 0)
|
||||
|
||||
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):
|
||||
self.client.post('/admin_password_reset/',
|
||||
{'email': 'staffmember@example.com'},
|
||||
HTTP_HOST='www.example:dr.frankenstein@evil.tld'
|
||||
)
|
||||
self.assertEqual(len(mail.outbox), 0)
|
||||
|
||||
def _test_confirm_start(self):
|
||||
# Start by creating the email
|
||||
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})
|
||||
|
@ -172,6 +212,30 @@ class PasswordResetTest(AuthViewsTestCase):
|
|||
self.assertContainsEscaped(response, SetPasswordForm.error_messages['password_mismatch'])
|
||||
|
||||
|
||||
@override_settings(AUTH_USER_MODEL='auth.CustomUser')
|
||||
class CustomUserPasswordResetTest(AuthViewsTestCase):
|
||||
fixtures = ['custom_user.json']
|
||||
|
||||
def _test_confirm_start(self):
|
||||
# Start by creating the email
|
||||
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
return self._read_signup_email(mail.outbox[0])
|
||||
|
||||
def _read_signup_email(self, email):
|
||||
urlmatch = re.search(r"https?://[^/]*(/.*reset/\S*)", email.body)
|
||||
self.assertTrue(urlmatch is not None, "No URL found in sent email")
|
||||
return urlmatch.group(), urlmatch.groups()[0]
|
||||
|
||||
def test_confirm_valid_custom_user(self):
|
||||
url, path = self._test_confirm_start()
|
||||
response = self.client.get(path)
|
||||
# redirect to a 'complete' page:
|
||||
self.assertContains(response, "Please enter your new password")
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class ChangePasswordTest(AuthViewsTestCase):
|
||||
|
||||
def fail_login(self, password='password'):
|
||||
|
@ -231,6 +295,7 @@ class ChangePasswordTest(AuthViewsTestCase):
|
|||
self.assertTrue(response['Location'].endswith('/login/?next=/password_change/done/'))
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class LoginTest(AuthViewsTestCase):
|
||||
|
||||
def test_current_site_in_context_after_login(self):
|
||||
|
@ -289,6 +354,7 @@ class LoginTest(AuthViewsTestCase):
|
|||
"%s should be allowed" % good_url)
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class LoginURLSettings(AuthViewsTestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
@ -347,6 +413,7 @@ class LoginURLSettings(AuthViewsTestCase):
|
|||
querystring.urlencode('/')))
|
||||
|
||||
|
||||
@skipIfCustomUser
|
||||
class LogoutTest(AuthViewsTestCase):
|
||||
|
||||
def confirm_logged_out(self):
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.utils.http import int_to_base36, base36_to_int
|
|||
from django.utils.crypto import constant_time_compare, salted_hmac
|
||||
from django.utils import six
|
||||
|
||||
|
||||
class PasswordResetTokenGenerator(object):
|
||||
"""
|
||||
Strategy object used to generate and check tokens for the password
|
||||
|
|
|
@ -15,10 +15,9 @@ from django.views.decorators.cache import never_cache
|
|||
from django.views.decorators.csrf import csrf_protect
|
||||
|
||||
# Avoid shadowing the login() and logout() views below.
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME, login as auth_login, logout as auth_logout
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME, login as auth_login, logout as auth_logout, get_user_model
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm, SetPasswordForm, PasswordChangeForm
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.contrib.sites.models import get_current_site
|
||||
|
||||
|
@ -74,6 +73,7 @@ def login(request, template_name='registration/login.html',
|
|||
return TemplateResponse(request, template_name, context,
|
||||
current_app=current_app)
|
||||
|
||||
|
||||
def logout(request, next_page=None,
|
||||
template_name='registration/logged_out.html',
|
||||
redirect_field_name=REDIRECT_FIELD_NAME,
|
||||
|
@ -104,6 +104,7 @@ def logout(request, next_page=None,
|
|||
# Redirect to this page until the session has been cleared.
|
||||
return HttpResponseRedirect(next_page or request.path)
|
||||
|
||||
|
||||
def logout_then_login(request, login_url=None, current_app=None, extra_context=None):
|
||||
"""
|
||||
Logs out the user if he is logged in. Then redirects to the log-in page.
|
||||
|
@ -113,6 +114,7 @@ def logout_then_login(request, login_url=None, current_app=None, extra_context=N
|
|||
login_url = resolve_url(login_url)
|
||||
return logout(request, login_url, current_app=current_app, extra_context=extra_context)
|
||||
|
||||
|
||||
def redirect_to_login(next, login_url=None,
|
||||
redirect_field_name=REDIRECT_FIELD_NAME):
|
||||
"""
|
||||
|
@ -128,6 +130,7 @@ def redirect_to_login(next, login_url=None,
|
|||
|
||||
return HttpResponseRedirect(urlunparse(login_url_parts))
|
||||
|
||||
|
||||
# 4 views for password reset:
|
||||
# - password_reset sends the mail
|
||||
# - password_reset_done shows a success message for the above
|
||||
|
@ -160,7 +163,7 @@ def password_reset(request, is_admin_site=False,
|
|||
'request': request,
|
||||
}
|
||||
if is_admin_site:
|
||||
opts = dict(opts, domain_override=request.META['HTTP_HOST'])
|
||||
opts = dict(opts, domain_override=request.get_host())
|
||||
form.save(**opts)
|
||||
return HttpResponseRedirect(post_reset_redirect)
|
||||
else:
|
||||
|
@ -173,6 +176,7 @@ def password_reset(request, is_admin_site=False,
|
|||
return TemplateResponse(request, template_name, context,
|
||||
current_app=current_app)
|
||||
|
||||
|
||||
def password_reset_done(request,
|
||||
template_name='registration/password_reset_done.html',
|
||||
current_app=None, extra_context=None):
|
||||
|
@ -182,6 +186,7 @@ def password_reset_done(request,
|
|||
return TemplateResponse(request, template_name, context,
|
||||
current_app=current_app)
|
||||
|
||||
|
||||
# Doesn't need csrf_protect since no-one can guess the URL
|
||||
@sensitive_post_parameters()
|
||||
@never_cache
|
||||
|
@ -195,13 +200,14 @@ def password_reset_confirm(request, uidb36=None, token=None,
|
|||
View that checks the hash in a password reset link and presents a
|
||||
form for entering a new password.
|
||||
"""
|
||||
assert uidb36 is not None and token is not None # checked by URLconf
|
||||
UserModel = get_user_model()
|
||||
assert uidb36 is not None and token is not None # checked by URLconf
|
||||
if post_reset_redirect is None:
|
||||
post_reset_redirect = reverse('django.contrib.auth.views.password_reset_complete')
|
||||
try:
|
||||
uid_int = base36_to_int(uidb36)
|
||||
user = User.objects.get(id=uid_int)
|
||||
except (ValueError, OverflowError, User.DoesNotExist):
|
||||
user = UserModel.objects.get(id=uid_int)
|
||||
except (ValueError, OverflowError, UserModel.DoesNotExist):
|
||||
user = None
|
||||
|
||||
if user is not None and token_generator.check_token(user, token):
|
||||
|
@ -225,6 +231,7 @@ def password_reset_confirm(request, uidb36=None, token=None,
|
|||
return TemplateResponse(request, template_name, context,
|
||||
current_app=current_app)
|
||||
|
||||
|
||||
def password_reset_complete(request,
|
||||
template_name='registration/password_reset_complete.html',
|
||||
current_app=None, extra_context=None):
|
||||
|
@ -236,6 +243,7 @@ def password_reset_complete(request,
|
|||
return TemplateResponse(request, template_name, context,
|
||||
current_app=current_app)
|
||||
|
||||
|
||||
@sensitive_post_parameters()
|
||||
@csrf_protect
|
||||
@login_required
|
||||
|
@ -261,6 +269,7 @@ def password_change(request,
|
|||
return TemplateResponse(request, template_name, context,
|
||||
current_app=current_app)
|
||||
|
||||
|
||||
@login_required
|
||||
def password_change_done(request,
|
||||
template_name='registration/password_change_done.html',
|
||||
|
|
|
@ -1,11 +1,22 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.comments.models import Comment
|
||||
from django.utils.translation import ugettext_lazy as _, ungettext
|
||||
from django.contrib.comments import get_model
|
||||
from django.contrib.comments.views.moderation import perform_flag, perform_approve, perform_delete
|
||||
|
||||
|
||||
class UsernameSearch(object):
|
||||
"""The User object may not be auth.User, so we need to provide
|
||||
a mechanism for issuing the equivalent of a .filter(user__username=...)
|
||||
search in CommentAdmin.
|
||||
"""
|
||||
def __str__(self):
|
||||
return 'user__%s' % get_user_model().USERNAME_FIELD
|
||||
|
||||
|
||||
class CommentsAdmin(admin.ModelAdmin):
|
||||
fieldsets = (
|
||||
(None,
|
||||
|
@ -24,7 +35,7 @@ class CommentsAdmin(admin.ModelAdmin):
|
|||
date_hierarchy = 'submit_date'
|
||||
ordering = ('-submit_date',)
|
||||
raw_id_fields = ('user',)
|
||||
search_fields = ('comment', 'user__username', 'user_name', 'user_email', 'user_url', 'ip_address')
|
||||
search_fields = ('comment', UsernameSearch(), 'user_name', 'user_email', 'user_url', 'ip_address')
|
||||
actions = ["flag_comments", "approve_comments", "remove_comments"]
|
||||
|
||||
def get_actions(self, request):
|
||||
|
|
|
@ -1,30 +1,27 @@
|
|||
from django.conf import settings
|
||||
from django.contrib.syndication.views import Feed
|
||||
from django.contrib.sites.models import Site
|
||||
from django.contrib.sites.models import get_current_site
|
||||
from django.contrib import comments
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
class LatestCommentFeed(Feed):
|
||||
"""Feed of latest comments on the current site."""
|
||||
|
||||
def __call__(self, request, *args, **kwargs):
|
||||
self.site = get_current_site(request)
|
||||
return super(LatestCommentFeed, self).__call__(request, *args, **kwargs)
|
||||
|
||||
def title(self):
|
||||
if not hasattr(self, '_site'):
|
||||
self._site = Site.objects.get_current()
|
||||
return _("%(site_name)s comments") % dict(site_name=self._site.name)
|
||||
return _("%(site_name)s comments") % dict(site_name=self.site.name)
|
||||
|
||||
def link(self):
|
||||
if not hasattr(self, '_site'):
|
||||
self._site = Site.objects.get_current()
|
||||
return "http://%s/" % (self._site.domain)
|
||||
return "http://%s/" % (self.site.domain)
|
||||
|
||||
def description(self):
|
||||
if not hasattr(self, '_site'):
|
||||
self._site = Site.objects.get_current()
|
||||
return _("Latest comments on %(site_name)s") % dict(site_name=self._site.name)
|
||||
return _("Latest comments on %(site_name)s") % dict(site_name=self.site.name)
|
||||
|
||||
def items(self):
|
||||
qs = comments.get_model().objects.filter(
|
||||
site__pk = settings.SITE_ID,
|
||||
site__pk = self.site.pk,
|
||||
is_public = True,
|
||||
is_removed = False,
|
||||
)
|
||||
|
|
|
@ -4,7 +4,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2012-03-23 02:37+0100\n"
|
||||
"POT-Creation-Date: 2012-10-15 10:56+0200\n"
|
||||
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
|
||||
"Last-Translator: Django team\n"
|
||||
"Language-Team: English <en@li.org>\n"
|
||||
|
@ -13,57 +13,57 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: admin.py:12
|
||||
#: admin.py:25
|
||||
msgid "Content"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:15
|
||||
#: admin.py:28
|
||||
msgid "Metadata"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:42
|
||||
#: admin.py:55
|
||||
msgid "flagged"
|
||||
msgid_plural "flagged"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: admin.py:43
|
||||
#: admin.py:56
|
||||
msgid "Flag selected comments"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:47
|
||||
#: admin.py:60
|
||||
msgid "approved"
|
||||
msgid_plural "approved"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: admin.py:48
|
||||
#: admin.py:61
|
||||
msgid "Approve selected comments"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:52
|
||||
#: admin.py:65
|
||||
msgid "removed"
|
||||
msgid_plural "removed"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: admin.py:53
|
||||
#: admin.py:66
|
||||
msgid "Remove selected comments"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:65
|
||||
#: admin.py:78
|
||||
#, python-format
|
||||
msgid "1 comment was successfully %(action)s."
|
||||
msgid_plural "%(count)s comments were successfully %(action)s."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: feeds.py:13
|
||||
#: feeds.py:14
|
||||
#, python-format
|
||||
msgid "%(site_name)s comments"
|
||||
msgstr ""
|
||||
|
||||
#: feeds.py:23
|
||||
#: feeds.py:20
|
||||
#, python-format
|
||||
msgid "Latest comments on %(site_name)s"
|
||||
msgstr ""
|
||||
|
@ -100,78 +100,78 @@ msgid ""
|
|||
"If you enter anything in this field your comment will be treated as spam"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:22
|
||||
#: models.py:23
|
||||
msgid "content type"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:24
|
||||
#: models.py:25
|
||||
msgid "object ID"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:50 models.py:168
|
||||
#: models.py:53 models.py:177
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:52
|
||||
#: models.py:55
|
||||
msgid "user's name"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:53
|
||||
#: models.py:56
|
||||
msgid "user's email address"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:54
|
||||
#: models.py:57
|
||||
msgid "user's URL"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:56 models.py:76 models.py:169
|
||||
#: models.py:59 models.py:79 models.py:178
|
||||
msgid "comment"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:59
|
||||
#: models.py:62
|
||||
msgid "date/time submitted"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:60
|
||||
#: models.py:63
|
||||
msgid "IP address"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:61
|
||||
#: models.py:64
|
||||
msgid "is public"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:62
|
||||
#: models.py:65
|
||||
msgid ""
|
||||
"Uncheck this box to make the comment effectively disappear from the site."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:64
|
||||
#: models.py:67
|
||||
msgid "is removed"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:65
|
||||
#: models.py:68
|
||||
msgid ""
|
||||
"Check this box if the comment is inappropriate. A \"This comment has been "
|
||||
"removed\" message will be displayed instead."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:77
|
||||
#: models.py:80
|
||||
msgid "comments"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:119
|
||||
#: models.py:124
|
||||
msgid ""
|
||||
"This comment was posted by an authenticated user and thus the name is read-"
|
||||
"only."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:128
|
||||
#: models.py:134
|
||||
msgid ""
|
||||
"This comment was posted by an authenticated user and thus the email is read-"
|
||||
"only."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:153
|
||||
#: models.py:160
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Posted by %(user)s at %(date)s\n"
|
||||
|
@ -181,19 +181,19 @@ msgid ""
|
|||
"http://%(domain)s%(url)s"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:170
|
||||
#: models.py:179
|
||||
msgid "flag"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:171
|
||||
#: models.py:180
|
||||
msgid "date"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:181
|
||||
#: models.py:190
|
||||
msgid "comment flag"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:182
|
||||
#: models.py:191
|
||||
msgid "comment flags"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
from django.contrib.comments.managers import CommentManager
|
||||
from django.contrib.contenttypes import generic
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.sites.models import Site
|
||||
from django.db import models
|
||||
from django.core import urlresolvers
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
|
||||
COMMENT_MAX_LENGTH = getattr(settings,'COMMENT_MAX_LENGTH',3000)
|
||||
COMMENT_MAX_LENGTH = getattr(settings, 'COMMENT_MAX_LENGTH', 3000)
|
||||
|
||||
|
||||
class BaseCommentAbstractModel(models.Model):
|
||||
"""
|
||||
|
@ -19,14 +19,14 @@ class BaseCommentAbstractModel(models.Model):
|
|||
"""
|
||||
|
||||
# Content-object field
|
||||
content_type = models.ForeignKey(ContentType,
|
||||
content_type = models.ForeignKey(ContentType,
|
||||
verbose_name=_('content type'),
|
||||
related_name="content_type_set_for_%(class)s")
|
||||
object_pk = models.TextField(_('object ID'))
|
||||
object_pk = models.TextField(_('object ID'))
|
||||
content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_pk")
|
||||
|
||||
# Metadata about the comment
|
||||
site = models.ForeignKey(Site)
|
||||
site = models.ForeignKey(Site)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
@ -40,6 +40,7 @@ class BaseCommentAbstractModel(models.Model):
|
|||
args=(self.content_type_id, self.object_pk)
|
||||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Comment(BaseCommentAbstractModel):
|
||||
"""
|
||||
|
@ -49,21 +50,21 @@ class Comment(BaseCommentAbstractModel):
|
|||
# Who posted this comment? If ``user`` is set then it was an authenticated
|
||||
# user; otherwise at least user_name should have been set and the comment
|
||||
# was posted by a non-authenticated user.
|
||||
user = models.ForeignKey(User, verbose_name=_('user'),
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'),
|
||||
blank=True, null=True, related_name="%(class)s_comments")
|
||||
user_name = models.CharField(_("user's name"), max_length=50, blank=True)
|
||||
user_email = models.EmailField(_("user's email address"), blank=True)
|
||||
user_url = models.URLField(_("user's URL"), blank=True)
|
||||
user_name = models.CharField(_("user's name"), max_length=50, blank=True)
|
||||
user_email = models.EmailField(_("user's email address"), blank=True)
|
||||
user_url = models.URLField(_("user's URL"), blank=True)
|
||||
|
||||
comment = models.TextField(_('comment'), max_length=COMMENT_MAX_LENGTH)
|
||||
|
||||
# Metadata about the comment
|
||||
submit_date = models.DateTimeField(_('date/time submitted'), default=None)
|
||||
ip_address = models.IPAddressField(_('IP address'), blank=True, null=True)
|
||||
is_public = models.BooleanField(_('is public'), default=True,
|
||||
ip_address = models.IPAddressField(_('IP address'), blank=True, null=True)
|
||||
is_public = models.BooleanField(_('is public'), default=True,
|
||||
help_text=_('Uncheck this box to make the comment effectively ' \
|
||||
'disappear from the site.'))
|
||||
is_removed = models.BooleanField(_('is removed'), default=False,
|
||||
is_removed = models.BooleanField(_('is removed'), default=False,
|
||||
help_text=_('Check this box if the comment is inappropriate. ' \
|
||||
'A "This comment has been removed" message will ' \
|
||||
'be displayed instead.'))
|
||||
|
@ -95,9 +96,9 @@ class Comment(BaseCommentAbstractModel):
|
|||
"""
|
||||
if not hasattr(self, "_userinfo"):
|
||||
userinfo = {
|
||||
"name" : self.user_name,
|
||||
"email" : self.user_email,
|
||||
"url" : self.user_url
|
||||
"name": self.user_name,
|
||||
"email": self.user_email,
|
||||
"url": self.user_url
|
||||
}
|
||||
if self.user_id:
|
||||
u = self.user
|
||||
|
@ -110,13 +111,14 @@ class Comment(BaseCommentAbstractModel):
|
|||
if u.get_full_name():
|
||||
userinfo["name"] = self.user.get_full_name()
|
||||
elif not self.user_name:
|
||||
userinfo["name"] = u.username
|
||||
userinfo["name"] = u.get_username()
|
||||
self._userinfo = userinfo
|
||||
return self._userinfo
|
||||
userinfo = property(_get_userinfo, doc=_get_userinfo.__doc__)
|
||||
|
||||
def _get_name(self):
|
||||
return self.userinfo["name"]
|
||||
|
||||
def _set_name(self, val):
|
||||
if self.user_id:
|
||||
raise AttributeError(_("This comment was posted by an authenticated "\
|
||||
|
@ -126,6 +128,7 @@ class Comment(BaseCommentAbstractModel):
|
|||
|
||||
def _get_email(self):
|
||||
return self.userinfo["email"]
|
||||
|
||||
def _set_email(self, val):
|
||||
if self.user_id:
|
||||
raise AttributeError(_("This comment was posted by an authenticated "\
|
||||
|
@ -135,6 +138,7 @@ class Comment(BaseCommentAbstractModel):
|
|||
|
||||
def _get_url(self):
|
||||
return self.userinfo["url"]
|
||||
|
||||
def _set_url(self, val):
|
||||
self.user_url = val
|
||||
url = property(_get_url, _set_url, doc="The URL given by the user who posted this comment")
|
||||
|
@ -155,6 +159,7 @@ class Comment(BaseCommentAbstractModel):
|
|||
}
|
||||
return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % d
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class CommentFlag(models.Model):
|
||||
"""
|
||||
|
@ -169,9 +174,9 @@ class CommentFlag(models.Model):
|
|||
design users are only allowed to flag a comment with a given flag once;
|
||||
if you want rating look elsewhere.
|
||||
"""
|
||||
user = models.ForeignKey(User, verbose_name=_('user'), related_name="comment_flags")
|
||||
comment = models.ForeignKey(Comment, verbose_name=_('comment'), related_name="flags")
|
||||
flag = models.CharField(_('flag'), max_length=30, db_index=True)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'), related_name="comment_flags")
|
||||
comment = models.ForeignKey(Comment, verbose_name=_('comment'), related_name="flags")
|
||||
flag = models.CharField(_('flag'), max_length=30, db_index=True)
|
||||
flag_date = models.DateTimeField(_('date'), default=None)
|
||||
|
||||
# Constants for flag types
|
||||
|
@ -187,7 +192,7 @@ class CommentFlag(models.Model):
|
|||
|
||||
def __str__(self):
|
||||
return "%s flag of comment ID %s by %s" % \
|
||||
(self.flag, self.comment_id, self.user.username)
|
||||
(self.flag, self.comment_id, self.user.get_username())
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.flag_date is None:
|
||||
|
|
|
@ -62,7 +62,7 @@ from django.contrib.comments import signals
|
|||
from django.db.models.base import ModelBase
|
||||
from django.template import Context, loader
|
||||
from django.contrib import comments
|
||||
from django.contrib.sites.models import Site
|
||||
from django.contrib.sites.models import get_current_site
|
||||
from django.utils import timezone
|
||||
|
||||
class AlreadyModerated(Exception):
|
||||
|
@ -240,7 +240,7 @@ class CommentModerator(object):
|
|||
t = loader.get_template('comments/comment_notification_email.txt')
|
||||
c = Context({ 'comment': comment,
|
||||
'content_object': content_object })
|
||||
subject = '[%s] New comment posted on "%s"' % (Site.objects.get_current().name,
|
||||
subject = '[%s] New comment posted on "%s"' % (get_current_site(request).name,
|
||||
content_object)
|
||||
message = t.render(c)
|
||||
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, recipient_list, fail_silently=True)
|
||||
|
|
|
@ -15,7 +15,6 @@ from django.views.decorators.csrf import csrf_protect
|
|||
from django.views.decorators.http import require_POST
|
||||
|
||||
|
||||
|
||||
class CommentPostBadRequest(http.HttpResponseBadRequest):
|
||||
"""
|
||||
Response returned when a comment post is invalid. If ``DEBUG`` is on a
|
||||
|
@ -27,6 +26,7 @@ class CommentPostBadRequest(http.HttpResponseBadRequest):
|
|||
if settings.DEBUG:
|
||||
self.content = render_to_string("comments/400-debug.html", {"why": why})
|
||||
|
||||
|
||||
@csrf_protect
|
||||
@require_POST
|
||||
def post_comment(request, next=None, using=None):
|
||||
|
@ -40,7 +40,7 @@ def post_comment(request, next=None, using=None):
|
|||
data = request.POST.copy()
|
||||
if request.user.is_authenticated():
|
||||
if not data.get('name', ''):
|
||||
data["name"] = request.user.get_full_name() or request.user.username
|
||||
data["name"] = request.user.get_full_name() or request.user.get_username()
|
||||
if not data.get('email', ''):
|
||||
data["email"] = request.user.email
|
||||
|
||||
|
@ -98,8 +98,8 @@ def post_comment(request, next=None, using=None):
|
|||
]
|
||||
return render_to_response(
|
||||
template_list, {
|
||||
"comment" : form.data.get("comment", ""),
|
||||
"form" : form,
|
||||
"comment": form.data.get("comment", ""),
|
||||
"form": form,
|
||||
"next": next,
|
||||
},
|
||||
RequestContext(request, {})
|
||||
|
@ -113,9 +113,9 @@ def post_comment(request, next=None, using=None):
|
|||
|
||||
# Signal that the comment is about to be saved
|
||||
responses = signals.comment_will_be_posted.send(
|
||||
sender = comment.__class__,
|
||||
comment = comment,
|
||||
request = request
|
||||
sender=comment.__class__,
|
||||
comment=comment,
|
||||
request=request
|
||||
)
|
||||
|
||||
for (receiver, response) in responses:
|
||||
|
@ -126,15 +126,14 @@ def post_comment(request, next=None, using=None):
|
|||
# Save the comment and signal that it was saved
|
||||
comment.save()
|
||||
signals.comment_was_posted.send(
|
||||
sender = comment.__class__,
|
||||
comment = comment,
|
||||
request = request
|
||||
sender=comment.__class__,
|
||||
comment=comment,
|
||||
request=request
|
||||
)
|
||||
|
||||
return next_redirect(data, next, comment_done, c=comment._get_pk_val())
|
||||
|
||||
comment_done = confirmation_view(
|
||||
template = "comments/posted.html",
|
||||
doc = """Display a "comment was posted" success page."""
|
||||
template="comments/posted.html",
|
||||
doc="""Display a "comment was posted" success page."""
|
||||
)
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2012-03-23 02:37+0100\n"
|
||||
"POT-Creation-Date: 2012-10-15 10:56+0200\n"
|
||||
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
|
||||
"Last-Translator: Django team\n"
|
||||
"Language-Team: English <en@li.org>\n"
|
||||
|
@ -13,29 +13,29 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: models.py:123
|
||||
#: models.py:130
|
||||
msgid "python model class name"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:127
|
||||
#: models.py:134
|
||||
msgid "content type"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:128
|
||||
#: models.py:135
|
||||
msgid "content types"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:15
|
||||
#: views.py:17
|
||||
#, python-format
|
||||
msgid "Content type %(ct_id)s object has no associated model"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:19
|
||||
#: views.py:21
|
||||
#, python-format
|
||||
msgid "Content type %(ct_id)s object %(obj_id)s doesn't exist"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:25
|
||||
#: views.py:27
|
||||
#, python-format
|
||||
msgid "%(ct_name)s objects don't have a get_absolute_url() method"
|
||||
msgstr ""
|
||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import unicode_literals
|
|||
from django.db import models
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.contenttypes.views import shortcut
|
||||
from django.contrib.sites.models import Site
|
||||
from django.contrib.sites.models import Site, get_current_site
|
||||
from django.http import HttpRequest, Http404
|
||||
from django.test import TestCase
|
||||
from django.utils.http import urlquote
|
||||
|
@ -219,9 +219,8 @@ class ContentTypesTests(TestCase):
|
|||
obj = FooWithUrl.objects.create(name="john")
|
||||
|
||||
if Site._meta.installed:
|
||||
current_site = Site.objects.get_current()
|
||||
response = shortcut(request, user_ct.id, obj.id)
|
||||
self.assertEqual("http://%s/users/john/" % current_site.domain,
|
||||
self.assertEqual("http://%s/users/john/" % get_current_site(request).domain,
|
||||
response._headers.get("location")[1])
|
||||
|
||||
Site._meta.installed = False
|
||||
|
|
|
@ -4,7 +4,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2012-03-23 02:37+0100\n"
|
||||
"POT-Creation-Date: 2012-10-15 10:56+0200\n"
|
||||
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
|
||||
"Last-Translator: Django team\n"
|
||||
"Language-Team: English <en@li.org>\n"
|
||||
|
@ -17,7 +17,7 @@ msgstr ""
|
|||
msgid "Advanced options"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:7 models.py:7
|
||||
#: forms.py:7 models.py:11
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
|
@ -45,40 +45,44 @@ msgstr ""
|
|||
msgid "Flatpage with url %(url)s already exists for site %(site)s"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:8
|
||||
#: models.py:12
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:9
|
||||
#: models.py:13
|
||||
msgid "content"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:10
|
||||
#: models.py:14
|
||||
msgid "enable comments"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:11
|
||||
#: models.py:15
|
||||
msgid "template name"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:12
|
||||
#: models.py:16
|
||||
msgid ""
|
||||
"Example: 'flatpages/contact_page.html'. If this isn't provided, the system "
|
||||
"will use 'flatpages/default.html'."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:13
|
||||
#: models.py:17
|
||||
msgid "registration required"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:13
|
||||
#: models.py:17
|
||||
msgid "If this is checked, only logged-in users will be able to view the page."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:18
|
||||
#: models.py:22
|
||||
msgid "flat page"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:19
|
||||
#: models.py:23
|
||||
msgid "flat pages"
|
||||
msgstr ""
|
||||
|
||||
#: tests/forms.py:97
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from django import template
|
||||
from django.conf import settings
|
||||
from django.contrib.flatpages.models import FlatPage
|
||||
from django.contrib.sites.models import get_current_site
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
@ -19,7 +20,11 @@ class FlatpageNode(template.Node):
|
|||
self.user = None
|
||||
|
||||
def render(self, context):
|
||||
flatpages = FlatPage.objects.filter(sites__id=settings.SITE_ID)
|
||||
if 'request' in context:
|
||||
site_pk = get_current_site(context['request']).pk
|
||||
else:
|
||||
site_pk = settings.SITE_ID
|
||||
flatpages = FlatPage.objects.filter(sites__id=site_pk)
|
||||
# If a prefix was specified, add a filter
|
||||
if self.starts_with:
|
||||
flatpages = flatpages.filter(
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
from django.contrib.flatpages.models import FlatPage
|
||||
from django.template import loader, RequestContext
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.http import Http404, HttpResponse, HttpResponsePermanentRedirect
|
||||
from django.conf import settings
|
||||
from django.contrib.flatpages.models import FlatPage
|
||||
from django.contrib.sites.models import get_current_site
|
||||
from django.core.xheaders import populate_xheaders
|
||||
from django.http import Http404, HttpResponse, HttpResponsePermanentRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template import loader, RequestContext
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
|
||||
|
@ -30,14 +31,15 @@ def flatpage(request, url):
|
|||
"""
|
||||
if not url.startswith('/'):
|
||||
url = '/' + url
|
||||
site_id = get_current_site(request).id
|
||||
try:
|
||||
f = get_object_or_404(FlatPage,
|
||||
url__exact=url, sites__id__exact=settings.SITE_ID)
|
||||
url__exact=url, sites__id__exact=site_id)
|
||||
except Http404:
|
||||
if not url.endswith('/') and settings.APPEND_SLASH:
|
||||
url += '/'
|
||||
f = get_object_or_404(FlatPage,
|
||||
url__exact=url, sites__id__exact=settings.SITE_ID)
|
||||
url__exact=url, sites__id__exact=site_id)
|
||||
return HttpResponsePermanentRedirect('%s/' % request.path)
|
||||
else:
|
||||
raise
|
||||
|
|
|
@ -4,7 +4,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2012-03-23 02:38+0100\n"
|
||||
"POT-Creation-Date: 2012-10-15 10:56+0200\n"
|
||||
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
|
||||
"Last-Translator: Django team\n"
|
||||
"Language-Team: English <en@li.org>\n"
|
||||
|
|
|
@ -12,6 +12,7 @@ from django.conf import settings
|
|||
from django.contrib.formtools import preview, utils
|
||||
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 import unittest
|
||||
|
||||
|
@ -218,7 +219,6 @@ class DummyRequest(http.HttpRequest):
|
|||
)
|
||||
class WizardTests(TestCase):
|
||||
urls = 'django.contrib.formtools.tests.urls'
|
||||
input_re = re.compile('name="([^"]+)" value="([^"]+)"')
|
||||
wizard_step_data = (
|
||||
{
|
||||
'0-name': 'Pony',
|
||||
|
@ -409,14 +409,13 @@ class WizardTests(TestCase):
|
|||
"""
|
||||
Pull the appropriate field data from the context to pass to the next wizard step
|
||||
"""
|
||||
previous_fields = response.context['previous_fields']
|
||||
previous_fields = parse_html(response.context['previous_fields'])
|
||||
fields = {'wizard_step': response.context['step0']}
|
||||
|
||||
def grab(m):
|
||||
fields[m.group(1)] = m.group(2)
|
||||
return ''
|
||||
for input_field in previous_fields:
|
||||
input_attrs = dict(input_field.attributes)
|
||||
fields[input_attrs["name"]] = input_attrs["value"]
|
||||
|
||||
self.input_re.sub(grab, previous_fields)
|
||||
return fields
|
||||
|
||||
def check_wizard_step(self, response, step_no):
|
||||
|
@ -428,7 +427,6 @@ class WizardTests(TestCase):
|
|||
"""
|
||||
step_count = len(self.wizard_step_data)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'Step %d of %d' % (step_no, step_count))
|
||||
|
||||
data = self.grab_field_data(response)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import json
|
||||
|
||||
from django.test import TestCase
|
||||
from django.core import signing
|
||||
from django.core.exceptions import SuspiciousOperation
|
||||
|
@ -41,4 +43,5 @@ class TestCookieStorage(TestStorage, TestCase):
|
|||
storage.init_data()
|
||||
storage.update_response(response)
|
||||
unsigned_cookie_data = cookie_signer.unsign(response.cookies[storage.prefix].value)
|
||||
self.assertEqual(unsigned_cookie_data, '{"step_files":{},"step":null,"extra_data":{},"step_data":{}}')
|
||||
self.assertEqual(json.loads(unsigned_cookie_data),
|
||||
{"step_files": {}, "step": None, "extra_data": {}, "step_data": {}})
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
from django.utils import six
|
||||
|
||||
if six.PY3:
|
||||
memoryview = memoryview
|
||||
else:
|
||||
memoryview = buffer
|
|
@ -1,3 +1,5 @@
|
|||
import logging
|
||||
|
||||
from django.forms.widgets import Textarea
|
||||
from django.template import loader, Context
|
||||
from django.templatetags.static import static
|
||||
|
@ -10,6 +12,8 @@ from django.contrib.gis.geos import GEOSGeometry, GEOSException, fromstr
|
|||
# Creating a template context that contains Django settings
|
||||
# values needed by admin map templates.
|
||||
geo_context = Context({'LANGUAGE_BIDI' : translation.get_language_bidi()})
|
||||
logger = logging.getLogger('django.contrib.gis')
|
||||
|
||||
|
||||
class OpenLayersWidget(Textarea):
|
||||
"""
|
||||
|
@ -29,7 +33,11 @@ class OpenLayersWidget(Textarea):
|
|||
if isinstance(value, six.string_types):
|
||||
try:
|
||||
value = GEOSGeometry(value)
|
||||
except (GEOSException, ValueError):
|
||||
except (GEOSException, ValueError) as err:
|
||||
logger.error(
|
||||
"Error creating geometry from value '%s' (%s)" % (
|
||||
value, err)
|
||||
)
|
||||
value = None
|
||||
|
||||
if value and value.geom_type.upper() != self.geom_type:
|
||||
|
@ -56,7 +64,11 @@ class OpenLayersWidget(Textarea):
|
|||
ogr = value.ogr
|
||||
ogr.transform(srid)
|
||||
wkt = ogr.wkt
|
||||
except OGRException:
|
||||
except OGRException as err:
|
||||
logger.error(
|
||||
"Error transforming geometry from srid '%s' to srid '%s' (%s)" % (
|
||||
value.srid, srid, err)
|
||||
)
|
||||
wkt = ''
|
||||
else:
|
||||
wkt = value.wkt
|
||||
|
|
|
@ -32,8 +32,9 @@ class BaseSpatialOperations(object):
|
|||
# How the geometry column should be selected.
|
||||
select = None
|
||||
|
||||
# Does the spatial database have a geography type?
|
||||
# Does the spatial database have a geometry or geography type?
|
||||
geography = False
|
||||
geometry = False
|
||||
|
||||
area = False
|
||||
centroid = False
|
||||
|
@ -116,6 +117,16 @@ class BaseSpatialOperations(object):
|
|||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_expression_column(self, evaluator):
|
||||
"""
|
||||
Helper method to return the quoted column string from the evaluator
|
||||
for its expression.
|
||||
"""
|
||||
for expr, col_tup in evaluator.cols:
|
||||
if expr is evaluator.expression:
|
||||
return '%s.%s' % tuple(map(self.quote_name, col_tup))
|
||||
raise Exception("Could not find the column for the expression.")
|
||||
|
||||
# Spatial SQL Construction
|
||||
def spatial_aggregate_sql(self, agg):
|
||||
raise NotImplementedError('Aggregate support not implemented for this spatial backend.')
|
||||
|
|
|
@ -44,7 +44,7 @@ class MySQLOperations(DatabaseOperations, BaseSpatialOperations):
|
|||
modify the placeholder based on the contents of the given value.
|
||||
"""
|
||||
if hasattr(value, 'expression'):
|
||||
placeholder = '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
|
||||
placeholder = self.get_expression_column(value)
|
||||
else:
|
||||
placeholder = '%s(%%s)' % self.from_text
|
||||
return placeholder
|
||||
|
|
|
@ -213,7 +213,7 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
|
|||
placeholder = '%s'
|
||||
# No geometry value used for F expression, substitue in
|
||||
# the column name instead.
|
||||
return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
|
||||
return placeholder % self.get_expression_column(value)
|
||||
else:
|
||||
if transform_value(value, f.srid):
|
||||
return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (self.transform, value.srid, f.srid)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""
|
||||
This object provides quoting for GEOS geometries into PostgreSQL/PostGIS.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from psycopg2 import Binary
|
||||
from psycopg2.extensions import ISQLQuote
|
||||
|
@ -10,7 +11,7 @@ class PostGISAdapter(object):
|
|||
"Initializes on the geometry."
|
||||
# Getting the WKB (in string form, to allow easy pickling of
|
||||
# the adaptor) and the SRID from the geometry.
|
||||
self.ewkb = str(geom.ewkb)
|
||||
self.ewkb = bytes(geom.ewkb)
|
||||
self.srid = geom.srid
|
||||
self._adapter = Binary(self.ewkb)
|
||||
|
||||
|
@ -39,7 +40,7 @@ class PostGISAdapter(object):
|
|||
def getquoted(self):
|
||||
"Returns a properly quoted string for use in PostgreSQL/PostGIS."
|
||||
# psycopg will figure out whether to use E'\\000' or '\000'
|
||||
return 'ST_GeomFromEWKB(%s)' % self._adapter.getquoted()
|
||||
return str('ST_GeomFromEWKB(%s)' % self._adapter.getquoted().decode())
|
||||
|
||||
def prepare_database_save(self, unused):
|
||||
return self
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation
|
||||
|
||||
class PostGISCreation(DatabaseCreation):
|
||||
geom_index_type = 'GIST'
|
||||
geom_index_opts = 'GIST_GEOMETRY_OPS'
|
||||
geom_index_ops = 'GIST_GEOMETRY_OPS'
|
||||
geom_index_ops_nd = 'GIST_GEOMETRY_OPS_ND'
|
||||
|
||||
def sql_indexes_for_field(self, model, f, style):
|
||||
"Return any spatial index creation SQL for the field."
|
||||
|
@ -16,8 +18,9 @@ class PostGISCreation(DatabaseCreation):
|
|||
qn = self.connection.ops.quote_name
|
||||
db_table = model._meta.db_table
|
||||
|
||||
if f.geography:
|
||||
# Geogrophy columns are created normally.
|
||||
if f.geography or self.connection.ops.geometry:
|
||||
# Geography and Geometry (PostGIS 2.0+) columns are
|
||||
# created normally.
|
||||
pass
|
||||
else:
|
||||
# Geometry columns are created by `AddGeometryColumn`
|
||||
|
@ -38,23 +41,31 @@ class PostGISCreation(DatabaseCreation):
|
|||
style.SQL_FIELD(qn(f.column)) +
|
||||
style.SQL_KEYWORD(' SET NOT NULL') + ';')
|
||||
|
||||
|
||||
if f.spatial_index:
|
||||
# Spatial indexes created the same way for both Geometry and
|
||||
# Geography columns
|
||||
# Geography columns.
|
||||
# PostGIS 2.0 does not support GIST_GEOMETRY_OPS. So, on 1.5
|
||||
# we use GIST_GEOMETRY_OPS, on 2.0 we use either "nd" ops
|
||||
# which are fast on multidimensional cases, or just plain
|
||||
# gist index for the 2d case.
|
||||
if f.geography:
|
||||
index_opts = ''
|
||||
index_ops = ''
|
||||
elif self.connection.ops.geometry:
|
||||
if f.dim > 2:
|
||||
index_ops = ' ' + style.SQL_KEYWORD(self.geom_index_ops_nd)
|
||||
else:
|
||||
index_ops = ''
|
||||
else:
|
||||
index_opts = ' ' + style.SQL_KEYWORD(self.geom_index_opts)
|
||||
index_ops = ' ' + style.SQL_KEYWORD(self.geom_index_ops)
|
||||
output.append(style.SQL_KEYWORD('CREATE INDEX ') +
|
||||
style.SQL_TABLE(qn('%s_%s_id' % (db_table, f.column))) +
|
||||
style.SQL_KEYWORD(' ON ') +
|
||||
style.SQL_TABLE(qn(db_table)) +
|
||||
style.SQL_KEYWORD(' USING ') +
|
||||
style.SQL_COLTYPE(self.geom_index_type) + ' ( ' +
|
||||
style.SQL_FIELD(qn(f.column)) + index_opts + ' );')
|
||||
style.SQL_FIELD(qn(f.column)) + index_ops + ' );')
|
||||
return output
|
||||
|
||||
def sql_table_creation_suffix(self):
|
||||
qn = self.connection.ops.quote_name
|
||||
return ' TEMPLATE %s' % qn(getattr(settings, 'POSTGIS_TEMPLATE', 'template_postgis'))
|
||||
postgis_template = getattr(settings, 'POSTGIS_TEMPLATE', 'template_postgis')
|
||||
return ' TEMPLATE %s' % self.connection.ops.quote_name(postgis_template)
|
||||
|
|
|
@ -103,11 +103,12 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
|||
self.geom_func_prefix = prefix
|
||||
self.spatial_version = version
|
||||
except DatabaseError:
|
||||
raise ImproperlyConfigured('Cannot determine PostGIS version for database "%s". '
|
||||
'GeoDjango requires at least PostGIS version 1.3. '
|
||||
'Was the database created from a spatial database '
|
||||
'template?' % self.connection.settings_dict['NAME']
|
||||
)
|
||||
raise ImproperlyConfigured(
|
||||
'Cannot determine PostGIS version for database "%s". '
|
||||
'GeoDjango requires at least PostGIS version 1.3. '
|
||||
'Was the database created from a spatial database '
|
||||
'template?' % self.connection.settings_dict['NAME']
|
||||
)
|
||||
# TODO: Raise helpful exceptions as they become known.
|
||||
|
||||
# PostGIS-specific operators. The commented descriptions of these
|
||||
|
@ -215,6 +216,10 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
|||
'bboverlaps' : PostGISOperator('&&'),
|
||||
}
|
||||
|
||||
# Native geometry type support added in PostGIS 2.0.
|
||||
if version >= (2, 0, 0):
|
||||
self.geometry = True
|
||||
|
||||
# Creating a dictionary lookup of all GIS terms for PostGIS.
|
||||
gis_terms = ['isnull']
|
||||
gis_terms += list(self.geometry_operators)
|
||||
|
@ -231,7 +236,6 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
|||
self.distance_spheroid = prefix + 'distance_spheroid'
|
||||
self.envelope = prefix + 'Envelope'
|
||||
self.extent = prefix + 'Extent'
|
||||
self.extent3d = prefix + 'Extent3D'
|
||||
self.force_rhr = prefix + 'ForceRHR'
|
||||
self.geohash = GEOHASH
|
||||
self.geojson = GEOJSON
|
||||
|
@ -239,14 +243,12 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
|||
self.intersection = prefix + 'Intersection'
|
||||
self.kml = prefix + 'AsKML'
|
||||
self.length = prefix + 'Length'
|
||||
self.length3d = prefix + 'Length3D'
|
||||
self.length_spheroid = prefix + 'length_spheroid'
|
||||
self.makeline = prefix + 'MakeLine'
|
||||
self.mem_size = prefix + 'mem_size'
|
||||
self.num_geom = prefix + 'NumGeometries'
|
||||
self.num_points =prefix + 'npoints'
|
||||
self.perimeter = prefix + 'Perimeter'
|
||||
self.perimeter3d = prefix + 'Perimeter3D'
|
||||
self.point_on_surface = prefix + 'PointOnSurface'
|
||||
self.polygonize = prefix + 'Polygonize'
|
||||
self.reverse = prefix + 'Reverse'
|
||||
|
@ -259,6 +261,15 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
|||
self.union = prefix + 'Union'
|
||||
self.unionagg = prefix + 'Union'
|
||||
|
||||
if version >= (2, 0, 0):
|
||||
self.extent3d = prefix + '3DExtent'
|
||||
self.length3d = prefix + '3DLength'
|
||||
self.perimeter3d = prefix + '3DPerimeter'
|
||||
else:
|
||||
self.extent3d = prefix + 'Extent3D'
|
||||
self.length3d = prefix + 'Length3D'
|
||||
self.perimeter3d = prefix + 'Perimeter3D'
|
||||
|
||||
def check_aggregate_support(self, aggregate):
|
||||
"""
|
||||
Checks if the given aggregate name is supported (that is, if it's
|
||||
|
@ -314,6 +325,14 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
|||
'only with an SRID of 4326.')
|
||||
|
||||
return 'geography(%s,%d)'% (f.geom_type, f.srid)
|
||||
elif self.geometry:
|
||||
# Postgis 2.0 supports type-based geometries.
|
||||
# TODO: Support 'M' extension.
|
||||
if f.dim == 3:
|
||||
geom_type = f.geom_type + 'Z'
|
||||
else:
|
||||
geom_type = f.geom_type
|
||||
return 'geometry(%s,%d)' % (geom_type, f.srid)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
@ -375,7 +394,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
|
|||
# If this is an F expression, then we don't really want
|
||||
# a placeholder and instead substitute in the column
|
||||
# of the expression.
|
||||
placeholder = placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
|
||||
placeholder = placeholder % self.get_expression_column(value)
|
||||
|
||||
return placeholder
|
||||
|
||||
|
|
|
@ -146,6 +146,8 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
|
|||
except DatabaseError:
|
||||
# we are using < 2.4.0-RC4
|
||||
pass
|
||||
if version >= (3, 0, 0):
|
||||
self.geojson = 'AsGeoJSON'
|
||||
|
||||
def check_aggregate_support(self, aggregate):
|
||||
"""
|
||||
|
@ -208,7 +210,7 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
|
|||
placeholder = '%s'
|
||||
# No geometry value used for F expression, substitue in
|
||||
# the column name instead.
|
||||
return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
|
||||
return placeholder % self.get_expression_column(value)
|
||||
else:
|
||||
if transform_value(value, f.srid):
|
||||
# Adding Transform() to the SQL placeholder.
|
||||
|
|
|
@ -95,7 +95,7 @@ class GeometryField(Field):
|
|||
# Is this a geography rather than a geometry column?
|
||||
self.geography = geography
|
||||
|
||||
# Oracle-specific private attributes for creating the entrie in
|
||||
# Oracle-specific private attributes for creating the entry in
|
||||
# `USER_SDO_GEOM_METADATA`
|
||||
self._extent = kwargs.pop('extent', (-180.0, -90.0, 180.0, 90.0))
|
||||
self._tolerance = kwargs.pop('tolerance', 0.05)
|
||||
|
@ -160,7 +160,7 @@ class GeometryField(Field):
|
|||
# from the given string input.
|
||||
if isinstance(geom, Geometry):
|
||||
pass
|
||||
elif isinstance(geom, six.string_types) or hasattr(geom, '__geo_interface__'):
|
||||
elif isinstance(geom, (bytes, six.string_types)) or hasattr(geom, '__geo_interface__'):
|
||||
try:
|
||||
geom = Geometry(geom)
|
||||
except GeometryException:
|
||||
|
|
|
@ -5,6 +5,7 @@ corresponding to geographic model fields.
|
|||
|
||||
Thanks to Robert Coup for providing this functionality (see #4322).
|
||||
"""
|
||||
from django.contrib.gis import memoryview
|
||||
from django.utils import six
|
||||
|
||||
class GeometryProxy(object):
|
||||
|
@ -54,7 +55,7 @@ class GeometryProxy(object):
|
|||
if isinstance(value, self._klass) and (str(value.geom_type).upper() == gtype or gtype == 'GEOMETRY'):
|
||||
# Assigning the SRID to the geometry.
|
||||
if value.srid is None: value.srid = self._field.srid
|
||||
elif value is None or isinstance(value, six.string_types + (buffer,)):
|
||||
elif value is None or isinstance(value, six.string_types + (memoryview,)):
|
||||
# Set with None, WKT, HEX, or WKB
|
||||
pass
|
||||
else:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from django.db import connections
|
||||
from django.db.models.query import QuerySet, ValuesQuerySet, ValuesListQuerySet
|
||||
|
||||
from django.contrib.gis import memoryview
|
||||
from django.contrib.gis.db.models import aggregates
|
||||
from django.contrib.gis.db.models.fields import get_srid_info, PointField, LineStringField
|
||||
from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery
|
||||
|
@ -145,13 +146,14 @@ class GeoQuerySet(QuerySet):
|
|||
"""
|
||||
backend = connections[self.db].ops
|
||||
if not backend.geojson:
|
||||
raise NotImplementedError('Only PostGIS 1.3.4+ supports GeoJSON serialization.')
|
||||
raise NotImplementedError('Only PostGIS 1.3.4+ and SpatiaLite 3.0+ '
|
||||
'support GeoJSON serialization.')
|
||||
|
||||
if not isinstance(precision, six.integer_types):
|
||||
raise TypeError('Precision keyword must be set with an integer.')
|
||||
|
||||
# Setting the options flag -- which depends on which version of
|
||||
# PostGIS we're using.
|
||||
# PostGIS we're using. SpatiaLite only uses the first group of options.
|
||||
if backend.spatial_version >= (1, 4, 0):
|
||||
options = 0
|
||||
if crs and bbox: options = 3
|
||||
|
@ -193,9 +195,9 @@ class GeoQuerySet(QuerySet):
|
|||
# PostGIS AsGML() aggregate function parameter order depends on the
|
||||
# version -- uggh.
|
||||
if backend.spatial_version > (1, 3, 1):
|
||||
procedure_fmt = '%(version)s,%(geo_col)s,%(precision)s'
|
||||
s['procedure_fmt'] = '%(version)s,%(geo_col)s,%(precision)s'
|
||||
else:
|
||||
procedure_fmt = '%(geo_col)s,%(precision)s,%(version)s'
|
||||
s['procedure_fmt'] = '%(geo_col)s,%(precision)s,%(version)s'
|
||||
s['procedure_args'] = {'precision' : precision, 'version' : version}
|
||||
|
||||
return self._spatial_attribute('gml', s, **kwargs)
|
||||
|
@ -676,7 +678,7 @@ class GeoQuerySet(QuerySet):
|
|||
if not backend.geography:
|
||||
if not isinstance(geo_field, PointField):
|
||||
raise ValueError('Spherical distance calculation only supported on PointFields.')
|
||||
if not str(Geometry(buffer(params[0].ewkb)).geom_type) == 'Point':
|
||||
if not str(Geometry(memoryview(params[0].ewkb)).geom_type) == 'Point':
|
||||
raise ValueError('Spherical distance calculation only supported with Point Geometry parameters')
|
||||
# The `function` procedure argument needs to be set differently for
|
||||
# geodetic distance calculations.
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
try:
|
||||
from itertools import zip_longest
|
||||
except ImportError:
|
||||
from itertools import izip_longest as zip_longest
|
||||
|
||||
from django.utils.six.moves import zip
|
||||
|
||||
from django.db.backends.util import truncate_name, typecast_timestamp
|
||||
|
@ -114,10 +119,10 @@ class GeoSQLCompiler(compiler.SQLCompiler):
|
|||
result = []
|
||||
if opts is None:
|
||||
opts = self.query.model._meta
|
||||
# Skip all proxy to the root proxied model
|
||||
opts = opts.concrete_model._meta
|
||||
aliases = set()
|
||||
only_load = self.deferred_to_columns()
|
||||
# Skip all proxy to the root proxied model
|
||||
proxied_model = opts.concrete_model
|
||||
|
||||
if start_alias:
|
||||
seen = {None: start_alias}
|
||||
|
@ -128,12 +133,9 @@ class GeoSQLCompiler(compiler.SQLCompiler):
|
|||
try:
|
||||
alias = seen[model]
|
||||
except KeyError:
|
||||
if model is proxied_model:
|
||||
alias = start_alias
|
||||
else:
|
||||
link_field = opts.get_ancestor_link(model)
|
||||
alias = self.query.join((start_alias, model._meta.db_table,
|
||||
link_field.column, model._meta.pk.column))
|
||||
link_field = opts.get_ancestor_link(model)
|
||||
alias = self.query.join((start_alias, model._meta.db_table,
|
||||
link_field.column, model._meta.pk.column))
|
||||
seen[model] = alias
|
||||
else:
|
||||
# If we're starting from the base model of the queryset, the
|
||||
|
@ -190,7 +192,7 @@ class GeoSQLCompiler(compiler.SQLCompiler):
|
|||
if self.connection.ops.oracle or getattr(self.query, 'geo_values', False):
|
||||
# We resolve the rest of the columns if we're on Oracle or if
|
||||
# the `geo_values` attribute is defined.
|
||||
for value, field in map(None, row[index_start:], fields):
|
||||
for value, field in zip_longest(row[index_start:], fields):
|
||||
values.append(self.query.convert_values(value, field, self.connection))
|
||||
else:
|
||||
values.extend(row[index_start:])
|
||||
|
|
|
@ -37,11 +37,11 @@
|
|||
try:
|
||||
from django.contrib.gis.gdal.driver import Driver
|
||||
from django.contrib.gis.gdal.datasource import DataSource
|
||||
from django.contrib.gis.gdal.libgdal import gdal_version, gdal_full_version, gdal_release_date, GDAL_VERSION
|
||||
from django.contrib.gis.gdal.libgdal import gdal_version, gdal_full_version, GDAL_VERSION
|
||||
from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform
|
||||
from django.contrib.gis.gdal.geometries import OGRGeometry
|
||||
HAS_GDAL = True
|
||||
except:
|
||||
except Exception:
|
||||
HAS_GDAL = False
|
||||
|
||||
try:
|
||||
|
|
|
@ -45,6 +45,7 @@ from django.contrib.gis.gdal.layer import Layer
|
|||
# Getting the ctypes prototypes for the DataSource.
|
||||
from django.contrib.gis.gdal.prototypes import ds as capi
|
||||
|
||||
from django.utils.encoding import force_bytes, force_text
|
||||
from django.utils import six
|
||||
from django.utils.six.moves import xrange
|
||||
|
||||
|
@ -56,12 +57,14 @@ class DataSource(GDALBase):
|
|||
"Wraps an OGR Data Source object."
|
||||
|
||||
#### Python 'magic' routines ####
|
||||
def __init__(self, ds_input, ds_driver=False, write=False):
|
||||
def __init__(self, ds_input, ds_driver=False, write=False, encoding='utf-8'):
|
||||
# The write flag.
|
||||
if write:
|
||||
self._write = 1
|
||||
else:
|
||||
self._write = 0
|
||||
# See also http://trac.osgeo.org/gdal/wiki/rfc23_ogr_unicode
|
||||
self.encoding = encoding
|
||||
|
||||
# Registering all the drivers, this needs to be done
|
||||
# _before_ we try to open up a data source.
|
||||
|
@ -73,7 +76,7 @@ class DataSource(GDALBase):
|
|||
ds_driver = Driver.ptr_type()
|
||||
try:
|
||||
# OGROpen will auto-detect the data source type.
|
||||
ds = capi.open_ds(ds_input, self._write, byref(ds_driver))
|
||||
ds = capi.open_ds(force_bytes(ds_input), self._write, byref(ds_driver))
|
||||
except OGRException:
|
||||
# Making the error message more clear rather than something
|
||||
# like "Invalid pointer returned from OGROpen".
|
||||
|
@ -102,7 +105,7 @@ class DataSource(GDALBase):
|
|||
def __getitem__(self, index):
|
||||
"Allows use of the index [] operator to get a layer at the index."
|
||||
if isinstance(index, six.string_types):
|
||||
l = capi.get_layer_by_name(self.ptr, index)
|
||||
l = capi.get_layer_by_name(self.ptr, force_bytes(index))
|
||||
if not l: raise OGRIndexError('invalid OGR Layer name given: "%s"' % index)
|
||||
elif isinstance(index, int):
|
||||
if index < 0 or index >= self.layer_count:
|
||||
|
@ -128,4 +131,5 @@ class DataSource(GDALBase):
|
|||
@property
|
||||
def name(self):
|
||||
"Returns the name of the data source."
|
||||
return capi.get_ds_name(self._ptr)
|
||||
name = capi.get_ds_name(self._ptr)
|
||||
return force_text(name, self.encoding, strings_only=True)
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.contrib.gis.gdal.error import OGRException
|
|||
from django.contrib.gis.gdal.prototypes import ds as capi
|
||||
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_bytes
|
||||
|
||||
# For more information, see the OGR C API source code:
|
||||
# http://www.gdal.org/ogr/ogr__api_8h.html
|
||||
|
@ -36,7 +37,7 @@ class Driver(GDALBase):
|
|||
name = dr_input
|
||||
|
||||
# Attempting to get the OGR driver by the string name.
|
||||
dr = capi.get_driver_by_name(name)
|
||||
dr = capi.get_driver_by_name(force_bytes(name))
|
||||
elif isinstance(dr_input, int):
|
||||
self._register()
|
||||
dr = capi.get_driver(dr_input)
|
||||
|
|
|
@ -52,7 +52,7 @@ class Envelope(object):
|
|||
elif len(args) == 4:
|
||||
# Individual parameters passed in.
|
||||
# Thanks to ww for the help
|
||||
self._from_sequence(map(float, args))
|
||||
self._from_sequence([float(a) for a in args])
|
||||
else:
|
||||
raise OGRException('Incorrect number (%d) of arguments.' % len(args))
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue