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:
Andrew Godwin 2012-10-26 08:41:13 +01:00
commit 6a632e0457
521 changed files with 11938 additions and 13372 deletions

4
.gitignore vendored
View File

@ -1,4 +1,8 @@
*.egg-info
*.pot
*.py[co]
MANIFEST
dist/
docs/_build/
tests/coverage_html/
tests/.coverage

View File

@ -4,3 +4,5 @@ syntax:glob
*.pot
*.py[co]
docs/_build/
tests/coverage_html/
tests/.coverage

View File

@ -1,5 +1,5 @@
[main]
host = https://www.transifex.net
host = https://www.transifex.com
lang_map = sr@latin:sr_Latn
[django.core]

View File

@ -33,6 +33,7 @@ The PRIMARY AUTHORS are (and/or have been):
* Florian Apolloner
* Jeremy Dunck
* Bryan Veloso
* Preston Holmes
More information on the main contributors to Django can be found in
docs/internals/committers.txt.
@ -424,6 +425,7 @@ answer newbie questions, and generally made Django that much better:
phil@produxion.net
phil.h.smith@gmail.com
Gustavo Picon
Travis Pinney
Michael Placentra II <someone@michaelplacentra2.net>
plisk
Daniel Poelzleithner <http://poelzi.org/>
@ -499,6 +501,7 @@ answer newbie questions, and generally made Django that much better:
Wiliam Alves de Souza <wiliamsouza83@gmail.com>
Don Spaulding <donspauldingii@gmail.com>
Calvin Spealman <ironfroggy@gmail.com>
Dane Springmeyer
Bjørn Stabell <bjorn@exoweb.net>
Georgi Stanojevski <glisha@gmail.com>
starrynight <cmorgh@gmail.com>

16
CONTRIBUTING.rst Normal file
View 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/

View File

@ -32,3 +32,5 @@ recursive-include django/contrib/gis/tests/geogapp/fixtures *
recursive-include django/contrib/gis/tests/relatedapp/fixtures *
recursive-include django/contrib/sitemaps/templates *
recursive-include django/contrib/sitemaps/tests/templates *
recursive-exclude * __pycache__
recursive-exclude * *.py[co]

View File

@ -25,7 +25,7 @@ class LazySettings(LazyObject):
The user can manually configure settings prior to using them. Otherwise,
Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE.
"""
def _setup(self, name):
def _setup(self, name=None):
"""
Load the settings module pointed to by the environment variable. This
is used the first time we need any settings at all, if the user has not
@ -36,20 +36,40 @@ class LazySettings(LazyObject):
if not settings_module: # If it's set but is an empty string.
raise KeyError
except KeyError:
desc = ("setting %s" % name) if name else "settings"
raise ImproperlyConfigured(
"Requested setting %s, but settings are not configured. "
"Requested %s, but settings are not configured. "
"You must either define the environment variable %s "
"or call settings.configure() before accessing settings."
% (name, ENVIRONMENT_VARIABLE))
% (desc, ENVIRONMENT_VARIABLE))
self._wrapped = Settings(settings_module)
self._configure_logging()
def __getattr__(self, name):
if self._wrapped is empty:
self._setup(name)
return getattr(self._wrapped, name)
def _configure_logging(self):
"""
Setup logging from LOGGING_CONFIG and LOGGING settings.
"""
if self.LOGGING_CONFIG:
from django.utils.log import DEFAULT_LOGGING
# First find the logging configuration function ...
logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1)
logging_config_module = importlib.import_module(logging_config_path)
logging_config_func = getattr(logging_config_module, logging_config_func_name)
logging_config_func(DEFAULT_LOGGING)
if self.LOGGING:
# Backwards-compatibility shim for #16288 fix
compat_patch_logging_config(self.LOGGING)
# ... then invoke it with the logging settings
logging_config_func(self.LOGGING)
def configure(self, default_settings=global_settings, **options):
"""
@ -133,19 +153,6 @@ class Settings(BaseSettings):
os.environ['TZ'] = self.TIME_ZONE
time.tzset()
# Settings are configured, so we can set up the logger if required
if self.LOGGING_CONFIG:
# First find the logging configuration function ...
logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1)
logging_config_module = importlib.import_module(logging_config_path)
logging_config_func = getattr(logging_config_module, logging_config_func_name)
# Backwards-compatibility shim for #16288 fix
compat_patch_logging_config(self.LOGGING)
# ... then invoke it with the logging settings
logging_config_func(self.LOGGING)
class UserSettingsHolder(BaseSettings):
"""

View File

@ -144,7 +144,7 @@ DEFAULT_CHARSET = 'utf-8'
# Encoding of files read from disk (template and initial SQL files).
FILE_CHARSET = 'utf-8'
# E-mail address that error messages come from.
# Email address that error messages come from.
SERVER_EMAIL = 'root@localhost'
# Whether to send broken-link emails.
@ -488,6 +488,8 @@ PROFANITIES_LIST = ()
# AUTHENTICATION #
##################
AUTH_USER_MODEL = 'auth.User'
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
LOGIN_URL = '/accounts/login/'
@ -549,33 +551,8 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.fallback.FallbackStorage'
# The callable to use to configure logging
LOGGING_CONFIG = 'django.utils.log.dictConfig'
# The default logging configuration. This sends an email to
# the site admins on every HTTP 500 error. All other log
# records are sent to the bit bucket.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}
# Custom logging configuration.
LOGGING = {}
# Default exception reporter filter class used in case none has been
# specifically assigned to the HttpRequest instance.

View File

@ -35,7 +35,7 @@ DATETIME_INPUT_FORMATS = (
'%Y-%m-%d', # '2006-10-25'
)
# these are the separators for non-monetary numbers. For monetary numbers,
# these are the separators for non-monetary numbers. For monetary numbers,
# the DECIMAL_SEPARATOR is a . (decimal point) and the THOUSAND_SEPARATOR is a
# ' (single quote).
# For details, please refer to http://www.bk.admin.ch/dokumentation/sprachen/04915/05016/index.html?lang=de

View File

@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-03-23 02:29+0100\n"
"POT-Creation-Date: 2012-10-15 10:55+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English <en@li.org>\n"
@ -297,385 +297,386 @@ msgstr ""
msgid "Traditional Chinese"
msgstr ""
#: core/validators.py:24 forms/fields.py:51
#: core/validators.py:21 forms/fields.py:52
msgid "Enter a valid value."
msgstr ""
#: core/validators.py:99 forms/fields.py:601
msgid "This URL appears to be a broken link."
#: core/validators.py:104 forms/fields.py:464
msgid "Enter a valid email address."
msgstr ""
#: core/validators.py:131 forms/fields.py:600
msgid "Enter a valid URL."
msgstr ""
#: core/validators.py:165 forms/fields.py:474
msgid "Enter a valid e-mail address."
msgstr ""
#: core/validators.py:168 forms/fields.py:1023
#: core/validators.py:107 forms/fields.py:1013
msgid ""
"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."
msgstr ""
#: core/validators.py:171 core/validators.py:188 forms/fields.py:997
#: core/validators.py:110 core/validators.py:129 forms/fields.py:987
msgid "Enter a valid IPv4 address."
msgstr ""
#: core/validators.py:175 core/validators.py:189
#: core/validators.py:115 core/validators.py:130
msgid "Enter a valid IPv6 address."
msgstr ""
#: core/validators.py:184 core/validators.py:187
#: core/validators.py:125 core/validators.py:128
msgid "Enter a valid IPv4 or IPv6 address."
msgstr ""
#: core/validators.py:209 db/models/fields/__init__.py:638
#: core/validators.py:151 db/models/fields/__init__.py:655
msgid "Enter only digits separated by commas."
msgstr ""
#: core/validators.py:215
#: core/validators.py:157
#, python-format
msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)."
msgstr ""
#: core/validators.py:233 forms/fields.py:209 forms/fields.py:262
#: core/validators.py:176 forms/fields.py:210 forms/fields.py:263
#, python-format
msgid "Ensure this value is less than or equal to %(limit_value)s."
msgstr ""
#: core/validators.py:238 forms/fields.py:210 forms/fields.py:263
#: core/validators.py:182 forms/fields.py:211 forms/fields.py:264
#, python-format
msgid "Ensure this value is greater than or equal to %(limit_value)s."
msgstr ""
#: core/validators.py:244
#: core/validators.py:189
#, python-format
msgid ""
"Ensure this value has at least %(limit_value)d characters (it has "
"%(show_value)d)."
msgstr ""
#: core/validators.py:250
#: core/validators.py:196
#, python-format
msgid ""
"Ensure this value has at most %(limit_value)d characters (it has "
"%(show_value)d)."
msgstr ""
#: db/models/base.py:764
#: db/models/base.py:843
#, python-format
msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s."
msgstr ""
#: db/models/base.py:787 forms/models.py:577
#: db/models/base.py:866 forms/models.py:573
msgid "and"
msgstr ""
#: db/models/base.py:788 db/models/fields/__init__.py:65
#: db/models/base.py:867 db/models/fields/__init__.py:70
#, python-format
msgid "%(model_name)s with this %(field_label)s already exists."
msgstr ""
#: db/models/fields/__init__.py:62
#: db/models/fields/__init__.py:67
#, python-format
msgid "Value %r is not a valid choice."
msgstr ""
#: db/models/fields/__init__.py:63
#: db/models/fields/__init__.py:68
msgid "This field cannot be null."
msgstr ""
#: db/models/fields/__init__.py:64
#: db/models/fields/__init__.py:69
msgid "This field cannot be blank."
msgstr ""
#: db/models/fields/__init__.py:71
#: db/models/fields/__init__.py:76
#, python-format
msgid "Field of type: %(field_type)s"
msgstr ""
#: db/models/fields/__init__.py:506 db/models/fields/__init__.py:961
#: db/models/fields/__init__.py:517 db/models/fields/__init__.py:985
msgid "Integer"
msgstr ""
#: db/models/fields/__init__.py:510 db/models/fields/__init__.py:959
#: db/models/fields/__init__.py:521 db/models/fields/__init__.py:983
#, python-format
msgid "'%s' value must be an integer."
msgstr ""
#: db/models/fields/__init__.py:552
#: db/models/fields/__init__.py:569
#, python-format
msgid "'%s' value must be either True or False."
msgstr ""
#: db/models/fields/__init__.py:554
#: db/models/fields/__init__.py:571
msgid "Boolean (Either True or False)"
msgstr ""
#: db/models/fields/__init__.py:605
#: db/models/fields/__init__.py:622
#, python-format
msgid "String (up to %(max_length)s)"
msgstr ""
#: db/models/fields/__init__.py:633
#: db/models/fields/__init__.py:650
msgid "Comma-separated integers"
msgstr ""
#: db/models/fields/__init__.py:647
#: db/models/fields/__init__.py:664
#, python-format
msgid "'%s' value has an invalid date format. It must be in YYYY-MM-DD format."
msgstr ""
#: db/models/fields/__init__.py:649 db/models/fields/__init__.py:734
#: db/models/fields/__init__.py:666 db/models/fields/__init__.py:754
#, python-format
msgid ""
"'%s' value has the correct format (YYYY-MM-DD) but it is an invalid date."
msgstr ""
#: db/models/fields/__init__.py:652
#: db/models/fields/__init__.py:669
msgid "Date (without time)"
msgstr ""
#: db/models/fields/__init__.py:732
#: db/models/fields/__init__.py:752
#, python-format
msgid ""
"'%s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[."
"uuuuuu]][TZ] format."
msgstr ""
#: db/models/fields/__init__.py:736
#: db/models/fields/__init__.py:756
#, python-format
msgid ""
"'%s' value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) but "
"it is an invalid date/time."
msgstr ""
#: db/models/fields/__init__.py:740
#: db/models/fields/__init__.py:760
msgid "Date (with time)"
msgstr ""
#: db/models/fields/__init__.py:831
#: db/models/fields/__init__.py:849
#, python-format
msgid "'%s' value must be a decimal number."
msgstr ""
#: db/models/fields/__init__.py:833
#: db/models/fields/__init__.py:851
msgid "Decimal number"
msgstr ""
#: db/models/fields/__init__.py:890
msgid "E-mail address"
#: db/models/fields/__init__.py:908
msgid "Email address"
msgstr ""
#: db/models/fields/__init__.py:906
#: db/models/fields/__init__.py:927
msgid "File path"
msgstr ""
#: db/models/fields/__init__.py:930
#: db/models/fields/__init__.py:954
#, python-format
msgid "'%s' value must be a float."
msgstr ""
#: db/models/fields/__init__.py:932
#: db/models/fields/__init__.py:956
msgid "Floating point number"
msgstr ""
#: db/models/fields/__init__.py:993
#: db/models/fields/__init__.py:1017
msgid "Big (8 byte) integer"
msgstr ""
#: db/models/fields/__init__.py:1007
#: db/models/fields/__init__.py:1031
msgid "IPv4 address"
msgstr ""
#: db/models/fields/__init__.py:1023
#: db/models/fields/__init__.py:1047
msgid "IP address"
msgstr ""
#: db/models/fields/__init__.py:1065
#: db/models/fields/__init__.py:1090
#, python-format
msgid "'%s' value must be either None, True or False."
msgstr ""
#: db/models/fields/__init__.py:1067
#: db/models/fields/__init__.py:1092
msgid "Boolean (Either True, False or None)"
msgstr ""
#: db/models/fields/__init__.py:1116
#: db/models/fields/__init__.py:1141
msgid "Positive integer"
msgstr ""
#: db/models/fields/__init__.py:1127
#: db/models/fields/__init__.py:1152
msgid "Positive small integer"
msgstr ""
#: db/models/fields/__init__.py:1138
#: db/models/fields/__init__.py:1163
#, python-format
msgid "Slug (up to %(max_length)s)"
msgstr ""
#: db/models/fields/__init__.py:1156
#: db/models/fields/__init__.py:1181
msgid "Small integer"
msgstr ""
#: db/models/fields/__init__.py:1162
#: db/models/fields/__init__.py:1187
msgid "Text"
msgstr ""
#: db/models/fields/__init__.py:1180
#: db/models/fields/__init__.py:1205
#, python-format
msgid ""
"'%s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] format."
msgstr ""
#: db/models/fields/__init__.py:1182
#: db/models/fields/__init__.py:1207
#, python-format
msgid ""
"'%s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an invalid "
"time."
msgstr ""
#: db/models/fields/__init__.py:1185
#: db/models/fields/__init__.py:1210
msgid "Time"
msgstr ""
#: db/models/fields/__init__.py:1249
#: db/models/fields/__init__.py:1272
msgid "URL"
msgstr ""
#: db/models/fields/files.py:214
#: db/models/fields/files.py:211
#, python-format
msgid "Filename is %(extra)d characters too long."
msgstr ""
#: db/models/fields/files.py:221
msgid "File"
msgstr ""
#: db/models/fields/files.py:321
#: db/models/fields/files.py:347
msgid "Image"
msgstr ""
#: db/models/fields/related.py:903
#: db/models/fields/related.py:950
#, python-format
msgid "Model %(model)s with pk %(pk)r does not exist."
msgstr ""
#: db/models/fields/related.py:905
#: db/models/fields/related.py:952
msgid "Foreign Key (type determined by related field)"
msgstr ""
#: db/models/fields/related.py:1033
#: db/models/fields/related.py:1082
msgid "One-to-one relationship"
msgstr ""
#: db/models/fields/related.py:1096
#: db/models/fields/related.py:1149
msgid "Many-to-many relationship"
msgstr ""
#: db/models/fields/related.py:1120
#: db/models/fields/related.py:1174
msgid ""
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
msgstr ""
#: forms/fields.py:50
#: forms/fields.py:51
msgid "This field is required."
msgstr ""
#: forms/fields.py:208
#: forms/fields.py:209
msgid "Enter a whole number."
msgstr ""
#: forms/fields.py:240 forms/fields.py:261
#: forms/fields.py:241 forms/fields.py:262
msgid "Enter a number."
msgstr ""
#: forms/fields.py:264
#, python-format
msgid "Ensure that there are no more than %s digits in total."
msgstr ""
#: forms/fields.py:265
#, python-format
msgid "Ensure that there are no more than %s decimal places."
msgid "Ensure that there are no more than %s digits in total."
msgstr ""
#: forms/fields.py:266
#, python-format
msgid "Ensure that there are no more than %s decimal places."
msgstr ""
#: forms/fields.py:267
#, python-format
msgid "Ensure that there are no more than %s digits before the decimal point."
msgstr ""
#: forms/fields.py:365 forms/fields.py:963
#: forms/fields.py:355 forms/fields.py:953
msgid "Enter a valid date."
msgstr ""
#: forms/fields.py:388 forms/fields.py:964
#: forms/fields.py:378 forms/fields.py:954
msgid "Enter a valid time."
msgstr ""
#: forms/fields.py:409
#: forms/fields.py:399
msgid "Enter a valid date/time."
msgstr ""
#: forms/fields.py:485
#: forms/fields.py:475
msgid "No file was submitted. Check the encoding type on the form."
msgstr ""
#: forms/fields.py:486
#: forms/fields.py:476
msgid "No file was submitted."
msgstr ""
#: forms/fields.py:487
#: forms/fields.py:477
msgid "The submitted file is empty."
msgstr ""
#: forms/fields.py:488
#: forms/fields.py:478
#, python-format
msgid ""
"Ensure this filename has at most %(max)d characters (it has %(length)d)."
msgstr ""
#: forms/fields.py:489
#: forms/fields.py:479
msgid "Please either submit a file or check the clear checkbox, not both."
msgstr ""
#: forms/fields.py:544
#: forms/fields.py:534
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
#: forms/fields.py:689 forms/fields.py:769
#: forms/fields.py:580
msgid "Enter a valid URL."
msgstr ""
#: forms/fields.py:666 forms/fields.py:746
#, python-format
msgid "Select a valid choice. %(value)s is not one of the available choices."
msgstr ""
#: forms/fields.py:770 forms/fields.py:858 forms/models.py:999
#: forms/fields.py:747 forms/fields.py:835 forms/models.py:999
msgid "Enter a list of values."
msgstr ""
#: forms/formsets.py:317 forms/formsets.py:319
#: forms/formsets.py:323 forms/formsets.py:325
msgid "Order"
msgstr ""
#: forms/formsets.py:321
#: forms/formsets.py:327
msgid "Delete"
msgstr ""
#: forms/models.py:571
#: forms/models.py:567
#, python-format
msgid "Please correct the duplicate data for %(field)s."
msgstr ""
#: forms/models.py:575
#: forms/models.py:571
#, python-format
msgid "Please correct the duplicate data for %(field)s, which must be unique."
msgstr ""
#: forms/models.py:581
#: forms/models.py:577
#, python-format
msgid ""
"Please correct the duplicate data for %(field_name)s which must be unique "
"for the %(lookup)s in %(date_field)s."
msgstr ""
#: forms/models.py:589
#: forms/models.py:585
msgid "Please correct the duplicate values below."
msgstr ""
@ -697,94 +698,94 @@ msgstr ""
msgid "\"%s\" is not a valid value for a primary key."
msgstr ""
#: forms/util.py:70
#: forms/util.py:81
#, python-format
msgid ""
"%(datetime)s couldn't be interpreted in time zone %(current_timezone)s; it "
"may be ambiguous or it may not exist."
msgstr ""
#: forms/widgets.py:325
#: forms/widgets.py:336
msgid "Currently"
msgstr ""
#: forms/widgets.py:326
#: forms/widgets.py:337
msgid "Change"
msgstr ""
#: forms/widgets.py:327
#: forms/widgets.py:338
msgid "Clear"
msgstr ""
#: forms/widgets.py:582
#: forms/widgets.py:591
msgid "Unknown"
msgstr ""
#: forms/widgets.py:583
#: forms/widgets.py:592
msgid "Yes"
msgstr ""
#: forms/widgets.py:584
#: forms/widgets.py:593
msgid "No"
msgstr ""
#: template/defaultfilters.py:797
#: template/defaultfilters.py:794
msgid "yes,no,maybe"
msgstr ""
#: template/defaultfilters.py:825 template/defaultfilters.py:830
#: template/defaultfilters.py:822 template/defaultfilters.py:833
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] ""
msgstr[1] ""
#: template/defaultfilters.py:832
#: template/defaultfilters.py:835
#, python-format
msgid "%s KB"
msgstr ""
#: template/defaultfilters.py:834
#: template/defaultfilters.py:837
#, python-format
msgid "%s MB"
msgstr ""
#: template/defaultfilters.py:836
#: template/defaultfilters.py:839
#, python-format
msgid "%s GB"
msgstr ""
#: template/defaultfilters.py:838
#: template/defaultfilters.py:841
#, python-format
msgid "%s TB"
msgstr ""
#: template/defaultfilters.py:839
#: template/defaultfilters.py:842
#, python-format
msgid "%s PB"
msgstr ""
#: utils/dateformat.py:45
#: utils/dateformat.py:47
msgid "p.m."
msgstr ""
#: utils/dateformat.py:46
#: utils/dateformat.py:48
msgid "a.m."
msgstr ""
#: utils/dateformat.py:51
#: utils/dateformat.py:53
msgid "PM"
msgstr ""
#: utils/dateformat.py:52
#: utils/dateformat.py:54
msgid "AM"
msgstr ""
#: utils/dateformat.py:101
#: utils/dateformat.py:103
msgid "midnight"
msgstr ""
#: utils/dateformat.py:103
#: utils/dateformat.py:105
msgid "noon"
msgstr ""
@ -1060,148 +1061,133 @@ msgctxt "alt. month"
msgid "December"
msgstr ""
#: utils/text.py:65
#: utils/text.py:70
#, python-format
msgctxt "String to return when truncating text"
msgid "%(truncated_text)s..."
msgstr ""
#: utils/text.py:234
#: utils/text.py:239
msgid "or"
msgstr ""
#. Translators: This string is used as a separator between list elements
#: utils/text.py:251
#: utils/text.py:256
msgid ", "
msgstr ""
#: utils/timesince.py:20
#: utils/timesince.py:22
msgid "year"
msgid_plural "years"
msgstr[0] ""
msgstr[1] ""
#: utils/timesince.py:21
#: utils/timesince.py:23
msgid "month"
msgid_plural "months"
msgstr[0] ""
msgstr[1] ""
#: utils/timesince.py:22
#: utils/timesince.py:24
msgid "week"
msgid_plural "weeks"
msgstr[0] ""
msgstr[1] ""
#: utils/timesince.py:23
#: utils/timesince.py:25
msgid "day"
msgid_plural "days"
msgstr[0] ""
msgstr[1] ""
#: utils/timesince.py:24
#: utils/timesince.py:26
msgid "hour"
msgid_plural "hours"
msgstr[0] ""
msgstr[1] ""
#: utils/timesince.py:25
#: utils/timesince.py:27
msgid "minute"
msgid_plural "minutes"
msgstr[0] ""
msgstr[1] ""
#: utils/timesince.py:41
#: utils/timesince.py:43
msgid "minutes"
msgstr ""
#: utils/timesince.py:46
#: utils/timesince.py:48
#, python-format
msgid "%(number)d %(type)s"
msgstr ""
#: utils/timesince.py:52
#: utils/timesince.py:54
#, python-format
msgid ", %(number)d %(type)s"
msgstr ""
#: views/static.py:52
#: views/static.py:55
msgid "Directory indexes are not allowed here."
msgstr ""
#: views/static.py:54
#: views/static.py:57
#, python-format
msgid "\"%(path)s\" does not exist"
msgstr ""
#: views/static.py:95
#: views/static.py:98
#, python-format
msgid "Index of %(directory)s"
msgstr ""
#: views/generic/create_update.py:121
#, python-format
msgid "The %(verbose_name)s was created successfully."
msgstr ""
#: views/generic/create_update.py:164
#, python-format
msgid "The %(verbose_name)s was updated successfully."
msgstr ""
#: views/generic/create_update.py:207
#, python-format
msgid "The %(verbose_name)s was deleted."
msgstr ""
#: views/generic/dates.py:33
#: views/generic/dates.py:42
msgid "No year specified"
msgstr ""
#: views/generic/dates.py:58
#: views/generic/dates.py:98
msgid "No month specified"
msgstr ""
#: views/generic/dates.py:99
#: views/generic/dates.py:157
msgid "No day specified"
msgstr ""
#: views/generic/dates.py:138
#: views/generic/dates.py:213
msgid "No week specified"
msgstr ""
#: views/generic/dates.py:198 views/generic/dates.py:215
#: views/generic/dates.py:368 views/generic/dates.py:393
#, python-format
msgid "No %(verbose_name_plural)s available"
msgstr ""
#: views/generic/dates.py:467
#: views/generic/dates.py:646
#, python-format
msgid ""
"Future %(verbose_name_plural)s not available because %(class_name)s."
"allow_future is False."
msgstr ""
#: views/generic/dates.py:501
#: views/generic/dates.py:678
#, python-format
msgid "Invalid date string '%(datestr)s' given format '%(format)s'"
msgstr ""
#: views/generic/detail.py:51
#: views/generic/detail.py:54
#, python-format
msgid "No %(verbose_name)s found matching the query"
msgstr ""
#: views/generic/list.py:45
#: views/generic/list.py:49
msgid "Page is not 'last', nor can it be converted to an int."
msgstr ""
#: views/generic/list.py:50
#: views/generic/list.py:54
#, python-format
msgid "Invalid page (%(page_number)s)"
msgstr ""
#: views/generic/list.py:117
#: views/generic/list.py:134
#, python-format
msgid "Empty list and '%(class_name)s.allow_empty' is False."
msgstr ""

