summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xdjango/bin/compile-messages.py5
-rw-r--r--django/conf/global_settings.py2
-rw-r--r--django/conf/locale/pl/LC_MESSAGES/django.mobin49121 -> 50899 bytes
-rw-r--r--django/conf/locale/pl/LC_MESSAGES/django.po300
-rw-r--r--django/contrib/admin/media/css/base.css4
-rw-r--r--django/contrib/admin/media/js/admin/RelatedObjectLookups.js14
-rw-r--r--django/contrib/admin/templatetags/admin_list.py4
-rw-r--r--django/contrib/admin/templatetags/admin_modify.py2
-rw-r--r--django/contrib/admin/views/main.py7
-rw-r--r--django/core/cache/backends/locmem.py7
-rw-r--r--django/core/management/__init__.py49
-rw-r--r--django/core/paginator.py2
-rw-r--r--django/middleware/gzip.py18
-rw-r--r--django/newforms/extras/widgets.py3
-rw-r--r--django/newforms/models.py37
-rw-r--r--django/newforms/widgets.py25
-rw-r--r--django/template/__init__.py4
-rw-r--r--django/template/defaultfilters.py3
-rw-r--r--django/test/testcases.py6
-rw-r--r--django/utils/cache.py23
-rw-r--r--django/utils/datastructures.py71
-rw-r--r--django/utils/translation/trans_real.py7
-rw-r--r--django/views/debug.py12
-rw-r--r--docs/i18n.txt2
-rw-r--r--docs/settings.txt2
-rw-r--r--docs/templates_python.txt135
-rw-r--r--tests/modeltests/pagination/models.py3
-rw-r--r--tests/regressiontests/cache/tests.py30
-rw-r--r--tests/regressiontests/datastructures/tests.py12
-rw-r--r--tests/regressiontests/forms/error_messages.py360
-rw-r--r--tests/regressiontests/forms/models.py4
-rw-r--r--tests/regressiontests/forms/widgets.py54
-rw-r--r--tests/regressiontests/templates/filters.py6
-rw-r--r--tests/regressiontests/templates/tests.py6
34 files changed, 892 insertions, 327 deletions
diff --git a/django/bin/compile-messages.py b/django/bin/compile-messages.py
index 8693022644..0d71413e5a 100755
--- a/django/bin/compile-messages.py
+++ b/django/bin/compile-messages.py
@@ -11,11 +11,10 @@ except NameError:
def compile_messages(locale=None):
- basedirs = [os.path.join('conf', 'locale'), 'locale']
+ basedirs = (os.path.join('conf', 'locale'), 'locale')
if os.environ.get('DJANGO_SETTINGS_MODULE'):
from django.conf import settings
- if hasattr(settings, 'LOCALE_PATHS'):
- basedirs += settings.LOCALE_PATHS
+ basedirs += settings.LOCALE_PATHS
# Gather existing directories.
basedirs = set(map(os.path.abspath, filter(os.path.isdir, basedirs)))
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 994e908caf..2853d6c618 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -90,6 +90,8 @@ LANGUAGES_BIDI = ("he", "ar", "fa")
# to load the internationalization machinery.
USE_I18N = True
+LOCALE_PATHS = ()
+
# Not-necessarily-technical managers of the site. They get broken link
# notifications and other various e-mails.
MANAGERS = ADMINS
diff --git a/django/conf/locale/pl/LC_MESSAGES/django.mo b/django/conf/locale/pl/LC_MESSAGES/django.mo
index 391647fbb0..b5ed35225d 100644
--- a/django/conf/locale/pl/LC_MESSAGES/django.mo
+++ b/django/conf/locale/pl/LC_MESSAGES/django.mo
Binary files differ
diff --git a/django/conf/locale/pl/LC_MESSAGES/django.po b/django/conf/locale/pl/LC_MESSAGES/django.po
index a5d3549423..e8516bba33 100644
--- a/django/conf/locale/pl/LC_MESSAGES/django.po
+++ b/django/conf/locale/pl/LC_MESSAGES/django.po
@@ -5,7 +5,7 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-11-13 21:55+0100\n"
+"POT-Creation-Date: 2007-11-15 15:28+0100\n"
"PO-Revision-Date: 2007-05-08 20:29+0200\n"
"Last-Translator: Jarek Zgoda <jarek.zgoda@gmail.com>\n"
"Language-Team: Polish <pl@li.org>\n"
@@ -196,7 +196,7 @@ msgstr "Uproszczony chiński"
msgid "Traditional Chinese"
msgstr "Chiński tradycyjny"
-#: contrib/admin/filterspecs.py:42
+#: contrib/admin/filterspecs.py:44
#, python-format
msgid ""
"<h3>By %s:</h3>\n"
@@ -205,71 +205,71 @@ msgstr ""
"<h3>Przez %s:</h3>\n"
"</ul>\n"
-#: contrib/admin/filterspecs.py:72 contrib/admin/filterspecs.py:90
-#: contrib/admin/filterspecs.py:145 contrib/admin/filterspecs.py:171
+#: contrib/admin/filterspecs.py:74 contrib/admin/filterspecs.py:92
+#: contrib/admin/filterspecs.py:147 contrib/admin/filterspecs.py:173
msgid "All"
msgstr "Wszystko"
-#: contrib/admin/filterspecs.py:111
+#: contrib/admin/filterspecs.py:113
msgid "Any date"
msgstr "Dowolna data"
-#: contrib/admin/filterspecs.py:112
+#: contrib/admin/filterspecs.py:114
msgid "Today"
msgstr "Dzisiaj"
-#: contrib/admin/filterspecs.py:115
+#: contrib/admin/filterspecs.py:117
msgid "Past 7 days"
msgstr "Ostatnie 7 dni"
-#: contrib/admin/filterspecs.py:117
+#: contrib/admin/filterspecs.py:119
msgid "This month"
msgstr "Ten miesiąc"
-#: contrib/admin/filterspecs.py:119
+#: contrib/admin/filterspecs.py:121
msgid "This year"
msgstr "Ten rok"
-#: contrib/admin/filterspecs.py:145 newforms/widgets.py:221
-#: oldforms/__init__.py:591
+#: contrib/admin/filterspecs.py:147 newforms/widgets.py:229
+#: oldforms/__init__.py:592
msgid "Yes"
msgstr "Tak"
-#: contrib/admin/filterspecs.py:145 newforms/widgets.py:221
-#: oldforms/__init__.py:591
+#: contrib/admin/filterspecs.py:147 newforms/widgets.py:229
+#: oldforms/__init__.py:592
msgid "No"
msgstr "Nie"
-#: contrib/admin/filterspecs.py:152 newforms/widgets.py:221
-#: oldforms/__init__.py:591
+#: contrib/admin/filterspecs.py:154 newforms/widgets.py:229
+#: oldforms/__init__.py:592
msgid "Unknown"
msgstr "Nieznany"
-#: contrib/admin/models.py:17
+#: contrib/admin/models.py:18
msgid "action time"
msgstr "czas akcji"
-#: contrib/admin/models.py:20
+#: contrib/admin/models.py:21
msgid "object id"
msgstr "id obiektu"
-#: contrib/admin/models.py:21
+#: contrib/admin/models.py:22
msgid "object repr"
msgstr "reprezentacj obiektu"
-#: contrib/admin/models.py:22
+#: contrib/admin/models.py:23
msgid "action flag"
msgstr "flaga akcji"
-#: contrib/admin/models.py:23
+#: contrib/admin/models.py:24
msgid "change message"
msgstr "zmień wiadomość"
-#: contrib/admin/models.py:26
+#: contrib/admin/models.py:27
msgid "log entry"
msgstr "log"
-#: contrib/admin/models.py:27
+#: contrib/admin/models.py:28
msgid "log entries"
msgstr "logi"
@@ -472,7 +472,7 @@ msgid "Password:"
msgstr "Hasło:"
#: contrib/admin/templates/admin/login.html:25
-#: contrib/admin/views/decorators.py:24
+#: contrib/admin/views/decorators.py:25
msgid "Log in"
msgstr "Zaloguj się"
@@ -764,17 +764,17 @@ msgstr "Teraz:"
msgid "Change:"
msgstr "Zmień:"
-#: contrib/admin/templatetags/admin_list.py:254
+#: contrib/admin/templatetags/admin_list.py:255
msgid "All dates"
msgstr "Wszystkie daty"
-#: contrib/admin/views/auth.py:20 contrib/admin/views/main.py:264
+#: contrib/admin/views/auth.py:20 contrib/admin/views/main.py:267
#, python-format
msgid "The %(name)s \"%(obj)s\" was added successfully."
msgstr "%(name)s \"%(obj)s\" dodany pomyślnie."
-#: contrib/admin/views/auth.py:25 contrib/admin/views/main.py:268
-#: contrib/admin/views/main.py:354
+#: contrib/admin/views/auth.py:25 contrib/admin/views/main.py:271
+#: contrib/admin/views/main.py:357
msgid "You may edit it again below."
msgstr "Możesz ponownie edytować wpis poniżej."
@@ -791,7 +791,7 @@ msgstr "Hasło zostało zmienione pomyślnie."
msgid "Change password: %s"
msgstr "Zmień hasło: %s"
-#: contrib/admin/views/decorators.py:10 contrib/auth/forms.py:60
+#: contrib/admin/views/decorators.py:11 contrib/auth/forms.py:60
msgid ""
"Please enter a correct username and password. Note that both fields are case-"
"sensitive."
@@ -799,7 +799,7 @@ msgstr ""
"Proszę wpisać poprawną nazwę użytkownika i hasło. Uwaga: wielkość liter ma "
"znaczenie."
-#: contrib/admin/views/decorators.py:62
+#: contrib/admin/views/decorators.py:63
msgid ""
"Please log in again, because your session has expired. Don't worry: Your "
"submission has been saved."
@@ -807,7 +807,7 @@ msgstr ""
"Zaloguj się ponownie. Twoja sesja wygasła lecz twoje zgłoszenie zostało "
"zapisane."
-#: contrib/admin/views/decorators.py:69
+#: contrib/admin/views/decorators.py:70
msgid ""
"Looks like your browser isn't configured to accept cookies. Please enable "
"cookies, reload this page, and try again."
@@ -815,246 +815,246 @@ msgstr ""
"Twoja przeglądarka nie chce akceptować ciasteczek. Zmień jej ustawienia i "
"spróbuj ponownie."
-#: contrib/admin/views/decorators.py:83
+#: contrib/admin/views/decorators.py:84
msgid "Usernames cannot contain the '@' character."
msgstr "Nazwy użytkowników nie mogą zawierać znaków '@'."
-#: contrib/admin/views/decorators.py:85
+#: contrib/admin/views/decorators.py:86
#, python-format
msgid "Your e-mail address is not your username. Try '%s' instead."
msgstr "Twój adres e-mail to nie jest twój login. Spróbuj '%s'."
-#: contrib/admin/views/doc.py:47 contrib/admin/views/doc.py:49
-#: contrib/admin/views/doc.py:51
+#: contrib/admin/views/doc.py:48 contrib/admin/views/doc.py:50
+#: contrib/admin/views/doc.py:52
msgid "tag:"
msgstr "tag:"
-#: contrib/admin/views/doc.py:78 contrib/admin/views/doc.py:80
-#: contrib/admin/views/doc.py:82
+#: contrib/admin/views/doc.py:79 contrib/admin/views/doc.py:81
+#: contrib/admin/views/doc.py:83
msgid "filter:"
msgstr "filtr:"
-#: contrib/admin/views/doc.py:136 contrib/admin/views/doc.py:138
-#: contrib/admin/views/doc.py:140
+#: contrib/admin/views/doc.py:137 contrib/admin/views/doc.py:139
+#: contrib/admin/views/doc.py:141
msgid "view:"
msgstr "widok:"
-#: contrib/admin/views/doc.py:165
+#: contrib/admin/views/doc.py:166
#, python-format
msgid "App %r not found"
msgstr "Aplikacja %r nie została znaleziona"
-#: contrib/admin/views/doc.py:172
+#: contrib/admin/views/doc.py:173
#, python-format
msgid "Model %(name)r not found in app %(label)r"
msgstr "Model %(name)r nie został znaleziony w aplikacji %(label)r"
-#: contrib/admin/views/doc.py:184
+#: contrib/admin/views/doc.py:185
#, python-format
msgid "the related `%(label)s.%(type)s` object"
msgstr "powiązany obiekt `%(label)s.%(type)s`"
-#: contrib/admin/views/doc.py:184 contrib/admin/views/doc.py:206
-#: contrib/admin/views/doc.py:220 contrib/admin/views/doc.py:225
+#: contrib/admin/views/doc.py:185 contrib/admin/views/doc.py:207
+#: contrib/admin/views/doc.py:221 contrib/admin/views/doc.py:226
msgid "model:"
msgstr "model:"
-#: contrib/admin/views/doc.py:215
+#: contrib/admin/views/doc.py:216
#, python-format
msgid "related `%(label)s.%(name)s` objects"
msgstr "powiązane obiekty `%(label)s.%(name)s`"
-#: contrib/admin/views/doc.py:220
+#: contrib/admin/views/doc.py:221
#, python-format
msgid "all %s"
msgstr "wszystkie %s"
-#: contrib/admin/views/doc.py:225
+#: contrib/admin/views/doc.py:226
#, python-format
msgid "number of %s"
msgstr "liczba %s"
-#: contrib/admin/views/doc.py:230
+#: contrib/admin/views/doc.py:231
#, python-format
msgid "Fields on %s objects"
msgstr "Pola obiektów %s"
-#: contrib/admin/views/doc.py:292 contrib/admin/views/doc.py:303
-#: contrib/admin/views/doc.py:305 contrib/admin/views/doc.py:311
-#: contrib/admin/views/doc.py:312 contrib/admin/views/doc.py:314
+#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:304
+#: contrib/admin/views/doc.py:306 contrib/admin/views/doc.py:312
+#: contrib/admin/views/doc.py:313 contrib/admin/views/doc.py:315
msgid "Integer"
msgstr "Liczba całkowita"
-#: contrib/admin/views/doc.py:293
+#: contrib/admin/views/doc.py:294
msgid "Boolean (Either True or False)"
msgstr "Wartość logiczna (True, False - prawda lub fałsz)"
-#: contrib/admin/views/doc.py:294 contrib/admin/views/doc.py:313
+#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:314
#, python-format
msgid "String (up to %(max_length)s)"
msgstr "Łańcuch (do %(max_length)s znaków)"
-#: contrib/admin/views/doc.py:295
+#: contrib/admin/views/doc.py:296
msgid "Comma-separated integers"
msgstr "Liczby całkowite rozdzielone przecinkami"
-#: contrib/admin/views/doc.py:296
+#: contrib/admin/views/doc.py:297
msgid "Date (without time)"
msgstr "Data (bez godziny)"
-#: contrib/admin/views/doc.py:297
+#: contrib/admin/views/doc.py:298
msgid "Date (with time)"
msgstr "Data (z godziną)"
-#: contrib/admin/views/doc.py:298
+#: contrib/admin/views/doc.py:299
msgid "Decimal number"
msgstr "Numer dziesiętny"
-#: contrib/admin/views/doc.py:299
+#: contrib/admin/views/doc.py:300
msgid "E-mail address"
msgstr "Adres e-mail"
-#: contrib/admin/views/doc.py:300 contrib/admin/views/doc.py:301
-#: contrib/admin/views/doc.py:304
+#: contrib/admin/views/doc.py:301 contrib/admin/views/doc.py:302
+#: contrib/admin/views/doc.py:305
msgid "File path"
msgstr "Ścieżka do pliku"
-#: contrib/admin/views/doc.py:302
+#: contrib/admin/views/doc.py:303
msgid "Floating point number"
msgstr "Liczba zmiennoprzecinkowa"
-#: contrib/admin/views/doc.py:306 contrib/comments/models.py:85
+#: contrib/admin/views/doc.py:307 contrib/comments/models.py:85
msgid "IP address"
msgstr "Adres IP"
-#: contrib/admin/views/doc.py:308
+#: contrib/admin/views/doc.py:309
msgid "Boolean (Either True, False or None)"
msgstr "Wartość logiczna (True, False, None - prawda, fałsz lub nic)"
-#: contrib/admin/views/doc.py:309
+#: contrib/admin/views/doc.py:310
msgid "Relation to parent model"
msgstr "Relacja do modelu rodzica"
-#: contrib/admin/views/doc.py:310
+#: contrib/admin/views/doc.py:311
msgid "Phone number"
msgstr "Numer telefonu"
-#: contrib/admin/views/doc.py:315
+#: contrib/admin/views/doc.py:316
msgid "Text"
msgstr "Tekst"
-#: contrib/admin/views/doc.py:316
+#: contrib/admin/views/doc.py:317
msgid "Time"
msgstr "Czas"
-#: contrib/admin/views/doc.py:317 contrib/flatpages/models.py:7
+#: contrib/admin/views/doc.py:318 contrib/flatpages/models.py:7
msgid "URL"
msgstr "URL"
-#: contrib/admin/views/doc.py:318
+#: contrib/admin/views/doc.py:319
msgid "U.S. state (two uppercase letters)"
msgstr "Stan USA (dwie duże litery)"
-#: contrib/admin/views/doc.py:319
+#: contrib/admin/views/doc.py:320
msgid "XML text"
msgstr "Tekst XML"
-#: contrib/admin/views/doc.py:345
+#: contrib/admin/views/doc.py:346
#, python-format
msgid "%s does not appear to be a urlpattern object"
msgstr "%s nie jest obiektem urlpattern"
-#: contrib/admin/views/main.py:230
+#: contrib/admin/views/main.py:233
msgid "Site administration"
msgstr "Administracja stroną"
-#: contrib/admin/views/main.py:278 contrib/admin/views/main.py:363
+#: contrib/admin/views/main.py:281 contrib/admin/views/main.py:366
#, python-format
msgid "You may add another %s below."
msgstr "Możesz dodać nowy wpis %s poniżej."
-#: contrib/admin/views/main.py:296
+#: contrib/admin/views/main.py:299
#, python-format
msgid "Add %s"
msgstr "Dodaj %s"
-#: contrib/admin/views/main.py:342
+#: contrib/admin/views/main.py:345
#, python-format
msgid "Added %s."
msgstr "Dodano %s"
-#: contrib/admin/views/main.py:342 contrib/admin/views/main.py:344
-#: contrib/admin/views/main.py:346 core/validators.py:283
+#: contrib/admin/views/main.py:345 contrib/admin/views/main.py:347
+#: contrib/admin/views/main.py:349 core/validators.py:283
#: db/models/manipulators.py:309
msgid "and"
msgstr "i"
-#: contrib/admin/views/main.py:344
+#: contrib/admin/views/main.py:347
#, python-format
msgid "Changed %s."
msgstr "Zmieniono %s"
-#: contrib/admin/views/main.py:346
+#: contrib/admin/views/main.py:349
#, python-format
msgid "Deleted %s."
msgstr "Skasowano %s"
-#: contrib/admin/views/main.py:349
+#: contrib/admin/views/main.py:352
msgid "No fields changed."
msgstr "Żadne pole nie zmienione."
-#: contrib/admin/views/main.py:352
+#: contrib/admin/views/main.py:355
#, python-format
msgid "The %(name)s \"%(obj)s\" was changed successfully."
msgstr "%(name)s \"%(obj)s\" zostało pomyślnie zmienione."
-#: contrib/admin/views/main.py:360
+#: contrib/admin/views/main.py:363
#, python-format
msgid ""
"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
msgstr ""
"%(name)s \"%(obj)s\" dodane pomyślnie. Możesz edytować ponownie wpis poniżej."
-#: contrib/admin/views/main.py:398
+#: contrib/admin/views/main.py:401
#, python-format
msgid "Change %s"
msgstr "Zmień %s"
-#: contrib/admin/views/main.py:483
+#: contrib/admin/views/main.py:488
#, python-format
msgid "One or more %(fieldname)s in %(name)s: %(obj)s"
msgstr "Jedno lub więcej %(fieldname)s w %(name)s: %(obj)s"
-#: contrib/admin/views/main.py:488
+#: contrib/admin/views/main.py:493
#, python-format
msgid "One or more %(fieldname)s in %(name)s:"
msgstr "Jedno lub więcej %(fieldname)s w %(name)s:"
-#: contrib/admin/views/main.py:520
+#: contrib/admin/views/main.py:525
#, python-format
msgid "The %(name)s \"%(obj)s\" was deleted successfully."
msgstr "%(name)s \"%(obj)s\" usunięty pomyślnie."
-#: contrib/admin/views/main.py:523
+#: contrib/admin/views/main.py:528
msgid "Are you sure?"
msgstr "Jesteś pewien?"
-#: contrib/admin/views/main.py:545
+#: contrib/admin/views/main.py:550
#, python-format
msgid "Change history: %s"
msgstr "Historia zmian: %s"
-#: contrib/admin/views/main.py:579
+#: contrib/admin/views/main.py:584
#, python-format
msgid "Select %s"
msgstr "Zaznacz %s"
-#: contrib/admin/views/main.py:579
+#: contrib/admin/views/main.py:584
#, python-format
msgid "Select %s to change"
msgstr "Zaznacz %s aby zmienić"
-#: contrib/admin/views/main.py:780
+#: contrib/admin/views/main.py:785
msgid "Database error"
msgstr "Błąd bazy danych"
@@ -1618,7 +1618,7 @@ msgstr "-gi"
msgid "rd"
msgstr "-ci"
-#: contrib/humanize/templatetags/humanize.py:50
+#: contrib/humanize/templatetags/humanize.py:52
#, python-format
msgid "%(value).1f million"
msgid_plural "%(value).1f million"
@@ -1626,7 +1626,7 @@ msgstr[0] "%(value).1f milion"
msgstr[1] "%(value).1f miliony"
msgstr[2] "%(value).1f milionów"
-#: contrib/humanize/templatetags/humanize.py:53
+#: contrib/humanize/templatetags/humanize.py:55
#, python-format
msgid "%(value).1f billion"
msgid_plural "%(value).1f billion"
@@ -1634,7 +1634,7 @@ msgstr[0] "%(value).1f miliard"
msgstr[1] "%(value).1f miliardy"
msgstr[2] "%(value).1f miliardów"
-#: contrib/humanize/templatetags/humanize.py:56
+#: contrib/humanize/templatetags/humanize.py:58
#, python-format
msgid "%(value).1f trillion"
msgid_plural "%(value).1f trillion"
@@ -1642,51 +1642,51 @@ msgstr[0] "%(value).1f bilion"
msgstr[1] "%(value).1f biliony"
msgstr[2] "%(value).1f bilionów"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "one"
msgstr "jeden"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "two"
msgstr "dwa"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "three"
msgstr "trzy"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "four"
msgstr "cztery"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "five"
msgstr "pięć"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "six"
msgstr "sześć"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "seven"
msgstr "siedem"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "eight"
msgstr "osiem"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "nine"
msgstr "dziewięć"
-#: contrib/humanize/templatetags/humanize.py:90
+#: contrib/humanize/templatetags/humanize.py:94
msgid "today"
msgstr "dzisiaj"
-#: contrib/humanize/templatetags/humanize.py:92
+#: contrib/humanize/templatetags/humanize.py:96
msgid "tomorrow"
msgstr "jutro"
-#: contrib/humanize/templatetags/humanize.py:94
+#: contrib/humanize/templatetags/humanize.py:98
msgid "yesterday"
msgstr "wczoraj"
@@ -1705,8 +1705,7 @@ msgstr "To pole musi zawierać 7 lub 8 cyfr."
#: contrib/localflavor/ar/forms.py:75
msgid "Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format."
-msgstr ""
-"Podaj poprawny numer CUIT w formacie XX-XXXXXXXX-X lub XXXXXXXXXXXX."
+msgstr "Podaj poprawny numer CUIT w formacie XX-XXXXXXXX-X lub XXXXXXXXXXXX."
#: contrib/localflavor/ar/forms.py:88
msgid "Invalid CUIT."
@@ -1725,11 +1724,12 @@ msgid "Phone numbers must be in XX-XXXX-XXXX format."
msgstr "Numery telefoniczne muszą być w formacie XX-XXXX-XXXX."
#: contrib/localflavor/br/forms.py:68
-#, fuzzy
msgid ""
"Select a valid brazilian state. That state is not one of the available "
"states."
-msgstr "Wybierz poprawną wartość. Podana nie jest jednym z dostępnych wyborów."
+msgstr ""
+"Wybierz poprawny brazylijski stan. Ten stan nie jest jednym z dostępnych "
+"stanów."
#: contrib/localflavor/br/forms.py:105
msgid "This field requires at most 11 digits or 14 characters."
@@ -1748,14 +1748,13 @@ msgid "Invalid CNPJ number."
msgstr "Błędny numer CNPJ."
#: contrib/localflavor/ca/forms.py:19
-#, fuzzy
msgid "Enter a postal code in the format XXX XXX."
-msgstr "Wpisz kod pocztowy w formacie XXXXX."
+msgstr "Wpisz kod pocztowy w formacie XXX XXX."
#: contrib/localflavor/ca/forms.py:81
-#, fuzzy
msgid "Enter a valid Canadian Social Insurance number in XXX-XXX-XXXX format."
-msgstr "Wpisz poprawny numer U.S. Social Security w formacie XXX-XX-XXXX."
+msgstr ""
+"Wpisz poprawny numer kanadyjskiego ubezpieczenia w formacie XXX-XXX-XXXX."
#: contrib/localflavor/ch/ch_states.py:5
msgid "Aargau"
@@ -1870,7 +1869,7 @@ msgid ""
"Enter a valid Swiss identity or passport card number in X1234567<0 or "
"1234567890 format."
msgstr ""
-"Podaj poprawny numer szwajarskiego dowodu osobistego lub paszportu w "
+"Podaj poprawny numer szwajcarskiego dowodu osobistego lub paszportu w "
"formacie X1234567<0 lub 1234567890."
#: contrib/localflavor/cl/forms.py:32
@@ -2237,16 +2236,15 @@ msgid "Valencian Community"
msgstr ""
#: contrib/localflavor/es/forms.py:22
-#, fuzzy
msgid "Enter a valid postal code in the range and format 01XXX - 52XXX."
-msgstr "Wpisz kod pocztowy w formacie XXXXXXX lub XXX-XXXX."
+msgstr "Wpisz kod pocztowy w zakresie i formacie 01XXX - 52XX."
#: contrib/localflavor/es/forms.py:39
-#, fuzzy
msgid ""
"Enter a valid phone number in one of the formats 6XXXXXXXX, 8XXXXXXXX or "
"9XXXXXXXX."
-msgstr "Wpisz kod pocztowy w formacie XXXXXXX lub XXX-XXXX."
+msgstr ""
+"Wpisz numer telefoniczny w formacie 6XXXXXXXX, 8XXXXXXXX lub 9XXXXXXXX."
#: contrib/localflavor/es/forms.py:73 contrib/localflavor/es/forms.py:108
#: db/models/fields/related.py:55
@@ -2256,36 +2254,33 @@ msgstr "Proszę wpisać poprawne %s."
#: contrib/localflavor/es/forms.py:91
msgid "Invalid checksum for NIF."
-msgstr ""
+msgstr "Niepoprawna suma kontrolna NIF."
#: contrib/localflavor/es/forms.py:97
msgid "Invalid checksum for NIE."
-msgstr ""
+msgstr "Niepoprawna suma kontrolna NIE."
#: contrib/localflavor/es/forms.py:106
msgid "Invalid checksum for CIF."
-msgstr ""
+msgstr "Niepoprawna suma kontrolna CIF."
#: contrib/localflavor/es/forms.py:136
-#, fuzzy
msgid ""
"Please enter a valid bank account number in format XXXX-XXXX-XX-XXXXXXXXXX."
msgstr ""
-"Podaj poprawny niemiecki numer dowodu osobistego w formacie XXXXXXXXXXX-"
-"XXXXXXX-XXXXXXX-X."
+"Podaj poprawny numer konta bankowego w formacie XXXX-XXXX-XX-XXXXXXXXXX."
#: contrib/localflavor/es/forms.py:150
msgid "Invalid checksum for bank account number."
-msgstr ""
+msgstr "Niepoprawna suma kontrolna numeru konta bankowego."
#: contrib/localflavor/fi/forms.py:40 contrib/localflavor/fi/forms.py:45
msgid "Enter a valid Finnish social security number."
msgstr "Wpis poprawny numer fińskiego ubezpieczenia socjalnego."
#: contrib/localflavor/in_/forms.py:16
-#, fuzzy
msgid "Enter a zip code in the format XXXXXXX."
-msgstr "Wpisz kod pocztowy w formacie XXXXX-XXX."
+msgstr "Wpisz kod pocztowy w formacie XXXXXXX."
#: contrib/localflavor/is_/forms.py:17
msgid ""
@@ -2502,19 +2497,16 @@ msgid "Okinawa"
msgstr ""
#: contrib/localflavor/nl/forms.py:25
-#, fuzzy
msgid "Enter a valid postal code"
msgstr "Wpisz poprawny kod pocztowy."
#: contrib/localflavor/nl/forms.py:53
-#, fuzzy
msgid "Enter a valid phone number"
-msgstr "Wpisz poprawny numer VAT."
+msgstr "Wpisz poprawny numer telefonu."
#: contrib/localflavor/nl/forms.py:76
-#, fuzzy
msgid "Enter a valid SoFi number"
-msgstr "Wpisz poprawny numer VAT."
+msgstr "Wpisz poprawny numer SoFi."
#: contrib/localflavor/nl/nl_provinces.py:4
#, fuzzy
@@ -2574,13 +2566,12 @@ msgid "Enter a valid Norwegian social security number."
msgstr "Wpis poprawny numer norweskiego ubezpieczenia socjalnego."
#: contrib/localflavor/pe/forms.py:36
-#, fuzzy
msgid "This field requires 8 digits."
-msgstr "To pole musi zawierać co najmniej 14 cyfr."
+msgstr "To pole musi zawierać 8 cyfr."
#: contrib/localflavor/pe/forms.py:59
msgid "This field requires 11 digits."
-msgstr "To pole musi zawierać co najmniej 11 cyfr."
+msgstr "To pole musi zawierać 11 cyfr."
#: contrib/localflavor/pl/forms.py:41
msgid "National Identification Number consists of 11 digits."
@@ -2676,9 +2667,8 @@ msgid "West Pomerania"
msgstr "Zachodniopomorskie"
#: contrib/localflavor/sk/forms.py:32
-#, fuzzy
msgid "Enter a postal code in the format XXXXX or XXX XX."
-msgstr "Wpisz kod pocztowy w formacie XXXXXXX lub XXX-XXXX."
+msgstr "Wpisz kod pocztowy w formacie XXXXX or XXX XX."
#: contrib/localflavor/sk/sk_districts.py:8
msgid "Banska Bystrica"
@@ -3198,7 +3188,7 @@ msgid "Enter a valid e-mail address."
msgstr "Wprowadź poprawny adres e-mail."
#: core/validators.py:182 core/validators.py:474 newforms/fields.py:438
-#: oldforms/__init__.py:686
+#: oldforms/__init__.py:687
msgid "No file was submitted. Check the encoding type on the form."
msgstr "Nie wysłano żadnego pliku. Sprawdź typ kodowania formularza."
@@ -3460,7 +3450,7 @@ msgstr "Już istnieje %(optname)s z %(fieldname)s."
#: db/models/fields/__init__.py:161 db/models/fields/__init__.py:318
#: db/models/fields/__init__.py:735 db/models/fields/__init__.py:746
-#: newforms/fields.py:45 newforms/models.py:220 oldforms/__init__.py:373
+#: newforms/fields.py:45 newforms/models.py:220 oldforms/__init__.py:374
msgid "This field is required."
msgstr "To pole jest wymagane."
@@ -3523,15 +3513,15 @@ msgstr "Wpisz poprawną wartość."
#, python-format
msgid "Ensure this value has at most %(max)d characters (it has %(length)d)."
msgstr ""
-"Upewnij się, że ta wartość ma co najwyżej %(max)d znaków "
-"(ma długość %(length)d)."
+"Upewnij się, że ta wartość ma co najwyżej %(max)d znaków (ma długość %"
+"(length)d)."
#: newforms/fields.py:130
#, python-format
msgid "Ensure this value has at least %(min)d characters (it has %(length)d)."
msgstr ""
-"Upewnij się, że ta wartość ma co najmniej %(min)d znaków "
-"(ma długość %(length)d)."
+"Upewnij się, że ta wartość ma co najmniej %(min)d znaków (ma długość %"
+"(length)d)."
#: newforms/fields.py:158 newforms/fields.py:187 newforms/fields.py:216
#, python-format
@@ -3578,7 +3568,7 @@ msgstr "Wpisz poprawną datę/godzinę."
msgid "No file was submitted."
msgstr "Żaden plik nie został przesłany."
-#: newforms/fields.py:440 oldforms/__init__.py:688
+#: newforms/fields.py:440 oldforms/__init__.py:689
msgid "The submitted file is empty."
msgstr "Wysłany plik jest pusty."
@@ -3613,7 +3603,7 @@ msgstr "Wprowadź poprawny adres IPv4."
msgid "Select a valid choice. %s is not one of the available choices."
msgstr "Wybierz poprawną wartość. %s nie jest jednym z dostępnych wyborów."
-#: oldforms/__init__.py:408
+#: oldforms/__init__.py:409
#, python-format
msgid "Ensure your text is less than %s character."
msgid_plural "Ensure your text is less than %s characters."
@@ -3621,32 +3611,32 @@ msgstr[0] "Upewnij się, że tekst ma mniej niż %s znak."
msgstr[1] "Upewnij się, że tekst ma mniej niż %s znaki."
msgstr[2] "Upewnij się, że tekst ma mniej niż %s znaków."
-#: oldforms/__init__.py:413
+#: oldforms/__init__.py:414
msgid "Line breaks are not allowed here."
msgstr "Znaki nowej linii są tutaj niedopuszczalne."
-#: oldforms/__init__.py:511 oldforms/__init__.py:585 oldforms/__init__.py:624
+#: oldforms/__init__.py:512 oldforms/__init__.py:586 oldforms/__init__.py:625
#, python-format
msgid "Select a valid choice; '%(data)s' is not in %(choices)s."
msgstr "Wybierz poprawną opcję; '%(data)s' nie jest wśród %(choices)s."
-#: oldforms/__init__.py:744
+#: oldforms/__init__.py:745
msgid "Enter a whole number between -32,768 and 32,767."
msgstr "Proszę wpisać liczbę całkowitą z zakresu od -32 768 do 32 767"
-#: oldforms/__init__.py:754
+#: oldforms/__init__.py:755
msgid "Enter a positive number."
msgstr "Proszę wpisać liczbę dodatnią."
-#: oldforms/__init__.py:764
+#: oldforms/__init__.py:765
msgid "Enter a whole number between 0 and 32,767."
msgstr "Proszę wpisać liczbę całkowitą z zakresu od 0 do 32 767"
-#: template/defaultfilters.py:555
+#: template/defaultfilters.py:655
msgid "yes,no,maybe"
msgstr "tak,nie,może"
-#: template/defaultfilters.py:585
+#: template/defaultfilters.py:686
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
@@ -3654,17 +3644,17 @@ msgstr[0] "%(size)d bajt"
msgstr[1] "%(size)d bajty"
msgstr[2] "%(size)d bajtów"
-#: template/defaultfilters.py:587
+#: template/defaultfilters.py:688
#, python-format
msgid "%.1f KB"
msgstr "%.1f KB"
-#: template/defaultfilters.py:589
+#: template/defaultfilters.py:690
#, python-format
msgid "%.1f MB"
msgstr "%.1f MB"
-#: template/defaultfilters.py:590
+#: template/defaultfilters.py:691
#, python-format
msgid "%.1f GB"
msgstr "%.1f GB"
diff --git a/django/contrib/admin/media/css/base.css b/django/contrib/admin/media/css/base.css
index 88f7d9a95a..9760d67dc4 100644
--- a/django/contrib/admin/media/css/base.css
+++ b/django/contrib/admin/media/css/base.css
@@ -4,11 +4,11 @@
*/
/* Block IE 5 */
-@import "null?\"\{";
+@import "null.css?\"\{";
/* Import other styles */
@import url('global.css');
@import url('layout.css');
/* Import patch for IE 6 Windows */
-/*\*/ @import "patch-iewin.css"; /**/ \ No newline at end of file
+/*\*/ @import "patch-iewin.css"; /**/
diff --git a/django/contrib/admin/media/js/admin/RelatedObjectLookups.js b/django/contrib/admin/media/js/admin/RelatedObjectLookups.js
index 36ae21411d..f6a39ca091 100644
--- a/django/contrib/admin/media/js/admin/RelatedObjectLookups.js
+++ b/django/contrib/admin/media/js/admin/RelatedObjectLookups.js
@@ -1,6 +1,16 @@
// Handles related-objects functionality: lookup link for raw_id_admin=True
// and Add Another links.
+function html_unescape(text) {
+ // Unescape a string that was escaped using django.utils.html.escape.
+ text = text.replace(/&lt;/g, '<');
+ text = text.replace(/&gt;/g, '>');
+ text = text.replace(/&quot;/g, '"');
+ text = text.replace(/&#39;/g, "'");
+ text = text.replace(/&amp;/g, '&');
+ return text;
+}
+
function showRelatedObjectLookupPopup(triggeringLink) {
var name = triggeringLink.id.replace(/^lookup_/, '');
// IE doesn't like periods in the window name, so convert temporarily.
@@ -42,6 +52,10 @@ function showAddAnotherPopup(triggeringLink) {
}
function dismissAddAnotherPopup(win, newId, newRepr) {
+ // newId and newRepr are expected to have previously been escaped by
+ // django.utils.html.escape.
+ newId = html_unescape(newId);
+ newRepr = html_unescape(newRepr);
var name = win.name.replace(/___/g, '.');
var elem = document.getElementById(name);
if (elem) {
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index b23013becd..a4e6269b6f 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -148,6 +148,8 @@ def items_for_result(cl, result):
# function has an "allow_tags" attribute set to True.
if not allow_tags:
result_repr = escape(result_repr)
+ else:
+ result_repr = mark_safe(result_repr)
else:
field_val = getattr(result, f.attname)
@@ -185,7 +187,7 @@ def items_for_result(cl, result):
else:
result_repr = escape(field_val)
if force_unicode(result_repr) == '':
- result_repr = '&nbsp;'
+ result_repr = mark_safe('&nbsp;')
# If list_display_links not defined, add the link tag to the first field
if (first and not cl.lookup_opts.admin.list_display_links) or field_name in cl.lookup_opts.admin.list_display_links:
table_tag = {True:'th', False:'td'}[first]
diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py
index e5f31ba723..ef33bb33b0 100644
--- a/django/contrib/admin/templatetags/admin_modify.py
+++ b/django/contrib/admin/templatetags/admin_modify.py
@@ -118,7 +118,7 @@ class FieldWrapper(object):
return not isinstance(self.field, models.AutoField)
def header_class_attribute(self):
- return self.field.blank and ' class="optional"' or ''
+ return self.field.blank and mark_safe(' class="optional"') or ''
def use_raw_id_admin(self):
return isinstance(self.field.rel, (models.ManyToOneRel, models.ManyToManyRel)) \
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 947d09b852..9786935bf8 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -273,10 +273,9 @@ def add_stage(request, app_label, model_name, show_delete=False, form_url='', po
post_url_continue += "?_popup=1"
return HttpResponseRedirect(post_url_continue % pk_value)
if "_popup" in request.POST:
- if type(pk_value) is str: # Quote if string, so JavaScript doesn't think it's a variable.
- pk_value = '"%s"' % pk_value.replace('"', '\\"')
- return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, %s, "%s");</script>' % \
- (pk_value, force_unicode(new_object).replace('"', '\\"')))
+ return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
+ # escape() calls force_unicode.
+ (escape(pk_value), escape(new_object)))
elif "_addanother" in request.POST:
request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
return HttpResponseRedirect(request.path)
diff --git a/django/core/cache/backends/locmem.py b/django/core/cache/backends/locmem.py
index 5998f7bfd5..2d74e2b132 100644
--- a/django/core/cache/backends/locmem.py
+++ b/django/core/cache/backends/locmem.py
@@ -16,8 +16,12 @@ class CacheClass(SimpleCacheClass):
def add(self, key, value, timeout=None):
self._lock.writer_enters()
+ # Python 2.3 and 2.4 don't allow combined try-except-finally blocks.
try:
- SimpleCacheClass.add(self, key, value, timeout)
+ try:
+ super(CacheClass, self).add(key, pickle.dumps(value), timeout)
+ except pickle.PickleError:
+ pass
finally:
self._lock.writer_leaves()
@@ -49,6 +53,7 @@ class CacheClass(SimpleCacheClass):
def set(self, key, value, timeout=None):
self._lock.writer_enters()
+ # Python 2.3 and 2.4 don't allow combined try-except-finally blocks.
try:
try:
super(CacheClass, self).set(key, pickle.dumps(value), timeout)
diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py
index dce2fd493d..fcbc9d1110 100644
--- a/django/core/management/__init__.py
+++ b/django/core/management/__init__.py
@@ -52,7 +52,7 @@ def load_command_class(app_name, name):
return getattr(__import__('%s.management.commands.%s' % (app_name, name),
{}, {}, ['Command']), 'Command')()
-def get_commands(load_user_commands=True, project_directory=None):
+def get_commands():
"""
Returns a dictionary of commands against the application in which
those commands can be found. This works by looking for a
@@ -60,10 +60,10 @@ def get_commands(load_user_commands=True, project_directory=None):
application -- if a commands package exists, all commands in that
package are registered.
- Core commands are always included; user-defined commands will also
- be included if ``load_user_commands`` is True. If a project directory
- is provided, the startproject command will be disabled, and the
- startapp command will be modified to use that directory.
+ Core commands are always included. If a settings module has been
+ specified, user-defined commands will also be included, the
+ startproject command will be disabled, and the startapp command
+ will be modified to use the directory in which that module appears.
The dictionary is in the format {command_name: app_name}. Key-value
pairs from this dictionary can then be used in calls to
@@ -80,16 +80,27 @@ def get_commands(load_user_commands=True, project_directory=None):
if _commands is None:
_commands = dict([(name, 'django.core')
for name in find_commands(__path__[0])])
- if load_user_commands:
- # Get commands from all installed apps.
+ # Get commands from all installed apps.
+ try:
+ from django.conf import settings
+ apps = settings.INSTALLED_APPS
+ except (AttributeError, EnvironmentError):
+ apps = []
+
+ for app_name in apps:
+ try:
+ path = find_management_module(app_name)
+ _commands.update(dict([(name, app_name)
+ for name in find_commands(path)]))
+ except ImportError:
+ pass # No management module - ignore this app
+
+ # Try to determine the project directory
+ try:
from django.conf import settings
- for app_name in settings.INSTALLED_APPS:
- try:
- path = find_management_module(app_name)
- _commands.update(dict([(name, app_name)
- for name in find_commands(path)]))
- except ImportError:
- pass # No management module - ignore this app
+ project_directory = setup_environ(__import__(settings.SETTINGS_MODULE))
+ except (AttributeError, EnvironmentError, ImportError):
+ project_directory = None
if project_directory:
# Remove the "startproject" command from self.commands, because
@@ -146,8 +157,6 @@ class ManagementUtility(object):
def __init__(self, argv=None):
self.argv = argv or sys.argv[:]
self.prog_name = os.path.basename(self.argv[0])
- self.project_directory = None
- self.user_commands = False
def main_help_text(self):
"""
@@ -159,8 +168,7 @@ class ManagementUtility(object):
usage.append("Type '%s help <subcommand>' for help on a specific"
" subcommand." % self.prog_name)
usage.append('Available subcommands:')
- commands = get_commands(self.user_commands,
- self.project_directory).keys()
+ commands = get_commands().keys()
commands.sort()
for cmd in commands:
usage.append(' %s' % cmd)
@@ -173,8 +181,7 @@ class ManagementUtility(object):
django-admin.py or manage.py) if it can't be found.
"""
try:
- app_name = get_commands(self.user_commands,
- self.project_directory)[subcommand]
+ app_name = get_commands()[subcommand]
if isinstance(app_name, BaseCommand):
# If the command is already loaded, use it directly.
klass = app_name
@@ -235,8 +242,6 @@ class ProjectManagementUtility(ManagementUtility):
"""
def __init__(self, argv, project_directory):
super(ProjectManagementUtility, self).__init__(argv)
- self.project_directory = project_directory
- self.user_commands = True
def setup_environ(settings_mod):
"""
diff --git a/django/core/paginator.py b/django/core/paginator.py
index b50ca826c4..71a5479fd5 100644
--- a/django/core/paginator.py
+++ b/django/core/paginator.py
@@ -91,7 +91,7 @@ class ObjectPaginator(object):
a template for loop.
"""
if self._page_range is None:
- self._page_range = range(1, self._pages + 1)
+ self._page_range = range(1, self.pages + 1)
return self._page_range
hits = property(_get_hits)
diff --git a/django/middleware/gzip.py b/django/middleware/gzip.py
index aa2a8ea5a6..62a2456b97 100644
--- a/django/middleware/gzip.py
+++ b/django/middleware/gzip.py
@@ -1,4 +1,5 @@
import re
+
from django.utils.text import compress_string
from django.utils.cache import patch_vary_headers
@@ -11,18 +12,21 @@ class GZipMiddleware(object):
on the Accept-Encoding header.
"""
def process_response(self, request, response):
+ # It's not worth compressing non-OK or really short responses.
if response.status_code != 200 or len(response.content) < 200:
- # Not worth compressing really short responses or 304 status
- # responses, etc.
return response
patch_vary_headers(response, ('Accept-Encoding',))
- # Avoid gzipping if we've already got a content-encoding or if the
- # content-type is Javascript and the user's browser is IE.
- is_js = ("msie" in request.META.get('HTTP_USER_AGENT', '').lower() and
- "javascript" in response.get('Content-Type', '').lower())
- if response.has_header('Content-Encoding') or is_js:
+ # Avoid gzipping if we've already got a content-encoding.
+ if response.has_header('Content-Encoding'):
+ return response
+
+ # Older versions of IE have issues with gzipped javascript.
+ # See http://code.djangoproject.com/ticket/2449
+ is_ie = "msie" in request.META.get('HTTP_USER_AGENT', '').lower()
+ is_js = "javascript" in response.get('Content-Type', '').lower()
+ if is_ie and is_js:
return response
ae = request.META.get('HTTP_ACCEPT_ENCODING', '')
diff --git a/django/newforms/extras/widgets.py b/django/newforms/extras/widgets.py
index 60936a6bd6..0097ba3f54 100644
--- a/django/newforms/extras/widgets.py
+++ b/django/newforms/extras/widgets.py
@@ -6,6 +6,7 @@ import datetime
from django.newforms.widgets import Widget, Select
from django.utils.dates import MONTHS
+from django.utils.safestring import mark_safe
__all__ = ('SelectDateWidget',)
@@ -51,7 +52,7 @@ class SelectDateWidget(Widget):
select_html = Select(choices=year_choices).render(self.year_field % name, year_val)
output.append(select_html)
- return u'\n'.join(output)
+ return mark_safe(u'\n'.join(output))
def value_from_datadict(self, data, files, name):
y, m, d = data.get(self.year_field % name), data.get(self.month_field % name), data.get(self.day_field % name)
diff --git a/django/newforms/models.py b/django/newforms/models.py
index c86b9b3a42..51ed16ff7f 100644
--- a/django/newforms/models.py
+++ b/django/newforms/models.py
@@ -3,13 +3,13 @@ Helper functions for creating Form classes from Django models
and database field objects.
"""
-from django.utils.translation import ugettext
+from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode
from django.utils.datastructures import SortedDict
from util import ValidationError
from forms import BaseForm
-from fields import Field, ChoiceField
+from fields import Field, ChoiceField, EMPTY_VALUES
from widgets import Select, SelectMultiple, MultipleHiddenInput
__all__ = (
@@ -151,15 +151,20 @@ class ModelChoiceField(ChoiceField):
"""A ChoiceField whose choices are a model QuerySet."""
# This class is a subclass of ChoiceField for purity, but it doesn't
# actually use any of ChoiceField's implementation.
+ default_error_messages = {
+ 'invalid_choice': _(u'Select a valid choice. That choice is not one of'
+ u' the available choices.'),
+ }
def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
required=True, widget=Select, label=None, initial=None,
- help_text=None):
+ help_text=None, *args, **kwargs):
self.empty_label = empty_label
self.cache_choices = cache_choices
# Call Field instead of ChoiceField __init__() because we don't need
# ChoiceField.__init__().
- Field.__init__(self, required, widget, label, initial, help_text)
+ Field.__init__(self, required, widget, label, initial, help_text,
+ *args, **kwargs)
self.queryset = queryset
def _get_queryset(self):
@@ -195,41 +200,43 @@ class ModelChoiceField(ChoiceField):
def clean(self, value):
Field.clean(self, value)
- if value in ('', None):
+ if value in EMPTY_VALUES:
return None
try:
value = self.queryset.get(pk=value)
except self.queryset.model.DoesNotExist:
- raise ValidationError(ugettext(u'Select a valid choice. That'
- u' choice is not one of the'
- u' available choices.'))
+ raise ValidationError(self.error_messages['invalid_choice'])
return value
class ModelMultipleChoiceField(ModelChoiceField):
"""A MultipleChoiceField whose choices are a model QuerySet."""
hidden_widget = MultipleHiddenInput
+ default_error_messages = {
+ 'list': _(u'Enter a list of values.'),
+ 'invalid_choice': _(u'Select a valid choice. %s is not one of the'
+ u' available choices.'),
+ }
def __init__(self, queryset, cache_choices=False, required=True,
widget=SelectMultiple, label=None, initial=None,
- help_text=None):
+ help_text=None, *args, **kwargs):
super(ModelMultipleChoiceField, self).__init__(queryset, None,
- cache_choices, required, widget, label, initial, help_text)
+ cache_choices, required, widget, label, initial, help_text,
+ *args, **kwargs)
def clean(self, value):
if self.required and not value:
- raise ValidationError(ugettext(u'This field is required.'))
+ raise ValidationError(self.error_messages['required'])
elif not self.required and not value:
return []
if not isinstance(value, (list, tuple)):
- raise ValidationError(ugettext(u'Enter a list of values.'))
+ raise ValidationError(self.error_messages['list'])
final_values = []
for val in value:
try:
obj = self.queryset.get(pk=val)
except self.queryset.model.DoesNotExist:
- raise ValidationError(ugettext(u'Select a valid choice. %s is'
- u' not one of the available'
- u' choices.') % val)
+ raise ValidationError(self.error_messages['invalid_choice'] % val)
else:
final_values.append(obj)
return final_values
diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py
index 350b878af9..580834857e 100644
--- a/django/newforms/widgets.py
+++ b/django/newforms/widgets.py
@@ -11,7 +11,7 @@ import copy
from itertools import chain
from django.utils.datastructures import MultiValueDict
-from django.utils.html import escape
+from django.utils.html import escape, conditional_escape
from django.utils.translation import ugettext
from django.utils.encoding import StrAndUnicode, force_unicode
from django.utils.safestring import mark_safe
@@ -155,7 +155,7 @@ class Textarea(Widget):
value = force_unicode(value)
final_attrs = self.build_attrs(attrs, name=name)
return mark_safe(u'<textarea%s>%s</textarea>' % (flatatt(final_attrs),
- escape(value)))
+ conditional_escape(force_unicode(value))))
class DateTimeInput(Input):
input_type = 'text'
@@ -217,7 +217,9 @@ class Select(Widget):
for option_value, option_label in chain(self.choices, choices):
option_value = force_unicode(option_value)
selected_html = (option_value == str_value) and u' selected="selected"' or ''
- output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(force_unicode(option_label))))
+ output.append(u'<option value="%s"%s>%s</option>' % (
+ escape(option_value), selected_html,
+ conditional_escape(force_unicode(option_label))))
output.append(u'</select>')
return mark_safe(u'\n'.join(output))
@@ -254,7 +256,9 @@ class SelectMultiple(Widget):
for option_value, option_label in chain(self.choices, choices):
option_value = force_unicode(option_value)
selected_html = (option_value in str_values) and ' selected="selected"' or ''
- output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(force_unicode(option_label))))
+ output.append(u'<option value="%s"%s>%s</option>' % (
+ escape(option_value), selected_html,
+ conditional_escape(force_unicode(option_label))))
output.append(u'</select>')
return mark_safe(u'\n'.join(output))
@@ -278,7 +282,7 @@ class RadioInput(StrAndUnicode):
def __unicode__(self):
return mark_safe(u'<label>%s %s</label>' % (self.tag(),
- self.choice_label))
+ conditional_escape(force_unicode(self.choice_label))))
def is_checked(self):
return self.value == self.choice_value
@@ -317,11 +321,13 @@ class RadioFieldRenderer(StrAndUnicode):
% force_unicode(w) for w in self]))
class RadioSelect(Select):
+ renderer = RadioFieldRenderer
def __init__(self, *args, **kwargs):
- self.renderer = kwargs.pop('renderer', None)
- if not self.renderer:
- self.renderer = RadioFieldRenderer
+ # Override the default renderer if we were passed one.
+ renderer = kwargs.pop('renderer', None)
+ if renderer:
+ self.renderer = renderer
super(RadioSelect, self).__init__(*args, **kwargs)
def get_renderer(self, name, value, attrs=None, choices=()):
@@ -361,7 +367,8 @@ class CheckboxSelectMultiple(SelectMultiple):
cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
option_value = force_unicode(option_value)
rendered_cb = cb.render(name, option_value)
- output.append(u'<li><label>%s %s</label></li>' % (rendered_cb, escape(force_unicode(option_label))))
+ output.append(u'<li><label>%s %s</label></li>' % (rendered_cb,
+ conditional_escape(force_unicode(option_label))))
output.append(u'</ul>')
return mark_safe(u'\n'.join(output))
diff --git a/django/template/__init__.py b/django/template/__init__.py
index 761c08d6c9..c68a4b544d 100644
--- a/django/template/__init__.py
+++ b/django/template/__init__.py
@@ -547,9 +547,9 @@ class FilterExpression(object):
if var == None:
var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")
if i18n_constant:
- var = '"%s"' % _(i18n_constant)
+ var = '"%s"' % _(i18n_constant.replace(r'\"', '"'))
elif constant:
- var = '"%s"' % constant
+ var = '"%s"' % constant.replace(r'\"', '"')
upto = match.end()
if var == None:
raise TemplateSyntaxError, "Could not find variable at start of %s" % token
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index 7d4a72efb3..e62e2e3eaf 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -25,6 +25,8 @@ def stringfilter(func):
if args:
args = list(args)
args[0] = force_unicode(args[0])
+ if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False):
+ return mark_safe(func(*args, **kwargs))
return func(*args, **kwargs)
# Include a reference to the real function (used to check original
@@ -106,6 +108,7 @@ floatformat.is_safe = True
def iriencode(value):
"""Escapes an IRI value for use in a URL."""
return force_unicode(iri_to_uri(value))
+iriencode.is_safe = True
iriencode = stringfilter(iriencode)
def linenumbers(value, autoescape=None):
diff --git a/django/test/testcases.py b/django/test/testcases.py
index 2aa0a0783d..1d65ee1d23 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -51,9 +51,9 @@ class TestCase(unittest.TestCase):
def _pre_setup(self):
"""Performs any pre-test setup. This includes:
- * If the Test Case class has a 'fixtures' member, clearing the
- database and installing the named fixtures at the start of each
- test.
+ * Flushing the database.
+ * If the Test Case class has a 'fixtures' member, installing the
+ named fixtures.
* Clearing the mail test outbox.
"""
call_command('flush', verbosity=0, interactive=False)
diff --git a/django/utils/cache.py b/django/utils/cache.py
index ae4de6dd87..5654bed7aa 100644
--- a/django/utils/cache.py
+++ b/django/utils/cache.py
@@ -20,6 +20,10 @@ An example: i18n middleware would need to distinguish caches by the
import md5
import re
import time
+try:
+ set
+except NameError:
+ from sets import Set as set # Python 2.3 fallback
from django.conf import settings
from django.core.cache import cache
@@ -70,8 +74,6 @@ def patch_cache_control(response, **kwargs):
cc = ', '.join([dictvalue(el) for el in cc.items()])
response['Cache-Control'] = cc
-vary_delim_re = re.compile(r',\s*')
-
def patch_response_headers(response, cache_timeout=None):
"""
Adds some useful headers to the given HttpResponse object:
@@ -109,14 +111,15 @@ def patch_vary_headers(response, newheaders):
# Note that we need to keep the original order intact, because cache
# implementations may rely on the order of the Vary contents in, say,
# computing an MD5 hash.
- vary = []
if response.has_header('Vary'):
- vary = vary_delim_re.split(response['Vary'])
- oldheaders = dict([(el.lower(), 1) for el in vary])
- for newheader in newheaders:
- if not newheader.lower() in oldheaders:
- vary.append(newheader)
- response['Vary'] = ', '.join(vary)
+ vary_headers = cc_delim_re.split(response['Vary'])
+ else:
+ vary_headers = []
+ # Use .lower() here so we treat headers as case-insensitive.
+ existing_headers = set([header.lower() for header in vary_headers])
+ additional_headers = [newheader for newheader in newheaders
+ if newheader.lower() not in existing_headers]
+ response['Vary'] = ', '.join(vary_headers + additional_headers)
def _generate_cache_key(request, headerlist, key_prefix):
"""Returns a cache key from the headers given in the header list."""
@@ -169,7 +172,7 @@ def learn_cache_key(request, response, cache_timeout=None, key_prefix=None):
key_prefix, iri_to_uri(request.path))
if response.has_header('Vary'):
headerlist = ['HTTP_'+header.upper().replace('-', '_')
- for header in vary_delim_re.split(response['Vary'])]
+ for header in cc_delim_re.split(response['Vary'])]
cache.set(cache_key, headerlist, cache_timeout)
return _generate_cache_key(request, headerlist, key_prefix)
else:
diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py
index 549aa3f183..ffdc73f922 100644
--- a/django/utils/datastructures.py
+++ b/django/utils/datastructures.py
@@ -7,9 +7,9 @@ class MergeDict(object):
self.dicts = dicts
def __getitem__(self, key):
- for dict in self.dicts:
+ for dict_ in self.dicts:
try:
- return dict[key]
+ return dict_[key]
except KeyError:
pass
raise KeyError
@@ -24,22 +24,22 @@ class MergeDict(object):
return default
def getlist(self, key):
- for dict in self.dicts:
+ for dict_ in self.dicts:
try:
- return dict.getlist(key)
+ return dict_.getlist(key)
except KeyError:
pass
raise KeyError
def items(self):
item_list = []
- for dict in self.dicts:
- item_list.extend(dict.items())
+ for dict_ in self.dicts:
+ item_list.extend(dict_.items())
return item_list
def has_key(self, key):
- for dict in self.dicts:
- if key in dict:
+ for dict_ in self.dicts:
+ if key in dict_:
return True
return False
@@ -56,7 +56,7 @@ class SortedDict(dict):
def __init__(self, data=None):
if data is None:
data = {}
- dict.__init__(self, data)
+ super(SortedDict, self).__init__(data)
if isinstance(data, dict):
self.keyOrder = data.keys()
else:
@@ -68,12 +68,12 @@ class SortedDict(dict):
for key, value in self.iteritems()])
def __setitem__(self, key, value):
- dict.__setitem__(self, key, value)
+ super(SortedDict, self).__setitem__(key, value)
if key not in self.keyOrder:
self.keyOrder.append(key)
def __delitem__(self, key):
- dict.__delitem__(self, key)
+ super(SortedDict, self).__delitem__(key)
self.keyOrder.remove(key)
def __iter__(self):
@@ -81,7 +81,7 @@ class SortedDict(dict):
yield k
def pop(self, k, *args):
- result = dict.pop(self, k, *args)
+ result = super(SortedDict, self).pop(k, *args)
try:
self.keyOrder.remove(k)
except ValueError:
@@ -90,7 +90,7 @@ class SortedDict(dict):
return result
def popitem(self):
- result = dict.popitem(self)
+ result = super(SortedDict, self).popitem()
self.keyOrder.remove(result[0])
return result
@@ -99,7 +99,7 @@ class SortedDict(dict):
def iteritems(self):
for key in self.keyOrder:
- yield key, dict.__getitem__(self, key)
+ yield key, super(SortedDict, self).__getitem__(key)
def keys(self):
return self.keyOrder[:]
@@ -108,20 +108,20 @@ class SortedDict(dict):
return iter(self.keyOrder)
def values(self):
- return [dict.__getitem__(self, k) for k in self.keyOrder]
+ return [super(SortedDict, self).__getitem__(k) for k in self.keyOrder]
def itervalues(self):
for key in self.keyOrder:
- yield dict.__getitem__(self, key)
+ yield super(SortedDict, self).__getitem__(key)
- def update(self, dict):
- for k, v in dict.items():
+ def update(self, dict_):
+ for k, v in dict_.items():
self.__setitem__(k, v)
def setdefault(self, key, default):
if key not in self.keyOrder:
self.keyOrder.append(key)
- return dict.setdefault(self, key, default)
+ return super(SortedDict, self).setdefault(key, default)
def value_for_index(self, index):
"""Returns the value of the item at the given zero-based index."""
@@ -135,7 +135,7 @@ class SortedDict(dict):
if n < index:
index -= 1
self.keyOrder.insert(index, key)
- dict.__setitem__(self, key, value)
+ super(SortedDict, self).__setitem__(key, value)
def copy(self):
"""Returns a copy of this object."""
@@ -173,10 +173,11 @@ class MultiValueDict(dict):
single name-value pairs.
"""
def __init__(self, key_to_list_mapping=()):
- dict.__init__(self, key_to_list_mapping)
+ super(MultiValueDict, self).__init__(key_to_list_mapping)
def __repr__(self):
- return "<%s: %s>" % (self.__class__.__name__, dict.__repr__(self))
+ return "<%s: %s>" % (self.__class__.__name__,
+ super(MultiValueDict, self).__repr__())
def __getitem__(self, key):
"""
@@ -184,7 +185,7 @@ class MultiValueDict(dict):
raises KeyError if not found.
"""
try:
- list_ = dict.__getitem__(self, key)
+ list_ = super(MultiValueDict, self).__getitem__(key)
except KeyError:
raise MultiValueDictKeyError, "Key %r not found in %r" % (key, self)
try:
@@ -193,10 +194,10 @@ class MultiValueDict(dict):
return []
def __setitem__(self, key, value):
- dict.__setitem__(self, key, [value])
+ super(MultiValueDict, self).__setitem__(key, [value])
def __copy__(self):
- return self.__class__(dict.items(self))
+ return self.__class__(super(MultiValueDict, self).items())
def __deepcopy__(self, memo=None):
import copy
@@ -210,7 +211,10 @@ class MultiValueDict(dict):
return result
def get(self, key, default=None):
- """Returns the default value if the requested data doesn't exist."""
+ """
+ Returns the last data value for the passed key. If key doesn't exist
+ or value is an empty list, then default is returned.
+ """
try:
val = self[key]
except KeyError:
@@ -220,14 +224,17 @@ class MultiValueDict(dict):
return val
def getlist(self, key):
- """Returns an empty list if the requested data doesn't exist."""
+ """
+ Returns the list of values for the passed key. If key doesn't exist,
+ then an empty list is returned.
+ """
try:
- return dict.__getitem__(self, key)
+ return super(MultiValueDict, self).__getitem__(key)
except KeyError:
return []
def setlist(self, key, list_):
- dict.__setitem__(self, key, list_)
+ super(MultiValueDict, self).__setitem__(key, list_)
def setdefault(self, key, default=None):
if key not in self:
@@ -242,7 +249,7 @@ class MultiValueDict(dict):
def appendlist(self, key, value):
"""Appends an item to the internal list associated with key."""
self.setlistdefault(key, [])
- dict.__setitem__(self, key, self.getlist(key) + [value])
+ super(MultiValueDict, self).__setitem__(key, self.getlist(key) + [value])
def items(self):
"""
@@ -253,7 +260,7 @@ class MultiValueDict(dict):
def lists(self):
"""Returns a list of (key, list) pairs."""
- return dict.items(self)
+ return super(MultiValueDict, self).items()
def values(self):
"""Returns a list of the last value on every key list."""
@@ -315,7 +322,7 @@ class DotExpandedDict(dict):
try:
current[bits[-1]] = v
except TypeError: # Special-case if current isn't a dict.
- current = {bits[-1] : v}
+ current = {bits[-1]: v}
class FileDict(dict):
"""
diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
index c95c842a4f..a7259b3ce5 100644
--- a/django/utils/translation/trans_real.py
+++ b/django/utils/translation/trans_real.py
@@ -168,10 +168,9 @@ def translation(language):
res.merge(t)
return res
- if hasattr(settings, 'LOCALE_PATHS'):
- for localepath in settings.LOCALE_PATHS:
- if os.path.isdir(localepath):
- res = _merge(localepath)
+ for localepath in settings.LOCALE_PATHS:
+ if os.path.isdir(localepath):
+ res = _merge(localepath)
if projectpath and os.path.isdir(projectpath):
res = _merge(projectpath)
diff --git a/django/views/debug.py b/django/views/debug.py
index 7c45af230a..3358d2f08e 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -422,11 +422,11 @@ TECHNICAL_500_TEMPLATE = """
{% if frame.context_line %}
<div class="context" id="c{{ frame.id }}">
{% if frame.pre_context %}
- <ol start="{{ frame.pre_context_lineno }}" class="pre-context" id="pre{{ frame.id }}">{% for line in frame.pre_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line }}</li>{% endfor %}</ol>
+ <ol start="{{ frame.pre_context_lineno }}" class="pre-context" id="pre{{ frame.id }}">{% for line in frame.pre_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line|escape }}</li>{% endfor %}</ol>
{% endif %}
- <ol start="{{ frame.lineno }}" class="context-line"><li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ frame.context_line }} <span>...</span></li></ol>
+ <ol start="{{ frame.lineno }}" class="context-line"><li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ frame.context_line|escape }} <span>...</span></li></ol>
{% if frame.post_context %}
- <ol start='{{ frame.lineno|add:"1" }}' class="post-context" id="post{{ frame.id }}">{% for line in frame.post_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line }}</li>{% endfor %}</ol>
+ <ol start='{{ frame.lineno|add:"1" }}' class="post-context" id="post{{ frame.id }}">{% for line in frame.post_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line|escape }}</li>{% endfor %}</ol>
{% endif %}
</div>
{% endif %}
@@ -445,8 +445,8 @@ TECHNICAL_500_TEMPLATE = """
<tbody>
{% for var in frame.vars|dictsort:"0" %}
<tr>
- <td>{{ var.0 }}</td>
- <td class="code"><div>{{ var.1|pprint }}</div></td>
+ <td>{{ var.0|escape }}</td>
+ <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
</tr>
{% endfor %}
</tbody>
@@ -466,7 +466,7 @@ Traceback (most recent call last):<br/>
{% for frame in frames %}
File "{{ frame.filename }}" in {{ frame.function }}<br/>
{% if frame.context_line %}
- &nbsp;&nbsp;{{ frame.lineno }}. {{ frame.context_line }}<br/>
+ &nbsp;&nbsp;{{ frame.lineno }}. {{ frame.context_line|escape }}<br/>
{% endif %}
{% endfor %}<br/>
&nbsp;&nbsp;{{ exception_type }} at {{ request.path|escape }}<br/>
diff --git a/docs/i18n.txt b/docs/i18n.txt
index 2c43e7884e..8beb2188e8 100644
--- a/docs/i18n.txt
+++ b/docs/i18n.txt
@@ -658,7 +658,7 @@ message file. The choice is yours.
of the settings file to determine this, and a settings file doesn't exist
if you're manually configuring your settings.)
-.. _settings documentation: ../settings/#using-settings-without-the-django-settings-module-environment-variable
+.. _settings documentation: ../settings/#using-settings-without-setting-django-settings-module
All message file repositories are structured the same way. They are:
diff --git a/docs/settings.txt b/docs/settings.txt
index 6241749753..0219f9853a 100644
--- a/docs/settings.txt
+++ b/docs/settings.txt
@@ -583,7 +583,7 @@ LOCALE_PATHS
Default: ``()`` (Empty tuple)
-A list of directories where Django looks for translation files.
+A tuple of directories where Django looks for translation files.
See the `internationalization docs section`_ explaining the variable and the
default behavior.
diff --git a/docs/templates_python.txt b/docs/templates_python.txt
index e4658f6461..5ac93f5a58 100644
--- a/docs/templates_python.txt
+++ b/docs/templates_python.txt
@@ -755,61 +755,106 @@ inside the template code:
``EscapeString`` and ``EscapeUnicode``. You will not normally need to worry
about these; they exist for the implementation of the ``escape`` filter.
-Inside your filter, you will need to think about three areas in order to be
-auto-escaping compliant:
+When you are writing a filter, your code will typically fall into one of two
+situations:
- 1. If your filter returns a string that is ready for direct output (it should
- be considered a "safe" string), you should call
- ``django.utils.safestring.mark_safe()`` on the result prior to returning.
- This will turn the result into the appropriate ``SafeData`` type. This is
- often the case when you are returning raw HTML, for example.
+ 1. Your filter does not introduce any HTML-unsafe characters (``<``, ``>``,
+ ``'``, ``"`` or ``&``) into the result that were not already present. In
+ this case, you can let Django take care of all the auto-escaping handling
+ for you. All you need to do is put the ``is_safe`` attribute on your
+ filter function and set it to ``True``. This attribute tells Django that
+ is a "safe" string is passed into your filter, the result will still be
+ "safe" and if a non-safe string is passed in, Django will automatically
+ escape it, if necessary. The reason ``is_safe`` is necessary is because
+ there are plenty of normal string operations that will turn a ``SafeData``
+ object back into a normal ``str`` or ``unicode`` object and, rather than
+ try to catch them all, which would be very difficult, Django repairs the
+ damage after the filter has completed.
- 2. If your filter is given a "safe" string, is it guaranteed to return a
- "safe" string? If so, set the ``is_safe`` attribute on the function to be
- ``True``. For example, a filter that replaced a word consisting only of
- digits with the number spelt out in words is going to be
- safe-string-preserving, since it cannot introduce any of the five dangerous
- characters: <, >, ", ' or &. We can write::
+ For example, suppose you have a filter that adds the string ``xx`` to the
+ end of any input. Since this introduces no dangerous HTML characters into
+ the result (aside from any that were already present), you should mark
+ your filter with ``is_safe``::
@register.filter
- def convert_to_words(value):
- # ... implementation here ...
- return result
+ def add_xx(value):
+ return '%sxx' % value
+ add_xx.is_safe = True
- convert_to_words.is_safe = True
+ When this filter is used in a template where auto-escaping is enabled,
+ Django will escape the output whenever the input is not already marked as
+ "safe".
- Note that this filter does not return a universally safe result (it does
- not return ``mark_safe(result)``) because if it is handed a raw string such
- as '<a>', this will need further escaping in an auto-escape environment.
- The ``is_safe`` attribute only talks about the the result when a safe
- string is passed into the filter.
+ By default, ``is_safe`` defaults to ``False`` and you can omit it from
+ any filters where it isn't required.
- 3. Will your filter behave differently depending upon whether auto-escaping
- is currently in effect or not? This is normally a concern when you are
- returning mixed content (HTML elements mixed with user-supplied content).
- For example, the ``ordered_list`` filter that ships with Django needs to
- know whether to escape its content or not. It will always return a safe
- string. Since it returns raw HTML, we cannot apply escaping to the
- result -- it needs to be done in-situ.
+ Be careful when deciding if your filter really does leave safe strings
+ as safe. Sometimes if you are *removing* characters, you can
+ inadvertently leave unbalanced HTML tags or entities in the result.
+ For example, removing a ``>`` from the input might turn ``<a>`` into
+ ``<a``, which would need to be escaped on output to avoid causing
+ problems. Similarly, removing a semicolon (``;``) can turn ``&amp;``
+ into ``&amp``, which is no longer a valid entity and thus needs
+ further escaping. Most cases won't be nearly this tricky, but keep an
+ eye out for any problems like that when reviewing your code.
- For these cases, the filter function needs to be told what the current
- auto-escaping setting is. Set the ``needs_autoescape`` attribute on the
- filter to ``True`` and have your function take an extra argument called
- ``autoescape`` with a default value of ``None``. When the filter is called,
- the ``autoescape`` keyword argument will be ``True`` if auto-escaping is in
- effect. For example, the ``unordered_list`` filter is written as::
+ 2. Alternatively, your filter code can manually take care of any necessary
+ escaping. This is usually necessary when you are introducing new HTML
+ markup into the result. You want to mark the output as safe from further
+ escaping so that your HTML markup isn't escaped further, so you'll need to
+ handle the input yourself.
- def unordered_list(value, autoescape=None):
- # ... lots of code here ...
+ To mark the output as a safe string, use
+ ``django.utils.safestring.mark_safe()``.
- return mark_safe(...)
+ Be careful, though. You need to do more than just mark the output as
+ safe. You need to ensure it really *is* safe and what you do will often
+ depend upon whether or not auto-escaping is in effect. The idea is to
+ write filters than can operate in templates where auto-escaping is either
+ on or off in order to make things easier for your template authors.
- unordered_list.is_safe = True
- unordered_list.needs_autoescape = True
+ In order for you filter to know the current auto-escaping state, set the
+ ``needs_autoescape`` attribute to ``True`` on your function (if you don't
+ specify this attribute, it defaults to ``False``). This attribute tells
+ Django that your filter function wants to be passed an extra keyword
+ argument, called ``autoescape`` that is ``True`` is auto-escaping is in
+ effect and ``False`` otherwise.
-By default, both the ``is_safe`` and ``needs_autoescape`` attributes are
-``False``. You do not need to specify them if ``False`` is an acceptable
-value.
+ An example might make this clearer. Let's write a filter that emphasizes
+ the first character of a string::
+
+ from django.utils.html import conditional_escape
+ from django.utils.safestring import mark_safe
+
+ def initial_letter_filter(text, autoescape=None):
+ first, other = text[0] ,text[1:]
+ if autoescape:
+ esc = conditional_escape
+ else:
+ esc = lambda x: x
+ result = '<strong>%s</strong>%s' % (esc(first), esc(other))
+ return mark_safe(result)
+ initial_letter_filter.needs_autoescape = True
+
+ The ``needs_autoescape`` attribute on the filter function and the
+ ``autoescape`` keyword argument mean that our function will know whether
+ or not automatic escaping is in effect when the filter is called. We use
+ ``autoescape`` to decide whether the input data needs to be passed through
+ ``django.utils.html.conditional_escape`` or not (in the latter case, we
+ just use the identity function as the "escape" function). The
+ ``conditional_escape()`` function is like ``escape()`` except it only
+ escapes input that is **not** a ``SafeData`` instance. If a ``SafeData``
+ instance is passed to ``conditional_escape()``, the data is returned
+ unchanged.
+
+ Finally, in the above example, we remember to mark the result as safe
+ so that our HTML is inserted directly into the template without further
+ escaping.
+
+ There is no need to worry about the ``is_safe`` attribute in this case
+ (although including it wouldn't hurt anything). Whenever you are manually
+ handling the auto-escaping issues and returning a safe string, the
+ ``is_safe`` attribute won't change anything either way.
Writing custom template tags
----------------------------
@@ -932,7 +977,9 @@ without having to be parsed multiple times.
Auto-escaping considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The output from template tags is not automatically run through the
+**New in Django development version**
+
+The output from template tags is **not** automatically run through the
auto-escaping filters. However, there are still a couple of things you should
keep in mind when writing a template tag:
diff --git a/tests/modeltests/pagination/models.py b/tests/modeltests/pagination/models.py
index 1dcec00a32..f44c67a139 100644
--- a/tests/modeltests/pagination/models.py
+++ b/tests/modeltests/pagination/models.py
@@ -78,7 +78,8 @@ True
>>> paginator.pages
2
-# The paginator can provide a list of all available pages
+# The paginator can provide a list of all available pages.
+>>> paginator = ObjectPaginator(Article.objects.all(), 10)
>>> paginator.page_range
[1, 2]
"""}
diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
index 3879da7703..e94ea33139 100644
--- a/tests/regressiontests/cache/tests.py
+++ b/tests/regressiontests/cache/tests.py
@@ -3,9 +3,12 @@
# Unit tests for cache framework
# Uses whatever cache backend is set in the test settings file.
-from django.core.cache import cache
import time, unittest
+from django.core.cache import cache
+from django.utils.cache import patch_vary_headers
+from django.http import HttpResponse
+
# functions/classes for complex data type tests
def f():
return 42
@@ -87,5 +90,30 @@ class Cache(unittest.TestCase):
cache.set(key, value)
self.assertEqual(cache.get(key), value)
+
+class CacheUtils(unittest.TestCase):
+ """TestCase for django.utils.cache functions."""
+
+ def test_patch_vary_headers(self):
+ headers = (
+ # Initial vary, new headers, resulting vary.
+ (None, ('Accept-Encoding',), 'Accept-Encoding'),
+ ('Accept-Encoding', ('accept-encoding',), 'Accept-Encoding'),
+ ('Accept-Encoding', ('ACCEPT-ENCODING',), 'Accept-Encoding'),
+ ('Cookie', ('Accept-Encoding',), 'Cookie, Accept-Encoding'),
+ ('Cookie, Accept-Encoding', ('Accept-Encoding',), 'Cookie, Accept-Encoding'),
+ ('Cookie, Accept-Encoding', ('Accept-Encoding', 'cookie'), 'Cookie, Accept-Encoding'),
+ (None, ('Accept-Encoding', 'COOKIE'), 'Accept-Encoding, COOKIE'),
+ ('Cookie, Accept-Encoding', ('Accept-Encoding', 'cookie'), 'Cookie, Accept-Encoding'),
+ ('Cookie , Accept-Encoding', ('Accept-Encoding', 'cookie'), 'Cookie, Accept-Encoding'),
+ )
+ for initial_vary, newheaders, resulting_vary in headers:
+ response = HttpResponse()
+ if initial_vary is not None:
+ response['Vary'] = initial_vary
+ patch_vary_headers(response, newheaders)
+ self.assertEqual(response['Vary'], resulting_vary)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/regressiontests/datastructures/tests.py b/tests/regressiontests/datastructures/tests.py
index d1e21e673c..3b0ccde257 100644
--- a/tests/regressiontests/datastructures/tests.py
+++ b/tests/regressiontests/datastructures/tests.py
@@ -25,11 +25,23 @@
>>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']})
>>> d['name']
'Simon'
+>>> d.get('name')
+'Simon'
>>> d.getlist('name')
['Adrian', 'Simon']
+>>> d['lastname']
+Traceback (most recent call last):
+...
+MultiValueDictKeyError: "Key 'lastname' not found in <MultiValueDict: {'position': ['Developer'], 'name': ['Adrian', 'Simon']}>"
+>>> d.get('lastname')
+
>>> d.get('lastname', 'nonexistent')
'nonexistent'
+>>> d.getlist('lastname')
+[]
>>> d.setlist('lastname', ['Holovaty', 'Willison'])
+>>> d.getlist('lastname')
+['Holovaty', 'Willison']
### SortedDict #################################################################
diff --git a/tests/regressiontests/forms/error_messages.py b/tests/regressiontests/forms/error_messages.py
new file mode 100644
index 0000000000..9f972f5b90
--- /dev/null
+++ b/tests/regressiontests/forms/error_messages.py
@@ -0,0 +1,360 @@
+# -*- coding: utf-8 -*-
+tests = r"""
+>>> from django.newforms import *
+
+# CharField ###################################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['min_length'] = 'LENGTH %(length)s, MIN LENGTH %(min)s'
+>>> e['max_length'] = 'LENGTH %(length)s, MAX LENGTH %(max)s'
+>>> f = CharField(min_length=5, max_length=10, error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('1234')
+Traceback (most recent call last):
+...
+ValidationError: [u'LENGTH 4, MIN LENGTH 5']
+>>> f.clean('12345678901')
+Traceback (most recent call last):
+...
+ValidationError: [u'LENGTH 11, MAX LENGTH 10']
+
+# IntegerField ################################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid'] = 'INVALID'
+>>> e['min_value'] = 'MIN VALUE IS %s'
+>>> e['max_value'] = 'MAX VALUE IS %s'
+>>> f = IntegerField(min_value=5, max_value=10, error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('abc')
+Traceback (most recent call last):
+...
+ValidationError: [u'INVALID']
+>>> f.clean('4')
+Traceback (most recent call last):
+...
+ValidationError: [u'MIN VALUE IS 5']
+>>> f.clean('11')
+Traceback (most recent call last):
+...
+ValidationError: [u'MAX VALUE IS 10']
+
+# FloatField ##################################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid'] = 'INVALID'
+>>> e['min_value'] = 'MIN VALUE IS %s'
+>>> e['max_value'] = 'MAX VALUE IS %s'
+>>> f = FloatField(min_value=5, max_value=10, error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('abc')
+Traceback (most recent call last):
+...
+ValidationError: [u'INVALID']
+>>> f.clean('4')
+Traceback (most recent call last):
+...
+ValidationError: [u'MIN VALUE IS 5']
+>>> f.clean('11')
+Traceback (most recent call last):
+...
+ValidationError: [u'MAX VALUE IS 10']
+
+# DecimalField ################################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid'] = 'INVALID'
+>>> e['min_value'] = 'MIN VALUE IS %s'
+>>> e['max_value'] = 'MAX VALUE IS %s'
+>>> e['max_digits'] = 'MAX DIGITS IS %s'
+>>> e['max_decimal_places'] = 'MAX DP IS %s'
+>>> e['max_whole_digits'] = 'MAX DIGITS BEFORE DP IS %s'
+>>> f = DecimalField(min_value=5, max_value=10, error_messages=e)
+>>> f2 = DecimalField(max_digits=4, decimal_places=2, error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('abc')
+Traceback (most recent call last):
+...
+ValidationError: [u'INVALID']
+>>> f.clean('4')
+Traceback (most recent call last):
+...
+ValidationError: [u'MIN VALUE IS 5']
+>>> f.clean('11')
+Traceback (most recent call last):
+...
+ValidationError: [u'MAX VALUE IS 10']
+>>> f2.clean('123.45')
+Traceback (most recent call last):
+...
+ValidationError: [u'MAX DIGITS IS 4']
+>>> f2.clean('1.234')
+Traceback (most recent call last):
+...
+ValidationError: [u'MAX DP IS 2']
+>>> f2.clean('123.4')
+Traceback (most recent call last):
+...
+ValidationError: [u'MAX DIGITS BEFORE DP IS 2']
+
+# DateField ###################################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid'] = 'INVALID'
+>>> f = DateField(error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('abc')
+Traceback (most recent call last):
+...
+ValidationError: [u'INVALID']
+
+# TimeField ###################################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid'] = 'INVALID'
+>>> f = TimeField(error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('abc')
+Traceback (most recent call last):
+...
+ValidationError: [u'INVALID']
+
+# DateTimeField ###############################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid'] = 'INVALID'
+>>> f = DateTimeField(error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('abc')
+Traceback (most recent call last):
+...
+ValidationError: [u'INVALID']
+
+# RegexField ##################################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid'] = 'INVALID'
+>>> e['min_length'] = 'LENGTH %(length)s, MIN LENGTH %(min)s'
+>>> e['max_length'] = 'LENGTH %(length)s, MAX LENGTH %(max)s'
+>>> f = RegexField(r'^\d+$', min_length=5, max_length=10, error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('abcde')
+Traceback (most recent call last):
+...
+ValidationError: [u'INVALID']
+>>> f.clean('1234')
+Traceback (most recent call last):
+...
+ValidationError: [u'LENGTH 4, MIN LENGTH 5']
+>>> f.clean('12345678901')
+Traceback (most recent call last):
+...
+ValidationError: [u'LENGTH 11, MAX LENGTH 10']
+
+# EmailField ##################################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid'] = 'INVALID'
+>>> e['min_length'] = 'LENGTH %(length)s, MIN LENGTH %(min)s'
+>>> e['max_length'] = 'LENGTH %(length)s, MAX LENGTH %(max)s'
+>>> f = EmailField(min_length=8, max_length=10, error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('abcdefgh')
+Traceback (most recent call last):
+...
+ValidationError: [u'INVALID']
+>>> f.clean('a@b.com')
+Traceback (most recent call last):
+...
+ValidationError: [u'LENGTH 7, MIN LENGTH 8']
+>>> f.clean('aye@bee.com')
+Traceback (most recent call last):
+...
+ValidationError: [u'LENGTH 11, MAX LENGTH 10']
+
+# FileField ##################################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid'] = 'INVALID'
+>>> e['missing'] = 'MISSING'
+>>> e['empty'] = 'EMPTY FILE'
+>>> f = FileField(error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('abc')
+Traceback (most recent call last):
+...
+ValidationError: [u'INVALID']
+>>> f.clean({})
+Traceback (most recent call last):
+...
+ValidationError: [u'MISSING']
+>>> f.clean({'filename': 'name', 'content':''})
+Traceback (most recent call last):
+...
+ValidationError: [u'EMPTY FILE']
+
+# URLField ##################################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid'] = 'INVALID'
+>>> e['invalid_link'] = 'INVALID LINK'
+>>> f = URLField(verify_exists=True, error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('abc.c')
+Traceback (most recent call last):
+...
+ValidationError: [u'INVALID']
+>>> f.clean('http://www.jfoiwjfoi23jfoijoaijfoiwjofiwjefewl.com')
+Traceback (most recent call last):
+...
+ValidationError: [u'INVALID LINK']
+
+# BooleanField ################################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> f = BooleanField(error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+
+# ChoiceField #################################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid_choice'] = '%(value)s IS INVALID CHOICE'
+>>> f = ChoiceField(choices=[('a', 'aye')], error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('b')
+Traceback (most recent call last):
+...
+ValidationError: [u'b IS INVALID CHOICE']
+
+# MultipleChoiceField #########################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid_choice'] = '%(value)s IS INVALID CHOICE'
+>>> e['invalid_list'] = 'NOT A LIST'
+>>> f = MultipleChoiceField(choices=[('a', 'aye')], error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('b')
+Traceback (most recent call last):
+...
+ValidationError: [u'NOT A LIST']
+>>> f.clean(['b'])
+Traceback (most recent call last):
+...
+ValidationError: [u'b IS INVALID CHOICE']
+
+# SplitDateTimeField ##########################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid_date'] = 'INVALID DATE'
+>>> e['invalid_time'] = 'INVALID TIME'
+>>> f = SplitDateTimeField(error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean(['a', 'b'])
+Traceback (most recent call last):
+...
+ValidationError: [u'INVALID DATE', u'INVALID TIME']
+
+# IPAddressField ##############################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid'] = 'INVALID IP ADDRESS'
+>>> f = IPAddressField(error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('127.0.0')
+Traceback (most recent call last):
+...
+ValidationError: [u'INVALID IP ADDRESS']
+
+###############################################################################
+
+# Create choices for the model choice field tests below.
+
+>>> from regressiontests.forms.models import ChoiceModel
+>>> ChoiceModel.objects.create(pk=1, name='a')
+<ChoiceModel: ChoiceModel object>
+>>> ChoiceModel.objects.create(pk=2, name='b')
+<ChoiceModel: ChoiceModel object>
+>>> ChoiceModel.objects.create(pk=3, name='c')
+<ChoiceModel: ChoiceModel object>
+
+# ModelChoiceField ############################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid_choice'] = 'INVALID CHOICE'
+>>> f = ModelChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('4')
+Traceback (most recent call last):
+...
+ValidationError: [u'INVALID CHOICE']
+
+# ModelMultipleChoiceField ####################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid_choice'] = '%s IS INVALID CHOICE'
+>>> e['list'] = 'NOT A LIST OF VALUES'
+>>> f = ModelMultipleChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('3')
+Traceback (most recent call last):
+...
+ValidationError: [u'NOT A LIST OF VALUES']
+>>> f.clean(['4'])
+Traceback (most recent call last):
+...
+ValidationError: [u'4 IS INVALID CHOICE']
+"""
diff --git a/tests/regressiontests/forms/models.py b/tests/regressiontests/forms/models.py
index 1a6f566b6b..c7ce128560 100644
--- a/tests/regressiontests/forms/models.py
+++ b/tests/regressiontests/forms/models.py
@@ -10,6 +10,10 @@ class Defaults(models.Model):
def_date = models.DateField(default = datetime.date(1980, 1, 1))
value = models.IntegerField(default=42)
+class ChoiceModel(models.Model):
+ """For ModelChoiceField and ModelMultipleChoiceField tests."""
+ name = models.CharField(max_length=10)
+
__test__ = {'API_TESTS': """
>>> from django.newforms import form_for_model, form_for_instance
diff --git a/tests/regressiontests/forms/widgets.py b/tests/regressiontests/forms/widgets.py
index cb1d084631..ea8cf135aa 100644
--- a/tests/regressiontests/forms/widgets.py
+++ b/tests/regressiontests/forms/widgets.py
@@ -2,6 +2,7 @@
tests = r"""
>>> from django.newforms import *
>>> from django.newforms.widgets import RadioFieldRenderer
+>>> from django.utils.safestring import mark_safe
>>> import datetime
>>> import time
>>> import re
@@ -205,6 +206,8 @@ u'<textarea rows="10" cols="40" name="msg"></textarea>'
u'<textarea rows="10" cols="40" name="msg">value</textarea>'
>>> w.render('msg', 'some "quoted" & ampersanded value')
u'<textarea rows="10" cols="40" name="msg">some &quot;quoted&quot; &amp; ampersanded value</textarea>'
+>>> w.render('msg', mark_safe('pre &quot;quoted&quot; value'))
+u'<textarea rows="10" cols="40" name="msg">pre &quot;quoted&quot; value</textarea>'
>>> w.render('msg', 'value', attrs={'class': 'pretty', 'rows': 20})
u'<textarea class="pretty" rows="20" cols="40" name="msg">value</textarea>'
@@ -375,6 +378,17 @@ If 'choices' is passed to both the constructor and render(), then they'll both b
<option value="5">5</option>
</select>
+# Choices are escaped correctly
+>>> print w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you &gt; me'))))
+<select name="escape">
+<option value="1">1</option>
+<option value="2">2</option>
+<option value="3">3</option>
+<option value="bad">you &amp; me</option>
+<option value="good">you &gt; me</option>
+</select>
+
+# Unicode choices are correctly rendered as HTML
>>> w.render('email', 'ŠĐĆŽćžšđ', choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])
u'<select name="email">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" selected="selected">\u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</option>\n<option value="\u0107\u017e\u0161\u0111">abc\u0107\u017e\u0161\u0111</option>\n</select>'
@@ -538,6 +552,17 @@ If 'choices' is passed to both the constructor and render(), then they'll both b
<option value="5">5</option>
</select>
+# Choices are escaped correctly
+>>> print w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you &gt; me'))))
+<select multiple="multiple" name="escape">
+<option value="1">1</option>
+<option value="2">2</option>
+<option value="3">3</option>
+<option value="bad">you &amp; me</option>
+<option value="good">you &gt; me</option>
+</select>
+
+# Unicode choices are correctly rendered as HTML
>>> w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])
u'<select multiple="multiple" name="nums">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" selected="selected">\u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</option>\n<option value="\u0107\u017e\u0161\u0111">abc\u0107\u017e\u0161\u0111</option>\n</select>'
@@ -663,6 +688,16 @@ You can create your own custom renderers for RadioSelect to use.
<label><input checked="checked" type="radio" name="beatle" value="G" /> George</label><br />
<label><input type="radio" name="beatle" value="R" /> Ringo</label>
+Or you can use custom RadioSelect fields that use your custom renderer.
+>>> class CustomRadioSelect(RadioSelect):
+... renderer = MyRenderer
+>>> w = CustomRadioSelect()
+>>> print w.render('beatle', 'G', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+<label><input type="radio" name="beatle" value="J" /> John</label><br />
+<label><input type="radio" name="beatle" value="P" /> Paul</label><br />
+<label><input checked="checked" type="radio" name="beatle" value="G" /> George</label><br />
+<label><input type="radio" name="beatle" value="R" /> Ringo</label>
+
A RadioFieldRenderer object also allows index access to individual RadioInput
objects.
>>> w = RadioSelect()
@@ -682,6 +717,14 @@ Traceback (most recent call last):
...
IndexError: list index out of range
+# Choices are escaped correctly
+>>> w = RadioSelect()
+>>> print w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you &gt; me'))))
+<ul>
+<li><label><input type="radio" name="escape" value="bad" /> you &amp; me</label></li>
+<li><label><input type="radio" name="escape" value="good" /> you &gt; me</label></li>
+</ul>
+
# Unicode choices are correctly rendered as HTML
>>> w = RadioSelect()
>>> unicode(w.render('email', 'ŠĐĆŽćžšđ', choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]))
@@ -811,6 +854,17 @@ If 'choices' is passed to both the constructor and render(), then they'll both b
<li><label><input type="checkbox" name="nums" value="5" /> 5</label></li>
</ul>
+# Choices are escaped correctly
+>>> print w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you &gt; me'))))
+<ul>
+<li><label><input type="checkbox" name="escape" value="1" /> 1</label></li>
+<li><label><input type="checkbox" name="escape" value="2" /> 2</label></li>
+<li><label><input type="checkbox" name="escape" value="3" /> 3</label></li>
+<li><label><input type="checkbox" name="escape" value="bad" /> you &amp; me</label></li>
+<li><label><input type="checkbox" name="escape" value="good" /> you &gt; me</label></li>
+</ul>
+
+# Unicode choices are correctly rendered as HTML
>>> w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])
u'<ul>\n<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>\n<li><label><input type="checkbox" name="nums" value="2" /> 2</label></li>\n<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>\n<li><label><input checked="checked" type="checkbox" name="nums" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /> \u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</label></li>\n<li><label><input type="checkbox" name="nums" value="\u0107\u017e\u0161\u0111" /> abc\u0107\u017e\u0161\u0111</label></li>\n</ul>'
diff --git a/tests/regressiontests/templates/filters.py b/tests/regressiontests/templates/filters.py
index 27b24cb169..2a06703948 100644
--- a/tests/regressiontests/templates/filters.py
+++ b/tests/regressiontests/templates/filters.py
@@ -198,6 +198,12 @@ def get_filter_tests():
'filter-phone2numeric01': ('{{ a|phone2numeric }} {{ b|phone2numeric }}', {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>") }, "&lt;1-800-2255-63&gt; <1-800-2255-63>"),
'filter-phone2numeric02': ('{% autoescape off %}{{ a|phone2numeric }} {{ b|phone2numeric }}{% endautoescape %}', {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>") }, "<1-800-2255-63> <1-800-2255-63>"),
+ # Ensure iriencode keeps safe strings:
+ 'filter-iriencode01': ('{{ url|iriencode }}', {'url': '?test=1&me=2'}, '?test=1&amp;me=2'),
+ 'filter-iriencode02': ('{% autoescape off %}{{ url|iriencode }}{% endautoescape %}', {'url': '?test=1&me=2'}, '?test=1&me=2'),
+ 'filter-iriencode03': ('{{ url|iriencode }}', {'url': mark_safe('?test=1&me=2')}, '?test=1&me=2'),
+ 'filter-iriencode04': ('{% autoescape off %}{{ url|iriencode }}{% endautoescape %}', {'url': mark_safe('?test=1&me=2')}, '?test=1&me=2'),
+
# Chaining a bunch of safeness-preserving filters should not alter
# the safe status either way.
'chaining01': ('{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}', {"a": "a < b", "b": mark_safe("a < b")}, " A &lt; b . A < b "),
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index 5c3a0a9081..f3c131dd91 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -268,6 +268,12 @@ class Templates(unittest.TestCase):
# Embedded newlines make it not-a-tag.
'basic-syntax24': ("{{ moo\n }}", {}, "{{ moo\n }}"),
+ # Literal strings are permitted inside variables, mostly for i18n
+ # purposes.
+ 'basic-syntax25': ('{{ "fred" }}', {}, "fred"),
+ 'basic-syntax26': (r'{{ "\"fred\"" }}', {}, "\"fred\""),
+ 'basic-syntax27': (r'{{ _("\"fred\"") }}', {}, "\"fred\""),
+
# List-index syntax allows a template to access a certain item of a subscriptable object.
'list-index01': ("{{ var.1 }}", {"var": ["first item", "second item"]}, "second item"),