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