View File

@ -15,6 +15,10 @@ framework.
"""
import os
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
# if running multiple sites in the same mod_wsgi process. To fix this, use
# mod_wsgi daemon mode with each site in its own daemon process, or use
# os.environ["DJANGO_SETTINGS_MODULE"] = "{{ project_name }}.settings"
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings")
# This application object is used by any WSGI server configured to use this

View File

@ -4,12 +4,12 @@ from django import forms
from django.contrib.auth import authenticate
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy, ugettext as _
from django.utils.translation import ugettext_lazy
ERROR_MESSAGE = ugettext_lazy("Please enter the correct username and password "
"for a staff account. Note that both fields are case-sensitive.")
class AdminAuthenticationForm(AuthenticationForm):
"""
A custom authentication form used in the admin app.
@ -26,17 +26,6 @@ class AdminAuthenticationForm(AuthenticationForm):
if username and password:
self.user_cache = authenticate(username=username, password=password)
if self.user_cache is None:
if '@' in username:
# Mistakenly entered e-mail address instead of username? Look it up.
try:
user = User.objects.get(email=username)
except (User.DoesNotExist, User.MultipleObjectsReturned):
# Nothing to do here, moving along.
pass
else:
if user.check_password(password):
message = _("Your e-mail address is not your username."
" Try '%s' instead.") % user.username
raise forms.ValidationError(message)
elif not self.user_cache.is_active or not self.user_cache.is_staff:
raise forms.ValidationError(message)

View File

@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-03-23 02:34+0100\n"
"POT-Creation-Date: 2012-10-15 10:56+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English <en@li.org>\n"
@ -32,39 +32,39 @@ msgstr ""
msgid "Delete selected %(verbose_name_plural)s"
msgstr ""
#: filters.py:101 filters.py:191 filters.py:231 filters.py:268 filters.py:378
#: filters.py:101 filters.py:197 filters.py:237 filters.py:274 filters.py:380
msgid "All"
msgstr ""
#: filters.py:232
#: filters.py:238
msgid "Yes"
msgstr ""
#: filters.py:233
#: filters.py:239
msgid "No"
msgstr ""
#: filters.py:247
#: filters.py:253
msgid "Unknown"
msgstr ""
#: filters.py:306
#: filters.py:308
msgid "Any date"
msgstr ""
#: filters.py:307
#: filters.py:309
msgid "Today"
msgstr ""
#: filters.py:311
#: filters.py:313
msgid "Past 7 days"
msgstr ""
#: filters.py:315
#: filters.py:317
msgid "This month"
msgstr ""
#: filters.py:319
#: filters.py:321
msgid "This year"
msgstr ""
@ -74,134 +74,129 @@ msgid ""
"that both fields are case-sensitive."
msgstr ""
#: forms.py:18
#: forms.py:19
msgid "Please log in again, because your session has expired."
msgstr ""
#: forms.py:37
#, python-format
msgid "Your e-mail address is not your username. Try '%s' instead."
msgstr ""
#: helpers.py:20
#: helpers.py:23
msgid "Action:"
msgstr ""
#: models.py:19
#: models.py:24
msgid "action time"
msgstr ""
#: models.py:22
#: models.py:27
msgid "object id"
msgstr ""
#: models.py:23
#: models.py:28
msgid "object repr"
msgstr ""
#: models.py:24
#: models.py:29
msgid "action flag"
msgstr ""
#: models.py:25
#: models.py:30
msgid "change message"
msgstr ""
#: models.py:30
#: models.py:35
msgid "log entry"
msgstr ""
#: models.py:31
#: models.py:36
msgid "log entries"
msgstr ""
#: models.py:40
#: models.py:45
#, python-format
msgid "Added \"%(object)s\"."
msgstr ""
#: models.py:42
#: models.py:47
#, python-format
msgid "Changed \"%(object)s\" - %(changes)s"
msgstr ""
#: models.py:44
#: models.py:49
#, python-format
msgid "Deleted \"%(object)s.\""
msgstr ""
#: models.py:46
#: models.py:51
msgid "LogEntry Object"
msgstr ""
#: options.py:150 options.py:166
#: options.py:151 options.py:167
msgid "None"
msgstr ""
#: options.py:671
#: options.py:672
#, python-format
msgid "Changed %s."
msgstr ""
#: options.py:671 options.py:681
#: options.py:672 options.py:682
msgid "and"
msgstr ""
#: options.py:676
#: options.py:677
#, python-format
msgid "Added %(name)s \"%(object)s\"."
msgstr ""
#: options.py:680
#: options.py:681
#, python-format
msgid "Changed %(list)s for %(name)s \"%(object)s\"."
msgstr ""
#: options.py:685
#: options.py:686
#, python-format
msgid "Deleted %(name)s \"%(object)s\"."
msgstr ""
#: options.py:689
#: options.py:690
msgid "No fields changed."
msgstr ""
#: options.py:772
#: options.py:773
#, python-format
msgid "The %(name)s \"%(obj)s\" was added successfully."
msgstr ""
#: options.py:776 options.py:824
#: options.py:777 options.py:825
msgid "You may edit it again below."
msgstr ""
#: options.py:788 options.py:837
#: options.py:789 options.py:838
#, python-format
msgid "You may add another %s below."
msgstr ""
#: options.py:822
#: options.py:823
#, python-format
msgid "The %(name)s \"%(obj)s\" was changed successfully."
msgstr ""
#: options.py:830
#: options.py:831
#, python-format
msgid ""
"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
msgstr ""
#: options.py:899 options.py:1159
#: options.py:900 options.py:1159
msgid ""
"Items must be selected in order to perform actions on them. No items have "
"been changed."
msgstr ""
#: options.py:918
#: options.py:919
msgid "No action selected."
msgstr ""
#: options.py:998
#: options.py:999
#, python-format
msgid "Add %s"
msgstr ""
@ -249,34 +244,34 @@ msgstr ""
msgid "Change history: %s"
msgstr ""
#: sites.py:315 tests.py:61 templates/admin/login.html:49
#: templates/registration/password_reset_complete.html:20
#: views/decorators.py:23
#: sites.py:322 tests.py:57 templates/admin/login.html:48
#: templates/registration/password_reset_complete.html:19
#: views/decorators.py:24
msgid "Log in"
msgstr ""
#: sites.py:380
#: sites.py:388
msgid "Site administration"
msgstr ""
#: sites.py:432
#: sites.py:440
#, python-format
msgid "%s administration"
msgstr ""
#: widgets.py:87
#: widgets.py:90
msgid "Date:"
msgstr ""
#: widgets.py:87
#: widgets.py:91
msgid "Time:"
msgstr ""
#: widgets.py:161
#: widgets.py:165
msgid "Lookup"
msgstr ""
#: widgets.py:267
#: widgets.py:271
msgid "Add Another"
msgstr ""
@ -288,39 +283,39 @@ msgstr ""
msgid "We're sorry, but the requested page could not be found."
msgstr ""
#: templates/admin/500.html:7 templates/admin/app_index.html:8
#: templates/admin/base.html:45 templates/admin/change_form.html:21
#: templates/admin/change_list.html:43
#: templates/admin/delete_confirmation.html:8
#: templates/admin/delete_selected_confirmation.html:8
#: templates/admin/invalid_setup.html:7 templates/admin/object_history.html:8
#: templates/admin/auth/user/change_password.html:15
#: templates/registration/logged_out.html:5
#: templates/registration/password_change_done.html:7
#: templates/registration/password_change_form.html:8
#: templates/registration/password_reset_complete.html:7
#: templates/registration/password_reset_confirm.html:7
#: templates/registration/password_reset_done.html:7
#: templates/registration/password_reset_form.html:7
#: templates/admin/500.html:6 templates/admin/app_index.html:7
#: templates/admin/base.html:47 templates/admin/change_form.html:19
#: templates/admin/change_list.html:41
#: templates/admin/delete_confirmation.html:7
#: templates/admin/delete_selected_confirmation.html:7
#: templates/admin/invalid_setup.html:6 templates/admin/object_history.html:7
#: templates/admin/auth/user/change_password.html:13
#: templates/registration/logged_out.html:4
#: templates/registration/password_change_done.html:6
#: templates/registration/password_change_form.html:7
#: templates/registration/password_reset_complete.html:6
#: templates/registration/password_reset_confirm.html:6
#: templates/registration/password_reset_done.html:6
#: templates/registration/password_reset_form.html:6
msgid "Home"
msgstr ""
#: templates/admin/500.html:8
#: templates/admin/500.html:7
msgid "Server error"
msgstr ""
#: templates/admin/500.html:12
#: templates/admin/500.html:11
msgid "Server error (500)"
msgstr ""
#: templates/admin/500.html:15
#: templates/admin/500.html:14
msgid "Server Error <em>(500)</em>"
msgstr ""
#: templates/admin/500.html:16
#: templates/admin/500.html:15
msgid ""
"There's been an error. It's been reported to the site administrators via e-"
"mail and should be fixed shortly. Thanks for your patience."
"There's been an error. It's been reported to the site administrators via "
"email and should be fixed shortly. Thanks for your patience."
msgstr ""
#: templates/admin/actions.html:4
@ -344,7 +339,7 @@ msgstr ""
msgid "Clear selection"
msgstr ""
#: templates/admin/app_index.html:11 templates/admin/index.html:19
#: templates/admin/app_index.html:10 templates/admin/index.html:21
#, python-format
msgid "%(name)s"
msgstr ""
@ -354,22 +349,22 @@ msgid "Welcome,"
msgstr ""
#: templates/admin/base.html:33
#: templates/registration/password_change_done.html:4
#: templates/registration/password_change_form.html:5
#: templates/registration/password_change_done.html:3
#: templates/registration/password_change_form.html:4
msgid "Documentation"
msgstr ""
#: templates/admin/base.html:35
#: templates/admin/auth/user/change_password.html:19
#: templates/admin/auth/user/change_password.html:53
#: templates/registration/password_change_done.html:4
#: templates/registration/password_change_form.html:5
#: templates/admin/base.html:36
#: templates/admin/auth/user/change_password.html:17
#: templates/admin/auth/user/change_password.html:51
#: templates/registration/password_change_done.html:3
#: templates/registration/password_change_form.html:4
msgid "Change password"
msgstr ""
#: templates/admin/base.html:36
#: templates/registration/password_change_done.html:4
#: templates/registration/password_change_form.html:5
#: templates/admin/base.html:38
#: templates/registration/password_change_done.html:3
#: templates/registration/password_change_form.html:4
msgid "Log out"
msgstr ""
@ -381,35 +376,35 @@ msgstr ""
msgid "Django administration"
msgstr ""
#: templates/admin/change_form.html:24 templates/admin/index.html:29
#: templates/admin/change_form.html:22 templates/admin/index.html:33
msgid "Add"
msgstr ""
#: templates/admin/change_form.html:34 templates/admin/object_history.html:12
#: templates/admin/change_form.html:32 templates/admin/object_history.html:11
msgid "History"
msgstr ""
#: templates/admin/change_form.html:35
#: templates/admin/change_form.html:33
#: templates/admin/edit_inline/stacked.html:9
#: templates/admin/edit_inline/tabular.html:30
msgid "View on site"
msgstr ""
#: templates/admin/change_form.html:46 templates/admin/change_list.html:69
#: templates/admin/login.html:18
#: templates/admin/auth/user/change_password.html:29
#: templates/registration/password_change_form.html:21
#: templates/admin/change_form.html:44 templates/admin/change_list.html:67
#: templates/admin/login.html:17
#: templates/admin/auth/user/change_password.html:27
#: templates/registration/password_change_form.html:20
msgid "Please correct the error below."
msgid_plural "Please correct the errors below."
msgstr[0] ""
msgstr[1] ""
#: templates/admin/change_list.html:60
#: templates/admin/change_list.html:58
#, python-format
msgid "Add %(name)s"
msgstr ""
#: templates/admin/change_list.html:80
#: templates/admin/change_list.html:78
msgid "Filter"
msgstr ""
@ -426,12 +421,12 @@ msgstr ""
msgid "Toggle sorting"
msgstr ""
#: templates/admin/delete_confirmation.html:12
#: templates/admin/delete_confirmation.html:11
#: templates/admin/submit_line.html:4
msgid "Delete"
msgstr ""
#: templates/admin/delete_confirmation.html:19
#: templates/admin/delete_confirmation.html:18
#, python-format
msgid ""
"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting "
@ -439,30 +434,30 @@ msgid ""
"following types of objects:"
msgstr ""
#: templates/admin/delete_confirmation.html:27
#: templates/admin/delete_confirmation.html:26
#, python-format
msgid ""
"Deleting the %(object_name)s '%(escaped_object)s' would require deleting the "
"following protected related objects:"
msgstr ""
#: templates/admin/delete_confirmation.html:35
#: templates/admin/delete_confirmation.html:34
#, python-format
msgid ""
"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? "
"All of the following related items will be deleted:"
msgstr ""
#: templates/admin/delete_confirmation.html:40
#: templates/admin/delete_selected_confirmation.html:45
#: templates/admin/delete_confirmation.html:39
#: templates/admin/delete_selected_confirmation.html:44
msgid "Yes, I'm sure"
msgstr ""
#: templates/admin/delete_selected_confirmation.html:11
#: templates/admin/delete_selected_confirmation.html:10
msgid "Delete multiple objects"
msgstr ""
#: templates/admin/delete_selected_confirmation.html:18
#: templates/admin/delete_selected_confirmation.html:17
#, python-format
msgid ""
"Deleting the selected %(objects_name)s would result in deleting related "
@ -470,14 +465,14 @@ msgid ""
"types of objects:"
msgstr ""
#: templates/admin/delete_selected_confirmation.html:26
#: templates/admin/delete_selected_confirmation.html:25
#, python-format
msgid ""
"Deleting the selected %(objects_name)s would require deleting the following "
"protected related objects:"
msgstr ""
#: templates/admin/delete_selected_confirmation.html:34
#: templates/admin/delete_selected_confirmation.html:33
#, python-format
msgid ""
"Are you sure you want to delete the selected %(objects_name)s? All of the "
@ -489,67 +484,63 @@ msgstr ""
msgid " By %(filter_title)s "
msgstr ""
#: templates/admin/index.html:18
#: templates/admin/index.html:20
#, python-format
msgid "Models available in the %(name)s application."
msgid "Models in the %(name)s application"
msgstr ""
#: templates/admin/index.html:35
#: templates/admin/index.html:39
msgid "Change"
msgstr ""
#: templates/admin/index.html:45
#: templates/admin/index.html:49
msgid "You don't have permission to edit anything."
msgstr ""
#: templates/admin/index.html:53
#: templates/admin/index.html:57
msgid "Recent Actions"
msgstr ""
#: templates/admin/index.html:54
#: templates/admin/index.html:58
msgid "My Actions"
msgstr ""
#: templates/admin/index.html:58
#: templates/admin/index.html:62
msgid "None available"
msgstr ""
#: templates/admin/index.html:72
#: templates/admin/index.html:76
msgid "Unknown content"
msgstr ""
#: templates/admin/invalid_setup.html:13
#: templates/admin/invalid_setup.html:12
msgid ""
"Something's wrong with your database installation. Make sure the appropriate "
"database tables have been created, and make sure the database is readable by "
"the appropriate user."
msgstr ""
#: templates/admin/login.html:34
msgid "Username:"
msgstr ""
#: templates/admin/login.html:38
#: templates/admin/login.html:37
msgid "Password:"
msgstr ""
#: templates/admin/login.html:45
#: templates/admin/login.html:44
msgid "Forgotten your password or username?"
msgstr ""
#: templates/admin/object_history.html:24
#: templates/admin/object_history.html:23
msgid "Date/time"
msgstr ""
#: templates/admin/object_history.html:25
#: templates/admin/object_history.html:24
msgid "User"
msgstr ""
#: templates/admin/object_history.html:26
#: templates/admin/object_history.html:25
msgid "Action"
msgstr ""
#: templates/admin/object_history.html:40
#: templates/admin/object_history.html:39
msgid ""
"This object doesn't have a change history. It probably wasn't added via this "
"admin site."
@ -601,147 +592,147 @@ msgstr ""
msgid "Enter a username and password."
msgstr ""
#: templates/admin/auth/user/change_password.html:33
#: templates/admin/auth/user/change_password.html:31
#, python-format
msgid "Enter a new password for the user <strong>%(username)s</strong>."
msgstr ""
#: templates/admin/auth/user/change_password.html:40
#: templates/admin/auth/user/change_password.html:38
msgid "Password"
msgstr ""
#: templates/admin/auth/user/change_password.html:46
#: templates/registration/password_change_form.html:43
#: templates/admin/auth/user/change_password.html:44
#: templates/registration/password_change_form.html:42
msgid "Password (again)"
msgstr ""
#: templates/admin/auth/user/change_password.html:47
#: templates/admin/auth/user/change_password.html:45
msgid "Enter the same password as above, for verification."
msgstr ""
#: templates/admin/edit_inline/stacked.html:67
#: templates/admin/edit_inline/tabular.html:115
#, python-format
msgid "Add another %(verbose_name)s"
#: templates/admin/edit_inline/stacked.html:26
#: templates/admin/edit_inline/tabular.html:76
msgid "Remove"
msgstr ""
#: templates/admin/edit_inline/stacked.html:70
#: templates/admin/edit_inline/tabular.html:118
msgid "Remove"
#: templates/admin/edit_inline/stacked.html:27
#: templates/admin/edit_inline/tabular.html:75
#, python-format
msgid "Add another %(verbose_name)s"
msgstr ""
#: templates/admin/edit_inline/tabular.html:17
msgid "Delete?"
msgstr ""
#: templates/registration/logged_out.html:9
#: templates/registration/logged_out.html:8
msgid "Thanks for spending some quality time with the Web site today."
msgstr ""
#: templates/registration/logged_out.html:11
#: templates/registration/logged_out.html:10
msgid "Log in again"
msgstr ""
#: templates/registration/password_change_done.html:8
#: templates/registration/password_change_form.html:9
#: templates/registration/password_change_form.html:13
#: templates/registration/password_change_form.html:25
#: templates/registration/password_change_done.html:7
#: templates/registration/password_change_form.html:8
#: templates/registration/password_change_form.html:12
#: templates/registration/password_change_form.html:24
msgid "Password change"
msgstr ""
#: templates/registration/password_change_done.html:12
#: templates/registration/password_change_done.html:16
#: templates/registration/password_change_done.html:11
#: templates/registration/password_change_done.html:15
msgid "Password change successful"
msgstr ""
#: templates/registration/password_change_done.html:18
#: templates/registration/password_change_done.html:17
msgid "Your password was changed."
msgstr ""
#: templates/registration/password_change_form.html:27
#: templates/registration/password_change_form.html:26
msgid ""
"Please enter your old password, for security's sake, and then enter your new "
"password twice so we can verify you typed it in correctly."
msgstr ""
#: templates/registration/password_change_form.html:33
#: templates/registration/password_change_form.html:32
msgid "Old password"
msgstr ""
#: templates/registration/password_change_form.html:38
#: templates/registration/password_change_form.html:37
msgid "New password"
msgstr ""
#: templates/registration/password_change_form.html:49
#: templates/registration/password_reset_confirm.html:27
#: templates/registration/password_change_form.html:48
#: templates/registration/password_reset_confirm.html:26
msgid "Change my password"
msgstr ""
#: templates/registration/password_reset_complete.html:8
#: templates/registration/password_reset_confirm.html:12
#: templates/registration/password_reset_done.html:8
#: templates/registration/password_reset_form.html:8
#: templates/registration/password_reset_form.html:12
#: templates/registration/password_reset_form.html:16
#: templates/registration/password_reset_complete.html:7
#: templates/registration/password_reset_confirm.html:11
#: templates/registration/password_reset_done.html:7
#: templates/registration/password_reset_form.html:7
#: templates/registration/password_reset_form.html:11
#: templates/registration/password_reset_form.html:15
msgid "Password reset"
msgstr ""
#: templates/registration/password_reset_complete.html:12
#: templates/registration/password_reset_complete.html:16
#: templates/registration/password_reset_complete.html:11
#: templates/registration/password_reset_complete.html:15
msgid "Password reset complete"
msgstr ""
#: templates/registration/password_reset_complete.html:18
#: templates/registration/password_reset_complete.html:17
msgid "Your password has been set. You may go ahead and log in now."
msgstr ""
#: templates/registration/password_reset_confirm.html:8
#: templates/registration/password_reset_confirm.html:7
msgid "Password reset confirmation"
msgstr ""
#: templates/registration/password_reset_confirm.html:18
#: templates/registration/password_reset_confirm.html:17
msgid "Enter new password"
msgstr ""
#: templates/registration/password_reset_confirm.html:20
#: templates/registration/password_reset_confirm.html:19
msgid ""
"Please enter your new password twice so we can verify you typed it in "
"correctly."
msgstr ""
#: templates/registration/password_reset_confirm.html:24
#: templates/registration/password_reset_confirm.html:23
msgid "New password:"
msgstr ""
#: templates/registration/password_reset_confirm.html:26
#: templates/registration/password_reset_confirm.html:25
msgid "Confirm password:"
msgstr ""
#: templates/registration/password_reset_confirm.html:32
#: templates/registration/password_reset_confirm.html:31
msgid "Password reset unsuccessful"
msgstr ""
#: templates/registration/password_reset_confirm.html:34
#: templates/registration/password_reset_confirm.html:33
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used. Please request a new password reset."
msgstr ""
#: templates/registration/password_reset_done.html:12
#: templates/registration/password_reset_done.html:16
#: templates/registration/password_reset_done.html:11
#: templates/registration/password_reset_done.html:15
msgid "Password reset successful"
msgstr ""
#: templates/registration/password_reset_done.html:18
#: templates/registration/password_reset_done.html:17
msgid ""
"We've e-mailed you instructions for setting your password to the e-mail "
"We've emailed you instructions for setting your password to the email "
"address you submitted. You should be receiving it shortly."
msgstr ""
#: templates/registration/password_reset_email.html:2
#, python-format
msgid ""
"You're receiving this e-mail because you requested a password reset for your "
"You're receiving this email because you requested a password reset for your "
"user account at %(site_name)s."
msgstr ""
@ -762,34 +753,34 @@ msgstr ""
msgid "The %(site_name)s team"
msgstr ""
#: templates/registration/password_reset_form.html:18
#: templates/registration/password_reset_form.html:17
msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll e-mail "
"Forgotten your password? Enter your email address below, and we'll email "
"instructions for setting a new one."
msgstr ""
#: templates/registration/password_reset_form.html:22
msgid "E-mail address:"
#: templates/registration/password_reset_form.html:21
msgid "Email address:"
msgstr ""
#: templates/registration/password_reset_form.html:22
#: templates/registration/password_reset_form.html:21
msgid "Reset my password"
msgstr ""
#: templatetags/admin_list.py:336
#: templatetags/admin_list.py:344
msgid "All dates"
msgstr ""
#: views/main.py:31
#: views/main.py:33
msgid "(None)"
msgstr ""
#: views/main.py:74
#, python-format
msgid "Select %s"
msgstr ""
#: views/main.py:76
#, python-format
msgid "Select %s"
msgstr ""
#: views/main.py:78
#, python-format
msgid "Select %s to change"
msgstr ""

