1
0
mirror of https://github.com/django/django.git synced 2025-07-03 17:29:12 +00:00

newforms-admin: Merged from trunk up to [7766].

git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@7770 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Brian Rosner 2008-06-26 15:42:33 +00:00
parent f15845c573
commit c8da0874c7
30 changed files with 589 additions and 327 deletions

View File

@ -385,6 +385,7 @@ answer newbie questions, and generally made Django that much better:
Wang Chun <wangchun@exoweb.net> Wang Chun <wangchun@exoweb.net>
Filip Wasilewski <filip.wasilewski@gmail.com> Filip Wasilewski <filip.wasilewski@gmail.com>
Dan Watson <http://theidioteque.net/> Dan Watson <http://theidioteque.net/>
Joel Watts <joel@joelwatts.com>
Chris Wesseling <Chris.Wesseling@cwi.nl> Chris Wesseling <Chris.Wesseling@cwi.nl>
James Wheare <django@sparemint.com> James Wheare <django@sparemint.com>
charly.wilhelm@gmail.com charly.wilhelm@gmail.com

View File

@ -5,9 +5,9 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Django\n" "Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2008-04-14 18:02+0200\n" "POT-Creation-Date: 2008-06-24 07:36+0200\n"
"PO-Revision-Date: 2008-02-25 15:53+0100\n" "PO-Revision-Date: 2008-02-25 15:53+0100\n"
"Last-Translator: Piotr Lewandowski <django@icomputing.pl>\n" "Last-Translator: Jarek Zgoda <jarek.zgoda@gmail.com>\n"
"Language-Team: Polish <pl@li.org>\n" "Language-Team: Polish <pl@li.org>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -16,191 +16,199 @@ msgstr ""
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%"
"100<10 || n%100>=20) ? 1 : 2);\n" "100<10 || n%100>=20) ? 1 : 2);\n"
#: conf/global_settings.py:39 #: conf/global_settings.py:44
msgid "Arabic" msgid "Arabic"
msgstr "Arabski" msgstr "Arabski"
#: conf/global_settings.py:40 #: conf/global_settings.py:45
msgid "Bengali" msgid "Bengali"
msgstr "Bengalski" msgstr "Bengalski"
#: conf/global_settings.py:41 #: conf/global_settings.py:46
msgid "Bulgarian" msgid "Bulgarian"
msgstr "Bułgarski" msgstr "Bułgarski"
#: conf/global_settings.py:42 #: conf/global_settings.py:47
msgid "Catalan" msgid "Catalan"
msgstr "Kataloński" msgstr "Kataloński"
#: conf/global_settings.py:43 #: conf/global_settings.py:48
msgid "Czech" msgid "Czech"
msgstr "Czeski" msgstr "Czeski"
#: conf/global_settings.py:44 #: conf/global_settings.py:49
msgid "Welsh" msgid "Welsh"
msgstr "Walijski" msgstr "Walijski"
#: conf/global_settings.py:45 #: conf/global_settings.py:50
msgid "Danish" msgid "Danish"
msgstr "Duński" msgstr "Duński"
#: conf/global_settings.py:46 #: conf/global_settings.py:51
msgid "German" msgid "German"
msgstr "Niemiecki" msgstr "Niemiecki"
#: conf/global_settings.py:47 #: conf/global_settings.py:52
msgid "Greek" msgid "Greek"
msgstr "Grecki" msgstr "Grecki"
#: conf/global_settings.py:48 #: conf/global_settings.py:53
msgid "English" msgid "English"
msgstr "Angielski" msgstr "Angielski"
#: conf/global_settings.py:49 #: conf/global_settings.py:54
msgid "Spanish" msgid "Spanish"
msgstr "Hiszpański" msgstr "Hiszpański"
#: conf/global_settings.py:50 #: conf/global_settings.py:55
msgid "Estonian"
msgstr "Estoński"
#: conf/global_settings.py:56
msgid "Argentinean Spanish" msgid "Argentinean Spanish"
msgstr "Hiszpański argentyński" msgstr "Hiszpański argentyński"
#: conf/global_settings.py:51 #: conf/global_settings.py:57
msgid "Basque" msgid "Basque"
msgstr "Baskijski" msgstr "Baskijski"
#: conf/global_settings.py:52 #: conf/global_settings.py:58
msgid "Persian" msgid "Persian"
msgstr "Perski" msgstr "Perski"
#: conf/global_settings.py:53 #: conf/global_settings.py:59
msgid "Finnish" msgid "Finnish"
msgstr "Fiński" msgstr "Fiński"
#: conf/global_settings.py:54 #: conf/global_settings.py:60
msgid "French" msgid "French"
msgstr "Francuski" msgstr "Francuski"
#: conf/global_settings.py:55 #: conf/global_settings.py:61
msgid "Irish" msgid "Irish"
msgstr "Irlandzki" msgstr "Irlandzki"
#: conf/global_settings.py:56 #: conf/global_settings.py:62
msgid "Galician" msgid "Galician"
msgstr "Galicyjski" msgstr "Galicyjski"
#: conf/global_settings.py:57 #: conf/global_settings.py:63
msgid "Hungarian" msgid "Hungarian"
msgstr "Węgierski" msgstr "Węgierski"
#: conf/global_settings.py:58 #: conf/global_settings.py:64
msgid "Hebrew" msgid "Hebrew"
msgstr "Hebrajski" msgstr "Hebrajski"
#: conf/global_settings.py:59 #: conf/global_settings.py:65
msgid "Croatian" msgid "Croatian"
msgstr "Chorwacki" msgstr "Chorwacki"
#: conf/global_settings.py:60 #: conf/global_settings.py:66
msgid "Icelandic" msgid "Icelandic"
msgstr "Islandzki" msgstr "Islandzki"
#: conf/global_settings.py:61 #: conf/global_settings.py:67
msgid "Italian" msgid "Italian"
msgstr "Włoski" msgstr "Włoski"
#: conf/global_settings.py:62 #: conf/global_settings.py:68
msgid "Japanese" msgid "Japanese"
msgstr "Japoński" msgstr "Japoński"
#: conf/global_settings.py:63 #: conf/global_settings.py:69
msgid "Georgian" msgid "Georgian"
msgstr "Gruziński" msgstr "Gruziński"
#: conf/global_settings.py:64 #: conf/global_settings.py:70
msgid "Korean" msgid "Korean"
msgstr "Koreański" msgstr "Koreański"
#: conf/global_settings.py:65 #: conf/global_settings.py:71
msgid "Khmer" msgid "Khmer"
msgstr "Khmerski" msgstr "Khmerski"
#: conf/global_settings.py:66 #: conf/global_settings.py:72
msgid "Kannada" msgid "Kannada"
msgstr "Kannada" msgstr "Kannada"
#: conf/global_settings.py:67 #: conf/global_settings.py:73
msgid "Latvian" msgid "Latvian"
msgstr "Łotewski" msgstr "Łotewski"
#: conf/global_settings.py:68 #: conf/global_settings.py:74
msgid "Lithuanian"
msgstr "Litewski"
#: conf/global_settings.py:75
msgid "Macedonian" msgid "Macedonian"
msgstr "Macedoński" msgstr "Macedoński"
#: conf/global_settings.py:69 #: conf/global_settings.py:76
msgid "Dutch" msgid "Dutch"
msgstr "Holenderski" msgstr "Holenderski"
#: conf/global_settings.py:70 #: conf/global_settings.py:77
msgid "Norwegian" msgid "Norwegian"
msgstr "Norweski" msgstr "Norweski"
#: conf/global_settings.py:71 #: conf/global_settings.py:78
msgid "Polish" msgid "Polish"
msgstr "Polski" msgstr "Polski"
#: conf/global_settings.py:72 #: conf/global_settings.py:79
msgid "Portugese" msgid "Portugese"
msgstr "Portugalski" msgstr "Portugalski"
#: conf/global_settings.py:73 #: conf/global_settings.py:80
msgid "Brazilian Portuguese" msgid "Brazilian Portuguese"
msgstr "Brazylijski portugalski" msgstr "Brazylijski portugalski"
#: conf/global_settings.py:74 #: conf/global_settings.py:81
msgid "Romanian" msgid "Romanian"
msgstr "Rumuński" msgstr "Rumuński"
#: conf/global_settings.py:75 #: conf/global_settings.py:82
msgid "Russian" msgid "Russian"
msgstr "Rosyjski" msgstr "Rosyjski"
#: conf/global_settings.py:76 #: conf/global_settings.py:83
msgid "Slovak" msgid "Slovak"
msgstr "Słowacki" msgstr "Słowacki"
#: conf/global_settings.py:77 #: conf/global_settings.py:84
msgid "Slovenian" msgid "Slovenian"
msgstr "Słoweński" msgstr "Słoweński"
#: conf/global_settings.py:78 #: conf/global_settings.py:85
msgid "Serbian" msgid "Serbian"
msgstr "Serbski" msgstr "Serbski"
#: conf/global_settings.py:79 #: conf/global_settings.py:86
msgid "Swedish" msgid "Swedish"
msgstr "Szwedzki" msgstr "Szwedzki"
#: conf/global_settings.py:80 #: conf/global_settings.py:87
msgid "Tamil" msgid "Tamil"
msgstr "Tamilski" msgstr "Tamilski"
#: conf/global_settings.py:81 #: conf/global_settings.py:88
msgid "Telugu" msgid "Telugu"
msgstr "Telugu" msgstr "Telugu"
#: conf/global_settings.py:82 #: conf/global_settings.py:89
msgid "Turkish" msgid "Turkish"
msgstr "Turecki" msgstr "Turecki"
#: conf/global_settings.py:83 #: conf/global_settings.py:90
msgid "Ukrainian" msgid "Ukrainian"
msgstr "Ukraiński" msgstr "Ukraiński"
#: conf/global_settings.py:84 #: conf/global_settings.py:91
msgid "Simplified Chinese" msgid "Simplified Chinese"
msgstr "Uproszczony chiński" msgstr "Uproszczony chiński"
#: conf/global_settings.py:85 #: conf/global_settings.py:92
msgid "Traditional Chinese" msgid "Traditional Chinese"
msgstr "Chiński tradycyjny" msgstr "Chiński tradycyjny"
@ -417,7 +425,7 @@ msgstr ""
#: contrib/admin/templates/admin/delete_confirmation.html:25 #: contrib/admin/templates/admin/delete_confirmation.html:25
msgid "Yes, I'm sure" msgid "Yes, I'm sure"
msgstr "Tak, usuń" msgstr "Tak, na pewno"
#: contrib/admin/templates/admin/filter.html:2 #: contrib/admin/templates/admin/filter.html:2
#, python-format #, python-format
@ -824,15 +832,15 @@ msgstr ""
"Twoja przeglądarka nie chce akceptować ciasteczek. Zmień jej ustawienia i " "Twoja przeglądarka nie chce akceptować ciasteczek. Zmień jej ustawienia i "
"spróbuj ponownie." "spróbuj ponownie."
#: contrib/admin/views/decorators.py:90 #: contrib/admin/views/decorators.py:89
msgid "Usernames cannot contain the '@' character."
msgstr "Nazwy użytkowników nie mogą zawierać znaków '@'."
#: contrib/admin/views/decorators.py:92
#, python-format #, python-format
msgid "Your e-mail address is not your username. Try '%s' instead." 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'." msgstr "Twój adres e-mail to nie jest twój login. Spróbuj '%s'."
#: contrib/admin/views/decorators.py:93
msgid "Usernames cannot contain the '@' character."
msgstr "Nazwy użytkowników nie mogą zawierać znaków '@'."
#: contrib/admin/views/doc.py:48 contrib/admin/views/doc.py:50 #: contrib/admin/views/doc.py:48 contrib/admin/views/doc.py:50
#: contrib/admin/views/doc.py:52 #: contrib/admin/views/doc.py:52
msgid "tag:" msgid "tag:"
@ -1063,7 +1071,7 @@ msgstr "Zaznacz %s"
msgid "Select %s to change" msgid "Select %s to change"
msgstr "Zaznacz %s aby zmienić" msgstr "Zaznacz %s aby zmienić"
#: contrib/admin/views/main.py:784 #: contrib/admin/views/main.py:765
msgid "Database error" msgid "Database error"
msgstr "Błąd bazy danych" msgstr "Błąd bazy danych"
@ -1128,15 +1136,15 @@ msgstr "uprawnienia"
msgid "group" msgid "group"
msgstr "grupa" msgstr "grupa"
#: contrib/auth/models.py:98 contrib/auth/models.py:141 #: contrib/auth/models.py:98 contrib/auth/models.py:148
msgid "groups" msgid "groups"
msgstr "grupy" msgstr "grupy"
#: contrib/auth/models.py:131 #: contrib/auth/models.py:138
msgid "username" msgid "username"
msgstr "użytkownik" msgstr "użytkownik"
#: contrib/auth/models.py:131 #: contrib/auth/models.py:138
msgid "" msgid ""
"Required. 30 characters or fewer. Alphanumeric characters only (letters, " "Required. 30 characters or fewer. Alphanumeric characters only (letters, "
"digits and underscores)." "digits and underscores)."
@ -1144,23 +1152,23 @@ msgstr ""
"Wymagane. 30 znaków lub mniej. Tylko znaki alfanumeryczne (litery, cyfry i " "Wymagane. 30 znaków lub mniej. Tylko znaki alfanumeryczne (litery, cyfry i "
"podkreślenia)." "podkreślenia)."
#: contrib/auth/models.py:132 #: contrib/auth/models.py:139
msgid "first name" msgid "first name"
msgstr "Imię" msgstr "Imię"
#: contrib/auth/models.py:133 #: contrib/auth/models.py:140
msgid "last name" msgid "last name"
msgstr "Nazwisko" msgstr "Nazwisko"
#: contrib/auth/models.py:134 #: contrib/auth/models.py:141
msgid "e-mail address" msgid "e-mail address"
msgstr "adres e-mail" msgstr "adres e-mail"
#: contrib/auth/models.py:135 #: contrib/auth/models.py:142
msgid "password" msgid "password"
msgstr "hasło" msgstr "hasło"
#: contrib/auth/models.py:135 #: contrib/auth/models.py:142
msgid "" msgid ""
"Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change " "Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change "
"password form</a>." "password form</a>."
@ -1168,19 +1176,19 @@ msgstr ""
"Użyj '[algo]$[salt]$[hexdigest]' lub <a href=\"password/\">formularza zmiany " "Użyj '[algo]$[salt]$[hexdigest]' lub <a href=\"password/\">formularza zmiany "
"hasła</a>." "hasła</a>."
#: contrib/auth/models.py:136 #: contrib/auth/models.py:143
msgid "staff status" msgid "staff status"
msgstr "w zespole" msgstr "w zespole"
#: contrib/auth/models.py:136 #: contrib/auth/models.py:143
msgid "Designates whether the user can log into this admin site." msgid "Designates whether the user can log into this admin site."
msgstr "Oznacza czy użytkownik może zalogować się do panelu admina." msgstr "Oznacza czy użytkownik może zalogować się do panelu admina."
#: contrib/auth/models.py:137 #: contrib/auth/models.py:144
msgid "active" msgid "active"
msgstr "aktywny" msgstr "aktywny"
#: contrib/auth/models.py:137 #: contrib/auth/models.py:144
msgid "" msgid ""
"Designates whether this user should be treated as active. Unselect this " "Designates whether this user should be treated as active. Unselect this "
"instead of deleting accounts." "instead of deleting accounts."
@ -1188,11 +1196,11 @@ msgstr ""
"Oznacza czy użytkownika należy uważać za aktywnego. Odznacz tozamiast usuwać " "Oznacza czy użytkownika należy uważać za aktywnego. Odznacz tozamiast usuwać "
"konta." "konta."
#: contrib/auth/models.py:138 #: contrib/auth/models.py:145
msgid "superuser status" msgid "superuser status"
msgstr "Główny Administrator" msgstr "Główny Administrator"
#: contrib/auth/models.py:138 #: contrib/auth/models.py:145
msgid "" msgid ""
"Designates that this user has all permissions without explicitly assigning " "Designates that this user has all permissions without explicitly assigning "
"them." "them."
@ -1200,15 +1208,15 @@ msgstr ""
"Oznacza, że ten użytkownik ma wszystkie uprawnienia bez jawnego " "Oznacza, że ten użytkownik ma wszystkie uprawnienia bez jawnego "
"przypisywania ich." "przypisywania ich."
#: contrib/auth/models.py:139 #: contrib/auth/models.py:146
msgid "last login" msgid "last login"
msgstr "ostatnio zalogowany" msgstr "ostatnio zalogowany"
#: contrib/auth/models.py:140 #: contrib/auth/models.py:147
msgid "date joined" msgid "date joined"
msgstr "data przyłączenia" msgstr "data przyłączenia"
#: contrib/auth/models.py:142 #: contrib/auth/models.py:149
msgid "" msgid ""
"In addition to the permissions manually assigned, this user will also get " "In addition to the permissions manually assigned, this user will also get "
"all permissions granted to each group he/she is in." "all permissions granted to each group he/she is in."
@ -1216,39 +1224,39 @@ msgstr ""
"Oprócz uprawnień przypisanych bezpośrednio użytkownikowi otrzyma on " "Oprócz uprawnień przypisanych bezpośrednio użytkownikowi otrzyma on "
"uprawnienia grup, do których należy." "uprawnienia grup, do których należy."
#: contrib/auth/models.py:143 #: contrib/auth/models.py:150
msgid "user permissions" msgid "user permissions"
msgstr "uprawnienia użytkownika" msgstr "uprawnienia użytkownika"
#: contrib/auth/models.py:147 #: contrib/auth/models.py:154
msgid "user" msgid "user"
msgstr "użytkownik" msgstr "użytkownik"
#: contrib/auth/models.py:148 #: contrib/auth/models.py:155
msgid "users" msgid "users"
msgstr "użytkownicy" msgstr "użytkownicy"
#: contrib/auth/models.py:154 #: contrib/auth/models.py:161
msgid "Personal info" msgid "Personal info"
msgstr "Dane osobowe" msgstr "Dane osobowe"
#: contrib/auth/models.py:155 #: contrib/auth/models.py:162
msgid "Permissions" msgid "Permissions"
msgstr "Uprawnienia" msgstr "Uprawnienia"
#: contrib/auth/models.py:156 #: contrib/auth/models.py:163
msgid "Important dates" msgid "Important dates"
msgstr "Ważne daty" msgstr "Ważne daty"
#: contrib/auth/models.py:157 #: contrib/auth/models.py:164
msgid "Groups" msgid "Groups"
msgstr "Grupy" msgstr "Grupy"
#: contrib/auth/models.py:316 #: contrib/auth/models.py:323
msgid "message" msgid "message"
msgstr "wiadomość" msgstr "wiadomość"
#: contrib/auth/views.py:47 #: contrib/auth/views.py:48
msgid "Logged out" msgid "Logged out"
msgstr "Wylogowany" msgstr "Wylogowany"
@ -3516,23 +3524,23 @@ msgstr "przekieruj"
msgid "redirects" msgid "redirects"
msgstr "przekierowania" msgstr "przekierowania"
#: contrib/sessions/models.py:41 #: contrib/sessions/models.py:45
msgid "session key" msgid "session key"
msgstr "klucz sesji" msgstr "klucz sesji"
#: contrib/sessions/models.py:42 #: contrib/sessions/models.py:47
msgid "session data" msgid "session data"
msgstr "data sesji" msgstr "data sesji"
#: contrib/sessions/models.py:43 #: contrib/sessions/models.py:48
msgid "expire date" msgid "expire date"
msgstr "data wygaśnięcia sesji" msgstr "data wygaśnięcia sesji"
#: contrib/sessions/models.py:48 #: contrib/sessions/models.py:53
msgid "session" msgid "session"
msgstr "sesja" msgstr "sesja"
#: contrib/sessions/models.py:49 #: contrib/sessions/models.py:54
msgid "sessions" msgid "sessions"
msgstr "sesje" msgstr "sesje"
@ -3617,7 +3625,7 @@ msgstr "Rok nie może być wcześniejszy niż 1900."
msgid "Invalid date: %s" msgid "Invalid date: %s"
msgstr "Niepoprawna data: %s" msgstr "Niepoprawna data: %s"
#: core/validators.py:156 db/models/fields/__init__.py:527 #: core/validators.py:156 db/models/fields/__init__.py:548
msgid "Enter a valid date in YYYY-MM-DD format." msgid "Enter a valid date in YYYY-MM-DD format."
msgstr "Proszę wpisać poprawną datę w formacie RRRR-MM-DD." msgstr "Proszę wpisać poprawną datę w formacie RRRR-MM-DD."
@ -3625,7 +3633,7 @@ msgstr "Proszę wpisać poprawną datę w formacie RRRR-MM-DD."
msgid "Enter a valid time in HH:MM format." msgid "Enter a valid time in HH:MM format."
msgstr "Proszę wpisać poprawną godzinę w formacie HH:MM." msgstr "Proszę wpisać poprawną godzinę w formacie HH:MM."
#: core/validators.py:165 db/models/fields/__init__.py:604 #: core/validators.py:165 db/models/fields/__init__.py:625
msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format."
msgstr "Wprowadź poprawną datę i godzinę w formacie RRRR-MM-DD GG:MM." msgstr "Wprowadź poprawną datę i godzinę w formacie RRRR-MM-DD GG:MM."
@ -3888,60 +3896,60 @@ msgstr ""
msgid "%(object)s with this %(type)s already exists for the given %(field)s." msgid "%(object)s with this %(type)s already exists for the given %(field)s."
msgstr "%(object)s z %(type)s już istnieje dla %(field)s." msgstr "%(object)s z %(type)s już istnieje dla %(field)s."
#: db/models/fields/__init__.py:52 #: db/models/fields/__init__.py:51
#, python-format #, python-format
msgid "%(optname)s with this %(fieldname)s already exists." msgid "%(optname)s with this %(fieldname)s already exists."
msgstr "Już istnieje %(optname)s z %(fieldname)s." msgstr "Już istnieje %(optname)s z %(fieldname)s."
#: db/models/fields/__init__.py:161 db/models/fields/__init__.py:327 #: db/models/fields/__init__.py:176 db/models/fields/__init__.py:348
#: db/models/fields/__init__.py:759 db/models/fields/__init__.py:770 #: db/models/fields/__init__.py:780 db/models/fields/__init__.py:791
#: newforms/fields.py:46 oldforms/__init__.py:374 #: newforms/fields.py:46 oldforms/__init__.py:374
msgid "This field is required." msgid "This field is required."
msgstr "To pole jest wymagane." msgstr "To pole jest wymagane."
#: db/models/fields/__init__.py:427 #: db/models/fields/__init__.py:448
msgid "This value must be an integer." msgid "This value must be an integer."
msgstr "Ta wartość musi być liczbą całkowitą." msgstr "Ta wartość musi być liczbą całkowitą."
#: db/models/fields/__init__.py:466 #: db/models/fields/__init__.py:487
msgid "This value must be either True or False." msgid "This value must be either True or False."
msgstr "" msgstr ""
"Ta wartość musi być wartością logiczną (True, False - prawda lub fałsz)." "Ta wartość musi być wartością logiczną (True, False - prawda lub fałsz)."
#: db/models/fields/__init__.py:490 #: db/models/fields/__init__.py:511
msgid "This field cannot be null." msgid "This field cannot be null."
msgstr "To pole nie może być puste." msgstr "To pole nie może być puste."
#: db/models/fields/__init__.py:668 #: db/models/fields/__init__.py:689
msgid "This value must be a decimal number." msgid "This value must be a decimal number."
msgstr "Ta wartość musi być liczbą dziesiętną." msgstr "Ta wartość musi być liczbą dziesiętną."
#: db/models/fields/__init__.py:779 #: db/models/fields/__init__.py:800
msgid "Enter a valid filename." msgid "Enter a valid filename."
msgstr "Wpisz poprawną nazwę pliku." msgstr "Wpisz poprawną nazwę pliku."
#: db/models/fields/__init__.py:960 #: db/models/fields/__init__.py:981
msgid "This value must be either None, True or False." msgid "This value must be either None, True or False."
msgstr "" msgstr ""
"Ta wartość musi być jedną z None (nic), True (prawda) lub False (fałsz)." "Ta wartość musi być jedną z None (nic), True (prawda) lub False (fałsz)."
#: db/models/fields/related.py:93 #: db/models/fields/related.py:94
#, python-format #, python-format
msgid "Please enter a valid %s." msgid "Please enter a valid %s."
msgstr "Proszę wpisać poprawne %s." msgstr "Proszę wpisać poprawne %s."
#: db/models/fields/related.py:701 #: db/models/fields/related.py:746
msgid "Separate multiple IDs with commas." msgid "Separate multiple IDs with commas."
msgstr "Oddziel identyfikatory przecinkami." msgstr "Oddziel identyfikatory przecinkami."
#: db/models/fields/related.py:703 #: db/models/fields/related.py:748
msgid "" msgid ""
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." "Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
msgstr "" msgstr ""
"Przytrzymaj wciśnięty klawisz \"Ctrl\" lub \"Command\" na Mac'u aby " "Przytrzymaj wciśnięty klawisz \"Ctrl\" lub \"Command\" na Mac'u aby "
"zaznaczyć więcej niż jeden wybór." "zaznaczyć więcej niż jeden wybór."
#: db/models/fields/related.py:750 #: db/models/fields/related.py:795
#, python-format #, python-format
msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid."
msgid_plural "" msgid_plural ""
@ -4003,11 +4011,11 @@ msgstr "Upewnij się, że jest nie więcej niż %s miejsc po przecinku."
msgid "Ensure that there are no more than %s digits before the decimal point." msgid "Ensure that there are no more than %s digits before the decimal point."
msgstr "Upewnij się, że jest nie więcej niż %s miejsc przed przecinkiem." msgstr "Upewnij się, że jest nie więcej niż %s miejsc przed przecinkiem."
#: newforms/fields.py:263 newforms/fields.py:751 #: newforms/fields.py:263 newforms/fields.py:750
msgid "Enter a valid date." msgid "Enter a valid date."
msgstr "Wpisz poprawną datę." msgstr "Wpisz poprawną datę."
#: newforms/fields.py:296 newforms/fields.py:752 #: newforms/fields.py:296 newforms/fields.py:751
msgid "Enter a valid time." msgid "Enter a valid time."
msgstr "Wpisz poprawną godzinę." msgstr "Wpisz poprawną godzinę."
@ -4031,25 +4039,25 @@ msgstr "Wpisz poprawny URL."
msgid "This URL appears to be a broken link." msgid "This URL appears to be a broken link."
msgstr "Ten odnośnik jest nieprawidłowy." msgstr "Ten odnośnik jest nieprawidłowy."
#: newforms/fields.py:560 newforms/models.py:299 #: newforms/fields.py:559 newforms/models.py:305
msgid "Select a valid choice. That choice is not one of the available choices." msgid "Select a valid choice. That choice is not one of the available choices."
msgstr "Wybierz poprawną wartość. Podana nie jest jednym z dostępnych wyborów." msgstr "Wybierz poprawną wartość. Podana nie jest jednym z dostępnych wyborów."
#: newforms/fields.py:599 #: newforms/fields.py:598
#, python-format #, python-format
msgid "Select a valid choice. %(value)s is not one of the available choices." msgid "Select a valid choice. %(value)s is not one of the available choices."
msgstr "" msgstr ""
"Wybierz poprawną wartość. %(value)s nie jest jednym z dostępnych wyborów." "Wybierz poprawną wartość. %(value)s nie jest jednym z dostępnych wyborów."
#: newforms/fields.py:600 newforms/fields.py:662 newforms/models.py:371 #: newforms/fields.py:599 newforms/fields.py:661 newforms/models.py:372
msgid "Enter a list of values." msgid "Enter a list of values."
msgstr "Podaj listę wartości." msgstr "Podaj listę wartości."
#: newforms/fields.py:780 #: newforms/fields.py:779
msgid "Enter a valid IPv4 address." msgid "Enter a valid IPv4 address."
msgstr "Wprowadź poprawny adres IPv4." msgstr "Wprowadź poprawny adres IPv4."
#: newforms/models.py:372 #: newforms/models.py:373
#, python-format #, python-format
msgid "Select a valid choice. %s is not one of the available choices." 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." msgstr "Wybierz poprawną wartość. %s nie jest jednym z dostępnych wyborów."

