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 Binary files a/django/conf/locale/pl/LC_MESSAGES/django.mo and b/django/conf/locale/pl/LC_MESSAGES/django.mo 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 \n" "Language-Team: Polish \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 "" "

By %s:

\n" @@ -205,71 +205,71 @@ msgstr "" "

Przez %s:

\n" "\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(/</g, '<'); + text = text.replace(/>/g, '>'); + text = text.replace(/"/g, '"'); + text = text.replace(/'/g, "'"); + text = text.replace(/&/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 = ' ' + result_repr = mark_safe(' ') # 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('' % \ - (pk_value, force_unicode(new_object).replace('"', '\\"'))) + return HttpResponse('' % \ + # 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 - 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 + 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 + 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 ' 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'%s' % (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'' % (escape(option_value), selected_html, escape(force_unicode(option_label)))) + output.append(u'' % ( + escape(option_value), selected_html, + conditional_escape(force_unicode(option_label)))) output.append(u'') 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'' % (escape(option_value), selected_html, escape(force_unicode(option_label)))) + output.append(u'' % ( + escape(option_value), selected_html, + conditional_escape(force_unicode(option_label)))) output.append(u'') return mark_safe(u'\n'.join(output)) @@ -278,7 +282,7 @@ class RadioInput(StrAndUnicode): def __unicode__(self): return mark_safe(u'' % (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'
  • ' % (rendered_cb, escape(force_unicode(option_label)))) + output.append(u'
  • ' % (rendered_cb, + conditional_escape(force_unicode(option_label)))) output.append(u'') 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 %}
    {% if frame.pre_context %} -
      {% for line in frame.pre_context %}
    1. {{ line }}
    2. {% endfor %}
    +
      {% for line in frame.pre_context %}
    1. {{ line|escape }}
    2. {% endfor %}
    {% endif %} -
    1. {{ frame.context_line }} ...
    +
    1. {{ frame.context_line|escape }} ...
    {% if frame.post_context %} -
      {% for line in frame.post_context %}
    1. {{ line }}
    2. {% endfor %}
    +
      {% for line in frame.post_context %}
    1. {{ line|escape }}
    2. {% endfor %}
    {% endif %}
    {% endif %} @@ -445,8 +445,8 @@ TECHNICAL_500_TEMPLATE = """ {% for var in frame.vars|dictsort:"0" %} - {{ var.0 }} -
    {{ var.1|pprint }}
    + {{ var.0|escape }} +
    {{ var.1|pprint|escape }}
    {% endfor %} @@ -466,7 +466,7 @@ Traceback (most recent call last):
    {% for frame in frames %} File "{{ frame.filename }}" in {{ frame.function }}
    {% if frame.context_line %} -   {{ frame.lineno }}. {{ frame.context_line }}
    +   {{ frame.lineno }}. {{ frame.context_line|escape }}
    {% endif %} {% endfor %}
      {{ exception_type }} at {{ request.path|escape }}
    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 '', 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 ```` into + ``>> 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 " +>>> 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.objects.create(pk=2, name='b') + +>>> ChoiceModel.objects.create(pk=3, name='c') + + +# 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'' u'' >>> w.render('msg', 'some "quoted" & ampersanded value') u'' +>>> w.render('msg', mark_safe('pre "quoted" value')) +u'' >>> w.render('msg', 'value', attrs={'class': 'pretty', 'rows': 20}) u'' @@ -375,6 +378,17 @@ If 'choices' is passed to both the constructor and render(), then they'll both b +# Choices are escaped correctly +>>> print w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you > me')))) + + +# Unicode choices are correctly rendered as HTML >>> w.render('email', 'ŠĐĆŽćžšđ', choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]) u'' @@ -538,6 +552,17 @@ If 'choices' is passed to both the constructor and render(), then they'll both b +# Choices are escaped correctly +>>> print w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you > me')))) + + +# Unicode choices are correctly rendered as HTML >>> w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]) u'' @@ -663,6 +688,16 @@ You can create your own custom renderers for RadioSelect to use.
    +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'))) +
    +
    +
    + + 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 > me')))) +
      +
    • +
    • +
    + # 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
  • +# Choices are escaped correctly +>>> print w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you > me')))) +
      +
    • +
    • +
    • +
    • +
    • +
    + +# Unicode choices are correctly rendered as HTML >>> w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]) u'
      \n
    • \n
    • \n
    • \n
    • \n
    • \n
    ' 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>") }, "<1-800-2255-63> <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&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 < 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"),