View File

@ -1,8 +1,8 @@
from __future__ import unicode_literals
from django.db import models
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
from django.contrib.admin.util import quote
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_text
@ -12,15 +12,17 @@ ADDITION = 1
CHANGE = 2
DELETION = 3
class LogEntryManager(models.Manager):
def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''):
e = self.model(None, None, user_id, content_type_id, smart_text(object_id), object_repr[:200], action_flag, change_message)
e.save()
@python_2_unicode_compatible
class LogEntry(models.Model):
action_time = models.DateTimeField(_('action time'), auto_now=True)
user = models.ForeignKey(User)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.TextField(_('object id'), blank=True, null=True)
object_repr = models.CharField(_('object repr'), max_length=200)

View File

@ -1,4 +1,6 @@
from functools import update_wrapper, partial
import warnings
from django import forms
from django.conf import settings
from django.forms.formsets import all_valid
@ -6,7 +8,7 @@ from django.forms.models import (modelform_factory, modelformset_factory,
inlineformset_factory, BaseInlineFormSet)
from django.contrib.contenttypes.models import ContentType
from django.contrib.admin import widgets, helpers
from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict
from django.contrib.admin.util import quote, unquote, flatten_fieldsets, get_deleted_objects, model_format_dict
from django.contrib.admin.templatetags.admin_static import static
from django.contrib import messages
from django.views.decorators.csrf import csrf_protect
@ -344,14 +346,14 @@ class ModelAdmin(BaseModelAdmin):
self.admin_site = admin_site
super(ModelAdmin, self).__init__()
def get_inline_instances(self, request):
def get_inline_instances(self, request, obj=None):
inline_instances = []
for inline_class in self.inlines:
inline = inline_class(self.model, self.admin_site)
if request:
if not (inline.has_add_permission(request) or
inline.has_change_permission(request) or
inline.has_delete_permission(request)):
inline.has_change_permission(request, obj) or
inline.has_delete_permission(request, obj)):
continue
if not inline.has_add_permission(request):
inline.max_num = 0
@ -504,7 +506,7 @@ class ModelAdmin(BaseModelAdmin):
fields=self.list_editable, **defaults)
def get_formsets(self, request, obj=None):
for inline in self.get_inline_instances(request):
for inline in self.get_inline_instances(request, obj):
yield inline.get_formset(request, obj)
def get_paginator(self, request, queryset, per_page, orphans=0, allow_empty_first_page=True):
@ -763,21 +765,49 @@ class ModelAdmin(BaseModelAdmin):
"admin/change_form.html"
], context, current_app=self.admin_site.name)
def response_add(self, request, obj, post_url_continue='../%s/'):
def response_add(self, request, obj, post_url_continue='../%s/',
continue_editing_url=None, add_another_url=None,
hasperm_url=None, noperm_url=None):
"""
Determines the HttpResponse for the add_view stage.
"""
opts = obj._meta
pk_value = obj._get_pk_val()
msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
:param request: HttpRequest instance.
:param obj: Object just added.
:param post_url_continue: Deprecated/undocumented.
:param continue_editing_url: URL where user will be redirected after
pressing 'Save and continue editing'.
:param add_another_url: URL where user will be redirected after
pressing 'Save and add another'.
:param hasperm_url: URL to redirect after a successful object creation
when the user has change permissions.
:param noperm_url: URL to redirect after a successful object creation
when the user has no change permissions.
"""
if post_url_continue != '../%s/':
warnings.warn("The undocumented 'post_url_continue' argument to "
"ModelAdmin.response_add() is deprecated, use the new "
"*_url arguments instead.", DeprecationWarning,
stacklevel=2)
opts = obj._meta
pk_value = obj.pk
app_label = opts.app_label
model_name = opts.module_name
site_name = self.admin_site.name
msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
# Here, we distinguish between different save types by checking for
# the presence of keys in request.POST.
if "_continue" in request.POST:
self.message_user(request, msg + ' ' + _("You may edit it again below."))
msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
self.message_user(request, msg)
if continue_editing_url is None:
continue_editing_url = 'admin:%s_%s_change' % (app_label, model_name)
url = reverse(continue_editing_url, args=(quote(pk_value),),
current_app=site_name)
if "_popup" in request.POST:
post_url_continue += "?_popup=1"
return HttpResponseRedirect(post_url_continue % pk_value)
url += "?_popup=1"
return HttpResponseRedirect(url)
if "_popup" in request.POST:
return HttpResponse(
@ -786,72 +816,104 @@ class ModelAdmin(BaseModelAdmin):
# escape() calls force_text.
(escape(pk_value), escapejs(obj)))
elif "_addanother" in request.POST:
self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_text(opts.verbose_name)))
return HttpResponseRedirect(request.path)
msg = _('The %(name)s "%(obj)s" was added successfully. You may add another %(name)s below.') % msg_dict
self.message_user(request, msg)
if add_another_url is None:
add_another_url = 'admin:%s_%s_add' % (app_label, model_name)
url = reverse(add_another_url, current_app=site_name)
return HttpResponseRedirect(url)
else:
msg = _('The %(name)s "%(obj)s" was added successfully.') % msg_dict
self.message_user(request, msg)
# Figure out where to redirect. If the user has change permission,
# redirect to the change-list page for this object. Otherwise,
# redirect to the admin index.
if self.has_change_permission(request, None):
post_url = reverse('admin:%s_%s_changelist' %
(opts.app_label, opts.module_name),
current_app=self.admin_site.name)
if hasperm_url is None:
hasperm_url = 'admin:%s_%s_changelist' % (app_label, model_name)
url = reverse(hasperm_url, current_app=site_name)
else:
post_url = reverse('admin:index',
current_app=self.admin_site.name)
return HttpResponseRedirect(post_url)
if noperm_url is None:
noperm_url = 'admin:index'
url = reverse(noperm_url, current_app=site_name)
return HttpResponseRedirect(url)
def response_change(self, request, obj):
def response_change(self, request, obj, continue_editing_url=None,
save_as_new_url=None, add_another_url=None,
hasperm_url=None, noperm_url=None):
"""
Determines the HttpResponse for the change_view stage.
:param request: HttpRequest instance.
:param obj: Object just modified.
:param continue_editing_url: URL where user will be redirected after
pressing 'Save and continue editing'.
:param save_as_new_url: URL where user will be redirected after pressing
'Save as new' (when applicable).
:param add_another_url: URL where user will be redirected after pressing
'Save and add another'.
:param hasperm_url: URL to redirect after a successful object edition when
the user has change permissions.
:param noperm_url: URL to redirect after a successful object edition when
the user has no change permissions.
"""
opts = obj._meta
app_label = opts.app_label
model_name = opts.module_name
site_name = self.admin_site.name
verbose_name = opts.verbose_name
# Handle proxy models automatically created by .only() or .defer().
# Refs #14529
verbose_name = opts.verbose_name
module_name = opts.module_name
if obj._deferred:
opts_ = opts.proxy_for_model._meta
verbose_name = opts_.verbose_name
module_name = opts_.module_name
model_name = opts_.module_name
pk_value = obj._get_pk_val()
msg_dict = {'name': force_text(verbose_name), 'obj': force_text(obj)}
msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_text(verbose_name), 'obj': force_text(obj)}
if "_continue" in request.POST:
self.message_user(request, msg + ' ' + _("You may edit it again below."))
if "_popup" in request.REQUEST:
return HttpResponseRedirect(request.path + "?_popup=1")
else:
return HttpResponseRedirect(request.path)
elif "_saveasnew" in request.POST:
msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_text(verbose_name), 'obj': obj}
msg = _('The %(name)s "%(obj)s" was changed successfully. You may edit it again below.') % msg_dict
self.message_user(request, msg)
return HttpResponseRedirect(reverse('admin:%s_%s_change' %
(opts.app_label, module_name),
args=(pk_value,),
current_app=self.admin_site.name))
if continue_editing_url is None:
continue_editing_url = 'admin:%s_%s_change' % (app_label, model_name)
url = reverse(continue_editing_url, args=(quote(obj.pk),),
current_app=site_name)
if "_popup" in request.POST:
url += "?_popup=1"
return HttpResponseRedirect(url)
elif "_saveasnew" in request.POST:
msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
self.message_user(request, msg)
if save_as_new_url is None:
save_as_new_url = 'admin:%s_%s_change' % (app_label, model_name)
url = reverse(save_as_new_url, args=(quote(obj.pk),),
current_app=site_name)
return HttpResponseRedirect(url)
elif "_addanother" in request.POST:
self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_text(verbose_name)))
return HttpResponseRedirect(reverse('admin:%s_%s_add' %
(opts.app_label, module_name),
current_app=self.admin_site.name))
msg = _('The %(name)s "%(obj)s" was changed successfully. You may add another %(name)s below.') % msg_dict
self.message_user(request, msg)
if add_another_url is None:
add_another_url = 'admin:%s_%s_add' % (app_label, model_name)
url = reverse(add_another_url, current_app=site_name)
return HttpResponseRedirect(url)
else:
msg = _('The %(name)s "%(obj)s" was changed successfully.') % msg_dict
self.message_user(request, msg)
# Figure out where to redirect. If the user has change permission,
# redirect to the change-list page for this object. Otherwise,
# redirect to the admin index.
if self.has_change_permission(request, None):
post_url = reverse('admin:%s_%s_changelist' %
(opts.app_label, module_name),
current_app=self.admin_site.name)
if hasperm_url is None:
hasperm_url = 'admin:%s_%s_changelist' % (app_label,
model_name)
url = reverse(hasperm_url, current_app=site_name)
else:
post_url = reverse('admin:index',
current_app=self.admin_site.name)
return HttpResponseRedirect(post_url)
if noperm_url is None:
noperm_url = 'admin:index'
url = reverse(noperm_url, current_app=site_name)
return HttpResponseRedirect(url)
def response_action(self, request, queryset):
"""
@ -932,7 +994,7 @@ class ModelAdmin(BaseModelAdmin):
ModelForm = self.get_form(request)
formsets = []
inline_instances = self.get_inline_instances(request)
inline_instances = self.get_inline_instances(request, None)
if request.method == 'POST':
form = ModelForm(request.POST, request.FILES)
if form.is_valid():
@ -1029,7 +1091,7 @@ class ModelAdmin(BaseModelAdmin):
ModelForm = self.get_form(request, obj)
formsets = []
inline_instances = self.get_inline_instances(request)
inline_instances = self.get_inline_instances(request, obj)
if request.method == 'POST':
form = ModelForm(request.POST, request.FILES, instance=obj)
if form.is_valid():

View File

@ -9,7 +9,6 @@ from django.db.models.base import ModelBase
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse, NoReverseMatch
from django.template.response import TemplateResponse
from django.utils.safestring import mark_safe
from django.utils import six
from django.utils.text import capfirst
from django.utils.translation import ugettext as _
@ -18,12 +17,15 @@ from django.conf import settings
LOGIN_FORM_KEY = 'this_is_the_login_form'
class AlreadyRegistered(Exception):
pass
class NotRegistered(Exception):
pass
class AdminSite(object):
"""
An AdminSite object encapsulates an instance of the Django admin application, ready
@ -41,7 +43,7 @@ class AdminSite(object):
password_change_done_template = None
def __init__(self, name='admin', app_name='admin'):
self._registry = {} # model_class class -> admin_class instance
self._registry = {} # model_class class -> admin_class instance
self.name = name
self.app_name = app_name
self._actions = {'delete_selected': actions.delete_selected}
@ -80,20 +82,23 @@ class AdminSite(object):
if model in self._registry:
raise AlreadyRegistered('The model %s is already registered' % model.__name__)
# If we got **options then dynamically construct a subclass of
# admin_class with those **options.
if options:
# For reasons I don't quite understand, without a __module__
# the created class appears to "live" in the wrong place,
# which causes issues later on.
options['__module__'] = __name__
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
# Ignore the registration if the model has been
# swapped out.
if not model._meta.swapped:
# If we got **options then dynamically construct a subclass of
# admin_class with those **options.
if options:
# For reasons I don't quite understand, without a __module__
# the created class appears to "live" in the wrong place,
# which causes issues later on.
options['__module__'] = __name__
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
# Validate (which might be a no-op)
validate(admin_class, model)
# Validate (which might be a no-op)
validate(admin_class, model)
# Instantiate the admin class to save in the registry
self._registry[model] = admin_class(model, self)
# Instantiate the admin class to save in the registry
self._registry[model] = admin_class(model, self)
def unregister(self, model_or_iterable):
"""
@ -319,6 +324,7 @@ class AdminSite(object):
REDIRECT_FIELD_NAME: request.get_full_path(),
}
context.update(extra_context or {})
defaults = {
'extra_context': context,
'current_app': self.name,

View File

@ -12,6 +12,6 @@
{% block content %}
<h1>{% trans 'Server Error <em>(500)</em>' %}</h1>
<p>{% trans "There's been an error. It's been reported to the site administrators via e-mail and should be fixed shortly. Thanks for your patience." %}</p>
<p>{% trans "There's been an error. It's been reported to the site administrators via email and should be fixed shortly. Thanks for your patience." %}</p>
{% endblock %}

View File

@ -28,7 +28,7 @@
</p>
{% endif %}
<p>{% blocktrans with username=original.username %}Enter a new password for the user <strong>{{ username }}</strong>.{% endblocktrans %}</p>
<p>{% blocktrans with username=original %}Enter a new password for the user <strong>{{ username }}</strong>.{% endblocktrans %}</p>
<fieldset class="module aligned">

View File

@ -26,7 +26,7 @@
{% if user.is_active and user.is_staff %}
<div id="user-tools">
{% trans 'Welcome,' %}
<strong>{% filter force_escape %}{% firstof user.first_name user.username %}{% endfilter %}</strong>.
<strong>{% filter force_escape %}{% firstof user.get_short_name user.get_username %}{% endfilter %}</strong>.
{% block userlinks %}
{% url 'django-admindocs-docroot' as docsroot %}
{% if docsroot %}

View File

@ -29,7 +29,7 @@
{% if change %}{% if not is_popup %}
<ul class="object-tools">
{% block object-tools-items %}
<li><a href="{% url opts|admin_urlname:'history' original.pk %}" class="historylink">{% trans "History" %}</a></li>
<li><a href="{% url opts|admin_urlname:'history' original.pk|admin_urlquote %}" class="historylink">{% trans "History" %}</a></li>
{% if has_absolute_url %}<li><a href="{% url 'admin:view_on_site' content_type_id original.pk %}" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
{% endblock %}
</ul>

View File

@ -30,7 +30,7 @@
<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
<div class="form-row">
{% if not form.this_is_the_login_form.errors %}{{ form.username.errors }}{% endif %}
<label for="id_username" class="required">{% trans 'Username:' %}</label> {{ form.username }}
<label for="id_username" class="required">{{ form.username.label }}:</label> {{ form.username }}
</div>
<div class="form-row">
{% if not form.this_is_the_login_form.errors %}{{ form.password.errors }}{% endif %}

View File

@ -29,7 +29,7 @@
{% for action in action_list %}
<tr>
<th scope="row">{{ action.action_time|date:"DATETIME_FORMAT" }}</th>
<td>{{ action.user.username }}{% if action.user.get_full_name %} ({{ action.user.get_full_name }}){% endif %}</td>
<td>{{ action.user.get_username }}{% if action.user.get_full_name %} ({{ action.user.get_full_name }}){% endif %}</td>
<td>{{ action.change_message }}</td>
</tr>
{% endfor %}

View File

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

View File

@ -14,6 +14,6 @@
<h1>{% trans 'Password reset successful' %}</h1>
<p>{% trans "We've e-mailed you instructions for setting your password to the e-mail address you submitted. You should be receiving it shortly." %}</p>
<p>{% trans "We've emailed you instructions for setting your password to the email address you submitted. You should be receiving it shortly." %}</p>
{% endblock %}

View File

@ -1,11 +1,11 @@
{% load i18n %}{% autoescape off %}
{% blocktrans %}You're receiving this e-mail because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'django.contrib.auth.views.password_reset_confirm' uidb36=uid token=token %}
{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.username }}
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
{% trans "Thanks for using our site!" %}

View File

@ -14,11 +14,11 @@
<h1>{% trans "Password reset" %}</h1>
<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll e-mail instructions for setting a new one." %}</p>
<p>{% trans "Forgotten your password? Enter your email address below, and we'll email instructions for setting a new one." %}</p>
<form action="" method="post">{% csrf_token %}
{{ form.email.errors }}
<p><label for="id_email">{% trans 'E-mail address:' %}</label> {{ form.email }} <input type="submit" value="{% trans 'Reset my password' %}" /></p>
<p><label for="id_email">{% trans 'Email address:' %}</label> {{ form.email }} <input type="submit" value="{% trans 'Reset my password' %}" /></p>
</form>
{% endblock %}

View File