View File

@ -1,12 +1,13 @@
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm, AdminPasswordChangeForm from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm, AdminPasswordChangeForm
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.shortcuts import render_to_response, get_object_or_404 from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
from django.contrib.sites.models import Site, RequestSite from django.contrib.sites.models import Site, RequestSite
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.contrib.auth.decorators import login_required from django.template import RequestContext
from django.contrib.auth import REDIRECT_FIELD_NAME from django.utils.http import urlquote
from django.utils.html import escape from django.utils.html import escape
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -62,7 +63,7 @@ def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_N
if not login_url: if not login_url:
from django.conf import settings from django.conf import settings
login_url = settings.LOGIN_URL login_url = settings.LOGIN_URL
return HttpResponseRedirect('%s?%s=%s' % (login_url, redirect_field_name, next)) return HttpResponseRedirect('%s?%s=%s' % (login_url, urlquote(redirect_field_name), urlquote(next)))
def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html', def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html',
email_template_name='registration/password_reset_email.html', email_template_name='registration/password_reset_email.html',
@ -73,7 +74,10 @@ def password_reset(request, is_admin_site=False, template_name='registration/pas
if is_admin_site: if is_admin_site:
form.save(domain_override=request.META['HTTP_HOST']) form.save(domain_override=request.META['HTTP_HOST'])
else: else:
form.save(email_template_name=email_template_name) if Site._meta.installed:
form.save(email_template_name=email_template_name)
else:
form.save(domain_override=RequestSite(request).domain, email_template_name=email_template_name)
return HttpResponseRedirect('%sdone/' % request.path) return HttpResponseRedirect('%sdone/' % request.path)
else: else:
form = password_reset_form() form = password_reset_form()

View File

@ -2,8 +2,8 @@
>>> # Make sure that get_current() does not return a deleted Site object. >>> # Make sure that get_current() does not return a deleted Site object.
>>> from django.contrib.sites.models import Site >>> from django.contrib.sites.models import Site
>>> s = Site.objects.get_current() >>> s = Site.objects.get_current()
>>> s >>> isinstance(s, Site)
<Site: example.com> True
>>> s.delete() >>> s.delete()
>>> Site.objects.get_current() >>> Site.objects.get_current()

View File

@ -17,7 +17,7 @@ import urllib
from django.utils.http import http_date from django.utils.http import http_date
__version__ = "0.1" __version__ = "0.1"
__all__ = ['WSGIServer','WSGIRequestHandler','demo_app'] __all__ = ['WSGIServer','WSGIRequestHandler']
server_version = "WSGIServer/" + __version__ server_version = "WSGIServer/" + __version__
sys_version = "Python/" + sys.version.split()[0] sys_version = "Python/" + sys.version.split()[0]

View File

@ -45,7 +45,6 @@ class BaseDatabaseFeatures(object):
autoindexes_primary_keys = True autoindexes_primary_keys = True
inline_fk_references = True inline_fk_references = True
needs_datetime_string_cast = True needs_datetime_string_cast = True
needs_upper_for_iops = False
supports_constraints = True supports_constraints = True
supports_tablespaces = False supports_tablespaces = False
uses_case_insensitive_names = False uses_case_insensitive_names = False

View File

@ -27,7 +27,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
allows_unique_and_pk = False # Suppress UNIQUE/PK for Oracle (ORA-02259) allows_unique_and_pk = False # Suppress UNIQUE/PK for Oracle (ORA-02259)
empty_fetchmany_value = () empty_fetchmany_value = ()
needs_datetime_string_cast = False needs_datetime_string_cast = False
needs_upper_for_iops = True
supports_tablespaces = True supports_tablespaces = True
uses_case_insensitive_names = True uses_case_insensitive_names = True
uses_custom_query_class = True uses_custom_query_class = True

View File

@ -5,9 +5,13 @@ from django.core import management
# types, as strings. Column-type strings can contain format strings; they'll # types, as strings. Column-type strings can contain format strings; they'll
# be interpolated against the values of Field.__dict__ before being output. # be interpolated against the values of Field.__dict__ before being output.
# If a column type is set to None, it won't be included in the output. # If a column type is set to None, it won't be included in the output.
#
# Any format strings starting with "qn_" are quoted before being used in the
# output (the "qn_" prefix is stripped before the lookup is performed.
DATA_TYPES = { DATA_TYPES = {
'AutoField': 'NUMBER(11)', 'AutoField': 'NUMBER(11)',
'BooleanField': 'NUMBER(1) CHECK (%(column)s IN (0,1))', 'BooleanField': 'NUMBER(1) CHECK (%(qn_column)s IN (0,1))',
'CharField': 'NVARCHAR2(%(max_length)s)', 'CharField': 'NVARCHAR2(%(max_length)s)',
'CommaSeparatedIntegerField': 'VARCHAR2(%(max_length)s)', 'CommaSeparatedIntegerField': 'VARCHAR2(%(max_length)s)',
'DateField': 'DATE', 'DateField': 'DATE',
@ -19,11 +23,11 @@ DATA_TYPES = {
'ImageField': 'NVARCHAR2(%(max_length)s)', 'ImageField': 'NVARCHAR2(%(max_length)s)',
'IntegerField': 'NUMBER(11)', 'IntegerField': 'NUMBER(11)',
'IPAddressField': 'VARCHAR2(15)', 'IPAddressField': 'VARCHAR2(15)',
'NullBooleanField': 'NUMBER(1) CHECK ((%(column)s IN (0,1)) OR (%(column)s IS NULL))', 'NullBooleanField': 'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(column)s IS NULL))',
'OneToOneField': 'NUMBER(11)', 'OneToOneField': 'NUMBER(11)',
'PhoneNumberField': 'VARCHAR2(20)', 'PhoneNumberField': 'VARCHAR2(20)',
'PositiveIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)', 'PositiveIntegerField': 'NUMBER(11) CHECK (%(qn_column)s >= 0)',
'PositiveSmallIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)', 'PositiveSmallIntegerField': 'NUMBER(11) CHECK (%(qn_column)s >= 0)',
'SlugField': 'NVARCHAR2(50)', 'SlugField': 'NVARCHAR2(50)',
'SmallIntegerField': 'NUMBER(11)', 'SmallIntegerField': 'NUMBER(11)',
'TextField': 'NCLOB', 'TextField': 'NCLOB',

View File

@ -16,6 +16,7 @@ from django.core import validators
from django import oldforms from django import oldforms
from django import newforms as forms from django import newforms as forms
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.utils.datastructures import DictWrapper
from django.utils.functional import curry from django.utils.functional import curry
from django.utils.itercompat import tee from django.utils.itercompat import tee
from django.utils.text import capfirst from django.utils.text import capfirst
@ -153,8 +154,9 @@ class Field(object):
# mapped to one of the built-in Django field types. In this case, you # mapped to one of the built-in Django field types. In this case, you
# can implement db_type() instead of get_internal_type() to specify # can implement db_type() instead of get_internal_type() to specify
# exactly which wacky database column type you want to use. # exactly which wacky database column type you want to use.
data = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_")
try: try:
return get_creation_module().DATA_TYPES[self.get_internal_type()] % self.__dict__ return get_creation_module().DATA_TYPES[self.get_internal_type()] % data
except KeyError: except KeyError:
return None return None

View File

@ -103,13 +103,15 @@ class RelatedField(object):
if hasattr(sup, 'contribute_to_class'): if hasattr(sup, 'contribute_to_class'):
sup.contribute_to_class(cls, name) sup.contribute_to_class(cls, name)
if not cls._meta.abstract and self.rel.related_name:
self.rel.related_name = self.rel.related_name % {'class': cls.__name__.lower()}
other = self.rel.to other = self.rel.to
if isinstance(other, basestring): if isinstance(other, basestring):
add_lazy_relation(cls, self, other) add_lazy_relation(cls, self, other)
else: else:
self.do_related_class(other, cls) self.do_related_class(other, cls)
if not cls._meta.abstract and self.rel.related_name:
self.rel.related_name = self.rel.related_name % {'class': cls.__name__.lower()}
def set_attributes_from_rel(self): def set_attributes_from_rel(self):
self.name = self.name or (self.rel.to._meta.object_name.lower() + '_' + self.rel.to._meta.pk.name) self.name = self.name or (self.rel.to._meta.object_name.lower() + '_' + self.rel.to._meta.pk.name)
@ -119,7 +121,8 @@ class RelatedField(object):
def do_related_class(self, other, cls): def do_related_class(self, other, cls):
self.set_attributes_from_rel() self.set_attributes_from_rel()
related = RelatedObject(other, cls, self) related = RelatedObject(other, cls, self)
self.contribute_to_related_class(other, related) if not cls._meta.abstract:
self.contribute_to_related_class(other, related)
def get_db_prep_lookup(self, lookup_type, value): def get_db_prep_lookup(self, lookup_type, value):
# If we are doing a lookup on a Related Field, we must be # If we are doing a lookup on a Related Field, we must be

View File

@ -273,14 +273,17 @@ class Options(object):
""" """
Initialises the field name -> field object mapping. Initialises the field name -> field object mapping.
""" """
cache = dict([(f.name, (f, m, True, False)) for f, m in cache = {}
self.get_fields_with_model()]) # We intentionally handle related m2m objects first so that symmetrical
for f, model in self.get_m2m_with_model(): # m2m accessor names can be overridden, if necessary.
cache[f.name] = (f, model, True, True)
for f, model in self.get_all_related_m2m_objects_with_model(): for f, model in self.get_all_related_m2m_objects_with_model():
cache[f.field.related_query_name()] = (f, model, False, True) cache[f.field.related_query_name()] = (f, model, False, True)
for f, model in self.get_all_related_objects_with_model(): for f, model in self.get_all_related_objects_with_model():
cache[f.field.related_query_name()] = (f, model, False, False) cache[f.field.related_query_name()] = (f, model, False, False)
for f, model in self.get_m2m_with_model():
cache[f.name] = (f, model, True, True)
for f, model in self.get_fields_with_model():
cache[f.name] = (f, model, True, False)
if self.order_with_respect_to: if self.order_with_respect_to:
cache['_order'] = OrderWrt(), None, True, False cache['_order'] = OrderWrt(), None, True, False
if app_cache_ready(): if app_cache_ready():

View File

@ -36,7 +36,7 @@ class CollectedObjects(object):
""" """
Adds an item. Adds an item.
model is the class of the object being added, model is the class of the object being added,
pk is the primary key, obj is the object itself, pk is the primary key, obj is the object itself,
parent_model is the model of the parent object parent_model is the model of the parent object
that this object was reached through, nullable should that this object was reached through, nullable should
be True if this relation is nullable. be True if this relation is nullable.
@ -77,7 +77,7 @@ class CollectedObjects(object):
def ordered_keys(self): def ordered_keys(self):
""" """
Returns the models in the order that they should be Returns the models in the order that they should be
dealth with i.e. models with no dependencies first. dealth with i.e. models with no dependencies first.
""" """
dealt_with = SortedDict() dealt_with = SortedDict()
@ -92,7 +92,7 @@ class CollectedObjects(object):
found = True found = True
if not found: if not found:
raise CyclicDependency("There is a cyclic dependency of items to be processed.") raise CyclicDependency("There is a cyclic dependency of items to be processed.")
return dealt_with.keys() return dealt_with.keys()
def unordered_keys(self): def unordered_keys(self):
@ -218,6 +218,8 @@ class QuerySet(object):
def __and__(self, other): def __and__(self, other):
self._merge_sanity_check(other) self._merge_sanity_check(other)
if isinstance(other, EmptyQuerySet):
return other._clone()
combined = self._clone() combined = self._clone()
combined.query.combine(other.query, sql.AND) combined.query.combine(other.query, sql.AND)
return combined return combined
@ -225,6 +227,8 @@ class QuerySet(object):
def __or__(self, other): def __or__(self, other):
self._merge_sanity_check(other) self._merge_sanity_check(other)
combined = self._clone() combined = self._clone()
if isinstance(other, EmptyQuerySet):
return combined
combined.query.combine(other.query, sql.OR) combined.query.combine(other.query, sql.OR)
return combined return combined
@ -488,7 +492,9 @@ class QuerySet(object):
and usually it will be more natural to use other methods. and usually it will be more natural to use other methods.
""" """
if isinstance(filter_obj, Q) or hasattr(filter_obj, 'add_to_query'): if isinstance(filter_obj, Q) or hasattr(filter_obj, 'add_to_query'):
return self._filter_or_exclude(None, filter_obj) clone = self._clone()
clone.query.add_q(filter_obj)
return clone
else: else:
return self._filter_or_exclude(None, **filter_obj) return self._filter_or_exclude(None, **filter_obj)
@ -583,11 +589,11 @@ class QuerySet(object):
def _merge_sanity_check(self, other): def _merge_sanity_check(self, other):
""" """
Checks that we are merging two comparable queryset classes. Checks that we are merging two comparable queryset classes. By default
this does nothing, but see the ValuesQuerySet for an example of where
it's useful.
""" """
if self.__class__ is not other.__class__: pass
raise TypeError("Cannot merge querysets of different types ('%s' and '%s'."
% (self.__class__.__name__, other.__class__.__name__))
class ValuesQuerySet(QuerySet): class ValuesQuerySet(QuerySet):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -599,7 +605,7 @@ class ValuesQuerySet(QuerySet):
# names of the model fields to select. # names of the model fields to select.
def iterator(self): def iterator(self):
if (not self.extra_names and if (not self.extra_names and
len(self.field_names) != len(self.model._meta.fields)): len(self.field_names) != len(self.model._meta.fields)):
self.query.trim_extra_select(self.extra_names) self.query.trim_extra_select(self.extra_names)
names = self.query.extra_select.keys() + self.field_names names = self.query.extra_select.keys() + self.field_names
@ -688,9 +694,9 @@ class DateQuerySet(QuerySet):
""" """
self.query = self.query.clone(klass=sql.DateQuery, setup=True) self.query = self.query.clone(klass=sql.DateQuery, setup=True)
self.query.select = [] self.query.select = []
self.query.add_date_select(self._field.column, self._kind, self._order) self.query.add_date_select(self._field, self._kind, self._order)
if self._field.null: if self._field.null:
self.query.add_filter(('%s__isnull' % self._field.name, True)) self.query.add_filter(('%s__isnull' % self._field.name, False))
def _clone(self, klass=None, setup=False, **kwargs): def _clone(self, klass=None, setup=False, **kwargs):
c = super(DateQuerySet, self)._clone(klass, False, **kwargs) c = super(DateQuerySet, self)._clone(klass, False, **kwargs)
@ -705,6 +711,12 @@ class EmptyQuerySet(QuerySet):
super(EmptyQuerySet, self).__init__(model, query) super(EmptyQuerySet, self).__init__(model, query)
self._result_cache = [] self._result_cache = []
def __and__(self, other):
return self._clone()
def __or__(self, other):
return other._clone()
def count(self): def count(self):
return 0 return 0
@ -773,7 +785,7 @@ def delete_objects(seen_objs):
except CyclicDependency: except CyclicDependency:
# if there is a cyclic dependency, we cannot in general delete # if there is a cyclic dependency, we cannot in general delete
# the objects. However, if an appropriate transaction is set # the objects. However, if an appropriate transaction is set
# up, or if the database is lax enough, it will succeed. # up, or if the database is lax enough, it will succeed.
# So for now, we go ahead and try anway. # So for now, we go ahead and try anway.
ordered_classes = seen_objs.unordered_keys() ordered_classes = seen_objs.unordered_keys()

View File

@ -610,6 +610,10 @@ class Query(object):
alias = joins[-1] alias = joins[-1]
col = target.column col = target.column
# Must use left outer joins for nullable fields.
for join in joins:
self.promote_alias(join)
# If we get to this point and the field is a relation to another model, # If we get to this point and the field is a relation to another model,
# append the default ordering for that model. # append the default ordering for that model.
if field.rel and len(joins) > 1 and opts.ordering: if field.rel and len(joins) > 1 and opts.ordering:
@ -631,8 +635,10 @@ class Query(object):
# We have to do the same "final join" optimisation as in # We have to do the same "final join" optimisation as in
# add_filter, since the final column might not otherwise be part of # add_filter, since the final column might not otherwise be part of
# the select set (so we can't order on it). # the select set (so we can't order on it).
join = self.alias_map[alias] while 1:
if col == join[RHS_JOIN_COL]: join = self.alias_map[alias]
if col != join[RHS_JOIN_COL]:
break
self.unref_alias(alias) self.unref_alias(alias)
alias = join[LHS_ALIAS] alias = join[LHS_ALIAS]
col = join[LHS_JOIN_COL] col = join[LHS_JOIN_COL]
@ -679,12 +685,16 @@ class Query(object):
for the join to contain NULL values on the left. If 'unconditional' is for the join to contain NULL values on the left. If 'unconditional' is
False, the join is only promoted if it is nullable, otherwise it is False, the join is only promoted if it is nullable, otherwise it is
always promoted. always promoted.
Returns True if the join was promoted.
""" """
if ((unconditional or self.alias_map[alias][NULLABLE]) and if ((unconditional or self.alias_map[alias][NULLABLE]) and
self.alias_map[alias] != self.LOUTER): self.alias_map[alias] != self.LOUTER):
data = list(self.alias_map[alias]) data = list(self.alias_map[alias])
data[JOIN_TYPE] = self.LOUTER data[JOIN_TYPE] = self.LOUTER
self.alias_map[alias] = tuple(data) self.alias_map[alias] = tuple(data)
return True
return False
def change_aliases(self, change_map): def change_aliases(self, change_map):
""" """
@ -826,6 +836,10 @@ class Query(object):
if not always_create: if not always_create:
for alias in self.join_map.get(t_ident, ()): for alias in self.join_map.get(t_ident, ()):
if alias not in exclusions: if alias not in exclusions:
if lhs_table and not self.alias_refcount[self.alias_map[alias][LHS_ALIAS]]:
# The LHS of this join tuple is no longer part of the
# query, so skip this possibility.
continue
self.ref_alias(alias) self.ref_alias(alias)
if promote: if promote:
self.promote_alias(alias) self.promote_alias(alias)
@ -985,20 +999,22 @@ class Query(object):
col = target.column col = target.column
alias = join_list[-1] alias = join_list[-1]
if final > 1: while final > 1:
# An optimization: if the final join is against the same column as # An optimization: if the final join is against the same column as
# we are comparing against, we can go back one step in the join # we are comparing against, we can go back one step in the join
# chain and compare against the lhs of the join instead. The result # chain and compare against the lhs of the join instead (and then
# (potentially) involves one less table join. # repeat the optimization). The result, potentially, involves less
# table joins.
join = self.alias_map[alias] join = self.alias_map[alias]
if col == join[RHS_JOIN_COL]: if col != join[RHS_JOIN_COL]:
self.unref_alias(alias) break
alias = join[LHS_ALIAS] self.unref_alias(alias)
col = join[LHS_JOIN_COL] alias = join[LHS_ALIAS]
join_list = join_list[:-1] col = join[LHS_JOIN_COL]
final -= 1 join_list = join_list[:-1]
if final == penultimate: final -= 1
penultimate = last.pop() if final == penultimate:
penultimate = last.pop()
if (lookup_type == 'isnull' and value is True and not negate and if (lookup_type == 'isnull' and value is True and not negate and
final > 1): final > 1):
@ -1033,17 +1049,27 @@ class Query(object):
self.promote_alias(table) self.promote_alias(table)
self.where.add((alias, col, field, lookup_type, value), connector) self.where.add((alias, col, field, lookup_type, value), connector)
if negate: if negate:
for alias in join_list: for alias in join_list:
self.promote_alias(alias) self.promote_alias(alias)
if final > 1 and lookup_type != 'isnull': if lookup_type != 'isnull':
for alias in join_list: if final > 1:
if self.alias_map[alias] == self.LOUTER: for alias in join_list:
j_col = self.alias_map[alias][RHS_JOIN_COL] if self.alias_map[alias][JOIN_TYPE] == self.LOUTER:
entry = Node([(alias, j_col, None, 'isnull', True)]) j_col = self.alias_map[alias][RHS_JOIN_COL]
entry.negate() entry = Node([(alias, j_col, None, 'isnull', True)])
self.where.add(entry, AND) entry.negate()
break self.where.add(entry, AND)
break
elif not (lookup_type == 'in' and not value):
# Leaky abstraction artifact: We have to specifically
# exclude the "foo__in=[]" case from this handling, because
# it's short-circuited in the Where class.
entry = Node([(alias, col, field, 'isnull', True)])
entry.negate()
self.where.add(entry, AND)
if can_reuse is not None: if can_reuse is not None:
can_reuse.update(join_list) can_reuse.update(join_list)
@ -1294,10 +1320,12 @@ class Query(object):
final_alias = join[LHS_ALIAS] final_alias = join[LHS_ALIAS]
col = join[LHS_JOIN_COL] col = join[LHS_JOIN_COL]
joins = joins[:-1] joins = joins[:-1]
promote = False
for join in joins[1:]: for join in joins[1:]:
# Only nullable aliases are promoted, so we don't end up # Only nullable aliases are promoted, so we don't end up
# doing unnecessary left outer joins here. # doing unnecessary left outer joins here.
self.promote_alias(join) if self.promote_alias(join, promote):
promote = True
self.select.append((final_alias, col)) self.select.append((final_alias, col))
self.select_fields.append(field) self.select_fields.append(field)
except MultiJoin: except MultiJoin:

View File

@ -357,12 +357,14 @@ class DateQuery(Query):
date = typecast_timestamp(str(date)) date = typecast_timestamp(str(date))
yield date yield date
def add_date_select(self, column, lookup_type, order='ASC'): def add_date_select(self, field, lookup_type, order='ASC'):
""" """
Converts the query into a date extraction query. Converts the query into a date extraction query.
""" """
alias = self.join((None, self.model._meta.db_table, None, None)) result = self.setup_joins([field.name], self.get_meta(),
select = Date((alias, column), lookup_type, self.get_initial_alias(), False)
alias = result[3][-1]
select = Date((alias, field.column), lookup_type,
self.connection.ops.date_trunc_sql) self.connection.ops.date_trunc_sql)
self.select = [select] self.select = [select]
self.select_fields = [None] self.select_fields = [None]

View File

@ -39,12 +39,11 @@ class CommentNode(Node):
class CycleNode(Node): class CycleNode(Node):
def __init__(self, cyclevars, variable_name=None): def __init__(self, cyclevars, variable_name=None):
self.cycle_iter = itertools_cycle(cyclevars) self.cycle_iter = itertools_cycle([Variable(v) for v in cyclevars])
self.variable_name = variable_name self.variable_name = variable_name
def render(self, context): def render(self, context):
value = self.cycle_iter.next() value = self.cycle_iter.next().resolve(context)
value = Variable(value).resolve(context)
if self.variable_name: if self.variable_name:
context[self.variable_name] = value context[self.variable_name] = value
return value return value
@ -162,10 +161,12 @@ class IfChangedNode(Node):
self.nodelist = nodelist self.nodelist = nodelist
self._last_seen = None self._last_seen = None
self._varlist = map(Variable, varlist) self._varlist = map(Variable, varlist)
self._id = str(id(self))
def render(self, context): def render(self, context):
if 'forloop' in context and context['forloop']['first']: if 'forloop' in context and self._id not in context['forloop']:
self._last_seen = None self._last_seen = None
context['forloop'][self._id] = 1
try: try:
if self._varlist: if self._varlist:
# Consider multiple parameters. This automatically behaves # Consider multiple parameters. This automatically behaves
@ -452,17 +453,17 @@ def cycle(parser, token):
<tr class="{% cycle rowcolors %}">...</tr> <tr class="{% cycle rowcolors %}">...</tr>
<tr class="{% cycle rowcolors %}">...</tr> <tr class="{% cycle rowcolors %}">...</tr>
You can use any number of values, seperated by spaces. Commas can also You can use any number of values, separated by spaces. Commas can also
be used to separate values; if a comma is used, the cycle values are be used to separate values; if a comma is used, the cycle values are
interpreted as literal strings. interpreted as literal strings.
""" """
# Note: This returns the exact same node on each {% cycle name %} call; # Note: This returns the exact same node on each {% cycle name %} call;
# that is, the node object returned from {% cycle a b c as name %} and the # that is, the node object returned from {% cycle a b c as name %} and the
# one returned from {% cycle name %} are the exact same object. This # one returned from {% cycle name %} are the exact same object. This
# shouldn't cause problems (heh), but if it does, now you know. # shouldn't cause problems (heh), but if it does, now you know.
# #
# Ugly hack warning: this stuffs the named template dict into parser so # Ugly hack warning: This stuffs the named template dict into parser so
# that names are only unique within each template (as opposed to using # that names are only unique within each template (as opposed to using
# a global variable, which would make cycle names have to be unique across # a global variable, which would make cycle names have to be unique across
# *all* templates. # *all* templates.
@ -481,8 +482,7 @@ def cycle(parser, token):
# {% cycle foo %} case. # {% cycle foo %} case.
name = args[1] name = args[1]
if not hasattr(parser, '_namedCycleNodes'): if not hasattr(parser, '_namedCycleNodes'):
raise TemplateSyntaxError("No named cycles in template." raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)
" '%s' is not defined" % name)
if not name in parser._namedCycleNodes: if not name in parser._namedCycleNodes:
raise TemplateSyntaxError("Named cycle '%s' does not exist" % name) raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
return parser._namedCycleNodes[name] return parser._namedCycleNodes[name]
@ -682,8 +682,10 @@ ifnotequal = register.tag(ifnotequal)
def do_if(parser, token): def do_if(parser, token):
""" """
The ``{% if %}`` tag evaluates a variable, and if that variable is "true" The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
(i.e. exists, is not empty, and is not a false boolean value) the contents (i.e., exists, is not empty, and is not a false boolean value), the
of the block are output:: contents of the block are output:
::
{% if athlete_list %} {% if athlete_list %}
Number of athletes: {{ athlete_list|count }} Number of athletes: {{ athlete_list|count }}

View File

@ -1,4 +1,4 @@
from django.template import Library, Node, TemplateSyntaxError from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist
from django.template import resolve_variable from django.template import resolve_variable
from django.core.cache import cache from django.core.cache import cache
from django.utils.encoding import force_unicode from django.utils.encoding import force_unicode
@ -6,20 +6,27 @@ from django.utils.encoding import force_unicode
register = Library() register = Library()
class CacheNode(Node): class CacheNode(Node):
def __init__(self, nodelist, expire_time, fragment_name, vary_on): def __init__(self, nodelist, expire_time_var, fragment_name, vary_on):
self.nodelist = nodelist self.nodelist = nodelist
self.expire_time = expire_time self.expire_time_var = Variable(expire_time_var)
self.fragment_name = fragment_name self.fragment_name = fragment_name
self.vary_on = vary_on self.vary_on = vary_on
def render(self, context): def render(self, context):
try:
expire_time = self.expire_time_var.resolve(context)
except VariableDoesNotExist:
raise TemplateSyntaxError('"cache" tag got an unknkown variable: %r' % self.expire_time_var.var)
try:
expire_time = int(expire_time)
except (ValueError, TypeError):
raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time)
# Build a unicode key for this fragment and all vary-on's. # Build a unicode key for this fragment and all vary-on's.
cache_key = u':'.join([self.fragment_name] + \ cache_key = u':'.join([self.fragment_name] + [force_unicode(resolve_variable(var, context)) for var in self.vary_on])
[force_unicode(resolve_variable(var, context)) for var in self.vary_on])
value = cache.get(cache_key) value = cache.get(cache_key)
if value is None: if value is None:
value = self.nodelist.render(context) value = self.nodelist.render(context)
cache.set(cache_key, value, self.expire_time) cache.set(cache_key, value, expire_time)
return value return value
def do_cache(parser, token): def do_cache(parser, token):
@ -48,10 +55,6 @@ def do_cache(parser, token):
tokens = token.contents.split() tokens = token.contents.split()
if len(tokens) < 3: if len(tokens) < 3:
raise TemplateSyntaxError(u"'%r' tag requires at least 2 arguments." % tokens[0]) raise TemplateSyntaxError(u"'%r' tag requires at least 2 arguments." % tokens[0])
try: return CacheNode(nodelist, tokens[1], tokens[2], tokens[3:])
expire_time = int(tokens[1])
except ValueError:
raise TemplateSyntaxError(u"First argument to '%r' must be an integer (got '%s')." % (tokens[0], tokens[1]))
return CacheNode(nodelist, expire_time, tokens[2], tokens[3:])
register.tag('cache', do_cache) register.tag('cache', do_cache)

View File

@ -343,3 +343,34 @@ class FileDict(dict):
d = dict(self, content='<omitted>') d = dict(self, content='<omitted>')
return dict.__repr__(d) return dict.__repr__(d)
return dict.__repr__(self) return dict.__repr__(self)
class DictWrapper(dict):
"""
Wraps accesses to a dictionary so that certain values (those starting with
the specified prefix) are passed through a function before being returned.
The prefix is removed before looking up the real value.
Used by the SQL construction code to ensure that values are correctly
quoted before being used.
"""
def __init__(self, data, func, prefix):
super(DictWrapper, self).__init__(data)
self.func = func
self.prefix = prefix
def __getitem__(self, key):
"""
Retrieves the real value after stripping the prefix string (if
present). If the prefix is present, pass the value through self.func
before returning, otherwise return the raw value.
"""
if key.startswith(self.prefix):
use_func = True
key = key[len(self.prefix):]
else:
use_func = False
value = super(DictWrapper, self).__getitem__(key)
if use_func:
return self.func(value)
return value

View File

@ -76,20 +76,20 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
""" """
Converts any URLs in text into clickable links. Converts any URLs in text into clickable links.
Works on http://, https://, and www. links. Links can have trailing Works on http://, https://, www. links and links ending in .org, .net or
punctuation (periods, commas, close-parens) and leading punctuation .com. Links can have trailing punctuation (periods, commas, close-parens)
(opening parens) and it'll still do the right thing. and leading punctuation (opening parens) and it'll still do the right
thing.
If trim_url_limit is not None, the URLs in link text longer than this limit If trim_url_limit is not None, the URLs in link text longer than this limit
will truncated to trim_url_limit-3 characters and appended with an elipsis. will truncated to trim_url_limit-3 characters and appended with an elipsis.
If nofollow is True, the URLs in link text will get a rel="nofollow" If nofollow is True, the URLs in link text will get a rel="nofollow"
attribute. attribute.
If autoescape is True, the link text and URLs will get autoescaped.
""" """
if autoescape: trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
trim_url = lambda x, limit=trim_url_limit: conditional_escape(limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x)
else:
trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
safe_input = isinstance(text, SafeData) safe_input = isinstance(text, SafeData)
words = word_split_re.split(force_unicode(text)) words = word_split_re.split(force_unicode(text))
nofollow_attr = nofollow and ' rel="nofollow"' or '' nofollow_attr = nofollow and ' rel="nofollow"' or ''
@ -97,30 +97,30 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
match = punctuation_re.match(word) match = punctuation_re.match(word)
if match: if match:
lead, middle, trail = match.groups() lead, middle, trail = match.groups()
if safe_input: # Make URL we want to point to.
middle = mark_safe(middle) url = None
if middle.startswith('www.') or ('@' not in middle and not (middle.startswith('http://') or middle.startswith('https://')) and \
len(middle) > 0 and middle[0] in string.ascii_letters + string.digits and \
(middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))):
middle = 'http://%s' % middle
if middle.startswith('http://') or middle.startswith('https://'): if middle.startswith('http://') or middle.startswith('https://'):
url = urlquote(middle, safe='/&=:;#?+*') url = urlquote(middle, safe='/&=:;#?+*')
if autoescape and not safe_input: elif middle.startswith('www.') or ('@' not in middle and \
url = escape(url) len(middle) > 0 and middle[0] in string.ascii_letters + string.digits and \
trimmed_url = trim_url(middle) (middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))):
middle = '<a href="%s"%s>%s</a>' % (url, nofollow_attr, url = urlquote('http://%s' % middle, safe='/&=:;#?+*')
trimmed_url) elif '@' in middle and not ':' in middle and simple_email_re.match(middle):
elif '@' in middle and not middle.startswith('www.') and \ url = 'mailto:%s' % middle
not ':' in middle and simple_email_re.match(middle): nofollow_attr = ''
if autoescape: # Make link.
middle = conditional_escape(middle) if url:
middle = '<a href="mailto:%s">%s</a>' % (middle, middle) trimmed = trim_url(middle)
if lead + middle + trail != word:
if autoescape and not safe_input: if autoescape and not safe_input:
lead, trail = escape(lead), escape(trail) lead, trail = escape(lead), escape(trail)
url, trimmed = escape(url), escape(trimmed)
middle = '<a href="%s"%s>%s</a>' % (url, nofollow_attr, trimmed)
words[i] = mark_safe('%s%s%s' % (lead, middle, trail)) words[i] = mark_safe('%s%s%s' % (lead, middle, trail))
elif autoescape and not safe_input: else:
words[i] = escape(word) if safe_input:
words[i] = mark_safe(word)
elif autoescape:
words[i] = escape(word)
elif safe_input: elif safe_input:
words[i] = mark_safe(word) words[i] = mark_safe(word)
elif autoescape: elif autoescape:

View File

@ -336,6 +336,17 @@ template tag to uniquely identify the cache fragment::
It's perfectly fine to specify more than one argument to identify the fragment. It's perfectly fine to specify more than one argument to identify the fragment.
Simply pass as many arguments to ``{% cache %}`` as you need. Simply pass as many arguments to ``{% cache %}`` as you need.
The cache timeout can be a template variable, as long as the template variable
resolves to an integer value. For example, if the template variable
``my_timeout`` is set to the value ``600``, then the following two examples are
equivalent::
{% cache 600 sidebar %} ... {% endcache %}
{% cache my_timeout sidebar %} ... {% endcache %}
This feature is useful in avoiding repetition in templates. You can set the
timeout in a variable, in one place, and just reuse that value.
The low-level cache API The low-level cache API
======================= =======================

View File

@ -238,14 +238,14 @@ Since a picture is worth a thousand words, let's start there:
We've got two official roles here: We've got two official roles here:
* Core developers: people with commit access who make the big decisions * Core developers: people with commit access who make the big decisions
and write the bulk of the code. and write the bulk of the code.
* Ticket triagers: trusted community members with a proven history of * Ticket triagers: trusted community members with a proven history of
working with the Django community. As a result of this history, they working with the Django community. As a result of this history, they
have been entrusted by the core developers to make some of the smaller have been entrusted by the core developers to make some of the smaller
decisions about tickets. decisions about tickets.
Second, note the five triage stages: Second, note the five triage stages:
1. A ticket starts as "Unreviewed", meaning that nobody has examined 1. A ticket starts as "Unreviewed", meaning that nobody has examined
@ -254,7 +254,7 @@ Second, note the five triage stages:
2. "Design decision needed" means "this concept requires a design 2. "Design decision needed" means "this concept requires a design
decision," which should be discussed either in the ticket comments or on decision," which should be discussed either in the ticket comments or on
`django-developers`_. The "Design decision needed" step will generally `django-developers`_. The "Design decision needed" step will generally
only be used for feature requests. It can also be used for issues only be used for feature requests. It can also be used for issues
that *might* be bugs, depending on opinion or interpretation. Obvious that *might* be bugs, depending on opinion or interpretation. Obvious
bugs (such as crashes, incorrect query results, or non-compliance with a bugs (such as crashes, incorrect query results, or non-compliance with a
standard) skip this step and move straight to "Accepted". standard) skip this step and move straight to "Accepted".
@ -317,7 +317,7 @@ A ticket can be resolved in a number of ways:
tickets, we keep all the discussion in one place, which helps everyone. tickets, we keep all the discussion in one place, which helps everyone.
"worksforme" "worksforme"
Used when the the ticket doesn't contain enough detail to replicate Used when the the ticket doesn't contain enough detail to replicate
the original bug. the original bug.
If you believe that the ticket was closed in error -- because you're If you believe that the ticket was closed in error -- because you're
@ -332,50 +332,49 @@ reopen tickets that have been marked as "wontfix" by core developers.
Triage by the general community Triage by the general community
------------------------------- -------------------------------
Although the Core Developers and Ticket Triagers make the big decisions in Although the core developers and ticket triagers make the big decisions in
the ticket triage process, there is also a lot that general community the ticket triage process, there's also a lot that general community
members can do to help the triage process. In particular, you can help out by: members can do to help the triage process. In particular, you can help out by:
* Closing "Unreviewed" tickets as "invalid", "worksforme", or "duplicate". * Closing "Unreviewed" tickets as "invalid", "worksforme" or "duplicate."
* Promoting "Unreviewed" tickets to "Design Decision Required" if there * Promoting "Unreviewed" tickets to "Design decision needed" if a design
is a design decision that needs to be made, or "Accepted" if they are decision needs to be made, or "Accepted" in case of obvious bugs.
an obvious bug.
* Correcting the "Needs Tests", "Needs documentation", or "Has Patch" flags * Correcting the "Needs tests", "Needs documentation", or "Has patch" flags
for tickets where they are incorrectly set. for tickets where they are incorrectly set.
* Checking that old tickets are still valid. If a ticket hasn't seen * Checking that old tickets are still valid. If a ticket hasn't seen
any activity in a long time, it's possible that the problem has been any activity in a long time, it's possible that the problem has been
fixed, but the ticket hasn't been closed. fixed but the ticket hasn't yet been closed.
* Contact the owners of tickets that have been claimed, but have not seen * Contacting the owners of tickets that have been claimed but have not seen
any recent activity. If the owner doesn't respond after a week or so, any recent activity. If the owner doesn't respond after a week or so,
remove the owner's claim on the ticket. remove the owner's claim on the ticket.
* Identifying trends and themes in the tickets. If there a lot of bug reports * Identifying trends and themes in the tickets. If there a lot of bug reports
about a particular part of Django, it possibly indicates that we need about a particular part of Django, it may indicate we should consider
to consider refactoring that part of the code. If a trend is emerging, refactoring that part of the code. If a trend is emerging, you should
you should raise it for discussion (referencing the relevant tickets) raise it for discussion (referencing the relevant tickets) on
on `django-developers`_. `django-developers`_.
However, we do ask that as a general community member working in the However, we do ask the following of all general community members working in
ticket database: the ticket database:
* Please **don't** close tickets as "wontfix". The core developers will * Please **don't** close tickets as "wontfix." The core developers will
make the final determination of the fate of a ticket, usually after make the final determination of the fate of a ticket, usually after
consultation with the community. consultation with the community.
* Please **don't** promote tickets to "Ready for checkin" unless they are
*trivial* changes - for example, spelling mistakes or
broken links in documentation.
* Please **don't** reverse a decision that has been made by a core * Please **don't** promote tickets to "Ready for checkin" unless they are
developer. If you disagree with a discussion that has been made, *trivial* changes -- for example, spelling mistakes or broken links in
documentation.
* Please **don't** reverse a decision that has been made by a core
developer. If you disagree with a discussion that has been made,
please post a message to `django-developers`_. please post a message to `django-developers`_.
* Please be conservative in your actions. If you're unsure if you should * Please be conservative in your actions. If you're unsure if you should
be making a change, don't make the change - leave a comment with your be making a change, don't make the change -- leave a comment with your
concerns on the ticket, or post a message to `django-developers`_. concerns on the ticket, or post a message to `django-developers`_.
Submitting and maintaining translations Submitting and maintaining translations
@ -739,8 +738,8 @@ If you're using another backend:
deleted when the tests are finished. This means your user account needs deleted when the tests are finished. This means your user account needs
permission to execute ``CREATE DATABASE``. permission to execute ``CREATE DATABASE``.
If you want to run the full suite of tests, there are a number of dependencies that If you want to run the full suite of tests, you'll need to install a number of
you should install: dependencies:
* PyYAML_ * PyYAML_
* Markdown_ * Markdown_
@ -748,10 +747,8 @@ you should install:
* Docutils_ * Docutils_
* setuptools_ * setuptools_
Of these dependencies, setuptools_ is the only dependency that is required - if Each of these dependencies is optional. If you're missing any of them, the
setuptools_ is not installed, you will get import errors when running one of associated tests will be skipped.
the template tests. The tests using the other libraries will be skipped if the
dependency can't be found.
.. _PyYAML: http://pyyaml.org/wiki/PyYAML .. _PyYAML: http://pyyaml.org/wiki/PyYAML
.. _Markdown: http://pypi.python.org/pypi/Markdown/1.7 .. _Markdown: http://pypi.python.org/pypi/Markdown/1.7
@ -773,13 +770,13 @@ for generic relations and internationalization, type::
Contrib apps Contrib apps
------------ ------------
Tests for apps in ``django/contrib/`` go in their respective directories, Tests for apps in ``django/contrib/`` go in their respective directories under
in a ``tests.py`` file. (You can split the tests over multiple modules ``django/contrib/``, in a ``tests.py`` file. (You can split the tests over
by using a ``tests`` folder in the normal Python way). multiple modules by using a ``tests`` directory in the normal Python way.)
For the tests to be found, a ``models.py`` file must exist (it doesn't For the tests to be found, a ``models.py`` file must exist (it doesn't
have to have anything in it). If you have URLs that need to be have to have anything in it). If you have URLs that need to be
mapped, you must add them in ``tests/urls.py``. mapped, put them in ``tests/urls.py``.
To run tests for just one contrib app (e.g. ``markup``), use the same To run tests for just one contrib app (e.g. ``markup``), use the same
method as above:: method as above::