@ -28,7 +28,8 @@ def submit_row(context):
change = context['change']
is_popup = context['is_popup']
save_as = context['save_as']
return {
ctx = {
'opts': opts,
'onclick_attrib': (opts.get_ordered_objects() and change
and 'onclick="submitOrderForm();"' or ''),
'show_delete_link': (not is_popup and context['has_delete_permission']
@ -40,6 +41,9 @@ def submit_row(context):
'is_popup': is_popup,
'show_save': True
}
if context.get('original') is not None:
ctx['original'] = context['original']
return ctx
@register.filter
def cell_count(inline_admin_form):

View File

@ -21,9 +21,9 @@ class AdminSeleniumWebDriverTestCase(LiveServerTestCase):
@classmethod
def tearDownClass(cls):
super(AdminSeleniumWebDriverTestCase, cls).tearDownClass()
if hasattr(cls, 'selenium'):
cls.selenium.quit()
super(AdminSeleniumWebDriverTestCase, cls).tearDownClass()
def wait_until(self, callback, timeout=10):
"""
@ -98,4 +98,4 @@ class AdminSeleniumWebDriverTestCase(LiveServerTestCase):
`klass`.
"""
return (self.selenium.find_element_by_css_selector(selector)
.get_attribute('class').find(klass) != -1)
.get_attribute('class').find(klass) != -1)

View File

@ -48,9 +48,9 @@ def prepare_lookup_value(key, value):
def quote(s):
"""
Ensure that primary key values do not confuse the admin URLs by escaping
any '/', '_' and ':' characters. Similar to urllib.quote, except that the
quoting is slightly different so that it doesn't get automatically
unquoted by the Web browser.
any '/', '_' and ':' and similarly problematic characters.
Similar to urllib.quote, except that the quoting is slightly different so
that it doesn't get automatically unquoted by the Web browser.
"""
if not isinstance(s, six.string_types):
return s
@ -191,6 +191,13 @@ class NestedObjects(Collector):
roots.extend(self._nested(root, seen, format_callback))
return roots
def can_fast_delete(self, *args, **kwargs):
"""
We always want to load the objects into memory so that we can display
them to the user in confirm page.
"""
return False
def model_format_dict(obj):
"""

View File

@ -4,6 +4,7 @@ from django.contrib.admin.forms import AdminAuthenticationForm
from django.contrib.auth.views import login
from django.contrib.auth import REDIRECT_FIELD_NAME
def staff_member_required(view_func):
"""
Decorator for views that checks that the user is logged in and is a staff

View File

@ -3,6 +3,7 @@ from functools import reduce
from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured
from django.core.paginator import InvalidPage
from django.core.urlresolvers import reverse
from django.db import models
from django.db.models.fields import FieldDoesNotExist
from django.utils.datastructures import SortedDict
@ -376,4 +377,8 @@ class ChangeList(object):
return qs
def url_for_result(self, result):
return "%s/" % quote(getattr(result, self.pk_attname))
pk = getattr(result, self.pk_attname)
return reverse('admin:%s_%s_change' % (self.opts.app_label,
self.opts.module_name),
args=(quote(pk),),
current_app=self.model_admin.admin_site.name)

View File

@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-03-23 02:36+0100\n"
"POT-Creation-Date: 2012-10-22 09:28+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English <en@li.org>\n"
@ -13,63 +13,75 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: views.py:57 views.py:59 views.py:61
#: views.py:58 views.py:60 views.py:62
msgid "tag:"
msgstr ""
#: views.py:92 views.py:94 views.py:96
#: views.py:93 views.py:95 views.py:97
msgid "filter:"
msgstr ""
#: views.py:155 views.py:157 views.py:159
#: views.py:156 views.py:158 views.py:160
msgid "view:"
msgstr ""
#: views.py:187
#: views.py:188
#, python-format
msgid "App %r not found"
msgstr ""
#: views.py:194
#: views.py:195
#, python-format
msgid "Model %(model_name)r not found in app %(app_label)r"
msgstr ""
#: views.py:206
#: views.py:207
#, python-format
msgid "the related `%(app_label)s.%(data_type)s` object"
msgstr ""
#: views.py:206 views.py:225 views.py:230 views.py:244 views.py:258
#: views.py:263
#: views.py:207 views.py:226 views.py:231 views.py:245 views.py:259
#: views.py:264
msgid "model:"
msgstr ""
#: views.py:221 views.py:253
#: views.py:222 views.py:254
#, python-format
msgid "related `%(app_label)s.%(object_name)s` objects"
msgstr ""
#: views.py:225 views.py:258
#: views.py:226 views.py:259
#, python-format
msgid "all %s"
msgstr ""
#: views.py:230 views.py:263
#: views.py:231 views.py:264
#, python-format
msgid "number of %s"
msgstr ""
#: views.py:268
#: views.py:269
#, python-format
msgid "Fields on %s objects"
msgstr ""
#: views.py:360
#: views.py:361
#, python-format
msgid "%s does not appear to be a urlpattern object"
msgstr ""
#: templates/admin_doc/bookmarklets.html:6 templates/admin_doc/index.html:6
#: templates/admin_doc/missing_docutils.html:6
#: templates/admin_doc/model_detail.html:14
#: templates/admin_doc/model_index.html:8
#: templates/admin_doc/template_detail.html:6
#: templates/admin_doc/template_filter_index.html:7
#: templates/admin_doc/template_tag_index.html:7
#: templates/admin_doc/view_detail.html:6
#: templates/admin_doc/view_index.html:7
msgid "Home"
msgstr ""
#: templates/admin_doc/bookmarklets.html:7 templates/admin_doc/index.html:7
#: templates/admin_doc/missing_docutils.html:7
#: templates/admin_doc/model_detail.html:15
@ -79,30 +91,18 @@ msgstr ""
#: templates/admin_doc/template_tag_index.html:8
#: templates/admin_doc/view_detail.html:7
#: templates/admin_doc/view_index.html:8
msgid "Home"
msgstr ""
#: templates/admin_doc/bookmarklets.html:8 templates/admin_doc/index.html:8
#: templates/admin_doc/missing_docutils.html:8
#: templates/admin_doc/model_detail.html:16
#: templates/admin_doc/model_index.html:10
#: templates/admin_doc/template_detail.html:8
#: templates/admin_doc/template_filter_index.html:9
#: templates/admin_doc/template_tag_index.html:9
#: templates/admin_doc/view_detail.html:8
#: templates/admin_doc/view_index.html:9
msgid "Documentation"
msgstr ""
#: templates/admin_doc/bookmarklets.html:9
#: templates/admin_doc/bookmarklets.html:8
msgid "Bookmarklets"
msgstr ""
#: templates/admin_doc/bookmarklets.html:12
#: templates/admin_doc/bookmarklets.html:11
msgid "Documentation bookmarklets"
msgstr ""
#: templates/admin_doc/bookmarklets.html:16
#: templates/admin_doc/bookmarklets.html:15
msgid ""
"\n"
"<p class=\"help\">To install bookmarklets, drag the link to your bookmarks\n"
@ -113,60 +113,69 @@ msgid ""
"your computer is \"internal\").</p>\n"
msgstr ""
#: templates/admin_doc/bookmarklets.html:26
#: templates/admin_doc/bookmarklets.html:25
msgid "Documentation for this page"
msgstr ""
#: templates/admin_doc/bookmarklets.html:27
#: templates/admin_doc/bookmarklets.html:26
msgid ""
"Jumps you from any page to the documentation for the view that generates "
"that page."
msgstr ""
#: templates/admin_doc/bookmarklets.html:29
#: templates/admin_doc/bookmarklets.html:28
msgid "Show object ID"
msgstr ""
#: templates/admin_doc/bookmarklets.html:30
#: templates/admin_doc/bookmarklets.html:29
msgid ""
"Shows the content-type and unique ID for pages that represent a single "
"object."
msgstr ""
#: templates/admin_doc/bookmarklets.html:32
#: templates/admin_doc/bookmarklets.html:31
msgid "Edit this object (current window)"
msgstr ""
#: templates/admin_doc/bookmarklets.html:33
#: templates/admin_doc/bookmarklets.html:32
msgid "Jumps to the admin page for pages that represent a single object."
msgstr ""
#: templates/admin_doc/bookmarklets.html:35
#: templates/admin_doc/bookmarklets.html:34
msgid "Edit this object (new window)"
msgstr ""
#: templates/admin_doc/bookmarklets.html:36
#: templates/admin_doc/bookmarklets.html:35
msgid "As above, but opens the admin page in a new window."
msgstr ""
#: templates/admin_doc/model_detail.html:17
#: templates/admin_doc/model_index.html:11
#: templates/admin_doc/model_detail.html:16
#: templates/admin_doc/model_index.html:10
msgid "Models"
msgstr ""
#: templates/admin_doc/template_detail.html:9
#: templates/admin_doc/template_detail.html:8
msgid "Templates"
msgstr ""
#: templates/admin_doc/template_filter_index.html:10
#: templates/admin_doc/template_filter_index.html:9
msgid "Filters"
msgstr ""
#: templates/admin_doc/template_tag_index.html:10
#: templates/admin_doc/template_tag_index.html:9
msgid "Tags"
msgstr ""
#: templates/admin_doc/view_detail.html:9
#: templates/admin_doc/view_index.html:10
#: templates/admin_doc/view_detail.html:8
#: templates/admin_doc/view_index.html:9
msgid "Views"
msgstr ""
#: tests/__init__.py:23
msgid "Boolean (Either True or False)"
msgstr ""
#: tests/__init__.py:33
#, python-format
msgid "Field of type: %(field_type)s"
msgstr ""

View File

@ -26,7 +26,7 @@ class TestFieldType(unittest.TestCase):
def test_custom_fields(self):
self.assertEqual(
views.get_readable_field_data_type(fields.CustomField()),
_('A custom field type')
'A custom field type'
)
self.assertEqual(
views.get_readable_field_data_type(fields.DescriptionLackingField()),

View File

@ -1,14 +1,17 @@
import re
from django.core.exceptions import ImproperlyConfigured
from django.utils.importlib import import_module
from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
SESSION_KEY = '_auth_user_id'
BACKEND_SESSION_KEY = '_auth_user_backend'
REDIRECT_FIELD_NAME = 'next'
def load_backend(path):
i = path.rfind('.')
module, attr = path[:i], path[i+1:]
module, attr = path[:i], path[i + 1:]
try:
mod = import_module(module)
except ImportError as e:
@ -21,6 +24,7 @@ def load_backend(path):
raise ImproperlyConfigured('Module "%s" does not define a "%s" authentication backend' % (module, attr))
return cls()
def get_backends():
from django.conf import settings
backends = []
@ -30,6 +34,22 @@ def get_backends():
raise ImproperlyConfigured('No authentication backends have been defined. Does AUTHENTICATION_BACKENDS contain anything?')
return backends
def _clean_credentials(credentials):
"""
Cleans a dictionary of credentials of potentially sensitive info before
sending to less secure functions.
Not comprehensive - intended for user_login_failed signal
"""
SENSITIVE_CREDENTIALS = re.compile('api|token|key|secret|password|signature', re.I)
CLEANSED_SUBSTITUTE = '********************'
for key in credentials:
if SENSITIVE_CREDENTIALS.search(key):
credentials[key] = CLEANSED_SUBSTITUTE
return credentials
def authenticate(**credentials):
"""
If the given credentials are valid, return a User object.
@ -46,6 +66,11 @@ def authenticate(**credentials):
user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__)
return user
# The credentials supplied are invalid to all backends, fire signal
user_login_failed.send(sender=__name__,
credentials=_clean_credentials(credentials))
def login(request, user):
"""
Persist a user id and a backend in the request. This way a user doesn't
@ -69,6 +94,7 @@ def login(request, user):
request.user = user
user_logged_in.send(sender=user.__class__, request=request, user=user)
def logout(request):
"""
Removes the authenticated user's ID from the request and flushes their
@ -86,6 +112,22 @@ def logout(request):
from django.contrib.auth.models import AnonymousUser
request.user = AnonymousUser()
def get_user_model():
"Return the User model that is active in this project"
from django.conf import settings
from django.db.models import get_model
try:
app_label, model_name = settings.AUTH_USER_MODEL.split('.')
except ValueError:
raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'")
user_model = get_model(app_label, model_name)
if user_model is None:
raise ImproperlyConfigured("AUTH_USER_MODEL refers to model '%s' that has not been installed" % settings.AUTH_USER_MODEL)
return user_model
def get_user(request):
from django.contrib.auth.models import AnonymousUser
try:

View File

@ -11,14 +11,13 @@ from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from django.utils.html import escape
from django.utils.decorators import method_decorator
from django.utils.safestring import mark_safe
from django.utils import six
from django.utils.translation import ugettext, ugettext_lazy as _
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
csrf_protect_m = method_decorator(csrf_protect)
class GroupAdmin(admin.ModelAdmin):
search_fields = ('name',)
ordering = ('name',)
@ -54,10 +53,10 @@ class UserAdmin(admin.ModelAdmin):
add_form = UserCreationForm
change_password_form = AdminPasswordChangeForm
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
list_filter = ('is_staff', 'is_superuser', 'is_active')
list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups')
search_fields = ('username', 'first_name', 'last_name', 'email')
ordering = ('username',)
filter_horizontal = ('user_permissions',)
filter_horizontal = ('groups', 'user_permissions',)
def get_fieldsets(self, request, obj=None):
if not obj:
@ -106,9 +105,10 @@ class UserAdmin(admin.ModelAdmin):
raise PermissionDenied
if extra_context is None:
extra_context = {}
username_field = self.model._meta.get_field(self.model.USERNAME_FIELD)
defaults = {
'auto_populated_fields': (),
'username_help_text': self.model._meta.get_field('username').help_text,
'username_help_text': username_field.help_text,
}
extra_context.update(defaults)
return super(UserAdmin, self).add_view(request, form_url,
@ -153,7 +153,7 @@ class UserAdmin(admin.ModelAdmin):
'admin/auth/user/change_password.html'
], context, current_app=self.admin_site.name)
def response_add(self, request, obj, post_url_continue='../%s/'):
def response_add(self, request, obj, **kwargs):
"""
Determines the HttpResponse for the add_view stage. It mostly defers to
its superclass implementation but is customized because the User model
@ -166,9 +166,7 @@ class UserAdmin(admin.ModelAdmin):
# * We are adding a user in a popup
if '_addanother' not in request.POST and '_popup' not in request.POST:
request.POST['_continue'] = 1
return super(UserAdmin, self).response_add(request, obj,
post_url_continue)
return super(UserAdmin, self).response_add(request, obj, **kwargs)
admin.site.register(Group, GroupAdmin)
admin.site.register(User, UserAdmin)

View File

@ -1,6 +1,6 @@
from __future__ import unicode_literals
from django.contrib.auth.models import User, Permission
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
class ModelBackend(object):
@ -12,10 +12,11 @@ class ModelBackend(object):
# configurable.
def authenticate(self, username=None, password=None):
try:
user = User.objects.get(username=username)
UserModel = get_user_model()
user = UserModel.objects.get_by_natural_key(username)
if user.check_password(password):
return user
except User.DoesNotExist:
except UserModel.DoesNotExist:
return None
def get_group_permissions(self, user_obj, obj=None):
@ -29,7 +30,9 @@ class ModelBackend(object):
if user_obj.is_superuser:
perms = Permission.objects.all()
else:
perms = Permission.objects.filter(group__user=user_obj)
user_groups_field = get_user_model()._meta.get_field('groups')
user_groups_query = 'group__%s' % user_groups_field.related_query_name()
perms = Permission.objects.filter(**{user_groups_query: user_obj})
perms = perms.values_list('content_type__app_label', 'codename').order_by()
user_obj._group_perm_cache = set(["%s.%s" % (ct, name) for ct, name in perms])
return user_obj._group_perm_cache
@ -60,8 +63,9 @@ class ModelBackend(object):
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
UserModel = get_user_model()
return UserModel.objects.get(pk=user_id)
except UserModel.DoesNotExist:
return None
@ -94,17 +98,21 @@ class RemoteUserBackend(ModelBackend):
user = None
username = self.clean_username(remote_user)
UserModel = get_user_model()
# Note that this could be accomplished in one try-except clause, but
# instead we use get_or_create when creating unknown users since it has
# built-in safeguards for multiple threads.
if self.create_unknown_user:
user, created = User.objects.get_or_create(username=username)
user, created = UserModel.objects.get_or_create(**{
UserModel.USERNAME_FIELD: username
})
if created:
user = self.configure_user(user)
else:
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = UserModel.objects.get_by_natural_key(username)
except UserModel.DoesNotExist:
pass
return user

View File

@ -11,6 +11,11 @@ class PermLookupDict(object):
def __getitem__(self, perm_name):
return self.user.has_perm("%s.%s" % (self.module_name, perm_name))
def __iter__(self):
# To fix 'item in perms.someapp' and __getitem__ iteraction we need to
# define __iter__. See #18979 for details.
raise TypeError("PermLookupDict is not iterable.")
def __bool__(self):
return self.user.has_module_perms(self.module_name)
__nonzero__ = __bool__ # Python 2
@ -27,6 +32,17 @@ class PermWrapper(object):
# I am large, I contain multitudes.
raise TypeError("PermWrapper is not iterable.")
def __contains__(self, perm_name):
"""
Lookup by "someapp" or "someapp.someperm" in perms.
"""
if '.' not in perm_name:
# The name refers to module.
return bool(self[perm_name])
module_name, perm_name = perm_name.split('.', 1)
return self[module_name][perm_name]
def auth(request):
"""
Returns context variables required by apps that use Django's authentication

View 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"
}
}
]

View File

@ -7,9 +7,10 @@ from django.utils.datastructures import SortedDict
from django.utils.html import format_html, format_html_join
from django.utils.http import int_to_base36
from django.utils.safestring import mark_safe
from django.utils.text import capfirst
from django.utils.translation import ugettext, ugettext_lazy as _
from django.contrib.auth import authenticate
from django.contrib.auth import authenticate, get_user_model
from django.contrib.auth.models import User
from django.contrib.auth.hashers import UNUSABLE_PASSWORD, identify_hasher
from django.contrib.auth.tokens import default_token_generator
@ -117,9 +118,6 @@ class UserChangeForm(forms.ModelForm):
"this user's password, but you can change the password "
"using <a href=\"password/\">this form</a>."))
def clean_password(self):
return self.initial["password"]
class Meta:
model = User
@ -129,13 +127,19 @@ class UserChangeForm(forms.ModelForm):
if f is not None:
f.queryset = f.queryset.select_related('content_type')
def clean_password(self):
# Regardless of what the user provides, return the initial value.
# This is done here, rather than on the field, because the
# field does not have access to the initial value
return self.initial["password"]
class AuthenticationForm(forms.Form):
"""
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
username = forms.CharField(label=_("Username"), max_length=30)
username = forms.CharField(max_length=254)
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
error_messages = {
@ -157,6 +161,11 @@ class AuthenticationForm(forms.Form):
self.user_cache = None
super(AuthenticationForm, self).__init__(*args, **kwargs)
# Set the label for the "username" field.
UserModel = get_user_model()
username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
self.fields['username'].label = capfirst(username_field.verbose_name)
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
@ -187,20 +196,21 @@ class AuthenticationForm(forms.Form):
class PasswordResetForm(forms.Form):
error_messages = {
'unknown': _("That e-mail address doesn't have an associated "
'unknown': _("That email address doesn't have an associated "
"user account. Are you sure you've registered?"),
'unusable': _("The user account associated with this e-mail "
'unusable': _("The user account associated with this email "
"address cannot reset the password."),
}
email = forms.EmailField(label=_("E-mail"), max_length=75)
email = forms.EmailField(label=_("Email"), max_length=254)
def clean_email(self):
"""
Validates that an active user exists with the given email address.
"""
UserModel = get_user_model()
email = self.cleaned_data["email"]
self.users_cache = User.objects.filter(email__iexact=email,
is_active=True)
self.users_cache = UserModel.objects.filter(email__iexact=email,
is_active=True)
if not len(self.users_cache):
raise forms.ValidationError(self.error_messages['unknown'])
if any((user.password == UNUSABLE_PASSWORD)

View 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()

View File

@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-03-23 02:36+0100\n"
"POT-Creation-Date: 2012-10-15 10:56+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English <en@li.org>\n"
@ -25,48 +25,56 @@ msgstr ""
msgid "Important dates"
msgstr ""
#: admin.py:125
#: admin.py:126
msgid "Password changed successfully."
msgstr ""
#: admin.py:135
#: admin.py:136
#, python-format
msgid "Change password: %s"
msgstr ""
#: forms.py:62
#: forms.py:31 tests/forms.py:249 tests/forms.py:254
msgid "No password set."
msgstr ""
#: forms.py:37 tests/forms.py:259 tests/forms.py:265
msgid "Invalid password format or unknown hashing algorithm."
msgstr ""
#: forms.py:65
msgid "A user with that username already exists."
msgstr ""
#: forms.py:63 forms.py:251 forms.py:308
#: forms.py:66 forms.py:257 forms.py:317
msgid "The two password fields didn't match."
msgstr ""
#: forms.py:65 forms.py:110 forms.py:139
#: forms.py:68 forms.py:113
msgid "Username"
msgstr ""
#: forms.py:67 forms.py:111
#: forms.py:70 forms.py:114
msgid "Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only."
msgstr ""
#: forms.py:70 forms.py:114
#: forms.py:73 forms.py:117
msgid "This value may contain only letters, numbers and @/./+/-/_ characters."
msgstr ""
#: forms.py:72 forms.py:116 forms.py:140 forms.py:310
#: forms.py:75 forms.py:119 forms.py:140 forms.py:319
msgid "Password"
msgstr ""
#: forms.py:74
#: forms.py:77
msgid "Password confirmation"
msgstr ""
#: forms.py:76
#: forms.py:79
msgid "Enter the same password as above, for verification."
msgstr ""
#: forms.py:117
#: forms.py:120
msgid ""
"Raw passwords are not stored, so there is no way to see this user's "
"password, but you can change the password using <a href=\"password/\">this "
@ -89,178 +97,178 @@ msgstr ""
msgid "This account is inactive."
msgstr ""
#: forms.py:191
#: forms.py:196
msgid ""
"That e-mail address doesn't have an associated user account. Are you sure "
"That email address doesn't have an associated user account. Are you sure "
"you've registered?"
msgstr ""
#: forms.py:193
#: forms.py:198 tests/forms.py:347
msgid ""
"The user account associated with this e-mail address cannot reset the "
"The user account associated with this email address cannot reset the "
"password."
msgstr ""
#: forms.py:196
msgid "E-mail"
#: forms.py:201
msgid "Email"
msgstr ""
#: forms.py:253
#: forms.py:259
msgid "New password"
msgstr ""
#: forms.py:255
#: forms.py:261
msgid "New password confirmation"
msgstr ""
#: forms.py:284
#: forms.py:290
msgid "Your old password was entered incorrectly. Please enter it again."
msgstr ""
#: forms.py:287
#: forms.py:293
msgid "Old password"
msgstr ""
#: forms.py:312
#: forms.py:321
msgid "Password (again)"
msgstr ""
#: hashers.py:218 hashers.py:269 hashers.py:298 hashers.py:326 hashers.py:355
#: hashers.py:389
#: hashers.py:241 hashers.py:292 hashers.py:321 hashers.py:349 hashers.py:378
#: hashers.py:412
msgid "algorithm"
msgstr ""
#: hashers.py:219
#: hashers.py:242
msgid "iterations"
msgstr ""
#: hashers.py:220 hashers.py:271 hashers.py:299 hashers.py:327 hashers.py:390
#: hashers.py:243 hashers.py:294 hashers.py:322 hashers.py:350 hashers.py:413
msgid "salt"
msgstr ""
#: hashers.py:221 hashers.py:300 hashers.py:328 hashers.py:356 hashers.py:391
#: hashers.py:244 hashers.py:323 hashers.py:351 hashers.py:379 hashers.py:414
msgid "hash"
msgstr ""
#: hashers.py:270
#: hashers.py:293
msgid "work factor"
msgstr ""
#: hashers.py:272
#: hashers.py:295
msgid "checksum"
msgstr ""
#: models.py:66 models.py:113
#: models.py:72 models.py:121
msgid "name"
msgstr ""
#: models.py:68
#: models.py:74
msgid "codename"
msgstr ""
#: models.py:72
#: models.py:78
msgid "permission"
msgstr ""
#: models.py:73 models.py:115
#: models.py:79 models.py:123
msgid "permissions"
msgstr ""
#: models.py:120
#: models.py:128
msgid "group"
msgstr ""
#: models.py:121 models.py:250
#: models.py:129 models.py:317
msgid "groups"
msgstr ""
#: models.py:232
msgid "username"
msgid "password"
msgstr ""
#: models.py:233
msgid "last login"
msgstr ""
#: models.py:298
msgid "username"
msgstr ""
#: models.py:299
msgid ""
"Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"
msgstr ""
#: models.py:235
#: models.py:302
msgid "Enter a valid username."
msgstr ""
#: models.py:304
msgid "first name"
msgstr ""
#: models.py:236
#: models.py:305
msgid "last name"
msgstr ""
#: models.py:237
msgid "e-mail address"
#: models.py:306
msgid "email address"
msgstr ""
#: models.py:238
msgid "password"
msgstr ""
#: models.py:239
#: models.py:307
msgid "staff status"
msgstr ""
#: models.py:240
#: models.py:308
msgid "Designates whether the user can log into this admin site."
msgstr ""
#: models.py:242
#: models.py:310
msgid "active"
msgstr ""
#: models.py:243
#: models.py:311
msgid ""
"Designates whether this user should be treated as active. Unselect this "
"instead of deleting accounts."
msgstr ""
#: models.py:245
#: models.py:313
msgid "superuser status"
msgstr ""
#: models.py:246
#: models.py:314
msgid ""
"Designates that this user has all permissions without explicitly assigning "
"them."
msgstr ""
#: models.py:248
msgid "last login"
msgstr ""
#: models.py:249
#: models.py:316
msgid "date joined"
msgstr ""
#: models.py:251
#: models.py:318
msgid ""
"The groups this user belongs to. A user will get all permissions granted to "
"each of his/her group."
msgstr ""
#: models.py:255
#: models.py:322
msgid "user permissions"
msgstr ""
#: models.py:260
#: models.py:331
msgid "user"
msgstr ""
#: models.py:261
#: models.py:332
msgid "users"
msgstr ""
#: views.py:93
#: views.py:97
msgid "Logged out"
msgstr ""
#: management/commands/createsuperuser.py:27
msgid "Enter a valid e-mail address."
msgstr ""
#: templates/registration/password_reset_subject.txt:2
#, python-format
msgid "Password reset on %(site_name)s"

View File

@ -6,9 +6,11 @@ from __future__ import unicode_literals
import getpass
import locale
import unicodedata
from django.contrib.auth import models as auth_app
from django.contrib.auth import models as auth_app, get_user_model
from django.core import exceptions
from django.core.management.base import CommandError
from django.db.models import get_models, signals
from django.contrib.auth.models import User
from django.utils import six
from django.utils.six.moves import input
@ -17,13 +19,43 @@ def _get_permission_codename(action, opts):
return '%s_%s' % (action, opts.object_name.lower())
def _get_all_permissions(opts):
"Returns (codename, name) for all permissions in the given opts."
def _get_all_permissions(opts, ctype):
"""
Returns (codename, name) for all permissions in the given opts.
"""
builtin = _get_builtin_permissions(opts)
custom = list(opts.permissions)
_check_permission_clashing(custom, builtin, ctype)
return builtin + custom
def _get_builtin_permissions(opts):
"""
Returns (codename, name) for all autogenerated permissions.
"""
perms = []
for action in ('add', 'change', 'delete'):
perms.append((_get_permission_codename(action, opts), 'Can %s %s' % (action, opts.verbose_name_raw)))
return perms + list(opts.permissions)
perms.append((_get_permission_codename(action, opts),
'Can %s %s' % (action, opts.verbose_name_raw)))
return perms
def _check_permission_clashing(custom, builtin, ctype):
"""
Check that permissions for a model do not clash. Raises CommandError if
there are duplicate permissions.
"""
pool = set()
builtin_codenames = set(p[0] for p in builtin)
for codename, _name in custom:
if codename in pool:
raise CommandError(
"The permission codename '%s' is duplicated for model '%s.%s'." %
(codename, ctype.app_label, ctype.model_class().__name__))
elif codename in builtin_codenames:
raise CommandError(
"The permission codename '%s' clashes with a builtin permission "
"for model '%s.%s'." %
(codename, ctype.app_label, ctype.model_class().__name__))
pool.add(codename)
def create_permissions(app, created_models, verbosity, **kwargs):
from django.contrib.contenttypes.models import ContentType
@ -38,7 +70,7 @@ def create_permissions(app, created_models, verbosity, **kwargs):
for klass in app_models:
ctype = ContentType.objects.get_for_model(klass)
ctypes.add(ctype)
for perm in _get_all_permissions(klass._meta):
for perm in _get_all_permissions(klass._meta, ctype):
searched_perms.append((ctype, perm))
# Find all the Permissions that have a context_type for a model we're
@ -64,7 +96,9 @@ def create_permissions(app, created_models, verbosity, **kwargs):
def create_superuser(app, created_models, verbosity, db, **kwargs):
from django.core.management import call_command
if auth_app.User in created_models and kwargs.get('interactive', True):
UserModel = get_user_model()
if UserModel in created_models and kwargs.get('interactive', True):
msg = ("\nYou just installed Django's auth system, which means you "
"don't have any superusers defined.\nWould you like to create one "
"now? (yes/no): ")
@ -113,28 +147,35 @@ def get_default_username(check_db=True):
:returns: The username, or an empty string if no username can be
determined.
"""
from django.contrib.auth.management.commands.createsuperuser import (
RE_VALID_USERNAME)
# If the User model has been swapped out, we can't make any assumptions
# about the default user name.
if auth_app.User._meta.swapped:
return ''
default_username = get_system_username()
try:
default_username = unicodedata.normalize('NFKD', default_username)\
.encode('ascii', 'ignore').decode('ascii').replace(' ', '').lower()
except UnicodeDecodeError:
return ''
if not RE_VALID_USERNAME.match(default_username):
# Run the username validator
try:
auth_app.User._meta.get_field('username').run_validators(default_username)
except exceptions.ValidationError:
return ''
# Don't return the default username if it is already taken.
if check_db and default_username:
try:
User.objects.get(username=default_username)
except User.DoesNotExist:
auth_app.User.objects.get(username=default_username)
except auth_app.User.DoesNotExist:
pass
else:
return ''
return default_username
signals.post_syncdb.connect(create_permissions,
dispatch_uid = "django.contrib.auth.management.create_permissions")
dispatch_uid="django.contrib.auth.management.create_permissions")
signals.post_syncdb.connect(create_superuser,
sender=auth_app, dispatch_uid = "django.contrib.auth.management.create_superuser")
sender=auth_app, dispatch_uid="django.contrib.auth.management.create_superuser")

View File

@ -1,8 +1,8 @@
import getpass
from optparse import make_option
from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import User
from django.db import DEFAULT_DB_ALIAS
@ -30,12 +30,16 @@ class Command(BaseCommand):
else:
username = getpass.getuser()
UserModel = get_user_model()
try:
u = User.objects.using(options.get('database')).get(username=username)
except User.DoesNotExist:
u = UserModel.objects.using(options.get('database')).get(**{
UserModel.USERNAME_FIELD: username
})
except UserModel.DoesNotExist:
raise CommandError("user '%s' does not exist" % username)
self.stdout.write("Changing password for user '%s'\n" % u.username)
self.stdout.write("Changing password for user '%s'\n" % u)
MAX_TRIES = 3
count = 0
@ -48,9 +52,9 @@ class Command(BaseCommand):
count = count + 1
if count == MAX_TRIES:
raise CommandError("Aborting password change for user '%s' after %s attempts" % (username, count))
raise CommandError("Aborting password change for user '%s' after %s attempts" % (u, count))
u.set_password(p1)
u.save()
return "Password changed successfully for user '%s'" % u.username
return "Password changed successfully for user '%s'" % u

View File

@ -3,109 +3,119 @@ Management utility to create superusers.
"""
import getpass
import re
import sys
from optparse import make_option
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from django.contrib.auth.management import get_default_username
from django.core import exceptions
from django.core.management.base import BaseCommand, CommandError
from django.db import DEFAULT_DB_ALIAS
from django.utils.six.moves import input
from django.utils.translation import ugettext as _
RE_VALID_USERNAME = re.compile('[\w.@+-]+$')
EMAIL_RE = re.compile(
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' # quoted-string
r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain
def is_valid_email(value):
if not EMAIL_RE.search(value):
raise exceptions.ValidationError(_('Enter a valid e-mail address.'))
from django.utils.text import capfirst
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
make_option('--username', dest='username', default=None,
help='Specifies the username for the superuser.'),
make_option('--email', dest='email', default=None,
help='Specifies the email address for the superuser.'),
make_option('--noinput', action='store_false', dest='interactive', default=True,
help=('Tells Django to NOT prompt the user for input of any kind. '
'You must use --username and --email with --noinput, and '
'superusers created with --noinput will not be able to log '
'in until they\'re given a valid password.')),
make_option('--database', action='store', dest='database',
default=DEFAULT_DB_ALIAS, help='Specifies the database to use. Default is "default".'),
)
def __init__(self, *args, **kwargs):
# Options are defined in an __init__ method to support swapping out
# custom user models in tests.
super(Command, self).__init__(*args, **kwargs)
self.UserModel = get_user_model()
self.username_field = self.UserModel._meta.get_field(self.UserModel.USERNAME_FIELD)
self.option_list = BaseCommand.option_list + (
make_option('--%s' % self.UserModel.USERNAME_FIELD, dest=self.UserModel.USERNAME_FIELD, default=None,
help='Specifies the login for the superuser.'),
make_option('--noinput', action='store_false', dest='interactive', default=True,
help=('Tells Django to NOT prompt the user for input of any kind. '
'You must use --%s with --noinput, along with an option for '
'any other required field. Superusers created with --noinput will '
' not be able to log in until they\'re given a valid password.' %
self.UserModel.USERNAME_FIELD)),
make_option('--database', action='store', dest='database',
default=DEFAULT_DB_ALIAS, help='Specifies the database to use. Default is "default".'),
) + tuple(
make_option('--%s' % field, dest=field, default=None,
help='Specifies the %s for the superuser.' % field)
for field in self.UserModel.REQUIRED_FIELDS
)
option_list = BaseCommand.option_list
help = 'Used to create a superuser.'
def handle(self, *args, **options):
username = options.get('username', None)
email = options.get('email', None)
username = options.get(self.UserModel.USERNAME_FIELD, None)
interactive = options.get('interactive')
verbosity = int(options.get('verbosity', 1))
database = options.get('database')
# Do quick and dirty validation if --noinput
if not interactive:
if not username or not email:
raise CommandError("You must use --username and --email with --noinput.")
if not RE_VALID_USERNAME.match(username):
raise CommandError("Invalid username. Use only letters, digits, and underscores")
try:
is_valid_email(email)
except exceptions.ValidationError:
raise CommandError("Invalid email address.")
# If not provided, create the user with an unusable password
password = None
user_data = {}
# Prompt for username/email/password. Enclose this whole thing in a
# try/except to trap for a keyboard interrupt and exit gracefully.
if interactive:
# Do quick and dirty validation if --noinput
if not interactive:
try:
if not username:
raise CommandError("You must use --%s with --noinput." %
self.UserModel.USERNAME_FIELD)
username = self.username_field.clean(username, None)
for field_name in self.UserModel.REQUIRED_FIELDS:
if options.get(field_name):
field = self.UserModel._meta.get_field(field_name)
user_data[field_name] = field.clean(options[field_name], None)
else:
raise CommandError("You must use --%s with --noinput." % field_name)
except exceptions.ValidationError as e:
raise CommandError('; '.join(e.messages))
else:
# Prompt for username/password, and any other required fields.
# Enclose this whole thing in a try/except to trap for a
# keyboard interrupt and exit gracefully.
default_username = get_default_username()
try:
# Get a username
while 1:
while username is None:
if not username:
input_msg = 'Username'
input_msg = capfirst(self.username_field.verbose_name)
if default_username:
input_msg += ' (leave blank to use %r)' % default_username
username = input(input_msg + ': ')
if default_username and username == '':
username = default_username
if not RE_VALID_USERNAME.match(username):
self.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.")
input_msg += " (leave blank to use '%s')" % default_username
raw_value = input(input_msg + ': ')
if default_username and raw_value == '':
raw_value = default_username
try:
username = self.username_field.clean(raw_value, None)
except exceptions.ValidationError as e:
self.stderr.write("Error: %s" % '; '.join(e.messages))
username = None
continue
try:
User.objects.using(database).get(username=username)
except User.DoesNotExist:
break
self.UserModel.objects.db_manager(database).get_by_natural_key(username)
except self.UserModel.DoesNotExist:
pass
else:
self.stderr.write("Error: That username is already taken.")
self.stderr.write("Error: That %s is already taken." %
self.username_field.verbose_name)
username = None
# Get an email
while 1:
if not email:
email = input('E-mail address: ')
try:
is_valid_email(email)
except exceptions.ValidationError:
self.stderr.write("Error: That e-mail address is invalid.")
email = None
else:
break
for field_name in self.UserModel.REQUIRED_FIELDS:
field = self.UserModel._meta.get_field(field_name)
user_data[field_name] = options.get(field_name)
while user_data[field_name] is None:
raw_value = input(capfirst(field.verbose_name + ': '))
try:
user_data[field_name] = field.clean(raw_value, None)
except exceptions.ValidationError as e:
self.stderr.write("Error: %s" % '; '.join(e.messages))
user_data[field_name] = None
# Get a password
while 1:
while password is None:
if not password:
password = getpass.getpass()
password2 = getpass.getpass('Password (again): ')
@ -117,12 +127,13 @@ class Command(BaseCommand):
self.stderr.write("Error: Blank passwords aren't allowed.")
password = None
continue
break
except KeyboardInterrupt:
self.stderr.write("\nOperation cancelled.")
sys.exit(1)
User.objects.db_manager(database).create_superuser(username, email, password)
user_data[self.UserModel.USERNAME_FIELD] = username
user_data['password'] = password
self.UserModel.objects.db_manager(database).create_superuser(**user_data)
if verbosity >= 1:
self.stdout.write("Superuser created successfully.")
self.stdout.write("Superuser created successfully.")

View File

@ -55,7 +55,7 @@ class RemoteUserMiddleware(object):
# getting passed in the headers, then the correct user is already
# persisted in the session and we don't need to continue.
if request.user.is_authenticated():
if request.user.username == self.clean_username(username, request):
if request.user.get_username() == self.clean_username(username, request):
return
# We are seeing this user for the first time in this session, attempt
# to authenticate the user.
@ -75,6 +75,6 @@ class RemoteUserMiddleware(object):
backend = auth.load_backend(backend_str)
try:
username = backend.clean_username(username)
except AttributeError: # Backend has no clean_username method.
except AttributeError: # Backend has no clean_username method.
pass
return username

View File

@ -1,7 +1,10 @@
from __future__ import unicode_literals
import re
import warnings
from django.core.exceptions import ImproperlyConfigured
from django.core.mail import send_mail
from django.core import validators
from django.db import models
from django.db.models.manager import EmptyManager
from django.utils.crypto import get_random_string
@ -96,6 +99,7 @@ class GroupManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(name=name)
@python_2_unicode_compatible
class Group(models.Model):
"""
@ -131,7 +135,7 @@ class Group(models.Model):
return (self.name,)
class UserManager(models.Manager):
class BaseUserManager(models.Manager):
@classmethod
def normalize_email(cls, email):
@ -148,30 +152,6 @@ class UserManager(models.Manager):
email = '@'.join([email_name, domain_part.lower()])
return email
def create_user(self, username, email=None, password=None):
"""
Creates and saves a User with the given username, email and password.
"""
now = timezone.now()
if not username:
raise ValueError('The given username must be set')
email = UserManager.normalize_email(email)
user = self.model(username=username, email=email,
is_staff=False, is_active=True, is_superuser=False,
last_login=now, date_joined=now)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, email, password):
u = self.create_user(username, email, password)
u.is_staff = True
u.is_active = True
u.is_superuser = True
u.save(using=self._db)
return u
def make_random_password(self, length=10,
allowed_chars='abcdefghjkmnpqrstuvwxyz'
'ABCDEFGHJKLMNPQRSTUVWXYZ'
@ -185,7 +165,34 @@ class UserManager(models.Manager):
return get_random_string(length, allowed_chars)
def get_by_natural_key(self, username):
return self.get(username=username)
return self.get(**{self.model.USERNAME_FIELD: username})
class UserManager(BaseUserManager):
def create_user(self, username, email=None, password=None, **extra_fields):
"""
Creates and saves a User with the given username, email and password.
"""
now = timezone.now()
if not username:
raise ValueError('The given username must be set')
email = UserManager.normalize_email(email)
user = self.model(username=username, email=email,
is_staff=False, is_active=True, is_superuser=False,
last_login=now, date_joined=now, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, email, password, **extra_fields):
u = self.create_user(username, email, password, **extra_fields)
u.is_staff = True
u.is_active = True
u.is_superuser = True
u.save(using=self._db)
return u
# A few helper functions for common logic between User and AnonymousUser.
@ -201,8 +208,6 @@ def _user_get_all_permissions(user, obj):
def _user_has_perm(user, perm, obj):
anon = user.is_anonymous()
active = user.is_active
for backend in auth.get_backends():
if hasattr(backend, "has_perm"):
if obj is not None:
@ -215,8 +220,6 @@ def _user_has_perm(user, perm, obj):
def _user_has_module_perms(user, app_label):
anon = user.is_anonymous()
active = user.is_active
for backend in auth.get_backends():
if hasattr(backend, "has_module_perms"):
if backend.has_module_perms(user, app_label):
@ -225,52 +228,24 @@ def _user_has_module_perms(user, app_label):
@python_2_unicode_compatible
class User(models.Model):
"""
Users within the Django authentication system are represented by this
model.
Username and password are required. Other fields are optional.
"""
username = models.CharField(_('username'), max_length=30, unique=True,
help_text=_('Required. 30 characters or fewer. Letters, numbers and '
'@/./+/-/_ characters'))
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
email = models.EmailField(_('e-mail address'), blank=True)
class AbstractBaseUser(models.Model):
password = models.CharField(_('password'), max_length=128)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin '
'site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
is_superuser = models.BooleanField(_('superuser status'), default=False,
help_text=_('Designates that this user has all permissions without '
'explicitly assigning them.'))
last_login = models.DateTimeField(_('last login'), default=timezone.now)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
groups = models.ManyToManyField(Group, verbose_name=_('groups'),
blank=True, help_text=_('The groups this user belongs to. A user will '
'get all permissions granted to each of '
'his/her group.'))
user_permissions = models.ManyToManyField(Permission,
verbose_name=_('user permissions'), blank=True,
help_text='Specific permissions for this user.')
objects = UserManager()
REQUIRED_FIELDS = []
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
abstract = True
def get_username(self):
"Return the identifying username for this User"
return getattr(self, self.USERNAME_FIELD)
def __str__(self):
return self.username
return self.get_username()
def natural_key(self):
return (self.username,)
def get_absolute_url(self):
return "/users/%s/" % urlquote(self.username)
return (self.get_username(),)
def is_anonymous(self):
"""
@ -286,13 +261,6 @@ class User(models.Model):
"""
return True
def get_full_name(self):
"""
Returns the first_name plus the last_name, with a space in between.
"""
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def set_password(self, raw_password):
self.password = make_password(raw_password)
@ -313,6 +281,71 @@ class User(models.Model):
def has_usable_password(self):
return is_password_usable(self.password)
def get_full_name(self):
raise NotImplementedError()
def get_short_name(self):
raise NotImplementedError()
class AbstractUser(AbstractBaseUser):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
Username, password and email are required. Other fields are optional.
"""
username = models.CharField(_('username'), max_length=30, unique=True,
help_text=_('Required. 30 characters or fewer. Letters, numbers and '
'@/./+/-/_ characters'),
validators=[
validators.RegexValidator(re.compile('^[\w.@+-]+$'), _('Enter a valid username.'), 'invalid')
])
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
email = models.EmailField(_('email address'), blank=True)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin '
'site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
is_superuser = models.BooleanField(_('superuser status'), default=False,
help_text=_('Designates that this user has all permissions without '
'explicitly assigning them.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
groups = models.ManyToManyField(Group, verbose_name=_('groups'),
blank=True, help_text=_('The groups this user belongs to. A user will '
'get all permissions granted to each of '
'his/her group.'))
user_permissions = models.ManyToManyField(Permission,
verbose_name=_('user permissions'), blank=True,
help_text='Specific permissions for this user.')
objects = UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
abstract = True
def get_absolute_url(self):
return "/users/%s/" % urlquote(self.username)
def get_full_name(self):
"""
Returns the first_name plus the last_name, with a space in between.
"""
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
"Returns the short name for the user."
return self.first_name
def get_group_permissions(self, obj=None):
"""
Returns a list of permission strings that this user has through his/her
@ -381,6 +414,8 @@ class User(models.Model):
Returns site-specific profile for this user. Raises
SiteProfileNotAvailable if this site does not allow profiles.
"""
warnings.warn("The use of AUTH_PROFILE_MODULE to define user profiles has been deprecated.",
PendingDeprecationWarning)
if not hasattr(self, '_profile_cache'):
from django.conf import settings
if not getattr(settings, 'AUTH_PROFILE_MODULE', False):
@ -407,6 +442,17 @@ class User(models.Model):
return self._profile_cache
class User(AbstractUser):
"""
Users within the Django authentication system are represented by this
model.
Username, password and email are required. Other fields are optional.
"""
class Meta:
swappable = 'AUTH_USER_MODEL'
@python_2_unicode_compatible
class AnonymousUser(object):
id = None
@ -431,7 +477,7 @@ class AnonymousUser(object):
return not self.__eq__(other)
def __hash__(self):
return 1 # instances always return the same hash value
return 1 # instances always return the same hash value
def save(self):
raise NotImplementedError

View File

@ -1,4 +1,5 @@
from django.dispatch import Signal
user_logged_in = Signal(providing_args=['request', 'user'])
user_login_failed = Signal(providing_args=['credentials'])
user_logged_out = Signal(providing_args=['request', 'user'])

View File

@ -1,26 +1,16 @@
from django.contrib.auth.tests.auth_backends import (BackendTest,
RowlevelBackendTest, AnonymousUserBackendTest, NoBackendsTest,
InActiveUserBackendTest)
from django.contrib.auth.tests.basic import BasicTestCase
from django.contrib.auth.tests.context_processors import AuthContextProcessorTests
from django.contrib.auth.tests.decorators import LoginRequiredTestCase
from django.contrib.auth.tests.forms import (UserCreationFormTest,
AuthenticationFormTest, SetPasswordFormTest, PasswordChangeFormTest,
UserChangeFormTest, PasswordResetFormTest)
from django.contrib.auth.tests.remote_user import (RemoteUserTest,
RemoteUserNoCreateTest, RemoteUserCustomTest)
from django.contrib.auth.tests.management import (
GetDefaultUsernameTestCase,
ChangepasswordManagementCommandTestCase,
)
from django.contrib.auth.tests.models import (ProfileTestCase, NaturalKeysTestCase,
LoadDataWithoutNaturalKeysTestCase, LoadDataWithNaturalKeysTestCase,
UserManagerTestCase)
from django.contrib.auth.tests.hashers import TestUtilsHashPass
from django.contrib.auth.tests.signals import SignalTestCase
from django.contrib.auth.tests.tokens import TokenGeneratorTest
from django.contrib.auth.tests.views import (AuthViewNamedURLTests,
PasswordResetTest, ChangePasswordTest, LoginTest, LogoutTest,
LoginURLSettings)
from django.contrib.auth.tests.custom_user import *
from django.contrib.auth.tests.auth_backends import *
from django.contrib.auth.tests.basic import *
from django.contrib.auth.tests.context_processors import *
from django.contrib.auth.tests.decorators import *
from django.contrib.auth.tests.forms import *
from django.contrib.auth.tests.remote_user import *
from django.contrib.auth.tests.management import *
from django.contrib.auth.tests.models import *
from django.contrib.auth.tests.handlers import *
from django.contrib.auth.tests.hashers import *
from django.contrib.auth.tests.signals import *
from django.contrib.auth.tests.tokens import *
from django.contrib.auth.tests.views import *
# The password for the fixture data users is 'password'

View File

@ -1,22 +1,29 @@
from __future__ import unicode_literals
from datetime import date
from django.conf import settings
from django.contrib.auth.models import User, Group, Permission, AnonymousUser
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.auth.tests.custom_user import ExtensionUser
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase
from django.test.utils import override_settings
class BackendTest(TestCase):
class BaseModelBackendTest(object):
"""
A base class for tests that need to validate the ModelBackend
with different User models. Subclasses should define a class
level UserModel attribute, and a create_users() method to
construct two users for test purposes.
"""
backend = 'django.contrib.auth.backends.ModelBackend'
def setUp(self):
self.curr_auth = settings.AUTHENTICATION_BACKENDS
settings.AUTHENTICATION_BACKENDS = (self.backend,)
User.objects.create_user('test', 'test@example.com', 'test')
User.objects.create_superuser('test2', 'test2@example.com', 'test')
self.create_users()
def tearDown(self):
settings.AUTHENTICATION_BACKENDS = self.curr_auth
@ -26,7 +33,7 @@ class BackendTest(TestCase):
ContentType.objects.clear_cache()
def test_has_perm(self):
user = User.objects.get(username='test')
user = self.UserModel.objects.get(username='test')
self.assertEqual(user.has_perm('auth.test'), False)
user.is_staff = True
user.save()
@ -45,14 +52,14 @@ class BackendTest(TestCase):
self.assertEqual(user.has_perm('auth.test'), False)
def test_custom_perms(self):
user = User.objects.get(username='test')
content_type=ContentType.objects.get_for_model(Group)
user = self.UserModel.objects.get(username='test')
content_type = ContentType.objects.get_for_model(Group)
perm = Permission.objects.create(name='test', content_type=content_type, codename='test')
user.user_permissions.add(perm)
user.save()
# reloading user to purge the _perm_cache
user = User.objects.get(username='test')
user = self.UserModel.objects.get(username='test')
self.assertEqual(user.get_all_permissions() == set(['auth.test']), True)
self.assertEqual(user.get_group_permissions(), set([]))
self.assertEqual(user.has_module_perms('Group'), False)
@ -63,7 +70,7 @@ class BackendTest(TestCase):
perm = Permission.objects.create(name='test3', content_type=content_type, codename='test3')
user.user_permissions.add(perm)
user.save()
user = User.objects.get(username='test')
user = self.UserModel.objects.get(username='test')
self.assertEqual(user.get_all_permissions(), set(['auth.test2', 'auth.test', 'auth.test3']))
self.assertEqual(user.has_perm('test'), False)
self.assertEqual(user.has_perm('auth.test'), True)
@ -73,7 +80,7 @@ class BackendTest(TestCase):
group.permissions.add(perm)
group.save()
user.groups.add(group)
user = User.objects.get(username='test')
user = self.UserModel.objects.get(username='test')
exp = set(['auth.test2', 'auth.test', 'auth.test3', 'auth.test_group'])
self.assertEqual(user.get_all_permissions(), exp)
self.assertEqual(user.get_group_permissions(), set(['auth.test_group']))
@ -85,8 +92,8 @@ class BackendTest(TestCase):
def test_has_no_object_perm(self):
"""Regressiontest for #12462"""
user = User.objects.get(username='test')
content_type=ContentType.objects.get_for_model(Group)
user = self.UserModel.objects.get(username='test')
content_type = ContentType.objects.get_for_model(Group)
perm = Permission.objects.create(name='test', content_type=content_type, codename='test')
user.user_permissions.add(perm)
user.save()
@ -98,9 +105,65 @@ class BackendTest(TestCase):
def test_get_all_superuser_permissions(self):
"A superuser has all permissions. Refs #14795"
user = User.objects.get(username='test2')
user = self.UserModel.objects.get(username='test2')
self.assertEqual(len(user.get_all_permissions()), len(Permission.objects.all()))
@skipIfCustomUser
class ModelBackendTest(BaseModelBackendTest, TestCase):
"""
Tests for the ModelBackend using the default User model.
"""
UserModel = User
def create_users(self):
User.objects.create_user(
username='test',
email='test@example.com',
password='test',
)
User.objects.create_superuser(
username='test2',
email='test2@example.com',
password='test',
)
@override_settings(AUTH_USER_MODEL='auth.ExtensionUser')
class ExtensionUserModelBackendTest(BaseModelBackendTest, TestCase):
"""
Tests for the ModelBackend using the custom ExtensionUser model.
This isn't a perfect test, because both the User and ExtensionUser are
synchronized to the database, which wouldn't ordinary happen in
production. As a result, it doesn't catch errors caused by the non-
existence of the User table.
The specific problem is queries on .filter(groups__user) et al, which
makes an implicit assumption that the user model is called 'User'. In
production, the auth.User table won't exist, so the requested join
won't exist either; in testing, the auth.User *does* exist, and
so does the join. However, the join table won't contain any useful
data; for testing, we check that the data we expect actually does exist.
"""
UserModel = ExtensionUser
def create_users(self):
ExtensionUser.objects.create_user(
username='test',
email='test@example.com',
password='test',
date_of_birth=date(2006, 4, 25)
)
ExtensionUser.objects.create_superuser(
username='test2',
email='test2@example.com',
password='test',
date_of_birth=date(1976, 11, 8)
)
class TestObj(object):
pass
@ -108,7 +171,7 @@ class TestObj(object):
class SimpleRowlevelBackend(object):
def has_perm(self, user, perm, obj=None):
if not obj:
return # We only support row level perms
return # We only support row level perms
if isinstance(obj, TestObj):
if user.username == 'test2':
@ -126,7 +189,7 @@ class SimpleRowlevelBackend(object):
def get_all_permissions(self, user, obj=None):
if not obj:
return [] # We only support row level perms
return [] # We only support row level perms
if not isinstance(obj, TestObj):
return ['none']
@ -140,7 +203,7 @@ class SimpleRowlevelBackend(object):
def get_group_permissions(self, user, obj=None):
if not obj:
return # We only support row level perms
return # We only support row level perms
if not isinstance(obj, TestObj):
return ['none']
@ -151,6 +214,7 @@ class SimpleRowlevelBackend(object):
return ['none']
@skipIfCustomUser
class RowlevelBackendTest(TestCase):
"""
Tests for auth backend that supports object level permissions
@ -186,7 +250,6 @@ class RowlevelBackendTest(TestCase):
self.assertEqual(self.user2.get_all_permissions(), set([]))
def test_get_group_permissions(self):
content_type=ContentType.objects.get_for_model(Group)
group = Group.objects.create(name='test_group')
self.user3.groups.add(group)
self.assertEqual(self.user3.get_group_permissions(TestObj()), set(['group_perm']))
@ -223,6 +286,7 @@ class AnonymousUserBackendTest(TestCase):
self.assertEqual(self.user1.get_all_permissions(TestObj()), set(['anon']))
@skipIfCustomUser
@override_settings(AUTHENTICATION_BACKENDS=[])
class NoBackendsTest(TestCase):
"""
@ -235,6 +299,7 @@ class NoBackendsTest(TestCase):
self.assertRaises(ImproperlyConfigured, self.user.has_perm, ('perm', TestObj(),))
@skipIfCustomUser
class InActiveUserBackendTest(TestCase):
"""
Tests for a inactive user

View File

@ -1,13 +1,18 @@
import locale
import traceback
from django.contrib.auth import get_user_model
from django.contrib.auth.management.commands import createsuperuser
from django.contrib.auth.models import User, AnonymousUser
from django.contrib.auth.tests.custom_user import CustomUser
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.core.exceptions import ImproperlyConfigured
from django.core.management import call_command
from django.test import TestCase
from django.test.utils import override_settings
from django.utils.six import StringIO
@skipIfCustomUser
class BasicTestCase(TestCase):
def test_user(self):
"Check that users can be created and can set their password"
@ -34,7 +39,7 @@ class BasicTestCase(TestCase):
# Check API-based user creation with no password
u2 = User.objects.create_user('testuser2', 'test2@example.com')
self.assertFalse(u.has_usable_password())
self.assertFalse(u2.has_usable_password())
def test_user_no_email(self):
"Check that users can be created without an email"
@ -98,7 +103,6 @@ class BasicTestCase(TestCase):
self.assertEqual(u.email, 'joe2@somewhere.org')
self.assertFalse(u.has_usable_password())
new_io = StringIO()
call_command("createsuperuser",
interactive=False,
@ -124,15 +128,21 @@ class BasicTestCase(TestCase):
# Temporarily replace getpass to allow interactive code to be used
# non-interactively
class mock_getpass: pass
class mock_getpass:
pass
mock_getpass.getpass = staticmethod(lambda p=None: "nopasswd")
createsuperuser.getpass = mock_getpass
# Call the command in this new environment
new_io = StringIO()
call_command("createsuperuser", interactive=True, username="nolocale@somewhere.org", email="nolocale@somewhere.org", stdout=new_io)
call_command("createsuperuser",
interactive=True,
username="nolocale@somewhere.org",
email="nolocale@somewhere.org",
stdout=new_io
)
except TypeError as e:
except TypeError:
self.fail("createsuperuser fails if the OS provides no information about the current locale")
finally:
@ -143,3 +153,24 @@ class BasicTestCase(TestCase):
# If we were successful, a user should have been created
u = User.objects.get(username="nolocale@somewhere.org")
self.assertEqual(u.email, 'nolocale@somewhere.org')
def test_get_user_model(self):
"The current user model can be retrieved"
self.assertEqual(get_user_model(), User)
@override_settings(AUTH_USER_MODEL='auth.CustomUser')
def test_swappable_user(self):
"The current user model can be swapped out for another"
self.assertEqual(get_user_model(), CustomUser)
@override_settings(AUTH_USER_MODEL='badsetting')
def test_swappable_user_bad_setting(self):
"The alternate user setting must point to something in the format app.model"
with self.assertRaises(ImproperlyConfigured):
get_user_model()
@override_settings(AUTH_USER_MODEL='thismodel.doesntexist')
def test_swappable_user_nonexistent_model(self):
"The current user model must point to an installed model"
with self.assertRaises(ImproperlyConfigured):
get_user_model()

View File

@ -2,12 +2,65 @@ import os
from django.conf import global_settings
from django.contrib.auth import authenticate
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.auth.models import User, Permission
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.context_processors import PermWrapper, PermLookupDict
from django.db.models import Q
from django.template import context
from django.test import TestCase
from django.test.utils import override_settings
class MockUser(object):
def has_module_perms(self, perm):
if perm == 'mockapp':
return True
return False
def has_perm(self, perm):
if perm == 'mockapp.someperm':
return True
return False
class PermWrapperTests(TestCase):
"""
Test some details of the PermWrapper implementation.
"""
class EQLimiterObject(object):
"""
This object makes sure __eq__ will not be called endlessly.
"""
def __init__(self):
self.eq_calls = 0
def __eq__(self, other):
if self.eq_calls > 0:
return True
self.eq_calls += 1
return False
def test_permwrapper_in(self):
"""
Test that 'something' in PermWrapper works as expected.
"""
perms = PermWrapper(MockUser())
# Works for modules and full permissions.
self.assertTrue('mockapp' in perms)
self.assertFalse('nonexisting' in perms)
self.assertTrue('mockapp.someperm' in perms)
self.assertFalse('mockapp.nonexisting' in perms)
def test_permlookupdict_in(self):
"""
No endless loops if accessed with 'in' - refs #18979.
"""
pldict = PermLookupDict(MockUser(), 'mockapp')
with self.assertRaises(TypeError):
self.EQLimiterObject() in pldict
@skipIfCustomUser
@override_settings(
TEMPLATE_DIRS=(
os.path.join(os.path.dirname(__file__), 'templates'),
@ -47,9 +100,28 @@ class AuthContextProcessorTests(TestCase):
self.assertContains(response, "Session accessed")
def test_perms_attrs(self):
self.client.login(username='super', password='secret')
u = User.objects.create_user(username='normal', password='secret')
u.user_permissions.add(
Permission.objects.get(
content_type=ContentType.objects.get_for_model(Permission),
codename='add_permission'))
self.client.login(username='normal', password='secret')
response = self.client.get('/auth_processor_perms/')
self.assertContains(response, "Has auth permissions")
self.assertContains(response, "Has auth.add_permission permissions")
self.assertNotContains(response, "nonexisting")
def test_perm_in_perms_attrs(self):
u = User.objects.create_user(username='normal', password='secret')
u.user_permissions.add(
Permission.objects.get(
content_type=ContentType.objects.get_for_model(Permission),
codename='add_permission'))
self.client.login(username='normal', password='secret')
response = self.client.get('/auth_processor_perm_in_perms/')
self.assertContains(response, "Has auth permissions")
self.assertContains(response, "Has auth.add_permission permissions")
self.assertNotContains(response, "nonexisting")
def test_message_attrs(self):
self.client.login(username='super', password='secret')

View 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'

View File

@ -1,7 +1,9 @@
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.auth.tests.views import AuthViewsTestCase
from django.contrib.auth.tests.utils import skipIfCustomUser
@skipIfCustomUser
class LoginRequiredTestCase(AuthViewsTestCase):
"""
Tests the login_required decorators

View File

@ -4,16 +4,17 @@ import os
from django.contrib.auth.models import User
from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm,
PasswordChangeForm, SetPasswordForm, UserChangeForm, PasswordResetForm)
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.core import mail
from django.forms.fields import Field, EmailField
from django.test import TestCase
from django.test.utils import override_settings
from django.utils.encoding import force_text
from django.utils import six
from django.utils import translation
from django.utils.translation import ugettext as _
@skipIfCustomUser
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class UserCreationFormTest(TestCase):
@ -81,6 +82,7 @@ class UserCreationFormTest(TestCase):
self.assertEqual(repr(u), '<User: jsmith@example.com>')
@skipIfCustomUser
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AuthenticationFormTest(TestCase):
@ -133,6 +135,7 @@ class AuthenticationFormTest(TestCase):
self.assertEqual(form.non_field_errors(), [])
@skipIfCustomUser
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class SetPasswordFormTest(TestCase):
@ -160,6 +163,7 @@ class SetPasswordFormTest(TestCase):
self.assertTrue(form.is_valid())
@skipIfCustomUser
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class PasswordChangeFormTest(TestCase):
@ -208,6 +212,7 @@ class PasswordChangeFormTest(TestCase):
['old_password', 'new_password1', 'new_password2'])
@skipIfCustomUser
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class UserChangeFormTest(TestCase):
@ -260,7 +265,25 @@ class UserChangeFormTest(TestCase):
self.assertIn(_("Invalid password format or unknown hashing algorithm."),
form.as_table())
def test_bug_19133(self):
"The change form does not return the password value"
# Use the form to construct the POST data
user = User.objects.get(username='testclient')
form_for_data = UserChangeForm(instance=user)
post_data = form_for_data.initial
# The password field should be readonly, so anything
# posted here should be ignored; the form will be
# valid, and give back the 'initial' value for the
# password field.
post_data['password'] = 'new password'
form = UserChangeForm(instance=user, data=post_data)
self.assertTrue(form.is_valid())
self.assertEqual(form.cleaned_data['password'], 'sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161')
@skipIfCustomUser
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class PasswordResetFormTest(TestCase):
@ -338,4 +361,4 @@ class PasswordResetFormTest(TestCase):
form = PasswordResetForm(data)
self.assertFalse(form.is_valid())
self.assertEqual(form["email"].errors,
[_("The user account associated with this e-mail address cannot reset the password.")])
[_("The user account associated with this email address cannot reset the password.")])

View 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'), [])

View File

@ -1,13 +1,21 @@
from __future__ import unicode_literals
from datetime import date
from django.contrib.auth import models, management
from django.contrib.auth.management import create_permissions
from django.contrib.auth.management.commands import changepassword
from django.contrib.auth.models import User
from django.contrib.auth.tests import CustomUser
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.core.management import call_command
from django.core.management.base import CommandError
from django.test import TestCase
from django.test.utils import override_settings
from django.utils import six
from django.utils.six import StringIO
@skipIfCustomUser
class GetDefaultUsernameTestCase(TestCase):
def setUp(self):
@ -36,6 +44,7 @@ class GetDefaultUsernameTestCase(TestCase):
self.assertEqual(management.get_default_username(), 'julia')
@skipIfCustomUser
class ChangepasswordManagementCommandTestCase(TestCase):
def setUp(self):
@ -48,7 +57,7 @@ class ChangepasswordManagementCommandTestCase(TestCase):
self.stderr.close()
def test_that_changepassword_command_changes_joes_password(self):
" Executing the changepassword management command should change joe's password "
"Executing the changepassword management command should change joe's password"
self.assertTrue(self.user.check_password('qwerty'))
command = changepassword.Command()
command._get_pass = lambda *args: 'not qwerty'
@ -69,3 +78,133 @@ class ChangepasswordManagementCommandTestCase(TestCase):
with self.assertRaises(CommandError):
command.execute("joe", stdout=self.stdout, stderr=self.stderr)
@skipIfCustomUser
class CreatesuperuserManagementCommandTestCase(TestCase):
def test_createsuperuser(self):
"Check the operation of the createsuperuser management command"
# We can use the management command to create a superuser
new_io = StringIO()
call_command("createsuperuser",
interactive=False,
username="joe",
email="joe@somewhere.org",
stdout=new_io
)
command_output = new_io.getvalue().strip()
self.assertEqual(command_output, 'Superuser created successfully.')
u = User.objects.get(username="joe")
self.assertEqual(u.email, 'joe@somewhere.org')
# created password should be unusable
self.assertFalse(u.has_usable_password())
def test_verbosity_zero(self):
# We can supress output on the management command
new_io = StringIO()
call_command("createsuperuser",
interactive=False,
username="joe2",
email="joe2@somewhere.org",
verbosity=0,
stdout=new_io
)
command_output = new_io.getvalue().strip()
self.assertEqual(command_output, '')
u = User.objects.get(username="joe2")
self.assertEqual(u.email, 'joe2@somewhere.org')
self.assertFalse(u.has_usable_password())
def test_email_in_username(self):
new_io = StringIO()
call_command("createsuperuser",
interactive=False,
username="joe+admin@somewhere.org",
email="joe@somewhere.org",
stdout=new_io
)
u = User.objects.get(username="joe+admin@somewhere.org")
self.assertEqual(u.email, 'joe@somewhere.org')
self.assertFalse(u.has_usable_password())
@override_settings(AUTH_USER_MODEL='auth.CustomUser')
def test_swappable_user(self):
"A superuser can be created when a custom User model is in use"
# We can use the management command to create a superuser
# We skip validation because the temporary substitution of the
# swappable User model messes with validation.
new_io = StringIO()
call_command("createsuperuser",
interactive=False,
email="joe@somewhere.org",
date_of_birth="1976-04-01",
stdout=new_io,
skip_validation=True
)
command_output = new_io.getvalue().strip()
self.assertEqual(command_output, 'Superuser created successfully.')
u = CustomUser.objects.get(email="joe@somewhere.org")
self.assertEqual(u.date_of_birth, date(1976, 4, 1))
# created password should be unusable
self.assertFalse(u.has_usable_password())
@override_settings(AUTH_USER_MODEL='auth.CustomUser')
def test_swappable_user_missing_required_field(self):
"A Custom superuser won't be created when a required field isn't provided"
# We can use the management command to create a superuser
# We skip validation because the temporary substitution of the
# swappable User model messes with validation.
new_io = StringIO()
with self.assertRaises(CommandError):
call_command("createsuperuser",
interactive=False,
username="joe@somewhere.org",
stdout=new_io,
stderr=new_io,
skip_validation=True
)
self.assertEqual(CustomUser.objects.count(), 0)
class PermissionDuplicationTestCase(TestCase):
def setUp(self):
self._original_permissions = models.Permission._meta.permissions[:]
def tearDown(self):
models.Permission._meta.permissions = self._original_permissions
def test_duplicated_permissions(self):
"""
Test that we show proper error message if we are trying to create
duplicate permissions.
"""
# check duplicated default permission
models.Permission._meta.permissions = [
('change_permission', 'Can edit permission (duplicate)')]
self.assertRaisesRegexp(CommandError,
"The permission codename 'change_permission' clashes with a "
"builtin permission for model 'auth.Permission'.",
create_permissions, models, [], verbosity=0)
# check duplicated custom permissions
models.Permission._meta.permissions = [
('my_custom_permission', 'Some permission'),
('other_one', 'Some other permission'),
('my_custom_permission', 'Some permission with duplicate permission code'),
]
self.assertRaisesRegexp(CommandError,
"The permission codename 'my_custom_permission' is duplicated for model "
"'auth.Permission'.",
create_permissions, models, [], verbosity=0)
# should not raise anything
models.Permission._meta.permissions = [
('my_custom_permission', 'Some permission'),
('other_one', 'Some other permission'),
]
create_permissions(models, [], verbosity=0)

View File

@ -1,11 +1,13 @@
from django.conf import settings
from django.contrib.auth.models import (Group, User, SiteProfileNotAvailable,
UserManager)
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TestCase
from django.test.utils import override_settings
from django.utils import six
@skipIfCustomUser
@override_settings(USE_TZ=False, AUTH_PROFILE_MODULE='')
class ProfileTestCase(TestCase):
@ -31,6 +33,7 @@ class ProfileTestCase(TestCase):
user.get_profile()
@skipIfCustomUser
@override_settings(USE_TZ=False)
class NaturalKeysTestCase(TestCase):
fixtures = ['authtestdata.json']
@ -45,6 +48,7 @@ class NaturalKeysTestCase(TestCase):
self.assertEqual(Group.objects.get_by_natural_key('users'), users_group)
@skipIfCustomUser
@override_settings(USE_TZ=False)
class LoadDataWithoutNaturalKeysTestCase(TestCase):
fixtures = ['regular.json']
@ -55,6 +59,7 @@ class LoadDataWithoutNaturalKeysTestCase(TestCase):
self.assertEqual(group, user.groups.get())
@skipIfCustomUser
@override_settings(USE_TZ=False)
class LoadDataWithNaturalKeysTestCase(TestCase):
fixtures = ['natural.json']
@ -65,6 +70,7 @@ class LoadDataWithNaturalKeysTestCase(TestCase):
self.assertEqual(group, user.groups.get())
@skipIfCustomUser
class UserManagerTestCase(TestCase):
def test_create_user(self):

View File

@ -3,10 +3,12 @@ from datetime import datetime
from django.conf import settings
from django.contrib.auth.backends import RemoteUserBackend
from django.contrib.auth.models import User
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TestCase
from django.utils import timezone
@skipIfCustomUser
class RemoteUserTest(TestCase):
urls = 'django.contrib.auth.tests.urls'
@ -106,6 +108,7 @@ class RemoteUserNoCreateBackend(RemoteUserBackend):
create_unknown_user = False
@skipIfCustomUser
class RemoteUserNoCreateTest(RemoteUserTest):
"""
Contains the same tests as RemoteUserTest, but using a custom auth backend
@ -142,6 +145,7 @@ class CustomRemoteUserBackend(RemoteUserBackend):
return user
@skipIfCustomUser
class RemoteUserCustomTest(RemoteUserTest):
"""
Tests a custom RemoteUserBackend subclass that overrides the clean_username

View File

@ -1,8 +1,12 @@
from django.test import TestCase
from django.test.utils import override_settings
from django.contrib.auth import signals
from django.contrib.auth.models import User
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TestCase
from django.test.client import RequestFactory
from django.test.utils import override_settings
@skipIfCustomUser
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class SignalTestCase(TestCase):
urls = 'django.contrib.auth.tests.urls'
@ -14,27 +18,41 @@ class SignalTestCase(TestCase):
def listener_logout(self, user, **kwargs):
self.logged_out.append(user)
def listener_login_failed(self, sender, credentials, **kwargs):
self.login_failed.append(credentials)
def setUp(self):
"""Set up the listeners and reset the logged in/logged out counters"""
self.logged_in = []
self.logged_out = []
self.login_failed = []
signals.user_logged_in.connect(self.listener_login)
signals.user_logged_out.connect(self.listener_logout)
signals.user_login_failed.connect(self.listener_login_failed)
def tearDown(self):
"""Disconnect the listeners"""
signals.user_logged_in.disconnect(self.listener_login)
signals.user_logged_out.disconnect(self.listener_logout)
signals.user_login_failed.disconnect(self.listener_login_failed)
def test_login(self):
# Only a successful login will trigger the signal.
# Only a successful login will trigger the success signal.
self.client.login(username='testclient', password='bad')
self.assertEqual(len(self.logged_in), 0)
self.assertEqual(len(self.login_failed), 1)
self.assertEqual(self.login_failed[0]['username'], 'testclient')
# verify the password is cleansed
self.assertTrue('***' in self.login_failed[0]['password'])
# Like this:
self.client.login(username='testclient', password='password')
self.assertEqual(len(self.logged_in), 1)
self.assertEqual(self.logged_in[0].username, 'testclient')
# Ensure there were no more failures.
self.assertEqual(len(self.login_failed), 1)
def test_logout_anonymous(self):
# The log_out function will still trigger the signal for anonymous
# users.
@ -47,3 +65,16 @@ class SignalTestCase(TestCase):
self.client.get('/logout/next_page/')
self.assertEqual(len(self.logged_out), 1)
self.assertEqual(self.logged_out[0].username, 'testclient')
def test_update_last_login(self):
"""Ensure that only `last_login` is updated in `update_last_login`"""
user = User.objects.get(pk=3)
old_last_login = user.last_login
user.username = "This username shouldn't get saved"
request = RequestFactory().get('/login')
signals.user_logged_in.send(sender=user.__class__, request=request,
user=user)
user = User.objects.get(pk=3)
self.assertEqual(user.username, 'staff')
self.assertNotEqual(user.last_login, old_last_login)

View File

@ -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 %}

View File

@ -1 +1,4 @@
{% if perms.auth %}Has auth permissions{% endif %}
{% if perms.auth.add_permission %}Has auth.add_permission permissions{% endif %}
{% if perms.nonexisting %}nonexisting perm found{% endif %}
{% if perms.auth.nonexisting in perms %}auth.nonexisting perm found{% endif %}

View File

@ -1 +1 @@
E-mail sent
Email sent

View File

@ -4,10 +4,12 @@ from datetime import date, timedelta
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TestCase
from django.utils import unittest
@skipIfCustomUser
class TokenGeneratorTest(TestCase):
def test_make_token(self):

View File

@ -37,6 +37,10 @@ def auth_processor_perms(request):
return render_to_response('context_processors/auth_attrs_perms.html',
RequestContext(request, {}, processors=[context_processors.auth]))
def auth_processor_perm_in_perms(request):
return render_to_response('context_processors/auth_attrs_perm_in_perms.html',
RequestContext(request, {}, processors=[context_processors.auth]))
def auth_processor_messages(request):
info(request, "Message 1")
return render_to_response('context_processors/auth_attrs_messages.html',
@ -51,6 +55,7 @@ urlpatterns = urlpatterns + patterns('',
(r'^logout/next_page/$', 'django.contrib.auth.views.logout', dict(next_page='/somewhere/')),
(r'^remote_user/$', remote_user_auth_view),
(r'^password_reset_from_email/$', 'django.contrib.auth.views.password_reset', dict(from_email='staffmember@example.com')),
(r'^admin_password_reset/$', 'django.contrib.auth.views.password_reset', dict(is_admin_site=True)),
(r'^login_required/$', login_required(password_reset)),
(r'^login_required_login_url/$', login_required(password_reset, login_url='/somewhere/')),
@ -58,6 +63,7 @@ urlpatterns = urlpatterns + patterns('',
(r'^auth_processor_attr_access/$', auth_processor_attr_access),
(r'^auth_processor_user/$', auth_processor_user),
(r'^auth_processor_perms/$', auth_processor_perms),
(r'^auth_processor_perm_in_perms/$', auth_processor_perm_in_perms),
(r'^auth_processor_messages/$', auth_processor_messages),
url(r'^userpage/(.+)/$', userpage, name="userpage"),
)

View 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)

View File

@ -5,6 +5,7 @@ from django.conf import global_settings, settings
from django.contrib.sites.models import Site, RequestSite
from django.contrib.auth.models import User
from django.core import mail
from django.core.exceptions import SuspiciousOperation
from django.core.urlresolvers import reverse, NoReverseMatch
from django.http import QueryDict
from django.utils.encoding import force_text
@ -16,6 +17,7 @@ from django.test.utils import override_settings
from django.contrib.auth import SESSION_KEY, REDIRECT_FIELD_NAME
from django.contrib.auth.forms import (AuthenticationForm, PasswordChangeForm,
SetPasswordForm, PasswordResetForm)
from django.contrib.auth.tests.utils import skipIfCustomUser
@override_settings(
@ -50,6 +52,7 @@ class AuthViewsTestCase(TestCase):
return self.assertContains(response, escape(force_text(text)), **kwargs)
@skipIfCustomUser
class AuthViewNamedURLTests(AuthViewsTestCase):
urls = 'django.contrib.auth.urls'
@ -75,6 +78,7 @@ class AuthViewNamedURLTests(AuthViewsTestCase):
self.fail("Reversal of url named '%s' failed with NoReverseMatch" % name)
@skipIfCustomUser
class PasswordResetTest(AuthViewsTestCase):
def test_email_not_found(self):
@ -100,6 +104,42 @@ class PasswordResetTest(AuthViewsTestCase):
self.assertEqual(len(mail.outbox), 1)
self.assertEqual("staffmember@example.com", mail.outbox[0].from_email)
def test_admin_reset(self):
"If the reset view is marked as being for admin, the HTTP_HOST header is used for a domain override."
response = self.client.post('/admin_password_reset/',
{'email': 'staffmember@example.com'},
HTTP_HOST='adminsite.com'
)
self.assertEqual(response.status_code, 302)
self.assertEqual(len(mail.outbox), 1)
self.assertTrue("http://adminsite.com" in mail.outbox[0].body)
self.assertEqual(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email)
def test_poisoned_http_host(self):
"Poisoned HTTP_HOST headers can't be used for reset emails"
# This attack is based on the way browsers handle URLs. The colon
# should be used to separate the port, but if the URL contains an @,
# the colon is interpreted as part of a username for login purposes,
# making 'evil.com' the request domain. Since HTTP_HOST is used to
# produce a meaningful reset URL, we need to be certain that the
# HTTP_HOST header isn't poisoned. This is done as a check when get_host()
# is invoked, but we check here as a practical consequence.
with self.assertRaises(SuspiciousOperation):
self.client.post('/password_reset/',
{'email': 'staffmember@example.com'},
HTTP_HOST='www.example:dr.frankenstein@evil.tld'
)
self.assertEqual(len(mail.outbox), 0)
def test_poisoned_http_host_admin_site(self):
"Poisoned HTTP_HOST headers can't be used for reset emails on admin views"
with self.assertRaises(SuspiciousOperation):
self.client.post('/admin_password_reset/',
{'email': 'staffmember@example.com'},
HTTP_HOST='www.example:dr.frankenstein@evil.tld'
)
self.assertEqual(len(mail.outbox), 0)
def _test_confirm_start(self):
# Start by creating the email
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})
@ -172,6 +212,30 @@ class PasswordResetTest(AuthViewsTestCase):
self.assertContainsEscaped(response, SetPasswordForm.error_messages['password_mismatch'])
@override_settings(AUTH_USER_MODEL='auth.CustomUser')
class CustomUserPasswordResetTest(AuthViewsTestCase):
fixtures = ['custom_user.json']
def _test_confirm_start(self):
# Start by creating the email
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})
self.assertEqual(response.status_code, 302)
self.assertEqual(len(mail.outbox), 1)
return self._read_signup_email(mail.outbox[0])
def _read_signup_email(self, email):
urlmatch = re.search(r"https?://[^/]*(/.*reset/\S*)", email.body)
self.assertTrue(urlmatch is not None, "No URL found in sent email")
return urlmatch.group(), urlmatch.groups()[0]
def test_confirm_valid_custom_user(self):
url, path = self._test_confirm_start()
response = self.client.get(path)
# redirect to a 'complete' page:
self.assertContains(response, "Please enter your new password")
@skipIfCustomUser
class ChangePasswordTest(AuthViewsTestCase):
def fail_login(self, password='password'):
@ -231,6 +295,7 @@ class ChangePasswordTest(AuthViewsTestCase):
self.assertTrue(response['Location'].endswith('/login/?next=/password_change/done/'))
@skipIfCustomUser
class LoginTest(AuthViewsTestCase):
def test_current_site_in_context_after_login(self):
@ -289,6 +354,7 @@ class LoginTest(AuthViewsTestCase):
"%s should be allowed" % good_url)
@skipIfCustomUser
class LoginURLSettings(AuthViewsTestCase):
def setUp(self):
@ -347,6 +413,7 @@ class LoginURLSettings(AuthViewsTestCase):
querystring.urlencode('/')))
@skipIfCustomUser
class LogoutTest(AuthViewsTestCase):
def confirm_logged_out(self):