View File

@ -382,7 +382,7 @@ Pickling QuerySets
If you pickle_ a ``QuerySet``, this will also force all the results to be If you pickle_ a ``QuerySet``, this will also force all the results to be
loaded into memory prior to pickling. This is because pickling is usually used loaded into memory prior to pickling. This is because pickling is usually used
as a precursor to caching and when the cached queryset is reloaded, you want as a precursor to caching and when the cached ``QuerySet`` is reloaded, you want
the results to already be present. This means that when you unpickle a the results to already be present. This means that when you unpickle a
``QuerySet``, it contains the results at the moment it was pickled, rather ``QuerySet``, it contains the results at the moment it was pickled, rather
than the results that are currently in the database. than the results that are currently in the database.
@ -2040,7 +2040,7 @@ automatically saved to the database.
One-to-one relationships One-to-one relationships
------------------------ ------------------------
One-to-one relationships are very similar to Many-to-one relationships. One-to-one relationships are very similar to many-to-one relationships.
If you define a OneToOneField on your model, instances of that model will have If you define a OneToOneField on your model, instances of that model will have
access to the related object via a simple attribute of the model. access to the related object via a simple attribute of the model.
@ -2053,8 +2053,8 @@ For example::
ed = EntryDetail.objects.get(id=2) ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object. ed.entry # Returns the related Entry object.
The difference comes in reverse queries. The related model in a One-to-one The difference comes in "reverse" queries. The related model in a one-to-one
relationship also has access to a ``Manager`` object; however, that ``Manager`` relationship also has access to a ``Manager`` object, but that ``Manager``
represents a single object, rather than a collection of objects:: represents a single object, rather than a collection of objects::
e = Entry.objects.get(id=2) e = Entry.objects.get(id=2)

View File

@ -1544,7 +1544,7 @@ the ``url`` function)::
'django.views.generic.list_detail.object_detail', 'django.views.generic.list_detail.object_detail',
name='people_view'), name='people_view'),
and then using that name to perform the reverse URL resolution instead ...and then using that name to perform the reverse URL resolution instead
of the view name:: of the view name::
from django.db.models import permalink from django.db.models import permalink
@ -1553,7 +1553,7 @@ of the view name::
return ('people_view', [str(self.id)]) return ('people_view', [str(self.id)])
get_absolute_url = permalink(get_absolute_url) get_absolute_url = permalink(get_absolute_url)
More details on named URL patterns can be found in `URL dispatch documentation`_. More details on named URL patterns are in the `URL dispatch documentation`_.
.. _URL dispatch documentation: ../url_dispatch/#naming-url-patterns .. _URL dispatch documentation: ../url_dispatch/#naming-url-patterns

View File

@ -38,6 +38,29 @@ class Student(CommonInfo):
class Meta: class Meta:
pass pass
#
# Abstract base classes with related models
#
class Post(models.Model):
title = models.CharField(max_length=50)
class Attachment(models.Model):
post = models.ForeignKey(Post, related_name='attached_%(class)s_set')
content = models.TextField()
class Meta:
abstract = True
def __unicode__(self):
return self.content
class Comment(Attachment):
is_spam = models.BooleanField()
class Link(Attachment):
url = models.URLField()
# #
# Multi-table inheritance # Multi-table inheritance
# #
@ -128,9 +151,25 @@ Traceback (most recent call last):
... ...
AttributeError: type object 'CommonInfo' has no attribute 'objects' AttributeError: type object 'CommonInfo' has no attribute 'objects'
# The Place/Restaurant/ItalianRestaurant models, on the other hand, all exist # Create a Post
# as independent models. However, the subclasses also have transparent access >>> post = Post(title='Lorem Ipsum')
# to the fields of their ancestors. >>> post.save()
# The Post model has distinct accessors for the Comment and Link models.
>>> post.attached_comment_set.create(content='Save $ on V1agr@', is_spam=True)
<Comment: Save $ on V1agr@>
>>> post.attached_link_set.create(content='The Web framework for perfectionists with deadlines.', url='http://www.djangoproject.com/')
<Link: The Web framework for perfectionists with deadlines.>
# The Post model doesn't have an attribute called 'attached_%(class)s_set'.
>>> getattr(post, 'attached_%(class)s_set')
Traceback (most recent call last):
...
AttributeError: 'Post' object has no attribute 'attached_%(class)s_set'
# The Place/Restaurant/ItalianRestaurant models all exist as independent
# models. However, the subclasses also have transparent access to the fields of
# their ancestors.
# Create a couple of Places. # Create a couple of Places.
>>> p1 = Place(name='Master Shakes', address='666 W. Jersey') >>> p1 = Place(name='Master Shakes', address='666 W. Jersey')