View File

@ -4,6 +4,7 @@ from django.utils.http import int_to_base36, base36_to_int
from django.utils.crypto import constant_time_compare, salted_hmac
from django.utils import six
class PasswordResetTokenGenerator(object):
"""
Strategy object used to generate and check tokens for the password

View File

@ -15,10 +15,9 @@ from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
# Avoid shadowing the login() and logout() views below.
from django.contrib.auth import REDIRECT_FIELD_NAME, login as auth_login, logout as auth_logout
from django.contrib.auth import REDIRECT_FIELD_NAME, login as auth_login, logout as auth_logout, get_user_model
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm, SetPasswordForm, PasswordChangeForm
from django.contrib.auth.models import User
from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.models import get_current_site
@ -74,6 +73,7 @@ def login(request, template_name='registration/login.html',
return TemplateResponse(request, template_name, context,
current_app=current_app)
def logout(request, next_page=None,
template_name='registration/logged_out.html',
redirect_field_name=REDIRECT_FIELD_NAME,
@ -104,6 +104,7 @@ def logout(request, next_page=None,
# Redirect to this page until the session has been cleared.
return HttpResponseRedirect(next_page or request.path)
def logout_then_login(request, login_url=None, current_app=None, extra_context=None):
"""
Logs out the user if he is logged in. Then redirects to the log-in page.
@ -113,6 +114,7 @@ def logout_then_login(request, login_url=None, current_app=None, extra_context=N
login_url = resolve_url(login_url)
return logout(request, login_url, current_app=current_app, extra_context=extra_context)
def redirect_to_login(next, login_url=None,
redirect_field_name=REDIRECT_FIELD_NAME):
"""
@ -128,6 +130,7 @@ def redirect_to_login(next, login_url=None,
return HttpResponseRedirect(urlunparse(login_url_parts))
# 4 views for password reset:
# - password_reset sends the mail
# - password_reset_done shows a success message for the above
@ -160,7 +163,7 @@ def password_reset(request, is_admin_site=False,
'request': request,
}
if is_admin_site:
opts = dict(opts, domain_override=request.META['HTTP_HOST'])
opts = dict(opts, domain_override=request.get_host())
form.save(**opts)
return HttpResponseRedirect(post_reset_redirect)
else:
@ -173,6 +176,7 @@ def password_reset(request, is_admin_site=False,
return TemplateResponse(request, template_name, context,
current_app=current_app)
def password_reset_done(request,
template_name='registration/password_reset_done.html',
current_app=None, extra_context=None):
@ -182,6 +186,7 @@ def password_reset_done(request,
return TemplateResponse(request, template_name, context,
current_app=current_app)
# Doesn't need csrf_protect since no-one can guess the URL
@sensitive_post_parameters()
@never_cache
@ -195,13 +200,14 @@ def password_reset_confirm(request, uidb36=None, token=None,
View that checks the hash in a password reset link and presents a
form for entering a new password.
"""
assert uidb36 is not None and token is not None # checked by URLconf
UserModel = get_user_model()
assert uidb36 is not None and token is not None # checked by URLconf
if post_reset_redirect is None:
post_reset_redirect = reverse('django.contrib.auth.views.password_reset_complete')
try:
uid_int = base36_to_int(uidb36)
user = User.objects.get(id=uid_int)
except (ValueError, OverflowError, User.DoesNotExist):
user = UserModel.objects.get(id=uid_int)
except (ValueError, OverflowError, UserModel.DoesNotExist):
user = None
if user is not None and token_generator.check_token(user, token):
@ -225,6 +231,7 @@ def password_reset_confirm(request, uidb36=None, token=None,
return TemplateResponse(request, template_name, context,
current_app=current_app)
def password_reset_complete(request,
template_name='registration/password_reset_complete.html',
current_app=None, extra_context=None):
@ -236,6 +243,7 @@ def password_reset_complete(request,
return TemplateResponse(request, template_name, context,
current_app=current_app)
@sensitive_post_parameters()
@csrf_protect
@login_required
@ -261,6 +269,7 @@ def password_change(request,
return TemplateResponse(request, template_name, context,
current_app=current_app)
@login_required
def password_change_done(request,
template_name='registration/password_change_done.html',

View File

@ -1,11 +1,22 @@
from __future__ import unicode_literals
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.comments.models import Comment
from django.utils.translation import ugettext_lazy as _, ungettext
from django.contrib.comments import get_model
from django.contrib.comments.views.moderation import perform_flag, perform_approve, perform_delete
class UsernameSearch(object):
"""The User object may not be auth.User, so we need to provide
a mechanism for issuing the equivalent of a .filter(user__username=...)
search in CommentAdmin.
"""
def __str__(self):
return 'user__%s' % get_user_model().USERNAME_FIELD
class CommentsAdmin(admin.ModelAdmin):
fieldsets = (
(None,
@ -24,7 +35,7 @@ class CommentsAdmin(admin.ModelAdmin):
date_hierarchy = 'submit_date'
ordering = ('-submit_date',)
raw_id_fields = ('user',)
search_fields = ('comment', 'user__username', 'user_name', 'user_email', 'user_url', 'ip_address')
search_fields = ('comment', UsernameSearch(), 'user_name', 'user_email', 'user_url', 'ip_address')
actions = ["flag_comments", "approve_comments", "remove_comments"]
def get_actions(self, request):

View File

@ -1,30 +1,27 @@
from django.conf import settings
from django.contrib.syndication.views import Feed
from django.contrib.sites.models import Site
from django.contrib.sites.models import get_current_site
from django.contrib import comments
from django.utils.translation import ugettext as _
class LatestCommentFeed(Feed):
"""Feed of latest comments on the current site."""
def __call__(self, request, *args, **kwargs):
self.site = get_current_site(request)
return super(LatestCommentFeed, self).__call__(request, *args, **kwargs)
def title(self):
if not hasattr(self, '_site'):
self._site = Site.objects.get_current()
return _("%(site_name)s comments") % dict(site_name=self._site.name)
return _("%(site_name)s comments") % dict(site_name=self.site.name)
def link(self):
if not hasattr(self, '_site'):
self._site = Site.objects.get_current()
return "http://%s/" % (self._site.domain)
return "http://%s/" % (self.site.domain)
def description(self):
if not hasattr(self, '_site'):
self._site = Site.objects.get_current()
return _("Latest comments on %(site_name)s") % dict(site_name=self._site.name)
return _("Latest comments on %(site_name)s") % dict(site_name=self.site.name)
def items(self):
qs = comments.get_model().objects.filter(
site__pk = settings.SITE_ID,
site__pk = self.site.pk,
is_public = True,
is_removed = False,
)

View File

@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-03-23 02:37+0100\n"
"POT-Creation-Date: 2012-10-15 10:56+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English <en@li.org>\n"
@ -13,57 +13,57 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: admin.py:12
#: admin.py:25
msgid "Content"
msgstr ""
#: admin.py:15
#: admin.py:28
msgid "Metadata"
msgstr ""
#: admin.py:42
#: admin.py:55
msgid "flagged"
msgid_plural "flagged"
msgstr[0] ""
msgstr[1] ""
#: admin.py:43
#: admin.py:56
msgid "Flag selected comments"
msgstr ""
#: admin.py:47
#: admin.py:60
msgid "approved"
msgid_plural "approved"
msgstr[0] ""
msgstr[1] ""
#: admin.py:48
#: admin.py:61
msgid "Approve selected comments"
msgstr ""
#: admin.py:52
#: admin.py:65
msgid "removed"
msgid_plural "removed"
msgstr[0] ""
msgstr[1] ""
#: admin.py:53
#: admin.py:66
msgid "Remove selected comments"
msgstr ""
#: admin.py:65
#: admin.py:78
#, python-format
msgid "1 comment was successfully %(action)s."
msgid_plural "%(count)s comments were successfully %(action)s."
msgstr[0] ""
msgstr[1] ""
#: feeds.py:13
#: feeds.py:14
#, python-format
msgid "%(site_name)s comments"
msgstr ""
#: feeds.py:23
#: feeds.py:20
#, python-format
msgid "Latest comments on %(site_name)s"
msgstr ""
@ -100,78 +100,78 @@ msgid ""
"If you enter anything in this field your comment will be treated as spam"
msgstr ""
#: models.py:22
#: models.py:23
msgid "content type"
msgstr ""
#: models.py:24
#: models.py:25
msgid "object ID"
msgstr ""
#: models.py:50 models.py:168
#: models.py:53 models.py:177
msgid "user"
msgstr ""
#: models.py:52
#: models.py:55
msgid "user's name"
msgstr ""
#: models.py:53
#: models.py:56
msgid "user's email address"
msgstr ""
#: models.py:54
#: models.py:57
msgid "user's URL"
msgstr ""
#: models.py:56 models.py:76 models.py:169
#: models.py:59 models.py:79 models.py:178
msgid "comment"
msgstr ""
#: models.py:59
#: models.py:62
msgid "date/time submitted"
msgstr ""
#: models.py:60
#: models.py:63
msgid "IP address"
msgstr ""
#: models.py:61
#: models.py:64
msgid "is public"
msgstr ""
#: models.py:62
#: models.py:65
msgid ""
"Uncheck this box to make the comment effectively disappear from the site."
msgstr ""
#: models.py:64
#: models.py:67
msgid "is removed"
msgstr ""
#: models.py:65
#: models.py:68
msgid ""
"Check this box if the comment is inappropriate. A \"This comment has been "
"removed\" message will be displayed instead."
msgstr ""
#: models.py:77
#: models.py:80
msgid "comments"
msgstr ""
#: models.py:119
#: models.py:124
msgid ""
"This comment was posted by an authenticated user and thus the name is read-"
"only."
msgstr ""
#: models.py:128
#: models.py:134
msgid ""
"This comment was posted by an authenticated user and thus the email is read-"
"only."
msgstr ""
#: models.py:153
#: models.py:160
#, python-format
msgid ""
"Posted by %(user)s at %(date)s\n"
@ -181,19 +181,19 @@ msgid ""
"http://%(domain)s%(url)s"
msgstr ""
#: models.py:170
#: models.py:179
msgid "flag"
msgstr ""
#: models.py:171
#: models.py:180
msgid "date"
msgstr ""
#: models.py:181
#: models.py:190
msgid "comment flag"
msgstr ""
#: models.py:182
#: models.py:191
msgid "comment flags"
msgstr ""

View File

@ -1,16 +1,16 @@
from django.contrib.auth.models import User
from django.conf import settings
from django.contrib.comments.managers import CommentManager
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.db import models
from django.core import urlresolvers
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.conf import settings
from django.utils.encoding import python_2_unicode_compatible
COMMENT_MAX_LENGTH = getattr(settings,'COMMENT_MAX_LENGTH',3000)
COMMENT_MAX_LENGTH = getattr(settings, 'COMMENT_MAX_LENGTH', 3000)
class BaseCommentAbstractModel(models.Model):
"""
@ -19,14 +19,14 @@ class BaseCommentAbstractModel(models.Model):
"""
# Content-object field
content_type = models.ForeignKey(ContentType,
content_type = models.ForeignKey(ContentType,
verbose_name=_('content type'),
related_name="content_type_set_for_%(class)s")
object_pk = models.TextField(_('object ID'))
object_pk = models.TextField(_('object ID'))
content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_pk")
# Metadata about the comment
site = models.ForeignKey(Site)
site = models.ForeignKey(Site)
class Meta:
abstract = True
@ -40,6 +40,7 @@ class BaseCommentAbstractModel(models.Model):
args=(self.content_type_id, self.object_pk)
)
@python_2_unicode_compatible
class Comment(BaseCommentAbstractModel):
"""
@ -49,21 +50,21 @@ class Comment(BaseCommentAbstractModel):
# Who posted this comment? If ``user`` is set then it was an authenticated
# user; otherwise at least user_name should have been set and the comment
# was posted by a non-authenticated user.
user = models.ForeignKey(User, verbose_name=_('user'),
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'),
blank=True, null=True, related_name="%(class)s_comments")
user_name = models.CharField(_("user's name"), max_length=50, blank=True)
user_email = models.EmailField(_("user's email address"), blank=True)
user_url = models.URLField(_("user's URL"), blank=True)
user_name = models.CharField(_("user's name"), max_length=50, blank=True)
user_email = models.EmailField(_("user's email address"), blank=True)
user_url = models.URLField(_("user's URL"), blank=True)
comment = models.TextField(_('comment'), max_length=COMMENT_MAX_LENGTH)
# Metadata about the comment
submit_date = models.DateTimeField(_('date/time submitted'), default=None)
ip_address = models.IPAddressField(_('IP address'), blank=True, null=True)
is_public = models.BooleanField(_('is public'), default=True,
ip_address = models.IPAddressField(_('IP address'), blank=True, null=True)
is_public = models.BooleanField(_('is public'), default=True,
help_text=_('Uncheck this box to make the comment effectively ' \
'disappear from the site.'))
is_removed = models.BooleanField(_('is removed'), default=False,
is_removed = models.BooleanField(_('is removed'), default=False,
help_text=_('Check this box if the comment is inappropriate. ' \
'A "This comment has been removed" message will ' \
'be displayed instead.'))
@ -95,9 +96,9 @@ class Comment(BaseCommentAbstractModel):
"""
if not hasattr(self, "_userinfo"):
userinfo = {
"name" : self.user_name,
"email" : self.user_email,
"url" : self.user_url
"name": self.user_name,
"email": self.user_email,
"url": self.user_url
}
if self.user_id:
u = self.user
@ -110,13 +111,14 @@ class Comment(BaseCommentAbstractModel):
if u.get_full_name():
userinfo["name"] = self.user.get_full_name()
elif not self.user_name:
userinfo["name"] = u.username
userinfo["name"] = u.get_username()
self._userinfo = userinfo
return self._userinfo
userinfo = property(_get_userinfo, doc=_get_userinfo.__doc__)
def _get_name(self):
return self.userinfo["name"]
def _set_name(self, val):
if self.user_id:
raise AttributeError(_("This comment was posted by an authenticated "\
@ -126,6 +128,7 @@ class Comment(BaseCommentAbstractModel):
def _get_email(self):
return self.userinfo["email"]
def _set_email(self, val):
if self.user_id:
raise AttributeError(_("This comment was posted by an authenticated "\
@ -135,6 +138,7 @@ class Comment(BaseCommentAbstractModel):
def _get_url(self):
return self.userinfo["url"]
def _set_url(self, val):
self.user_url = val
url = property(_get_url, _set_url, doc="The URL given by the user who posted this comment")
@ -155,6 +159,7 @@ class Comment(BaseCommentAbstractModel):
}
return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % d
@python_2_unicode_compatible
class CommentFlag(models.Model):
"""
@ -169,9 +174,9 @@ class CommentFlag(models.Model):
design users are only allowed to flag a comment with a given flag once;
if you want rating look elsewhere.
"""
user = models.ForeignKey(User, verbose_name=_('user'), related_name="comment_flags")
comment = models.ForeignKey(Comment, verbose_name=_('comment'), related_name="flags")
flag = models.CharField(_('flag'), max_length=30, db_index=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'), related_name="comment_flags")
comment = models.ForeignKey(Comment, verbose_name=_('comment'), related_name="flags")
flag = models.CharField(_('flag'), max_length=30, db_index=True)
flag_date = models.DateTimeField(_('date'), default=None)
# Constants for flag types
@ -187,7 +192,7 @@ class CommentFlag(models.Model):
def __str__(self):
return "%s flag of comment ID %s by %s" % \
(self.flag, self.comment_id, self.user.username)
(self.flag, self.comment_id, self.user.get_username())
def save(self, *args, **kwargs):
if self.flag_date is None:

View File

@ -62,7 +62,7 @@ from django.contrib.comments import signals
from django.db.models.base import ModelBase
from django.template import Context, loader
from django.contrib import comments
from django.contrib.sites.models import Site
from django.contrib.sites.models import get_current_site
from django.utils import timezone
class AlreadyModerated(Exception):
@ -240,7 +240,7 @@ class CommentModerator(object):
t = loader.get_template('comments/comment_notification_email.txt')
c = Context({ 'comment': comment,
'content_object': content_object })
subject = '[%s] New comment posted on "%s"' % (Site.objects.get_current().name,
subject = '[%s] New comment posted on "%s"' % (get_current_site(request).name,
content_object)
message = t.render(c)
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, recipient_list, fail_silently=True)

View File

@ -15,7 +15,6 @@ from django.views.decorators.csrf import csrf_protect
from django.views.decorators.http import require_POST
class CommentPostBadRequest(http.HttpResponseBadRequest):
"""
Response returned when a comment post is invalid. If ``DEBUG`` is on a
@ -27,6 +26,7 @@ class CommentPostBadRequest(http.HttpResponseBadRequest):
if settings.DEBUG:
self.content = render_to_string("comments/400-debug.html", {"why": why})
@csrf_protect
@require_POST
def post_comment(request, next=None, using=None):
@ -40,7 +40,7 @@ def post_comment(request, next=None, using=None):
data = request.POST.copy()
if request.user.is_authenticated():
if not data.get('name', ''):
data["name"] = request.user.get_full_name() or request.user.username
data["name"] = request.user.get_full_name() or request.user.get_username()
if not data.get('email', ''):
data["email"] = request.user.email
@ -98,8 +98,8 @@ def post_comment(request, next=None, using=None):
]
return render_to_response(
template_list, {
"comment" : form.data.get("comment", ""),
"form" : form,
"comment": form.data.get("comment", ""),
"form": form,
"next": next,
},
RequestContext(request, {})
@ -113,9 +113,9 @@ def post_comment(request, next=None, using=None):
# Signal that the comment is about to be saved
responses = signals.comment_will_be_posted.send(
sender = comment.__class__,
comment = comment,
request = request
sender=comment.__class__,
comment=comment,
request=request
)
for (receiver, response) in responses:
@ -126,15 +126,14 @@ def post_comment(request, next=None, using=None):
# Save the comment and signal that it was saved
comment.save()
signals.comment_was_posted.send(
sender = comment.__class__,
comment = comment,
request = request
sender=comment.__class__,
comment=comment,
request=request
)
return next_redirect(data, next, comment_done, c=comment._get_pk_val())
comment_done = confirmation_view(
template = "comments/posted.html",
doc = """Display a "comment was posted" success page."""
template="comments/posted.html",
doc="""Display a "comment was posted" success page."""
)

View File

@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-03-23 02:37+0100\n"
"POT-Creation-Date: 2012-10-15 10:56+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English <en@li.org>\n"
@ -13,29 +13,29 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: models.py:123
#: models.py:130
msgid "python model class name"
msgstr ""
#: models.py:127
#: models.py:134
msgid "content type"
msgstr ""
#: models.py:128
#: models.py:135
msgid "content types"
msgstr ""
#: views.py:15
#: views.py:17
#, python-format
msgid "Content type %(ct_id)s object has no associated model"
msgstr ""
#: views.py:19
#: views.py:21
#, python-format
msgid "Content type %(ct_id)s object %(obj_id)s doesn't exist"
msgstr ""
#: views.py:25
#: views.py:27
#, python-format
msgid "%(ct_name)s objects don't have a get_absolute_url() method"
msgstr ""

View File

@ -3,7 +3,7 @@ from __future__ import unicode_literals
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.views import shortcut
from django.contrib.sites.models import Site
from django.contrib.sites.models import Site, get_current_site
from django.http import HttpRequest, Http404
from django.test import TestCase
from django.utils.http import urlquote
@ -219,9 +219,8 @@ class ContentTypesTests(TestCase):
obj = FooWithUrl.objects.create(name="john")
if Site._meta.installed:
current_site = Site.objects.get_current()
response = shortcut(request, user_ct.id, obj.id)
self.assertEqual("http://%s/users/john/" % current_site.domain,
self.assertEqual("http://%s/users/john/" % get_current_site(request).domain,
response._headers.get("location")[1])
Site._meta.installed = False

View File

@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-03-23 02:37+0100\n"
"POT-Creation-Date: 2012-10-15 10:56+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English <en@li.org>\n"
@ -17,7 +17,7 @@ msgstr ""
msgid "Advanced options"
msgstr ""
#: forms.py:7 models.py:7
#: forms.py:7 models.py:11
msgid "URL"
msgstr ""
@ -45,40 +45,44 @@ msgstr ""
msgid "Flatpage with url %(url)s already exists for site %(site)s"
msgstr ""
#: models.py:8
#: models.py:12
msgid "title"
msgstr ""
#: models.py:9
#: models.py:13
msgid "content"
msgstr ""
#: models.py:10
#: models.py:14
msgid "enable comments"
msgstr ""
#: models.py:11
#: models.py:15
msgid "template name"
msgstr ""
#: models.py:12
#: models.py:16
msgid ""
"Example: 'flatpages/contact_page.html'. If this isn't provided, the system "
"will use 'flatpages/default.html'."
msgstr ""
#: models.py:13
#: models.py:17
msgid "registration required"
msgstr ""
#: models.py:13
#: models.py:17
msgid "If this is checked, only logged-in users will be able to view the page."
msgstr ""
#: models.py:18
#: models.py:22
msgid "flat page"
msgstr ""
#: models.py:19
#: models.py:23
msgid "flat pages"
msgstr ""
#: tests/forms.py:97
msgid "This field is required."
msgstr ""

View File

@ -1,6 +1,7 @@
from django import template
from django.conf import settings
from django.contrib.flatpages.models import FlatPage
from django.contrib.sites.models import get_current_site
register = template.Library()
@ -19,7 +20,11 @@ class FlatpageNode(template.Node):
self.user = None
def render(self, context):
flatpages = FlatPage.objects.filter(sites__id=settings.SITE_ID)
if 'request' in context:
site_pk = get_current_site(context['request']).pk
else:
site_pk = settings.SITE_ID
flatpages = FlatPage.objects.filter(sites__id=site_pk)
# If a prefix was specified, add a filter
if self.starts_with:
flatpages = flatpages.filter(

View File

@ -1,9 +1,10 @@
from django.contrib.flatpages.models import FlatPage
from django.template import loader, RequestContext
from django.shortcuts import get_object_or_404
from django.http import Http404, HttpResponse, HttpResponsePermanentRedirect
from django.conf import settings
from django.contrib.flatpages.models import FlatPage
from django.contrib.sites.models import get_current_site
from django.core.xheaders import populate_xheaders
from django.http import Http404, HttpResponse, HttpResponsePermanentRedirect
from django.shortcuts import get_object_or_404
from django.template import loader, RequestContext
from django.utils.safestring import mark_safe
from django.views.decorators.csrf import csrf_protect
@ -30,14 +31,15 @@ def flatpage(request, url):
"""
if not url.startswith('/'):
url = '/' + url
site_id = get_current_site(request).id
try:
f = get_object_or_404(FlatPage,
url__exact=url, sites__id__exact=settings.SITE_ID)
url__exact=url, sites__id__exact=site_id)
except Http404:
if not url.endswith('/') and settings.APPEND_SLASH:
url += '/'
f = get_object_or_404(FlatPage,
url__exact=url, sites__id__exact=settings.SITE_ID)
url__exact=url, sites__id__exact=site_id)
return HttpResponsePermanentRedirect('%s/' % request.path)
else:
raise

View File

@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-03-23 02:38+0100\n"
"POT-Creation-Date: 2012-10-15 10:56+0200\n"
"PO-Revision-Date: 2010-05-13 15:35+0200\n"
"Last-Translator: Django team\n"
"Language-Team: English <en@li.org>\n"

View File

@ -12,6 +12,7 @@ from django.conf import settings
from django.contrib.formtools import preview, utils
from django.contrib.formtools.wizard import FormWizard
from django.test import TestCase
from django.test.html import parse_html
from django.test.utils import override_settings
from django.utils import unittest
@ -218,7 +219,6 @@ class DummyRequest(http.HttpRequest):
)
class WizardTests(TestCase):
urls = 'django.contrib.formtools.tests.urls'
input_re = re.compile('name="([^"]+)" value="([^"]+)"')
wizard_step_data = (
{
'0-name': 'Pony',
@ -409,14 +409,13 @@ class WizardTests(TestCase):
"""
Pull the appropriate field data from the context to pass to the next wizard step
"""
previous_fields = response.context['previous_fields']
previous_fields = parse_html(response.context['previous_fields'])
fields = {'wizard_step': response.context['step0']}
def grab(m):
fields[m.group(1)] = m.group(2)
return ''
for input_field in previous_fields:
input_attrs = dict(input_field.attributes)
fields[input_attrs["name"]] = input_attrs["value"]
self.input_re.sub(grab, previous_fields)
return fields
def check_wizard_step(self, response, step_no):
@ -428,7 +427,6 @@ class WizardTests(TestCase):
"""
step_count = len(self.wizard_step_data)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Step %d of %d' % (step_no, step_count))
data = self.grab_field_data(response)

View File

@ -1,3 +1,5 @@
import json
from django.test import TestCase
from django.core import signing
from django.core.exceptions import SuspiciousOperation
@ -41,4 +43,5 @@ class TestCookieStorage(TestStorage, TestCase):
storage.init_data()
storage.update_response(response)
unsigned_cookie_data = cookie_signer.unsign(response.cookies[storage.prefix].value)
self.assertEqual(unsigned_cookie_data, '{"step_files":{},"step":null,"extra_data":{},"step_data":{}}')
self.assertEqual(json.loads(unsigned_cookie_data),
{"step_files": {}, "step": None, "extra_data": {}, "step_data": {}})

View File

@ -0,0 +1,6 @@
from django.utils import six
if six.PY3:
memoryview = memoryview
else:
memoryview = buffer

View File

@ -1,3 +1,5 @@
import logging
from django.forms.widgets import Textarea
from django.template import loader, Context
from django.templatetags.static import static
@ -10,6 +12,8 @@ from django.contrib.gis.geos import GEOSGeometry, GEOSException, fromstr
# Creating a template context that contains Django settings
# values needed by admin map templates.
geo_context = Context({'LANGUAGE_BIDI' : translation.get_language_bidi()})
logger = logging.getLogger('django.contrib.gis')
class OpenLayersWidget(Textarea):
"""
@ -29,7 +33,11 @@ class OpenLayersWidget(Textarea):
if isinstance(value, six.string_types):
try:
value = GEOSGeometry(value)
except (GEOSException, ValueError):
except (GEOSException, ValueError) as err:
logger.error(
"Error creating geometry from value '%s' (%s)" % (
value, err)
)
value = None
if value and value.geom_type.upper() != self.geom_type:
@ -56,7 +64,11 @@ class OpenLayersWidget(Textarea):
ogr = value.ogr
ogr.transform(srid)
wkt = ogr.wkt
except OGRException:
except OGRException as err:
logger.error(
"Error transforming geometry from srid '%s' to srid '%s' (%s)" % (
value.srid, srid, err)
)
wkt = ''
else:
wkt = value.wkt

View File

@ -32,8 +32,9 @@ class BaseSpatialOperations(object):
# How the geometry column should be selected.
select = None
# Does the spatial database have a geography type?
# Does the spatial database have a geometry or geography type?
geography = False
geometry = False
area = False
centroid = False
@ -116,6 +117,16 @@ class BaseSpatialOperations(object):
"""
raise NotImplementedError
def get_expression_column(self, evaluator):
"""
Helper method to return the quoted column string from the evaluator
for its expression.
"""
for expr, col_tup in evaluator.cols:
if expr is evaluator.expression:
return '%s.%s' % tuple(map(self.quote_name, col_tup))
raise Exception("Could not find the column for the expression.")
# Spatial SQL Construction
def spatial_aggregate_sql(self, agg):
raise NotImplementedError('Aggregate support not implemented for this spatial backend.')

View File

@ -44,7 +44,7 @@ class MySQLOperations(DatabaseOperations, BaseSpatialOperations):
modify the placeholder based on the contents of the given value.
"""
if hasattr(value, 'expression'):
placeholder = '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
placeholder = self.get_expression_column(value)
else:
placeholder = '%s(%%s)' % self.from_text
return placeholder

View File

@ -213,7 +213,7 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations):
placeholder = '%s'
# No geometry value used for F expression, substitue in
# the column name instead.
return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
return placeholder % self.get_expression_column(value)
else:
if transform_value(value, f.srid):
return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (self.transform, value.srid, f.srid)

View File

@ -1,6 +1,7 @@
"""
This object provides quoting for GEOS geometries into PostgreSQL/PostGIS.
"""
from __future__ import unicode_literals
from psycopg2 import Binary
from psycopg2.extensions import ISQLQuote
@ -10,7 +11,7 @@ class PostGISAdapter(object):
"Initializes on the geometry."
# Getting the WKB (in string form, to allow easy pickling of
# the adaptor) and the SRID from the geometry.
self.ewkb = str(geom.ewkb)
self.ewkb = bytes(geom.ewkb)
self.srid = geom.srid
self._adapter = Binary(self.ewkb)
@ -39,7 +40,7 @@ class PostGISAdapter(object):
def getquoted(self):
"Returns a properly quoted string for use in PostgreSQL/PostGIS."
# psycopg will figure out whether to use E'\\000' or '\000'
return 'ST_GeomFromEWKB(%s)' % self._adapter.getquoted()
return str('ST_GeomFromEWKB(%s)' % self._adapter.getquoted().decode())
def prepare_database_save(self, unused):
return self

View File

@ -1,9 +1,11 @@
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation
class PostGISCreation(DatabaseCreation):
geom_index_type = 'GIST'
geom_index_opts = 'GIST_GEOMETRY_OPS'
geom_index_ops = 'GIST_GEOMETRY_OPS'
geom_index_ops_nd = 'GIST_GEOMETRY_OPS_ND'
def sql_indexes_for_field(self, model, f, style):
"Return any spatial index creation SQL for the field."
@ -16,8 +18,9 @@ class PostGISCreation(DatabaseCreation):
qn = self.connection.ops.quote_name
db_table = model._meta.db_table
if f.geography:
# Geogrophy columns are created normally.
if f.geography or self.connection.ops.geometry:
# Geography and Geometry (PostGIS 2.0+) columns are
# created normally.
pass
else:
# Geometry columns are created by `AddGeometryColumn`
@ -38,23 +41,31 @@ class PostGISCreation(DatabaseCreation):
style.SQL_FIELD(qn(f.column)) +
style.SQL_KEYWORD(' SET NOT NULL') + ';')
if f.spatial_index:
# Spatial indexes created the same way for both Geometry and
# Geography columns
# Geography columns.
# PostGIS 2.0 does not support GIST_GEOMETRY_OPS. So, on 1.5
# we use GIST_GEOMETRY_OPS, on 2.0 we use either "nd" ops
# which are fast on multidimensional cases, or just plain
# gist index for the 2d case.
if f.geography:
index_opts = ''
index_ops = ''
elif self.connection.ops.geometry:
if f.dim > 2:
index_ops = ' ' + style.SQL_KEYWORD(self.geom_index_ops_nd)
else:
index_ops = ''
else:
index_opts = ' ' + style.SQL_KEYWORD(self.geom_index_opts)
index_ops = ' ' + style.SQL_KEYWORD(self.geom_index_ops)
output.append(style.SQL_KEYWORD('CREATE INDEX ') +
style.SQL_TABLE(qn('%s_%s_id' % (db_table, f.column))) +
style.SQL_KEYWORD(' ON ') +
style.SQL_TABLE(qn(db_table)) +
style.SQL_KEYWORD(' USING ') +
style.SQL_COLTYPE(self.geom_index_type) + ' ( ' +
style.SQL_FIELD(qn(f.column)) + index_opts + ' );')
style.SQL_FIELD(qn(f.column)) + index_ops + ' );')
return output
def sql_table_creation_suffix(self):
qn = self.connection.ops.quote_name
return ' TEMPLATE %s' % qn(getattr(settings, 'POSTGIS_TEMPLATE', 'template_postgis'))
postgis_template = getattr(settings, 'POSTGIS_TEMPLATE', 'template_postgis')
return ' TEMPLATE %s' % self.connection.ops.quote_name(postgis_template)

View File

@ -103,11 +103,12 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
self.geom_func_prefix = prefix
self.spatial_version = version
except DatabaseError:
raise ImproperlyConfigured('Cannot determine PostGIS version for database "%s". '
'GeoDjango requires at least PostGIS version 1.3. '
'Was the database created from a spatial database '
'template?' % self.connection.settings_dict['NAME']
)
raise ImproperlyConfigured(
'Cannot determine PostGIS version for database "%s". '
'GeoDjango requires at least PostGIS version 1.3. '
'Was the database created from a spatial database '
'template?' % self.connection.settings_dict['NAME']
)
# TODO: Raise helpful exceptions as they become known.
# PostGIS-specific operators. The commented descriptions of these
@ -215,6 +216,10 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
'bboverlaps' : PostGISOperator('&&'),
}
# Native geometry type support added in PostGIS 2.0.
if version >= (2, 0, 0):
self.geometry = True
# Creating a dictionary lookup of all GIS terms for PostGIS.
gis_terms = ['isnull']
gis_terms += list(self.geometry_operators)
@ -231,7 +236,6 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
self.distance_spheroid = prefix + 'distance_spheroid'
self.envelope = prefix + 'Envelope'
self.extent = prefix + 'Extent'
self.extent3d = prefix + 'Extent3D'
self.force_rhr = prefix + 'ForceRHR'
self.geohash = GEOHASH
self.geojson = GEOJSON
@ -239,14 +243,12 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
self.intersection = prefix + 'Intersection'
self.kml = prefix + 'AsKML'
self.length = prefix + 'Length'
self.length3d = prefix + 'Length3D'
self.length_spheroid = prefix + 'length_spheroid'
self.makeline = prefix + 'MakeLine'
self.mem_size = prefix + 'mem_size'
self.num_geom = prefix + 'NumGeometries'
self.num_points =prefix + 'npoints'
self.perimeter = prefix + 'Perimeter'
self.perimeter3d = prefix + 'Perimeter3D'
self.point_on_surface = prefix + 'PointOnSurface'
self.polygonize = prefix + 'Polygonize'
self.reverse = prefix + 'Reverse'
@ -259,6 +261,15 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
self.union = prefix + 'Union'
self.unionagg = prefix + 'Union'
if version >= (2, 0, 0):
self.extent3d = prefix + '3DExtent'
self.length3d = prefix + '3DLength'
self.perimeter3d = prefix + '3DPerimeter'
else:
self.extent3d = prefix + 'Extent3D'
self.length3d = prefix + 'Length3D'
self.perimeter3d = prefix + 'Perimeter3D'
def check_aggregate_support(self, aggregate):
"""
Checks if the given aggregate name is supported (that is, if it's
@ -314,6 +325,14 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
'only with an SRID of 4326.')
return 'geography(%s,%d)'% (f.geom_type, f.srid)
elif self.geometry:
# Postgis 2.0 supports type-based geometries.
# TODO: Support 'M' extension.
if f.dim == 3:
geom_type = f.geom_type + 'Z'
else:
geom_type = f.geom_type
return 'geometry(%s,%d)' % (geom_type, f.srid)
else:
return None
@ -375,7 +394,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
# If this is an F expression, then we don't really want
# a placeholder and instead substitute in the column
# of the expression.
placeholder = placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
placeholder = placeholder % self.get_expression_column(value)
return placeholder

View File

@ -146,6 +146,8 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
except DatabaseError:
# we are using < 2.4.0-RC4
pass
if version >= (3, 0, 0):
self.geojson = 'AsGeoJSON'
def check_aggregate_support(self, aggregate):
"""
@ -208,7 +210,7 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
placeholder = '%s'
# No geometry value used for F expression, substitue in
# the column name instead.
return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
return placeholder % self.get_expression_column(value)
else:
if transform_value(value, f.srid):
# Adding Transform() to the SQL placeholder.

View File

@ -95,7 +95,7 @@ class GeometryField(Field):
# Is this a geography rather than a geometry column?
self.geography = geography
# Oracle-specific private attributes for creating the entrie in
# Oracle-specific private attributes for creating the entry in
# `USER_SDO_GEOM_METADATA`
self._extent = kwargs.pop('extent', (-180.0, -90.0, 180.0, 90.0))
self._tolerance = kwargs.pop('tolerance', 0.05)
@ -160,7 +160,7 @@ class GeometryField(Field):
# from the given string input.
if isinstance(geom, Geometry):
pass
elif isinstance(geom, six.string_types) or hasattr(geom, '__geo_interface__'):
elif isinstance(geom, (bytes, six.string_types)) or hasattr(geom, '__geo_interface__'):
try:
geom = Geometry(geom)
except GeometryException:

View File

@ -5,6 +5,7 @@ corresponding to geographic model fields.
Thanks to Robert Coup for providing this functionality (see #4322).
"""
from django.contrib.gis import memoryview
from django.utils import six
class GeometryProxy(object):
@ -54,7 +55,7 @@ class GeometryProxy(object):
if isinstance(value, self._klass) and (str(value.geom_type).upper() == gtype or gtype == 'GEOMETRY'):
# Assigning the SRID to the geometry.
if value.srid is None: value.srid = self._field.srid
elif value is None or isinstance(value, six.string_types + (buffer,)):
elif value is None or isinstance(value, six.string_types + (memoryview,)):
# Set with None, WKT, HEX, or WKB
pass
else:

View File

@ -1,6 +1,7 @@
from django.db import connections
from django.db.models.query import QuerySet, ValuesQuerySet, ValuesListQuerySet
from django.contrib.gis import memoryview
from django.contrib.gis.db.models import aggregates
from django.contrib.gis.db.models.fields import get_srid_info, PointField, LineStringField
from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery
@ -145,13 +146,14 @@ class GeoQuerySet(QuerySet):
"""
backend = connections[self.db].ops
if not backend.geojson:
raise NotImplementedError('Only PostGIS 1.3.4+ supports GeoJSON serialization.')
raise NotImplementedError('Only PostGIS 1.3.4+ and SpatiaLite 3.0+ '
'support GeoJSON serialization.')
if not isinstance(precision, six.integer_types):
raise TypeError('Precision keyword must be set with an integer.')
# Setting the options flag -- which depends on which version of
# PostGIS we're using.
# PostGIS we're using. SpatiaLite only uses the first group of options.
if backend.spatial_version >= (1, 4, 0):
options = 0
if crs and bbox: options = 3
@ -193,9 +195,9 @@ class GeoQuerySet(QuerySet):
# PostGIS AsGML() aggregate function parameter order depends on the
# version -- uggh.
if backend.spatial_version > (1, 3, 1):
procedure_fmt = '%(version)s,%(geo_col)s,%(precision)s'
s['procedure_fmt'] = '%(version)s,%(geo_col)s,%(precision)s'
else:
procedure_fmt = '%(geo_col)s,%(precision)s,%(version)s'
s['procedure_fmt'] = '%(geo_col)s,%(precision)s,%(version)s'
s['procedure_args'] = {'precision' : precision, 'version' : version}
return self._spatial_attribute('gml', s, **kwargs)
@ -676,7 +678,7 @@ class GeoQuerySet(QuerySet):
if not backend.geography:
if not isinstance(geo_field, PointField):
raise ValueError('Spherical distance calculation only supported on PointFields.')
if not str(Geometry(buffer(params[0].ewkb)).geom_type) == 'Point':
if not str(Geometry(memoryview(params[0].ewkb)).geom_type) == 'Point':
raise ValueError('Spherical distance calculation only supported with Point Geometry parameters')
# The `function` procedure argument needs to be set differently for
# geodetic distance calculations.

View File

@ -1,3 +1,8 @@
try:
from itertools import zip_longest
except ImportError:
from itertools import izip_longest as zip_longest
from django.utils.six.moves import zip
from django.db.backends.util import truncate_name, typecast_timestamp
@ -114,10 +119,10 @@ class GeoSQLCompiler(compiler.SQLCompiler):
result = []
if opts is None:
opts = self.query.model._meta
# Skip all proxy to the root proxied model
opts = opts.concrete_model._meta
aliases = set()
only_load = self.deferred_to_columns()
# Skip all proxy to the root proxied model
proxied_model = opts.concrete_model
if start_alias:
seen = {None: start_alias}
@ -128,12 +133,9 @@ class GeoSQLCompiler(compiler.SQLCompiler):
try:
alias = seen[model]
except KeyError:
if model is proxied_model:
alias = start_alias
else:
link_field = opts.get_ancestor_link(model)
alias = self.query.join((start_alias, model._meta.db_table,
link_field.column, model._meta.pk.column))
link_field = opts.get_ancestor_link(model)
alias = self.query.join((start_alias, model._meta.db_table,
link_field.column, model._meta.pk.column))
seen[model] = alias
else:
# If we're starting from the base model of the queryset, the
@ -190,7 +192,7 @@ class GeoSQLCompiler(compiler.SQLCompiler):
if self.connection.ops.oracle or getattr(self.query, 'geo_values', False):
# We resolve the rest of the columns if we're on Oracle or if
# the `geo_values` attribute is defined.
for value, field in map(None, row[index_start:], fields):
for value, field in zip_longest(row[index_start:], fields):
values.append(self.query.convert_values(value, field, self.connection))
else:
values.extend(row[index_start:])

View File

@ -37,11 +37,11 @@
try:
from django.contrib.gis.gdal.driver import Driver
from django.contrib.gis.gdal.datasource import DataSource
from django.contrib.gis.gdal.libgdal import gdal_version, gdal_full_version, gdal_release_date, GDAL_VERSION
from django.contrib.gis.gdal.libgdal import gdal_version, gdal_full_version, GDAL_VERSION
from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform
from django.contrib.gis.gdal.geometries import OGRGeometry
HAS_GDAL = True
except:
except Exception:
HAS_GDAL = False
try:

View File

@ -45,6 +45,7 @@ from django.contrib.gis.gdal.layer import Layer
# Getting the ctypes prototypes for the DataSource.
from django.contrib.gis.gdal.prototypes import ds as capi
from django.utils.encoding import force_bytes, force_text
from django.utils import six
from django.utils.six.moves import xrange
@ -56,12 +57,14 @@ class DataSource(GDALBase):
"Wraps an OGR Data Source object."
#### Python 'magic' routines ####
def __init__(self, ds_input, ds_driver=False, write=False):
def __init__(self, ds_input, ds_driver=False, write=False, encoding='utf-8'):
# The write flag.
if write:
self._write = 1
else:
self._write = 0
# See also http://trac.osgeo.org/gdal/wiki/rfc23_ogr_unicode
self.encoding = encoding
# Registering all the drivers, this needs to be done
# _before_ we try to open up a data source.
@ -73,7 +76,7 @@ class DataSource(GDALBase):
ds_driver = Driver.ptr_type()
try:
# OGROpen will auto-detect the data source type.
ds = capi.open_ds(ds_input, self._write, byref(ds_driver))
ds = capi.open_ds(force_bytes(ds_input), self._write, byref(ds_driver))
except OGRException:
# Making the error message more clear rather than something
# like "Invalid pointer returned from OGROpen".
@ -102,7 +105,7 @@ class DataSource(GDALBase):
def __getitem__(self, index):
"Allows use of the index [] operator to get a layer at the index."
if isinstance(index, six.string_types):
l = capi.get_layer_by_name(self.ptr, index)
l = capi.get_layer_by_name(self.ptr, force_bytes(index))
if not l: raise OGRIndexError('invalid OGR Layer name given: "%s"' % index)
elif isinstance(index, int):
if index < 0 or index >= self.layer_count:
@ -128,4 +131,5 @@ class DataSource(GDALBase):
@property
def name(self):
"Returns the name of the data source."
return capi.get_ds_name(self._ptr)
name = capi.get_ds_name(self._ptr)
return force_text(name, self.encoding, strings_only=True)

View File

@ -5,6 +5,7 @@ from django.contrib.gis.gdal.error import OGRException
from django.contrib.gis.gdal.prototypes import ds as capi
from django.utils import six
from django.utils.encoding import force_bytes
# For more information, see the OGR C API source code:
# http://www.gdal.org/ogr/ogr__api_8h.html
@ -36,7 +37,7 @@ class Driver(GDALBase):
name = dr_input
# Attempting to get the OGR driver by the string name.
dr = capi.get_driver_by_name(name)
dr = capi.get_driver_by_name(force_bytes(name))
elif isinstance(dr_input, int):
self._register()
dr = capi.get_driver(dr_input)

View File

@ -52,7 +52,7 @@ class Envelope(object):
elif len(args) == 4:
# Individual parameters passed in.
# Thanks to ww for the help
self._from_sequence(map(float, args))
self._from_sequence([float(a) for a in args])
else:
raise OGRException('Incorrect number (%d) of arguments.' % len(args))

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