View File

@ -125,4 +125,12 @@ Init from sequence of tuples
>>> d = FileDict({'other-key': 'once upon a time...'}) >>> d = FileDict({'other-key': 'once upon a time...'})
>>> repr(d) >>> repr(d)
"{'other-key': 'once upon a time...'}" "{'other-key': 'once upon a time...'}"
### DictWrapper #############################################################
>>> f = lambda x: "*%s" % x
>>> d = DictWrapper({'a': 'a'}, f, 'xx_')
>>> "Normal: %(a)s. Modified: %(xx_a)s" % d
'Normal: a. Modified: *a'
""" """

View File

@ -150,7 +150,7 @@ u'fran%C3%A7ois%20%26%20jill'
u'<a href="http://short.com/" rel="nofollow">http://short.com/</a>' u'<a href="http://short.com/" rel="nofollow">http://short.com/</a>'
>>> urlizetrunc(u'http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=', 20) >>> urlizetrunc(u'http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=', 20)
u'<a href="http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=" rel="nofollow">http://www.google....</a>' u'<a href="http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=" rel="nofollow">http://www.google...</a>'
>>> urlizetrunc('http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=', 20) >>> urlizetrunc('http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=', 20)
u'<a href="http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=" rel="nofollow">http://www.google...</a>' u'<a href="http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=" rel="nofollow">http://www.google...</a>'
@ -174,10 +174,10 @@ u'<a href="http://google.com" rel="nofollow">http://google.com</a>'
u'<a href="http://google.com/" rel="nofollow">http://google.com/</a>' u'<a href="http://google.com/" rel="nofollow">http://google.com/</a>'
>>> urlize('www.google.com') >>> urlize('www.google.com')
u'<a href="http://www.google.com" rel="nofollow">http://www.google.com</a>' u'<a href="http://www.google.com" rel="nofollow">www.google.com</a>'
>>> urlize('djangoproject.org') >>> urlize('djangoproject.org')
u'<a href="http://djangoproject.org" rel="nofollow">http://djangoproject.org</a>' u'<a href="http://djangoproject.org" rel="nofollow">djangoproject.org</a>'
>>> urlize('info@djangoproject.org') >>> urlize('info@djangoproject.org')
u'<a href="mailto:info@djangoproject.org">info@djangoproject.org</a>' u'<a href="mailto:info@djangoproject.org">info@djangoproject.org</a>'

View File

@ -2,6 +2,8 @@
Regression tests for Model inheritance behaviour. Regression tests for Model inheritance behaviour.
""" """
import datetime
from django.db import models from django.db import models
class Place(models.Model): class Place(models.Model):
@ -10,7 +12,7 @@ class Place(models.Model):
class Meta: class Meta:
ordering = ('name',) ordering = ('name',)
def __unicode__(self): def __unicode__(self):
return u"%s the place" % self.name return u"%s the place" % self.name
@ -35,11 +37,17 @@ class ParkingLot(Place):
def __unicode__(self): def __unicode__(self):
return u"%s the parking lot" % self.name return u"%s the parking lot" % self.name
class Parent(models.Model):
created = models.DateTimeField(default=datetime.datetime.now)
class Child(Parent):
name = models.CharField(max_length=10)
__test__ = {'API_TESTS':""" __test__ = {'API_TESTS':"""
# Regression for #7350, #7202 # Regression for #7350, #7202
# Check that when you create a Parent object with a specific reference to an existent # Check that when you create a Parent object with a specific reference to an
# child instance, saving the Parent doesn't duplicate the child. # existent child instance, saving the Parent doesn't duplicate the child. This
# This behaviour is only activated during a raw save - it is mostly relevant to # behaviour is only activated during a raw save - it is mostly relevant to
# deserialization, but any sort of CORBA style 'narrow()' API would require a # deserialization, but any sort of CORBA style 'narrow()' API would require a
# similar approach. # similar approach.
@ -117,4 +125,10 @@ __test__ = {'API_TESTS':"""
>>> [sorted(d.items()) for d in dicts] >>> [sorted(d.items()) for d in dicts]
[[('name', u"Guido's All New House of Pasta"), ('serves_gnocchi', False), ('serves_hot_dogs', False)]] [[('name', u"Guido's All New House of Pasta"), ('serves_gnocchi', False), ('serves_hot_dogs', False)]]
# Regressions tests for #7105: dates() queries should be able to use fields
# from the parent model as easily as the child.
>>> obj = Child.objects.create(name='child', created=datetime.datetime(2008, 6, 26, 17, 0, 0))
>>> Child.objects.dates('created', 'month')
[datetime.datetime(2008, 6, 1, 0, 0)]
"""} """}

View File

@ -45,6 +45,7 @@ class Author(models.Model):
class Item(models.Model): class Item(models.Model):
name = models.CharField(max_length=10) name = models.CharField(max_length=10)
created = models.DateTimeField() created = models.DateTimeField()
modified = models.DateTimeField(blank=True, null=True)
tags = models.ManyToManyField(Tag, blank=True, null=True) tags = models.ManyToManyField(Tag, blank=True, null=True)
creator = models.ForeignKey(Author) creator = models.ForeignKey(Author)
note = models.ForeignKey(Note) note = models.ForeignKey(Note)
@ -57,7 +58,7 @@ class Item(models.Model):
class Report(models.Model): class Report(models.Model):
name = models.CharField(max_length=10) name = models.CharField(max_length=10)
creator = models.ForeignKey(Author, to_field='num') creator = models.ForeignKey(Author, to_field='num', null=True)
def __unicode__(self): def __unicode__(self):
return self.name return self.name
@ -89,6 +90,15 @@ class Number(models.Model):
def __unicode__(self): def __unicode__(self):
return unicode(self.num) return unicode(self.num)
# Symmetrical m2m field with a normal field using the reverse accesor name
# ("valid").
class Valid(models.Model):
valid = models.CharField(max_length=10)
parent = models.ManyToManyField('self')
class Meta:
ordering = ['valid']
# Some funky cross-linked models for testing a couple of infinite recursion # Some funky cross-linked models for testing a couple of infinite recursion
# cases. # cases.
class X(models.Model): class X(models.Model):
@ -121,12 +131,12 @@ class LoopZ(models.Model):
class CustomManager(models.Manager): class CustomManager(models.Manager):
def get_query_set(self): def get_query_set(self):
qs = super(CustomManager, self).get_query_set() qs = super(CustomManager, self).get_query_set()
return qs.filter(is_public=True, tag__name='t1') return qs.filter(public=True, tag__name='t1')
class ManagedModel(models.Model): class ManagedModel(models.Model):
data = models.CharField(max_length=10) data = models.CharField(max_length=10)
tag = models.ForeignKey(Tag) tag = models.ForeignKey(Tag)
is_public = models.BooleanField(default=True) public = models.BooleanField(default=True)
objects = CustomManager() objects = CustomManager()
normal_manager = models.Manager() normal_manager = models.Manager()
@ -134,6 +144,24 @@ class ManagedModel(models.Model):
def __unicode__(self): def __unicode__(self):
return self.data return self.data
# An inter-related setup with multiple paths from Child to Detail.
class Detail(models.Model):
data = models.CharField(max_length=10)
class MemberManager(models.Manager):
def get_query_set(self):
return super(MemberManager, self).get_query_set().select_related("details")
class Member(models.Model):
name = models.CharField(max_length=10)
details = models.OneToOneField(Detail, primary_key=True)
objects = MemberManager()
class Child(models.Model):
person = models.OneToOneField(Member, primary_key=True)
parent = models.ForeignKey(Member, related_name="children")
__test__ = {'API_TESTS':""" __test__ = {'API_TESTS':"""
>>> t1 = Tag(name='t1') >>> t1 = Tag(name='t1')
@ -174,7 +202,7 @@ by 'info'. Helps detect some problems later.
>>> time2 = datetime.datetime(2007, 12, 19, 21, 0, 0) >>> time2 = datetime.datetime(2007, 12, 19, 21, 0, 0)
>>> time3 = datetime.datetime(2007, 12, 20, 22, 25, 0) >>> time3 = datetime.datetime(2007, 12, 20, 22, 25, 0)
>>> time4 = datetime.datetime(2007, 12, 20, 21, 0, 0) >>> time4 = datetime.datetime(2007, 12, 20, 21, 0, 0)
>>> i1 = Item(name='one', created=time1, creator=a1, note=n3) >>> i1 = Item(name='one', created=time1, modified=time1, creator=a1, note=n3)
>>> i1.save() >>> i1.save()
>>> i1.tags = [t1, t2] >>> i1.tags = [t1, t2]
>>> i2 = Item(name='two', created=time2, creator=a2, note=n2) >>> i2 = Item(name='two', created=time2, creator=a2, note=n2)
@ -190,6 +218,8 @@ by 'info'. Helps detect some problems later.
>>> r1.save() >>> r1.save()
>>> r2 = Report(name='r2', creator=a3) >>> r2 = Report(name='r2', creator=a3)
>>> r2.save() >>> r2.save()
>>> r3 = Report(name='r3')
>>> r3.save()
Ordering by 'rank' gives us rank2, rank1, rank3. Ordering by the Meta.ordering Ordering by 'rank' gives us rank2, rank1, rank3. Ordering by the Meta.ordering
will be rank3, rank2, rank1. will be rank3, rank2, rank1.
@ -478,7 +508,7 @@ FieldError: Infinite loop caused by ordering.
# Ordering by a many-valued attribute (e.g. a many-to-many or reverse # Ordering by a many-valued attribute (e.g. a many-to-many or reverse
# ForeignKey) is legal, but the results might not make sense. That isn't # ForeignKey) is legal, but the results might not make sense. That isn't
# Django's problem. Garbage in, garbage out. # Django's problem. Garbage in, garbage out.
>>> Item.objects.all().order_by('tags', 'id') >>> Item.objects.filter(tags__isnull=False).order_by('tags', 'id')
[<Item: one>, <Item: two>, <Item: one>, <Item: two>, <Item: four>] [<Item: one>, <Item: two>, <Item: one>, <Item: two>, <Item: four>]
# If we replace the default ordering, Django adjusts the required tables # If we replace the default ordering, Django adjusts the required tables
@ -627,6 +657,10 @@ Bug #7087 -- dates with extra select columns
>>> Item.objects.dates('created', 'day').extra(select={'a': 1}) >>> Item.objects.dates('created', 'day').extra(select={'a': 1})
[datetime.datetime(2007, 12, 19, 0, 0), datetime.datetime(2007, 12, 20, 0, 0)] [datetime.datetime(2007, 12, 19, 0, 0), datetime.datetime(2007, 12, 20, 0, 0)]
Bug #7155 -- nullable dates
>>> Item.objects.dates('modified', 'day')
[datetime.datetime(2007, 12, 19, 0, 0)]
Test that parallel iterators work. Test that parallel iterators work.
>>> qs = Tag.objects.all() >>> qs = Tag.objects.all()
@ -705,8 +739,57 @@ More twisted cases, involving nested negations.
Bug #7095 Bug #7095
Updates that are filtered on the model being updated are somewhat tricky to get Updates that are filtered on the model being updated are somewhat tricky to get
in MySQL. This exercises that case. in MySQL. This exercises that case.
>>> mm = ManagedModel.objects.create(data='mm1', tag=t1, is_public=True) >>> mm = ManagedModel.objects.create(data='mm1', tag=t1, public=True)
>>> ManagedModel.objects.update(data='mm') >>> ManagedModel.objects.update(data='mm')
A values() or values_list() query across joined models must use outer joins
appropriately.
>>> Report.objects.values_list("creator__extra__info", flat=True).order_by("name")
[u'e1', u'e2', None]
Similarly for select_related(), joins beyond an initial nullable join must
use outer joins so that all results are included.
>>> Report.objects.select_related("creator", "creator__extra").order_by("name")
[<Report: r1>, <Report: r2>, <Report: r3>]
When there are multiple paths to a table from another table, we have to be
careful not to accidentally reuse an inappropriate join when using
select_related(). We used to return the parent's Detail record here by mistake.
>>> d1 = Detail.objects.create(data="d1")
>>> d2 = Detail.objects.create(data="d2")
>>> m1 = Member.objects.create(name="m1", details=d1)
>>> m2 = Member.objects.create(name="m2", details=d2)
>>> c1 = Child.objects.create(person=m2, parent=m1)
>>> obj = m1.children.select_related("person__details")[0]
>>> obj.person.details.data
u'd2'
Bug #7076 -- excluding shouldn't eliminate NULL entries.
>>> Item.objects.exclude(modified=time1).order_by('name')
[<Item: four>, <Item: three>, <Item: two>]
>>> Tag.objects.exclude(parent__name=t1.name)
[<Tag: t1>, <Tag: t4>, <Tag: t5>]
Bug #7181 -- ordering by related tables should accomodate nullable fields (this
test is a little tricky, since NULL ordering is database dependent. Instead, we
just count the number of results).
>>> len(Tag.objects.order_by('parent__name'))
5
Bug #7107 -- this shouldn't create an infinite loop.
>>> Valid.objects.all()
[]
Empty querysets can be merged with others.
>>> Note.objects.none() | Note.objects.all()
[<Note: n1>, <Note: n2>, <Note: n3>]
>>> Note.objects.all() | Note.objects.none()
[<Note: n1>, <Note: n2>, <Note: n3>]
>>> Note.objects.none() & Note.objects.all()
[]
>>> Note.objects.all() & Note.objects.none()
[]
"""} """}

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
""" """
Test cases for the template loaders Test cases for the template loaders
Note: This test requires setuptools!
""" """
from django.conf import settings from django.conf import settings
@ -17,7 +18,7 @@ import StringIO
from django.template import TemplateDoesNotExist from django.template import TemplateDoesNotExist
from django.template.loaders.eggs import load_template_source as lts_egg from django.template.loaders.eggs import load_template_source as lts_egg
#Mock classes and objects for pkg_resources functions # Mock classes and objects for pkg_resources functions.
class MockProvider(pkg_resources.NullProvider): class MockProvider(pkg_resources.NullProvider):
def __init__(self, module): def __init__(self, module):
pkg_resources.NullProvider.__init__(self, module) pkg_resources.NullProvider.__init__(self, module)
@ -35,25 +36,25 @@ class MockProvider(pkg_resources.NullProvider):
def _get(self, path): def _get(self, path):
return self.module._resources[path].read() return self.module._resources[path].read()
class MockLoader(object): pass class MockLoader(object):
pass
def create_egg(name, resources): def create_egg(name, resources):
""" """
Creates a mock egg with a list of resources Creates a mock egg with a list of resources.
name: The name of the module name: The name of the module.
resources: A dictionary of resources. Keys are the names and values the the data. resources: A dictionary of resources. Keys are the names and values the the data.
""" """
egg = imp.new_module(name) egg = imp.new_module(name)
egg.__loader__ = MockLoader() egg.__loader__ = MockLoader()
egg._resources = resources egg._resources = resources
sys.modules[name] = egg sys.modules[name] = egg
class EggLoader(unittest.TestCase): class EggLoader(unittest.TestCase):
def setUp(self): def setUp(self):
pkg_resources._provider_factories[MockLoader] = MockProvider pkg_resources._provider_factories[MockLoader] = MockProvider
self.empty_egg = create_egg("egg_empty", {}) self.empty_egg = create_egg("egg_empty", {})
self.egg_1 = create_egg("egg_1", { self.egg_1 = create_egg("egg_1", {
'templates/y.html' : StringIO.StringIO("y"), 'templates/y.html' : StringIO.StringIO("y"),
@ -61,7 +62,7 @@ class EggLoader(unittest.TestCase):
}) })
self._old_installed_apps = settings.INSTALLED_APPS self._old_installed_apps = settings.INSTALLED_APPS
settings.INSTALLED_APPS = [] settings.INSTALLED_APPS = []
def tearDown(self): def tearDown(self):
settings.INSTALLED_APPS = self._old_installed_apps settings.INSTALLED_APPS = self._old_installed_apps
@ -74,19 +75,18 @@ class EggLoader(unittest.TestCase):
"Template loading fails if the template is not in the egg" "Template loading fails if the template is not in the egg"
settings.INSTALLED_APPS = ['egg_1'] settings.INSTALLED_APPS = ['egg_1']
self.assertRaises(TemplateDoesNotExist, lts_egg, "not-existing.html") self.assertRaises(TemplateDoesNotExist, lts_egg, "not-existing.html")
def test_existing(self): def test_existing(self):
"A template can be loaded from an egg" "A template can be loaded from an egg"
settings.INSTALLED_APPS = ['egg_1'] settings.INSTALLED_APPS = ['egg_1']
contents, template_name = lts_egg("y.html") contents, template_name = lts_egg("y.html")
self.assertEqual(contents, "y") self.assertEqual(contents, "y")
self.assertEqual(template_name, "egg:egg_1:templates/y.html") self.assertEqual(template_name, "egg:egg_1:templates/y.html")
def test_not_installed(self): def test_not_installed(self):
"Loading an existent template from an egg not included in INSTALLED_APPS should fail" "Loading an existent template from an egg not included in INSTALLED_APPS should fail"
settings.INSTALLED_APPS = [] settings.INSTALLED_APPS = []
self.assertRaises(TemplateDoesNotExist, lts_egg, "y.html") self.assertRaises(TemplateDoesNotExist, lts_egg, "y.html")
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -20,7 +20,10 @@ from django.utils.tzinfo import LocalTimezone
from unicode import unicode_tests from unicode import unicode_tests
from context import context_tests from context import context_tests
from loaders import * try:
from loaders import *
except ImportError:
pass # If setuptools isn't installed, that's fine. Just move on.
import filters import filters
@ -132,8 +135,7 @@ class Templates(unittest.TestCase):
# Quickly check that we aren't accidentally using a name in both # Quickly check that we aren't accidentally using a name in both
# template and filter tests. # template and filter tests.
overlapping_names = [name for name in filter_tests if name in overlapping_names = [name for name in filter_tests if name in template_tests]
template_tests]
assert not overlapping_names, 'Duplicate test name(s): %s' % ', '.join(overlapping_names) assert not overlapping_names, 'Duplicate test name(s): %s' % ', '.join(overlapping_names)
template_tests.update(filter_tests) template_tests.update(filter_tests)
@ -156,7 +158,7 @@ class Templates(unittest.TestCase):
# Turn TEMPLATE_DEBUG off, because tests assume that. # Turn TEMPLATE_DEBUG off, because tests assume that.
old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
# Set TEMPLATE_STRING_IF_INVALID to a known string # Set TEMPLATE_STRING_IF_INVALID to a known string.
old_invalid = settings.TEMPLATE_STRING_IF_INVALID old_invalid = settings.TEMPLATE_STRING_IF_INVALID
expected_invalid_str = 'INVALID' expected_invalid_str = 'INVALID'
@ -539,13 +541,14 @@ class Templates(unittest.TestCase):
'if-tag-error05': ("{% if not foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError), 'if-tag-error05': ("{% if not foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
### IFCHANGED TAG ######################################################### ### IFCHANGED TAG #########################################################
'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,2,3) }, '123'), 'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', {'num': (1,2,3)}, '123'),
'ifchanged02': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,3) }, '13'), 'ifchanged02': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', {'num': (1,1,3)}, '13'),
'ifchanged03': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,1) }, '1'), 'ifchanged03': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', {'num': (1,1,1)}, '1'),
'ifchanged04': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 2, 3), 'numx': (2, 2, 2)}, '122232'), 'ifchanged04': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', {'num': (1, 2, 3), 'numx': (2, 2, 2)}, '122232'),
'ifchanged05': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (1, 2, 3)}, '1123123123'), 'ifchanged05': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', {'num': (1, 1, 1), 'numx': (1, 2, 3)}, '1123123123'),
'ifchanged06': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2)}, '1222'), 'ifchanged06': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', {'num': (1, 1, 1), 'numx': (2, 2, 2)}, '1222'),
'ifchanged07': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% for y in numy %}{% ifchanged %}{{ y }}{% endifchanged %}{% endfor %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2), 'numy': (3, 3, 3)}, '1233323332333'), 'ifchanged07': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% for y in numy %}{% ifchanged %}{{ y }}{% endifchanged %}{% endfor %}{% endfor %}{% endfor %}', {'num': (1, 1, 1), 'numx': (2, 2, 2), 'numy': (3, 3, 3)}, '1233323332333'),
'ifchanged08': ('{% for data in datalist %}{% for c,d in data %}{% if c %}{% ifchanged %}{{ d }}{% endifchanged %}{% endif %}{% endfor %}{% endfor %}', {'datalist': [[(1, 'a'), (1, 'a'), (0, 'b'), (1, 'c')], [(0, 'a'), (1, 'c'), (1, 'd'), (1, 'd'), (0, 'e')]]}, 'accd'),
# Test one parameter given to ifchanged. # Test one parameter given to ifchanged.
'ifchanged-param01': ('{% for n in num %}{% ifchanged n %}..{% endifchanged %}{{ n }}{% endfor %}', { 'num': (1,2,3) }, '..1..2..3'), 'ifchanged-param01': ('{% for n in num %}{% ifchanged n %}..{% endifchanged %}{{ n }}{% endfor %}', { 'num': (1,2,3) }, '..1..2..3'),
@ -860,40 +863,46 @@ class Templates(unittest.TestCase):
### NOW TAG ######################################################## ### NOW TAG ########################################################
# Simple case # Simple case
'now01' : ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)), 'now01': ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)),
# Check parsing of escaped and special characters # Check parsing of escaped and special characters
'now02' : ('{% now "j "n" Y"%}', {}, template.TemplateSyntaxError), 'now02': ('{% now "j "n" Y"%}', {}, template.TemplateSyntaxError),
# 'now03' : ('{% now "j \"n\" Y"%}', {}, str(datetime.now().day) + '"' + str(datetime.now().month) + '"' + str(datetime.now().year)), # 'now03': ('{% now "j \"n\" Y"%}', {}, str(datetime.now().day) + '"' + str(datetime.now().month) + '"' + str(datetime.now().year)),
# 'now04' : ('{% now "j \nn\n Y"%}', {}, str(datetime.now().day) + '\n' + str(datetime.now().month) + '\n' + str(datetime.now().year)) # 'now04': ('{% now "j \nn\n Y"%}', {}, str(datetime.now().day) + '\n' + str(datetime.now().month) + '\n' + str(datetime.now().year))
### URL TAG ######################################################## ### URL TAG ########################################################
# Successes # Successes
'url01' : ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'), 'url01': ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),
'url02' : ('{% url regressiontests.templates.views.client_action client.id, action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'), 'url02': ('{% url regressiontests.templates.views.client_action client.id, action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'url03' : ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'), 'url03': ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'),
'url04' : ('{% url named.client client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'), 'url04': ('{% url named.client client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'),
'url05' : (u'{% url метка_оператора v %}', {'v': u'Ω'}, 'url05': (u'{% url метка_оператора v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
'/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
# Failures # Failures
'url-fail01' : ('{% url %}', {}, template.TemplateSyntaxError), 'url-fail01': ('{% url %}', {}, template.TemplateSyntaxError),
'url-fail02' : ('{% url no_such_view %}', {}, ''), 'url-fail02': ('{% url no_such_view %}', {}, ''),
'url-fail03' : ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''), 'url-fail03': ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''),
### CACHE TAG ###################################################### ### CACHE TAG ######################################################
'cache01' : ('{% load cache %}{% cache -1 test %}cache01{% endcache %}', {}, 'cache01'), 'cache01': ('{% load cache %}{% cache -1 test %}cache01{% endcache %}', {}, 'cache01'),
'cache02' : ('{% load cache %}{% cache -1 test %}cache02{% endcache %}', {}, 'cache02'), 'cache02': ('{% load cache %}{% cache -1 test %}cache02{% endcache %}', {}, 'cache02'),
'cache03' : ('{% load cache %}{% cache 2 test %}cache03{% endcache %}', {}, 'cache03'), 'cache03': ('{% load cache %}{% cache 2 test %}cache03{% endcache %}', {}, 'cache03'),
'cache04' : ('{% load cache %}{% cache 2 test %}cache04{% endcache %}', {}, 'cache03'), 'cache04': ('{% load cache %}{% cache 2 test %}cache04{% endcache %}', {}, 'cache03'),
'cache05' : ('{% load cache %}{% cache 2 test foo %}cache05{% endcache %}', {'foo': 1}, 'cache05'), 'cache05': ('{% load cache %}{% cache 2 test foo %}cache05{% endcache %}', {'foo': 1}, 'cache05'),
'cache06' : ('{% load cache %}{% cache 2 test foo %}cache06{% endcache %}', {'foo': 2}, 'cache06'), 'cache06': ('{% load cache %}{% cache 2 test foo %}cache06{% endcache %}', {'foo': 2}, 'cache06'),
'cache07' : ('{% load cache %}{% cache 2 test foo %}cache06{% endcache %}', {'foo': 1}, 'cache05'), 'cache07': ('{% load cache %}{% cache 2 test foo %}cache07{% endcache %}', {'foo': 1}, 'cache05'),
# Raise exception if we dont have at least 2 args, first one integer. # Allow first argument to be a variable.
'cache08' : ('{% load cache %}{% cache %}{% endcache %}', {}, template.TemplateSyntaxError), 'cache08': ('{% load cache %}{% cache time test foo %}cache08{% endcache %}', {'foo': 2, 'time': 2}, 'cache06'),
'cache09' : ('{% load cache %}{% cache 1 %}{% endcache %}', {}, template.TemplateSyntaxError), 'cache09': ('{% load cache %}{% cache time test foo %}cache09{% endcache %}', {'foo': 3, 'time': -1}, 'cache09'),
'cache10' : ('{% load cache %}{% cache foo bar %}{% endcache %}', {}, template.TemplateSyntaxError), 'cache10': ('{% load cache %}{% cache time test foo %}cache10{% endcache %}', {'foo': 3, 'time': -1}, 'cache10'),
# Raise exception if we don't have at least 2 args, first one integer.
'cache11': ('{% load cache %}{% cache %}{% endcache %}', {}, template.TemplateSyntaxError),
'cache12': ('{% load cache %}{% cache 1 %}{% endcache %}', {}, template.TemplateSyntaxError),
'cache13': ('{% load cache %}{% cache foo bar %}{% endcache %}', {}, template.TemplateSyntaxError),
'cache14': ('{% load cache %}{% cache foo bar %}{% endcache %}', {'foo': 'fail'}, template.TemplateSyntaxError),
'cache15': ('{% load cache %}{% cache foo bar %}{% endcache %}', {'foo': []}, template.TemplateSyntaxError),
### AUTOESCAPE TAG ############################################## ### AUTOESCAPE TAG ##############################################
'autoescape-tag01': ("{% autoescape off %}hello{% endautoescape %}", {}, "hello"), 'autoescape-tag01': ("{% autoescape off %}hello{% endautoescape %}", {}, "hello"),