summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Gaynor <alex.gaynor@gmail.com>2009-12-12 02:10:28 +0000
committerAlex Gaynor <alex.gaynor@gmail.com>2009-12-12 02:10:28 +0000
commitc88113683d6538dbc2f779ff57fd1ea20211abec (patch)
tree48756d324a15b8d1d990855ad3249f607a81ebf5
parent353e0797920fbf5170653c9a9aabe2572a5ad214 (diff)
downloaddjango-c88113683d6538dbc2f779ff57fd1ea20211abec.tar.gz
[soc2009/multidb] Merged up to trunk r11810. There are many conflicts in this merge, these will be resolved in a subsequent commit.
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/multidb@11812 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--django/conf/global_settings.py3
-rw-r--r--django/conf/locale/pl/LC_MESSAGES/django.po196
-rw-r--r--django/contrib/admin/options.py2
-rw-r--r--django/contrib/auth/__init__.py7
-rw-r--r--django/contrib/auth/backends.py2
-rw-r--r--django/contrib/auth/models.py57
-rw-r--r--django/contrib/auth/tests/__init__.py1
-rw-r--r--django/contrib/auth/tests/auth_backends.py149
-rw-r--r--django/contrib/gis/tests/relatedapp/tests.py11
-rw-r--r--django/core/management/commands/dumpdata.py5
-rw-r--r--django/db/backends/oracle/base.py4
-rw-r--r--django/db/models/base.py10
-rw-r--r--django/db/models/fields/related.py23
-rw-r--r--django/db/models/manager.py3
-rw-r--r--django/db/models/query.py8
-rw-r--r--django/db/models/sql/query.py104
-rw-r--r--django/forms/models.py4
-rw-r--r--django/template/defaulttags.py101
-rw-r--r--django/template/smartif.py192
-rw-r--r--docs/internals/deprecation.txt18
-rw-r--r--docs/ref/databases.txt3
-rw-r--r--docs/ref/django-admin.txt51
-rw-r--r--docs/ref/settings.txt48
-rw-r--r--docs/ref/templates/builtins.txt160
-rw-r--r--docs/releases/1.2.txt51
-rw-r--r--docs/topics/auth.txt52
-rw-r--r--docs/topics/templates.txt21
-rw-r--r--tests/regressiontests/admin_views/tests.py6
-rw-r--r--tests/regressiontests/auth_backends/__init__.py0
-rw-r--r--tests/regressiontests/auth_backends/models.py0
-rw-r--r--tests/regressiontests/auth_backends/tests.py78
-rw-r--r--tests/regressiontests/backends/tests.py12
-rw-r--r--tests/regressiontests/templates/smartif.py46
-rw-r--r--tests/regressiontests/templates/tests.py44
34 files changed, 1239 insertions, 233 deletions
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 9a08348fa5..6b8c2c885c 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -131,9 +131,12 @@ DATABASE_HOST = '' # Set to empty string for localhost. Not used wit
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
DATABASE_OPTIONS = {} # Set to empty dictionary for default.
+<<<<<<< HEAD:django/conf/global_settings.py
DATABASES = {
}
+=======
+>>>>>>> master:django/conf/global_settings.py
# The email backend to use. For possible shortcuts see django.core.mail.
# The default is to use the SMTP backend.
# Third-party backends can be specified by providing a Python path
diff --git a/django/conf/locale/pl/LC_MESSAGES/django.po b/django/conf/locale/pl/LC_MESSAGES/django.po
index 28f12561ee..6e8e59de92 100644
--- a/django/conf/locale/pl/LC_MESSAGES/django.po
+++ b/django/conf/locale/pl/LC_MESSAGES/django.po
@@ -5,7 +5,11 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
"POT-Creation-Date: 2009-10-25 20:56+0100\n"
+=======
+"POT-Creation-Date: 2009-12-11 10:11+0100\n"
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
"PO-Revision-Date: 2008-02-25 15:53+0100\n"
"Last-Translator: Jarek Zgoda <jarek.zgoda@gmail.com>\n"
"MIME-Version: 1.0\n"
@@ -223,7 +227,11 @@ msgstr "chiński tradycyjny"
msgid "Successfully deleted %(count)d %(items)s."
msgstr "Usunięto %(count)d %(items)s."
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/actions.py:67 contrib/admin/options.py:1027
+=======
+#: contrib/admin/actions.py:67 contrib/admin/options.py:1034
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
msgid "Are you sure?"
msgstr "Jesteś pewien?"
@@ -310,87 +318,132 @@ msgstr "log"
msgid "log entries"
msgstr "logi"
-#: contrib/admin/options.py:133 contrib/admin/options.py:147
+#: contrib/admin/options.py:135 contrib/admin/options.py:149
msgid "None"
msgstr "brak"
-#: contrib/admin/options.py:519
+#: contrib/admin/options.py:522
#, python-format
msgid "Changed %s."
msgstr "Zmieniono %s"
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/options.py:519 contrib/admin/options.py:529
+=======
+#: contrib/admin/options.py:522 contrib/admin/options.py:532
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/comments/templates/comments/preview.html:16 forms/models.py:384
#: forms/models.py:596
msgid "and"
msgstr "i"
-#: contrib/admin/options.py:524
+#: contrib/admin/options.py:527
#, python-format
msgid "Added %(name)s \"%(object)s\"."
msgstr "Dodano %(name)s \"%(object)s\"."
-#: contrib/admin/options.py:528
+#: contrib/admin/options.py:531
#, python-format
msgid "Changed %(list)s for %(name)s \"%(object)s\"."
msgstr "Zmieniono %(list)s w %(name)s \"%(object)s\"."
-#: contrib/admin/options.py:533
+#: contrib/admin/options.py:536
#, python-format
msgid "Deleted %(name)s \"%(object)s\"."
msgstr "Usunięto %(name)s \"%(object)s\"."
-#: contrib/admin/options.py:537
+#: contrib/admin/options.py:540
msgid "No fields changed."
msgstr "Żadne pole nie zmienione."
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/options.py:599 contrib/auth/admin.py:67
+=======
+#: contrib/admin/options.py:602 contrib/auth/admin.py:68
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#, python-format
msgid "The %(name)s \"%(obj)s\" was added successfully."
msgstr "%(name)s \"%(obj)s\" dodany pomyślnie."
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/options.py:603 contrib/admin/options.py:636
#: contrib/auth/admin.py:75
msgid "You may edit it again below."
msgstr "Możesz ponownie edytować wpis poniżej."
#: contrib/admin/options.py:613 contrib/admin/options.py:646
+=======
+#: contrib/admin/options.py:606 contrib/admin/options.py:639
+#: contrib/auth/admin.py:77
+msgid "You may edit it again below."
+msgstr "Możesz ponownie edytować wpis poniżej."
+
+#: contrib/admin/options.py:616 contrib/admin/options.py:649
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#, python-format
msgid "You may add another %s below."
msgstr "Możesz dodać nowy wpis %s poniżej."
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/options.py:634
+=======
+#: contrib/admin/options.py:637
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#, python-format
msgid "The %(name)s \"%(obj)s\" was changed successfully."
msgstr "%(name)s \"%(obj)s\" zostało pomyślnie zmienione."
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/options.py:642
+=======
+#: contrib/admin/options.py:645
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#, python-format
msgid ""
"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
msgstr ""
"%(name)s \"%(obj)s\" dodane pomyślnie. Możesz edytować ponownie wpis poniżej."
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/options.py:773
+=======
+#: contrib/admin/options.py:778
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#, python-format
msgid "Add %s"
msgstr "Dodaj %s"
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/options.py:804 contrib/admin/options.py:1005
+=======
+#: contrib/admin/options.py:810 contrib/admin/options.py:1012
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#, python-format
msgid "%(name)s object with primary key %(key)r does not exist."
msgstr "Obiekt %(name)s o kluczu głównym %(key)r nie istnieje."
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/options.py:861
+=======
+#: contrib/admin/options.py:867
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#, python-format
msgid "Change %s"
msgstr "Zmień %s"
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/options.py:905
msgid "Database error"
msgstr "Błąd bazy danych"
#: contrib/admin/options.py:941
+=======
+#: contrib/admin/options.py:911
+msgid "Database error"
+msgstr "Błąd bazy danych"
+
+#: contrib/admin/options.py:947
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#, python-format
msgid "%(count)s %(name)s was changed successfully."
msgid_plural "%(count)s %(name)s were changed successfully."
@@ -398,17 +451,29 @@ msgstr[0] "%(count)s %(name)s został pomyślnie zmieniony."
msgstr[1] "%(count)s %(name)s zostały pomyślnie zmienione."
msgstr[2] "%(count)s %(name)s zostało pomyślnie zmienionych."
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/options.py:1020
+=======
+#: contrib/admin/options.py:1027
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#, python-format
msgid "The %(name)s \"%(obj)s\" was deleted successfully."
msgstr "%(name)s \"%(obj)s\" usunięty pomyślnie."
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/options.py:1057
+=======
+#: contrib/admin/options.py:1064
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#, python-format
msgid "Change history: %s"
msgstr "Historia zmian: %s"
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/sites.py:21 contrib/admin/views/decorators.py:14
+=======
+#: contrib/admin/sites.py:22 contrib/admin/views/decorators.py:14
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/auth/forms.py:80
msgid ""
"Please enter a correct username and password. Note that both fields are case-"
@@ -417,11 +482,19 @@ msgstr ""
"Proszę wpisać poprawną nazwę użytkownika i hasło. Uwaga: wielkość liter ma "
"znaczenie."
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/sites.py:288 contrib/admin/views/decorators.py:40
msgid "Please log in again, because your session has expired."
msgstr "Twoja sesja wygasła, zaloguj się ponownie."
#: contrib/admin/sites.py:295 contrib/admin/views/decorators.py:47
+=======
+#: contrib/admin/sites.py:292 contrib/admin/views/decorators.py:40
+msgid "Please log in again, because your session has expired."
+msgstr "Twoja sesja wygasła, zaloguj się ponownie."
+
+#: contrib/admin/sites.py:299 contrib/admin/views/decorators.py:47
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
msgid ""
"Looks like your browser isn't configured to accept cookies. Please enable "
"cookies, reload this page, and try again."
@@ -429,27 +502,47 @@ msgstr ""
"Twoja przeglądarka nie chce akceptować ciasteczek. Zmień jej ustawienia i "
"spróbuj ponownie."
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/sites.py:311 contrib/admin/sites.py:317
+=======
+#: contrib/admin/sites.py:315 contrib/admin/sites.py:321
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/views/decorators.py:66
msgid "Usernames cannot contain the '@' character."
msgstr "Nazwy użytkowników nie mogą zawierać znaku '@'."
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/sites.py:314 contrib/admin/views/decorators.py:62
+=======
+#: contrib/admin/sites.py:318 contrib/admin/views/decorators.py:62
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#, python-format
msgid "Your e-mail address is not your username. Try '%s' instead."
msgstr "Podany adres e-mail nie jest Twoją nazwą użytkownika. Spróbuj '%s'."
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/sites.py:370
msgid "Site administration"
msgstr "Administracja stroną"
#: contrib/admin/sites.py:384 contrib/admin/templates/admin/login.html:26
+=======
+#: contrib/admin/sites.py:374
+msgid "Site administration"
+msgstr "Administracja stroną"
+
+#: contrib/admin/sites.py:388 contrib/admin/templates/admin/login.html:26
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/templates/registration/password_reset_complete.html:14
#: contrib/admin/views/decorators.py:20
msgid "Log in"
msgstr "Zaloguj się"
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/sites.py:429
+=======
+#: contrib/admin/sites.py:433
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#, python-format
msgid "%s administration"
msgstr "%s - administracja"
@@ -670,8 +763,13 @@ msgid ""
"Are you sure you want to delete the selected %(object_name)s objects? All of "
"the following objects and their related items will be deleted:"
msgstr ""
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
"Czy chcesz skasować wybrane %(object_name)s? Następujące obiekty i zależne od "
"nich zostaną skasowane:"
+=======
+"Czy chcesz skasować wybrane %(object_name)s? Następujące obiekty i zależne "
+"od nich zostaną skasowane:"
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/admin/templates/admin/filter.html:2
#, python-format
@@ -977,7 +1075,7 @@ msgstr "Adres e-mail:"
msgid "Reset my password"
msgstr "Zresetuj moje hasło"
-#: contrib/admin/templatetags/admin_list.py:299
+#: contrib/admin/templatetags/admin_list.py:304
msgid "All dates"
msgstr "Wszystkie daty"
@@ -991,11 +1089,11 @@ msgstr "Zaznacz %s"
msgid "Select %s to change"
msgstr "Zaznacz %s aby zmienić"
-#: contrib/admin/views/template.py:37 contrib/sites/models.py:38
+#: contrib/admin/views/template.py:38 contrib/sites/models.py:38
msgid "site"
msgstr "strona"
-#: contrib/admin/views/template.py:39
+#: contrib/admin/views/template.py:40
msgid "template"
msgstr "szablon"
@@ -1209,37 +1307,37 @@ msgstr "Edytuj ten obiekt (nowe okno)"
msgid "As above, but opens the admin page in a new window."
msgstr "Jak wyżej, tyle że otwiera nowe okno."
-#: contrib/auth/admin.py:21
+#: contrib/auth/admin.py:22
msgid "Personal info"
msgstr "Dane osobowe"
-#: contrib/auth/admin.py:22
+#: contrib/auth/admin.py:23
msgid "Permissions"
msgstr "Uprawnienia"
-#: contrib/auth/admin.py:23
+#: contrib/auth/admin.py:24
msgid "Important dates"
msgstr "Ważne daty"
-#: contrib/auth/admin.py:24
+#: contrib/auth/admin.py:25
msgid "Groups"
msgstr "Grupy"
-#: contrib/auth/admin.py:80
+#: contrib/auth/admin.py:82
msgid "Add user"
msgstr "Dodaj użytkownika"
-#: contrib/auth/admin.py:106
+#: contrib/auth/admin.py:108
msgid "Password changed successfully."
msgstr "Hasło zostało zmienione pomyślnie."
-#: contrib/auth/admin.py:112
+#: contrib/auth/admin.py:114
#, python-format
msgid "Change password: %s"
msgstr "Zmień hasło: %s"
#: contrib/auth/forms.py:15 contrib/auth/forms.py:48
-#: contrib/auth/models.py:128
+#: contrib/auth/models.py:129
msgid ""
"Required. 30 characters or fewer. Alphanumeric characters only (letters, "
"digits and underscores)."
@@ -1329,31 +1427,31 @@ msgstr "uprawnienia"
msgid "group"
msgstr "grupa"
-#: contrib/auth/models.py:91 contrib/auth/models.py:138
+#: contrib/auth/models.py:91 contrib/auth/models.py:139
msgid "groups"
msgstr "grupy"
-#: contrib/auth/models.py:128
+#: contrib/auth/models.py:129
msgid "username"
msgstr "użytkownik"
-#: contrib/auth/models.py:129
+#: contrib/auth/models.py:130
msgid "first name"
msgstr "imię"
-#: contrib/auth/models.py:130
+#: contrib/auth/models.py:131
msgid "last name"
msgstr "nazwisko"
-#: contrib/auth/models.py:131
+#: contrib/auth/models.py:132
msgid "e-mail address"
msgstr "adres e-mail"
-#: contrib/auth/models.py:132
+#: contrib/auth/models.py:133
msgid "password"
msgstr "hasło"
-#: contrib/auth/models.py:132
+#: contrib/auth/models.py:133
msgid ""
"Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change "
"password form</a>."
@@ -1361,19 +1459,19 @@ msgstr ""
"Użyj '[algo]$[salt]$[hexdigest]' lub <a href=\"password/\">formularza zmiany "
"hasła</a>."
-#: contrib/auth/models.py:133
+#: contrib/auth/models.py:134
msgid "staff status"
msgstr "w zespole"
-#: contrib/auth/models.py:133
+#: contrib/auth/models.py:134
msgid "Designates whether the user can log into this admin site."
msgstr "Oznacza czy użytkownik może zalogować się do panelu admina."
-#: contrib/auth/models.py:134
+#: contrib/auth/models.py:135
msgid "active"
msgstr "aktywny"
-#: contrib/auth/models.py:134
+#: contrib/auth/models.py:135
msgid ""
"Designates whether this user should be treated as active. Unselect this "
"instead of deleting accounts."
@@ -1381,11 +1479,11 @@ msgstr ""
"Oznacza czy użytkownika należy uważać za aktywnego. Odznacz to, zamiast "
"usuwać konta."
-#: contrib/auth/models.py:135
+#: contrib/auth/models.py:136
msgid "superuser status"
msgstr "status administratora"
-#: contrib/auth/models.py:135
+#: contrib/auth/models.py:136
msgid ""
"Designates that this user has all permissions without explicitly assigning "
"them."
@@ -1393,15 +1491,15 @@ msgstr ""
"Oznacza, że ten użytkownik ma wszystkie uprawnienia bez jawnego "
"przypisywania ich."
-#: contrib/auth/models.py:136
+#: contrib/auth/models.py:137
msgid "last login"
msgstr "ostatnio zalogowany"
-#: contrib/auth/models.py:137
+#: contrib/auth/models.py:138
msgid "date joined"
msgstr "data przyłączenia"
-#: contrib/auth/models.py:139
+#: contrib/auth/models.py:140
msgid ""
"In addition to the permissions manually assigned, this user will also get "
"all permissions granted to each group he/she is in."
@@ -1409,24 +1507,28 @@ msgstr ""
"Oprócz uprawnień przypisanych bezpośrednio użytkownikowi otrzyma on "
"uprawnienia grup, do których należy."
-#: contrib/auth/models.py:140
+#: contrib/auth/models.py:141
msgid "user permissions"
msgstr "uprawnienia użytkownika"
-#: contrib/auth/models.py:144 contrib/comments/models.py:50
+#: contrib/auth/models.py:145 contrib/comments/models.py:50
#: contrib/comments/models.py:168
msgid "user"
msgstr "użytkownik"
-#: contrib/auth/models.py:145
+#: contrib/auth/models.py:146
msgid "users"
msgstr "użytkownicy"
-#: contrib/auth/models.py:301
+#: contrib/auth/models.py:334
msgid "message"
msgstr "wiadomość"
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: contrib/auth/views.py:58
+=======
+#: contrib/auth/views.py:60
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
msgid "Logged out"
msgstr "Wylogowany"
@@ -1774,7 +1876,7 @@ msgstr "strona statyczna"
msgid "flat pages"
msgstr "strony statyczne"
-#: contrib/formtools/wizard.py:130
+#: contrib/formtools/wizard.py:132
msgid ""
"We apologize, but your form has expired. Please continue filling out the "
"form from this page."
@@ -3811,6 +3913,10 @@ msgstr "Prowincja Północno-Zachodnia"
msgid "Western Cape"
msgstr "Prowincja Przylądkowa Zachodnia"
+#: contrib/messages/tests/base.py:97
+msgid "lazy message"
+msgstr "testowa wiadomość z opóźnioną ewaluacją"
+
#: contrib/redirects/models.py:7
msgid "redirect from"
msgstr "przekieruj z"
@@ -3919,14 +4025,22 @@ msgstr ""
msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format."
msgstr "Proszę wpisać poprawną godzinę w formacie HH:MM[:ss[.uuuuuu]]."
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: db/models/fields/related.py:816
+=======
+#: db/models/fields/related.py:869
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
msgid ""
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
msgstr ""
"Przytrzymaj wciśnięty klawisz \"Ctrl\" lub \"Command\" na Mac'u aby "
"zaznaczyć więcej niż jeden wybór."
+<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
#: db/models/fields/related.py:894
+=======
+#: db/models/fields/related.py:930
+>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
#, python-format
msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid."
msgid_plural ""
@@ -4427,17 +4541,17 @@ msgstr "Y-m"
msgid "MONTH_DAY_FORMAT"
msgstr "m-d"
-#: views/generic/create_update.py:114
+#: views/generic/create_update.py:115
#, python-format
msgid "The %(verbose_name)s was created successfully."
msgstr "%(verbose_name)s zostało pomyślnie utworzone."
-#: views/generic/create_update.py:156
+#: views/generic/create_update.py:158
#, python-format
msgid "The %(verbose_name)s was updated successfully."
msgstr "%(verbose_name)s zostało pomyślnie zmienione."
-#: views/generic/create_update.py:198
+#: views/generic/create_update.py:201
#, python-format
msgid "The %(verbose_name)s was deleted."
msgstr "%(verbose_name)s zostało usunięte."
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index aab5ddbb6c..7193beeee8 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -1059,7 +1059,7 @@ class ModelAdmin(BaseModelAdmin):
content_type__id__exact = ContentType.objects.get_for_model(model).id
).select_related().order_by('action_time')
# If no history was found, see whether this object even exists.
- obj = get_object_or_404(model, pk=object_id)
+ obj = get_object_or_404(model, pk=unquote(object_id))
context = {
'title': _('Change history: %s') % force_unicode(obj),
'action_list': action_list,
diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py
index b89aee1682..eda3e386d3 100644
--- a/django/contrib/auth/__init__.py
+++ b/django/contrib/auth/__init__.py
@@ -1,4 +1,5 @@
import datetime
+from warnings import warn
from django.core.exceptions import ImproperlyConfigured
from django.utils.importlib import import_module
@@ -19,6 +20,12 @@ def load_backend(path):
cls = getattr(mod, attr)
except AttributeError:
raise ImproperlyConfigured, 'Module "%s" does not define a "%s" authentication backend' % (module, attr)
+ try:
+ getattr(cls, 'supports_object_permissions')
+ except AttributeError:
+ warn("Authentication backends without a `supports_object_permissions` attribute are deprecated. Please define it in %s." % cls,
+ PendingDeprecationWarning)
+ cls.supports_object_permissions = False
return cls()
def get_backends():
diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py
index 05f98358b7..80a6bef136 100644
--- a/django/contrib/auth/backends.py
+++ b/django/contrib/auth/backends.py
@@ -11,6 +11,8 @@ class ModelBackend(object):
"""
Authenticates against django.contrib.auth.models.User.
"""
+ supports_object_permissions = False
+
# TODO: Model, login attribute name and password attribute name should be
# configurable.
def authenticate(self, username=None, password=None):
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index 55fbb39bec..053761cb56 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -121,7 +121,8 @@ class UserManager(models.Manager):
return ''.join([choice(allowed_chars) for i in range(length)])
class User(models.Model):
- """Users within the Django authentication system are represented by this model.
+ """
+ Users within the Django authentication system are represented by this model.
Username and password are required. Other fields are optional.
"""
@@ -151,11 +152,16 @@ class User(models.Model):
return "/users/%s/" % urllib.quote(smart_str(self.username))
def is_anonymous(self):
- "Always returns False. This is a way of comparing User objects to anonymous users."
+ """
+ Always returns False. This is a way of comparing User objects to
+ anonymous users.
+ """
return False
def is_authenticated(self):
- """Always return True. This is a way to tell if the user has been authenticated in templates.
+ """
+ Always return True. This is a way to tell if the user has been
+ authenticated in templates.
"""
return True
@@ -194,30 +200,41 @@ class User(models.Model):
def has_usable_password(self):
return self.password != UNUSABLE_PASSWORD
- def get_group_permissions(self):
+ def get_group_permissions(self, obj=None):
"""
Returns a list of permission strings that this user has through
his/her groups. This method queries all available auth backends.
+ If an object is passed in, only permissions matching this object
+ are returned.
"""
permissions = set()
for backend in auth.get_backends():
if hasattr(backend, "get_group_permissions"):
- permissions.update(backend.get_group_permissions(self))
+ if obj is not None and backend.supports_object_permissions:
+ group_permissions = backend.get_group_permissions(self, obj)
+ else:
+ group_permissions = backend.get_group_permissions(self)
+ permissions.update(group_permissions)
return permissions
- def get_all_permissions(self):
+ def get_all_permissions(self, obj=None):
permissions = set()
for backend in auth.get_backends():
if hasattr(backend, "get_all_permissions"):
- permissions.update(backend.get_all_permissions(self))
+ if obj is not None and backend.supports_object_permissions:
+ all_permissions = backend.get_all_permissions(self, obj)
+ else:
+ all_permissions = backend.get_all_permissions(self)
+ permissions.update(all_permissions)
return permissions
- def has_perm(self, perm):
+ def has_perm(self, perm, obj=None):
"""
Returns True if the user has the specified permission. This method
queries all available auth backends, but returns immediately if any
backend returns True. Thus, a user who has permission from a single
- auth backend is assumed to have permission in general.
+ auth backend is assumed to have permission in general. If an object
+ is provided, permissions for this specific object are checked.
"""
# Inactive users have no permissions.
if not self.is_active:
@@ -230,14 +247,22 @@ class User(models.Model):
# Otherwise we need to check the backends.
for backend in auth.get_backends():
if hasattr(backend, "has_perm"):
- if backend.has_perm(self, perm):
- return True
+ if obj is not None and backend.supports_object_permissions:
+ if backend.has_perm(self, perm, obj):
+ return True
+ else:
+ if backend.has_perm(self, perm):
+ return True
return False
- def has_perms(self, perm_list):
- """Returns True if the user has each of the specified permissions."""
+ def has_perms(self, perm_list, obj=None):
+ """
+ Returns True if the user has each of the specified permissions.
+ If object is passed, it checks if the user has all required perms
+ for this object.
+ """
for perm in perm_list:
- if not self.has_perm(perm):
+ if not self.has_perm(perm, obj):
return False
return True
@@ -358,10 +383,10 @@ class AnonymousUser(object):
return self._user_permissions
user_permissions = property(_get_user_permissions)
- def has_perm(self, perm):
+ def has_perm(self, perm, obj=None):
return False
- def has_perms(self, perm_list):
+ def has_perms(self, perm_list, obj=None):
return False
def has_module_perms(self, module):
diff --git a/django/contrib/auth/tests/__init__.py b/django/contrib/auth/tests/__init__.py
index 14428d0fc8..9a078cf643 100644
--- a/django/contrib/auth/tests/__init__.py
+++ b/django/contrib/auth/tests/__init__.py
@@ -4,6 +4,7 @@ from django.contrib.auth.tests.views \
from django.contrib.auth.tests.forms import FORM_TESTS
from django.contrib.auth.tests.remote_user \
import RemoteUserTest, RemoteUserNoCreateTest, RemoteUserCustomTest
+from django.contrib.auth.tests.auth_backends import BackendTest, RowlevelBackendTest
from django.contrib.auth.tests.tokens import TOKEN_GENERATOR_TESTS
# The password for the fixture data users is 'password'
diff --git a/django/contrib/auth/tests/auth_backends.py b/django/contrib/auth/tests/auth_backends.py
new file mode 100644
index 0000000000..bf5611aef0
--- /dev/null
+++ b/django/contrib/auth/tests/auth_backends.py
@@ -0,0 +1,149 @@
+from django.conf import settings
+from django.contrib.auth.models import User, Group, Permission, AnonymousUser
+from django.contrib.contenttypes.models import ContentType
+from django.test import TestCase
+
+
+class BackendTest(TestCase):
+
+ backend = 'django.contrib.auth.backends.ModelBackend'
+
+ def setUp(self):
+ self.curr_auth = settings.AUTHENTICATION_BACKENDS
+ settings.AUTHENTICATION_BACKENDS = (self.backend,)
+ User.objects.create_user('test', 'test@example.com', 'test')
+
+ def tearDown(self):
+ settings.AUTHENTICATION_BACKENDS = self.curr_auth
+
+ def test_has_perm(self):
+ user = User.objects.get(username='test')
+ self.assertEqual(user.has_perm('auth.test'), False)
+ user.is_staff = True
+ user.save()
+ self.assertEqual(user.has_perm('auth.test'), False)
+ user.is_superuser = True
+ user.save()
+ self.assertEqual(user.has_perm('auth.test'), True)
+ user.is_staff = False
+ user.is_superuser = False
+ user.save()
+ self.assertEqual(user.has_perm('auth.test'), False)
+
+ def test_custom_perms(self):
+ user = User.objects.get(username='test')
+ content_type=ContentType.objects.get_for_model(Group)
+ perm = Permission.objects.create(name='test', content_type=content_type, codename='test')
+ user.user_permissions.add(perm)
+ user.save()
+
+ # reloading user to purge the _perm_cache
+ user = User.objects.get(username='test')
+ self.assertEqual(user.get_all_permissions() == set([u'auth.test']), True)
+ self.assertEqual(user.get_group_permissions(), set([]))
+ self.assertEqual(user.has_module_perms('Group'), False)
+ self.assertEqual(user.has_module_perms('auth'), True)
+ perm = Permission.objects.create(name='test2', content_type=content_type, codename='test2')
+ user.user_permissions.add(perm)
+ user.save()
+ perm = Permission.objects.create(name='test3', content_type=content_type, codename='test3')
+ user.user_permissions.add(perm)
+ user.save()
+ user = User.objects.get(username='test')
+ self.assertEqual(user.get_all_permissions(), set([u'auth.test2', u'auth.test', u'auth.test3']))
+ self.assertEqual(user.has_perm('test'), False)
+ self.assertEqual(user.has_perm('auth.test'), True)
+ self.assertEqual(user.has_perms(['auth.test2', 'auth.test3']), True)
+ perm = Permission.objects.create(name='test_group', content_type=content_type, codename='test_group')
+ group = Group.objects.create(name='test_group')
+ group.permissions.add(perm)
+ group.save()
+ user.groups.add(group)
+ user = User.objects.get(username='test')
+ exp = set([u'auth.test2', u'auth.test', u'auth.test3', u'auth.test_group'])
+ self.assertEqual(user.get_all_permissions(), exp)
+ self.assertEqual(user.get_group_permissions(), set([u'auth.test_group']))
+ self.assertEqual(user.has_perms(['auth.test3', 'auth.test_group']), True)
+
+ user = AnonymousUser()
+ self.assertEqual(user.has_perm('test'), False)
+ self.assertEqual(user.has_perms(['auth.test2', 'auth.test3']), False)
+
+
+class TestObj(object):
+ pass
+
+
+class SimpleRowlevelBackend(object):
+ supports_object_permissions = True
+
+ def has_perm(self, user, perm, obj=None):
+ if not obj:
+ return # We only support row level perms
+
+ if isinstance(obj, TestObj):
+ if user.username == 'test2':
+ return True
+ elif isinstance(user, AnonymousUser) and perm == 'anon':
+ return True
+ return False
+
+ def get_all_permissions(self, user, obj=None):
+ if not obj:
+ return [] # We only support row level perms
+
+ if not isinstance(obj, TestObj):
+ return ['none']
+
+ if user.username == 'test2':
+ return ['simple', 'advanced']
+ else:
+ return ['simple']
+
+ def get_group_permissions(self, user, obj=None):
+ if not obj:
+ return # We only support row level perms
+
+ if not isinstance(obj, TestObj):
+ return ['none']
+
+ if 'test_group' in [group.name for group in user.groups.all()]:
+ return ['group_perm']
+ else:
+ return ['none']
+
+
+class RowlevelBackendTest(TestCase):
+
+ backend = 'django.contrib.auth.tests.auth_backends.SimpleRowlevelBackend'
+
+ def setUp(self):
+ self.curr_auth = settings.AUTHENTICATION_BACKENDS
+ settings.AUTHENTICATION_BACKENDS = self.curr_auth + (self.backend,)
+ self.user1 = User.objects.create_user('test', 'test@example.com', 'test')
+ self.user2 = User.objects.create_user('test2', 'test2@example.com', 'test')
+ self.user3 = AnonymousUser()
+ self.user4 = User.objects.create_user('test4', 'test4@example.com', 'test')
+
+ def tearDown(self):
+ settings.AUTHENTICATION_BACKENDS = self.curr_auth
+
+ def test_has_perm(self):
+ self.assertEqual(self.user1.has_perm('perm', TestObj()), False)
+ self.assertEqual(self.user2.has_perm('perm', TestObj()), True)
+ self.assertEqual(self.user2.has_perm('perm'), False)
+ self.assertEqual(self.user2.has_perms(['simple', 'advanced'], TestObj()), True)
+ self.assertEqual(self.user3.has_perm('perm', TestObj()), False)
+ self.assertEqual(self.user3.has_perm('anon', TestObj()), False)
+ self.assertEqual(self.user3.has_perms(['simple', 'advanced'], TestObj()), False)
+
+ def test_get_all_permissions(self):
+ self.assertEqual(self.user1.get_all_permissions(TestObj()), set(['simple']))
+ self.assertEqual(self.user2.get_all_permissions(TestObj()), set(['simple', 'advanced']))
+ self.assertEqual(self.user2.get_all_permissions(), set([]))
+
+ def test_get_group_permissions(self):
+ content_type=ContentType.objects.get_for_model(Group)
+ group = Group.objects.create(name='test_group')
+ self.user4.groups.add(group)
+ self.assertEqual(self.user4.get_group_permissions(TestObj()), set(['group_perm']))
diff --git a/django/contrib/gis/tests/relatedapp/tests.py b/django/contrib/gis/tests/relatedapp/tests.py
index 2bdf29304d..4166784a07 100644
--- a/django/contrib/gis/tests/relatedapp/tests.py
+++ b/django/contrib/gis/tests/relatedapp/tests.py
@@ -279,11 +279,19 @@ class RelatedGeoModelTest(unittest.TestCase):
def test14_collect(self):
"Testing the `collect` GeoQuerySet method and `Collect` aggregate."
# Reference query:
+<<<<<<< HEAD:django/contrib/gis/tests/relatedapp/tests.py
# SELECT AsText(ST_Collect("relatedapp_location"."point")) FROM "relatedapp_city" LEFT OUTER JOIN
# "relatedapp_location" ON ("relatedapp_city"."location_id" = "relatedapp_location"."id")
# WHERE "relatedapp_city"."state" = 'TX';
ref_geom = fromstr('MULTIPOINT(-97.516111 33.058333,-96.801611 32.782057,-95.363151 29.763374,-96.801611 32.782057)')
+=======
+ # SELECT AsText(ST_Collect("relatedapp_location"."point")) FROM "relatedapp_city" LEFT OUTER JOIN
+ # "relatedapp_location" ON ("relatedapp_city"."location_id" = "relatedapp_location"."id")
+ # WHERE "relatedapp_city"."state" = 'TX';
+ ref_geom = fromstr('MULTIPOINT(-97.516111 33.058333,-96.801611 32.782057,-95.363151 29.763374,-96.801611 32.782057)')
+
+>>>>>>> master:django/contrib/gis/tests/relatedapp/tests.py
c1 = City.objects.filter(state='TX').collect(field_name='location__point')
c2 = City.objects.filter(state='TX').aggregate(Collect('location__point'))['location__point__collect']
@@ -292,7 +300,10 @@ class RelatedGeoModelTest(unittest.TestCase):
# consolidate -- that's why 4 points in MultiPoint.
self.assertEqual(4, len(coll))
self.assertEqual(ref_geom, coll)
+<<<<<<< HEAD:django/contrib/gis/tests/relatedapp/tests.py
+=======
+>>>>>>> master:django/contrib/gis/tests/relatedapp/tests.py
# TODO: Related tests for KML, GML, and distance lookups.
diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py
index 215e58605e..6ca6d9c699 100644
--- a/django/core/management/commands/dumpdata.py
+++ b/django/core/management/commands/dumpdata.py
@@ -82,9 +82,14 @@ class Command(BaseCommand):
model_list = get_models(app)
for model in model_list:
+<<<<<<< HEAD:django/core/management/commands/dumpdata.py
# Don't serialize proxy models, or models that haven't been synchronized
if not model._meta.proxy and model._meta.db_table in tables:
objects.extend(model._default_manager.using(using).all())
+=======
+ if not model._meta.proxy:
+ objects.extend(model._default_manager.all())
+>>>>>>> master:django/core/management/commands/dumpdata.py
try:
return serializers.serialize(format, objects, indent=indent)
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index cc85685e5b..dd085008a2 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -357,7 +357,11 @@ class DatabaseWrapper(BaseDatabaseWrapper):
cursor = None
if not self._valid_connection():
conn_string = convert_unicode(self._connect_string())
+<<<<<<< HEAD:django/db/backends/oracle/base.py
self.connection = Database.connect(conn_string, **self.settings_dict['OPTIONS'])
+=======
+ self.connection = Database.connect(conn_string, **self.settings_dict['DATABASE_OPTIONS'])
+>>>>>>> master:django/db/backends/oracle/base.py
cursor = FormatStylePlaceholderCursor(self.connection)
# Set oracle date to ansi date format. This only needs to execute
# once when we create a new connection. We also set the Territory
diff --git a/django/db/models/base.py b/django/db/models/base.py
index eb484ea3bc..8a6ffd12b3 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -230,6 +230,7 @@ class ModelBase(type):
signals.class_prepared.send(sender=cls)
+<<<<<<< HEAD:django/db/models/base.py
class ModelState(object):
"""
A class for storing instance state
@@ -237,6 +238,8 @@ class ModelState(object):
def __init__(self, db=None):
self.db = db
+=======
+>>>>>>> master:django/db/models/base.py
class Model(object):
__metaclass__ = ModelBase
_deferred = False
@@ -488,7 +491,11 @@ class Model(object):
if pk_set:
# Determine whether a record with the primary key already exists.
if (force_update or (not force_insert and
+<<<<<<< HEAD:django/db/models/base.py
manager.using(using).filter(pk=pk_val).exists())):
+=======
+ manager.filter(pk=pk_val).exists())):
+>>>>>>> master:django/db/models/base.py
# It does already exist, so do an UPDATE.
if force_update or non_pks:
values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
@@ -527,7 +534,10 @@ class Model(object):
# Store the database on which the object was saved
self._state.db = using
+<<<<<<< HEAD:django/db/models/base.py
# Signal that the save is complete
+=======
+>>>>>>> master:django/db/models/base.py
if origin and not meta.auto_created:
signals.post_save.send(sender=origin, instance=self,
created=(not record_exists), raw=raw)
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index f8ae5b1f4e..e3520965e1 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -474,7 +474,11 @@ def create_many_related_manager(superclass, rel=False):
if not rel.through._meta.auto_created:
opts = through._meta
raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)
+<<<<<<< HEAD:django/db/models/fields/related.py
new_obj = super(ManyRelatedManager, self).using(self.instance._state.db).create(**kwargs)
+=======
+ new_obj = super(ManyRelatedManager, self).create(**kwargs)
+>>>>>>> master:django/db/models/fields/related.py
self.add(new_obj)
return new_obj
create.alters_data = True
@@ -501,15 +505,22 @@ def create_many_related_manager(superclass, rel=False):
new_ids = set()
for obj in objs:
if isinstance(obj, self.model):
+<<<<<<< HEAD:django/db/models/fields/related.py
if obj._state.db != self.instance._state.db:
raise ValueError('Cannot add "%r": instance is on database "%s", value is is on database "%s"' %
(obj, self.instance._state.db, obj._state.db))
+=======
+>>>>>>> master:django/db/models/fields/related.py
new_ids.add(obj.pk)
elif isinstance(obj, Model):
raise TypeError, "'%s' instance expected" % self.model._meta.object_name
else:
new_ids.add(obj)
+<<<<<<< HEAD:django/db/models/fields/related.py
vals = self.through._default_manager.using(self.instance._state.db).values_list(target_field_name, flat=True)
+=======
+ vals = self.through._default_manager.values_list(target_field_name, flat=True)
+>>>>>>> master:django/db/models/fields/related.py
vals = vals.filter(**{
source_field_name: self._pk_val,
'%s__in' % target_field_name: new_ids,
@@ -518,7 +529,11 @@ def create_many_related_manager(superclass, rel=False):
# Add the ones that aren't there already
for obj_id in (new_ids - vals):
+<<<<<<< HEAD:django/db/models/fields/related.py
self.through._default_manager.using(self.instance._state.db).create(**{
+=======
+ self.through._default_manager.create(**{
+>>>>>>> master:django/db/models/fields/related.py
'%s_id' % source_field_name: self._pk_val,
'%s_id' % target_field_name: obj_id,
})
@@ -538,14 +553,22 @@ def create_many_related_manager(superclass, rel=False):
else:
old_ids.add(obj)
# Remove the specified objects from the join table
+<<<<<<< HEAD:django/db/models/fields/related.py
self.through._default_manager.using(self.instance._state.db).filter(**{
+=======
+ self.through._default_manager.filter(**{
+>>>>>>> master:django/db/models/fields/related.py
source_field_name: self._pk_val,
'%s__in' % target_field_name: old_ids
}).delete()
def _clear_items(self, source_field_name):
# source_col_name: the PK colname in join_table for the source object
+<<<<<<< HEAD:django/db/models/fields/related.py
self.through._default_manager.using(self.instance._state.db).filter(**{
+=======
+ self.through._default_manager.filter(**{
+>>>>>>> master:django/db/models/fields/related.py
source_field_name: self._pk_val
}).delete()
diff --git a/django/db/models/manager.py b/django/db/models/manager.py
index d752519f5c..2bdc615615 100644
--- a/django/db/models/manager.py
+++ b/django/db/models/manager.py
@@ -172,9 +172,12 @@ class Manager(object):
def only(self, *args, **kwargs):
return self.get_query_set().only(*args, **kwargs)
+<<<<<<< HEAD:django/db/models/manager.py
def using(self, *args, **kwargs):
return self.get_query_set().using(*args, **kwargs)
+=======
+>>>>>>> master:django/db/models/manager.py
def exists(self, *args, **kwargs):
return self.get_query_set().exists(*args, **kwargs)
diff --git a/django/db/models/query.py b/django/db/models/query.py
index eba6f2f142..05d2514f69 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -3,8 +3,12 @@ The main QuerySet implementation. This provides the public API for the ORM.
"""
from copy import deepcopy
+<<<<<<< HEAD:django/db/models/query.py
from django.db import connections, transaction, IntegrityError, DEFAULT_DB_ALIAS
+=======
+from django.db import connection, transaction, IntegrityError
+>>>>>>> master:django/db/models/query.py
from django.db.models.aggregates import Aggregate
from django.db.models.fields import DateField
from django.db.models.query_utils import Q, select_related_descend, CollectedObjects, CyclicDependency, deferred_class_factory
@@ -476,7 +480,11 @@ class QuerySet(object):
def exists(self):
if self._result_cache is None:
+<<<<<<< HEAD:django/db/models/query.py
return self.query.has_results(using=self.db)
+=======
+ return self.query.has_results()
+>>>>>>> master:django/db/models/query.py
return bool(self._result_cache)
##################################################
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 0a53fc3bae..3176e7e9df 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -22,8 +22,12 @@ from django.db.models.sql.expressions import SQLEvaluator
from django.db.models.sql.where import WhereNode, Constraint, EverythingNode, AND, OR
from django.core.exceptions import FieldError
+<<<<<<< HEAD:django/db/models/sql/query.py
__all__ = ['Query']
+=======
+__all__ = ['Query', 'BaseQuery']
+>>>>>>> master:django/db/models/sql/query.py
class Query(object):
"""
@@ -337,7 +341,11 @@ class Query(object):
return number
+<<<<<<< HEAD:django/db/models/sql/query.py
def has_results(self, using):
+=======
+ def has_results(self):
+>>>>>>> master:django/db/models/sql/query.py
q = self.clone()
q.add_extra({'a': 1}, None, None, None, None, None)
q.add_fields(())
@@ -345,8 +353,104 @@ class Query(object):
q.set_aggregate_mask(())
q.clear_ordering()
q.set_limits(high=1)
+<<<<<<< HEAD:django/db/models/sql/query.py
compiler = q.get_compiler(using=using)
return bool(compiler.execute_sql(SINGLE))
+=======
+ return bool(q.execute_sql(SINGLE))
+
+ def as_sql(self, with_limits=True, with_col_aliases=False):
+ """
+ Creates the SQL for this query. Returns the SQL string and list of
+ parameters.
+
+ If 'with_limits' is False, any limit/offset information is not included
+ in the query.
+ """
+ self.pre_sql_setup()
+ out_cols = self.get_columns(with_col_aliases)
+ ordering, ordering_group_by = self.get_ordering()
+
+ # This must come after 'select' and 'ordering' -- see docstring of
+ # get_from_clause() for details.
+ from_, f_params = self.get_from_clause()
+
+ qn = self.quote_name_unless_alias
+ where, w_params = self.where.as_sql(qn=qn)
+ having, h_params = self.having.as_sql(qn=qn)
+ params = []
+ for val in self.extra_select.itervalues():
+ params.extend(val[1])
+
+ result = ['SELECT']
+ if self.distinct:
+ result.append('DISTINCT')
+ result.append(', '.join(out_cols + self.ordering_aliases))
+
+ result.append('FROM')
+ result.extend(from_)
+ params.extend(f_params)
+
+ if where:
+ result.append('WHERE %s' % where)
+ params.extend(w_params)
+ if self.extra_where:
+ if not where:
+ result.append('WHERE')
+ else:
+ result.append('AND')
+ result.append(' AND '.join(self.extra_where))
+
+ grouping, gb_params = self.get_grouping()
+ if grouping:
+ if ordering:
+ # If the backend can't group by PK (i.e., any database
+ # other than MySQL), then any fields mentioned in the
+ # ordering clause needs to be in the group by clause.
+ if not self.connection.features.allows_group_by_pk:
+ for col, col_params in ordering_group_by:
+ if col not in grouping:
+ grouping.append(str(col))
+ gb_params.extend(col_params)
+ else:
+ ordering = self.connection.ops.force_no_ordering()
+ result.append('GROUP BY %s' % ', '.join(grouping))
+ params.extend(gb_params)
+
+ if having:
+ result.append('HAVING %s' % having)
+ params.extend(h_params)
+
+ if ordering:
+ result.append('ORDER BY %s' % ', '.join(ordering))
+
+ if with_limits:
+ if self.high_mark is not None:
+ result.append('LIMIT %d' % (self.high_mark - self.low_mark))
+ if self.low_mark:
+ if self.high_mark is None:
+ val = self.connection.ops.no_limit_value()
+ if val:
+ result.append('LIMIT %d' % val)
+ result.append('OFFSET %d' % self.low_mark)
+
+ params.extend(self.extra_params)
+ return ' '.join(result), tuple(params)
+
+ def as_nested_sql(self):
+ """
+ Perform the same functionality as the as_sql() method, returning an
+ SQL string and parameters. However, the alias prefixes are bumped
+ beforehand (in a copy -- the current query isn't changed) and any
+ ordering is removed.
+
+ Used when nesting this query inside another.
+ """
+ obj = self.clone()
+ obj.clear_ordering(True)
+ obj.bump_prefix()
+ return obj.as_sql()
+>>>>>>> master:django/db/models/sql/query.py
def combine(self, rhs, connector):
"""
diff --git a/django/forms/models.py b/django/forms/models.py
index 6985448659..991c1fbdc6 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -471,8 +471,12 @@ class BaseModelFormSet(BaseFormSet):
pk_key = "%s-%s" % (self.add_prefix(i), self.model._meta.pk.name)
pk = self.data[pk_key]
pk_field = self.model._meta.pk
+<<<<<<< HEAD:django/forms/models.py
pk = pk_field.get_db_prep_lookup('exact', pk,
connection=connections[self.get_queryset().db])
+=======
+ pk = pk_field.get_db_prep_lookup('exact', pk)
+>>>>>>> master:django/forms/models.py
if isinstance(pk, list):
pk = pk[0]
kwargs['instance'] = self._existing_object(pk)
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 6d57cdeef8..77b9b9795c 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -11,6 +11,7 @@ except NameError:
from django.template import Node, NodeList, Template, Context, Variable
from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
from django.template import get_library, Library, InvalidTemplateLibrary
+from django.template.smartif import IfParser, Literal
from django.conf import settings
from django.utils.encoding import smart_str, smart_unicode
from django.utils.itercompat import groupby
@@ -227,10 +228,9 @@ class IfEqualNode(Node):
return self.nodelist_false.render(context)
class IfNode(Node):
- def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
- self.bool_exprs = bool_exprs
+ def __init__(self, var, nodelist_true, nodelist_false=None):
self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
- self.link_type = link_type
+ self.var = var
def __repr__(self):
return "<If node>"
@@ -250,28 +250,10 @@ class IfNode(Node):
return nodes
def render(self, context):
- if self.link_type == IfNode.LinkTypes.or_:
- for ifnot, bool_expr in self.bool_exprs:
- try:
- value = bool_expr.resolve(context, True)
- except VariableDoesNotExist:
- value = None
- if (value and not ifnot) or (ifnot and not value):
- return self.nodelist_true.render(context)
- return self.nodelist_false.render(context)
- else:
- for ifnot, bool_expr in self.bool_exprs:
- try:
- value = bool_expr.resolve(context, True)
- except VariableDoesNotExist:
- value = None
- if not ((value and not ifnot) or (ifnot and not value)):
- return self.nodelist_false.render(context)
+ if self.var.eval(context):
return self.nodelist_true.render(context)
-
- class LinkTypes:
- and_ = 0,
- or_ = 1
+ else:
+ return self.nodelist_false.render(context)
class RegroupNode(Node):
def __init__(self, target, expression, var_name):
@@ -761,6 +743,27 @@ def ifnotequal(parser, token):
return do_ifequal(parser, token, True)
ifnotequal = register.tag(ifnotequal)
+class TemplateLiteral(Literal):
+ def __init__(self, value, text):
+ self.value = value
+ self.text = text # for better error messages
+
+ def display(self):
+ return self.text
+
+ def eval(self, context):
+ return self.value.resolve(context, ignore_failures=True)
+
+class TemplateIfParser(IfParser):
+ error_class = TemplateSyntaxError
+
+ def __init__(self, parser, *args, **kwargs):
+ self.template_parser = parser
+ return super(TemplateIfParser, self).__init__(*args, **kwargs)
+
+ def create_var(self, value):
+ return TemplateLiteral(self.template_parser.compile_filter(value), value)
+
#@register.tag(name="if")
def do_if(parser, token):
"""
@@ -805,47 +808,21 @@ def do_if(parser, token):
There are some athletes and absolutely no coaches.
{% endif %}
- ``if`` tags do not allow ``and`` and ``or`` clauses with the same tag,
- because the order of logic would be ambigous. For example, this is
- invalid::
+ Comparison operators are also available, and the use of filters is also
+ allowed, for example:
- {% if athlete_list and coach_list or cheerleader_list %}
+ {% if articles|length >= 5 %}...{% endif %}
- If you need to combine ``and`` and ``or`` to do advanced logic, just use
- nested if tags. For example::
+ Arguments and operators _must_ have a space between them, so
+ ``{% if 1>2 %}`` is not a valid if tag.
- {% if athlete_list %}
- {% if coach_list or cheerleader_list %}
- We have athletes, and either coaches or cheerleaders!
- {% endif %}
- {% endif %}
+ All supported operators are: ``or``, ``and``, ``in``, ``==`` (or ``=``),
+ ``!=``, ``>``, ``>=``, ``<`` and ``<=``.
+
+ Operator precedence follows Python.
"""
- bits = token.contents.split()
- del bits[0]
- if not bits:
- raise TemplateSyntaxError("'if' statement requires at least one argument")
- # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
- bitstr = ' '.join(bits)
- boolpairs = bitstr.split(' and ')
- boolvars = []
- if len(boolpairs) == 1:
- link_type = IfNode.LinkTypes.or_
- boolpairs = bitstr.split(' or ')
- else:
- link_type = IfNode.LinkTypes.and_
- if ' or ' in bitstr:
- raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
- for boolpair in boolpairs:
- if ' ' in boolpair:
- try:
- not_, boolvar = boolpair.split()
- except ValueError:
- raise TemplateSyntaxError, "'if' statement improperly formatted"
- if not_ != 'not':
- raise TemplateSyntaxError, "Expected 'not' in if statement"
- boolvars.append((True, parser.compile_filter(boolvar)))
- else:
- boolvars.append((False, parser.compile_filter(boolpair)))
+ bits = token.split_contents()[1:]
+ var = TemplateIfParser(parser, bits).parse()
nodelist_true = parser.parse(('else', 'endif'))
token = parser.next_token()
if token.contents == 'else':
@@ -853,7 +830,7 @@ def do_if(parser, token):
parser.delete_first_token()
else:
nodelist_false = NodeList()
- return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
+ return IfNode(var, nodelist_true, nodelist_false)
do_if = register.tag("if", do_if)
#@register.tag
diff --git a/django/template/smartif.py b/django/template/smartif.py
new file mode 100644
index 0000000000..e8a08e9087
--- /dev/null
+++ b/django/template/smartif.py
@@ -0,0 +1,192 @@
+"""
+Parser and utilities for the smart 'if' tag
+"""
+import operator
+
+# Using a simple top down parser, as described here:
+# http://effbot.org/zone/simple-top-down-parsing.htm.
+# 'led' = left denotation
+# 'nud' = null denotation
+# 'bp' = binding power (left = lbp, right = rbp)
+
+class TokenBase(object):
+ """
+ Base class for operators and literals, mainly for debugging and for throwing
+ syntax errors.
+ """
+ id = None # node/token type name
+ value = None # used by literals
+ first = second = None # used by tree nodes
+
+ def nud(self, parser):
+ # Null denotation - called in prefix context
+ raise parser.error_class(
+ "Not expecting '%s' in this position in if tag." % self.id
+ )
+
+ def led(self, left, parser):
+ # Left denotation - called in infix context
+ raise parser.error_class(
+ "Not expecting '%s' as infix operator in if tag." % self.id
+ )
+
+ def display(self):
+ """
+ Returns what to display in error messages for this node
+ """
+ return self.id
+
+ def __repr__(self):
+ out = [str(x) for x in [self.id, self.first, self.second] if x is not None]
+ return "(" + " ".join(out) + ")"
+
+
+def infix(bp, func):
+ """
+ Creates an infix operator, given a binding power and a function that
+ evaluates the node
+ """
+ class Operator(TokenBase):
+ lbp = bp
+
+ def led(self, left, parser):
+ self.first = left
+ self.second = parser.expression(bp)
+ return self
+
+ def eval(self, context):
+ try:
+ return func(self.first.eval(context), self.second.eval(context))
+ except Exception:
+ # Templates shouldn't throw exceptions when rendering. We are
+ # most likely to get exceptions for things like {% if foo in bar
+ # %} where 'bar' does not support 'in', so default to False
+ return False
+
+ return Operator
+
+
+def prefix(bp, func):
+ """
+ Creates a prefix operator, given a binding power and a function that
+ evaluates the node.
+ """
+ class Operator(TokenBase):
+ lbp = bp
+
+ def nud(self, parser):
+ self.first = parser.expression(bp)
+ self.second = None
+ return self
+
+ def eval(self, context):
+ try:
+ return func(self.first.eval(context))
+ except Exception:
+ return False
+
+ return Operator
+
+
+# Operator precedence follows Python.
+# NB - we can get slightly more accurate syntax error messages by not using the
+# same object for '==' and '='.
+
+OPERATORS = {
+ 'or': infix(6, lambda x, y: x or y),
+ 'and': infix(7, lambda x, y: x and y),
+ 'not': prefix(8, operator.not_),
+ 'in': infix(9, lambda x, y: x in y),
+ '=': infix(10, operator.eq),
+ '==': infix(10, operator.eq),
+ '!=': infix(10, operator.ne),
+ '>': infix(10, operator.gt),
+ '>=': infix(10, operator.ge),
+ '<': infix(10, operator.lt),
+ '<=': infix(10, operator.le),
+}
+
+# Assign 'id' to each:
+for key, op in OPERATORS.items():
+ op.id = key
+
+
+class Literal(TokenBase):
+ """
+ A basic self-resolvable object similar to a Django template variable.
+ """
+ # IfParser uses Literal in create_var, but TemplateIfParser overrides
+ # create_var so that a proper implementation that actually resolves
+ # variables, filters etc is used.
+ id = "literal"
+ lbp = 0
+
+ def __init__(self, value):
+ self.value = value
+
+ def display(self):
+ return repr(self.value)
+
+ def nud(self, parser):
+ return self
+
+ def eval(self, context):
+ return self.value
+
+ def __repr__(self):
+ return "(%s %r)" % (self.id, self.value)
+
+
+class EndToken(TokenBase):
+ lbp = 0
+
+ def nud(self, parser):
+ raise parser.error_class("Unexpected end of expression in if tag.")
+
+EndToken = EndToken()
+
+
+class IfParser(object):
+ error_class = ValueError
+
+ def __init__(self, tokens):
+ self.tokens = map(self.translate_tokens, tokens)
+ self.pos = 0
+ self.current_token = self.next()
+
+ def translate_tokens(self, token):
+ try:
+ op = OPERATORS[token]
+ except (KeyError, TypeError):
+ return self.create_var(token)
+ else:
+ return op()
+
+ def next(self):
+ if self.pos >= len(self.tokens):
+ return EndToken
+ else:
+ retval = self.tokens[self.pos]
+ self.pos += 1
+ return retval
+
+ def parse(self):
+ retval = self.expression()
+ # Check that we have exhausted all the tokens
+ if self.current_token is not EndToken:
+ raise self.error_class("Unused '%s' at end of if expression." %
+ self.current_token.display())
+ return retval
+
+ def expression(self, rbp=0):
+ t = self.current_token
+ self.current_token = self.next()
+ left = t.nud(self)
+ while rbp < self.current_token.lbp:
+ t = self.current_token
+ self.current_token = self.next()
+ left = t.led(left, self)
+ return left
+
+ def create_var(self, value):
+ return Literal(value)
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index 685bc4a396..8a9892607c 100644
--- a/docs/internals/deprecation.txt
+++ b/docs/internals/deprecation.txt
@@ -13,6 +13,13 @@ their deprecation, as per the :ref:`Django deprecation policy
hooking up admin URLs. This has been deprecated since the 1.1
release.
+<<<<<<< HEAD:docs/internals/deprecation.txt
+=======
+ * Authentication backends need to define the boolean attribute
+ ``supports_object_permissions``. The old backend style is deprecated
+ since the 1.2 release.
+
+>>>>>>> master:docs/internals/deprecation.txt
* 1.4
* ``CsrfResponseMiddleware``. This has been deprecated since the 1.2
release, in favour of the template tag method for inserting the CSRF
@@ -26,6 +33,7 @@ their deprecation, as per the :ref:`Django deprecation policy
class in favor of a generic E-mail backend API.
* The many to many SQL generation functions on the database backends
+<<<<<<< HEAD:docs/internals/deprecation.txt
will be removed.
* The ability to use the ``DATABASE_*`` family of top-level settings to
@@ -39,6 +47,9 @@ their deprecation, as per the :ref:`Django deprecation policy
``get_db_prep_lookup`` methods on Field were modified in 1.2 to support
multiple databases. In 1.4, the support functions that allow methods
with the old prototype to continue working will be removed.
+=======
+ will be removed. These have been deprecated since the 1.2 release.
+>>>>>>> master:docs/internals/deprecation.txt
* The ``Message`` model (in ``django.contrib.auth``), its related
manager in the ``User`` model (``user.message_set``), and the
@@ -48,6 +59,13 @@ their deprecation, as per the :ref:`Django deprecation policy
:ref:`messages framework <ref-contrib-messages>` should be used
instead.
+<<<<<<< HEAD:docs/internals/deprecation.txt
+=======
+ * Authentication backends need to support the ``obj`` parameter for
+ permission checking. The ``supports_object_permissions`` variable
+ is not checked any longer and can be removed.
+
+>>>>>>> master:docs/internals/deprecation.txt
* 2.0
* ``django.views.defaults.shortcut()``. This function has been moved
to ``django.contrib.contenttypes.views.shortcut()`` as part of the
diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt
index 3cff1c61cb..738fd96fea 100644
--- a/docs/ref/databases.txt
+++ b/docs/ref/databases.txt
@@ -257,7 +257,10 @@ Here's a sample configuration which uses a MySQL option file::
}
}
+<<<<<<< HEAD:docs/ref/databases.txt
+=======
+>>>>>>> master:docs/ref/databases.txt
# my.cnf
[client]
database = NAME
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
index da9bb99224..7f0d9faaef 100644
--- a/docs/ref/django-admin.txt
+++ b/docs/ref/django-admin.txt
@@ -210,6 +210,24 @@ records to dump. If you're using a :ref:`custom manager <custom-managers>` as
the default manager and it filters some of the available records, not all of the
objects will be dumped.
+<<<<<<< HEAD:docs/ref/django-admin.txt
+=======
+.. django-admin-option:: --exclude
+
+.. versionadded:: 1.0
+
+Exclude a specific application from the applications whose contents is
+output. For example, to specifically exclude the `auth` application from
+the output, you would call::
+
+ django-admin.py dumpdata --exclude=auth
+
+If you want to exclude multiple applications, use multiple ``--exclude``
+directives::
+
+ django-admin.py dumpdata --exclude=auth --exclude=contenttypes
+
+>>>>>>> master:docs/ref/django-admin.txt
.. django-admin-option:: --format <fmt>
By default, ``dumpdata`` will format its output in JSON, but you can use the
@@ -221,11 +239,14 @@ are listed in :ref:`serialization-formats`.
By default, ``dumpdata`` will output all data on a single line. This isn't
easy for humans to read, so you can use the ``--indent`` option to
pretty-print the output with a number of indentation spaces.
+<<<<<<< HEAD:docs/ref/django-admin.txt
.. versionadded:: 1.0
The :djadminopt:`--exclude` option may be provided to prevent specific
applications from being dumped.
+=======
+>>>>>>> master:docs/ref/django-admin.txt
.. versionadded:: 1.1
@@ -252,12 +273,15 @@ fixture will be re-installed.
The :djadminopt:`--noinput` option may be provided to suppress all user
prompts.
+<<<<<<< HEAD:docs/ref/django-admin.txt
.. versionadded:: 1.2
The :djadminopt:`--database` option may be used to specify the database
to flush.
+=======
+>>>>>>> master:docs/ref/django-admin.txt
inspectdb
---------
@@ -487,6 +511,7 @@ reset <appname appname ...>
---------------------------
.. django-admin:: reset
+<<<<<<< HEAD:docs/ref/django-admin.txt
Executes the equivalent of ``sqlreset`` for the given app name(s).
@@ -497,6 +522,13 @@ prompts.
The :djadminopt:`--database` option can be used to specify the alias
of the database to reset.
+=======
+
+Executes the equivalent of ``sqlreset`` for the given app name(s).
+
+The :djadminopt:`--noinput` option may be provided to suppress all user
+prompts.
+>>>>>>> master:docs/ref/django-admin.txt
runfcgi [options]
-----------------
@@ -680,11 +712,14 @@ sqlflush
Prints the SQL statements that would be executed for the :djadmin:`flush`
command.
+<<<<<<< HEAD:docs/ref/django-admin.txt
.. versionadded:: 1.2
The :djadminopt:`--database` option can be used to specify the database for
which to print the SQL.
+=======
+>>>>>>> master:docs/ref/django-admin.txt
sqlindexes <appname appname ...>
--------------------------------
@@ -784,6 +819,7 @@ with an appropriate extension (e.g. ``json`` or ``xml``). See the
documentation for ``loaddata`` for details on the specification of fixture
data files.
+<<<<<<< HEAD:docs/ref/django-admin.txt
--noinput
~~~~~~~~~
The :djadminopt:`--noinput` option may be provided to suppress all user
@@ -793,15 +829,27 @@ prompts.
The :djadminopt:`--database` option can be used to specify the database to
synchronize.
+=======
+The :djadminopt:`--noinput` option may be provided to suppress all user
+prompts.
+
+test <app or test identifier>
+-----------------------------
+
+.. django-admin:: test
+>>>>>>> master:docs/ref/django-admin.txt
test <app or test identifier>
-----------------------------
+<<<<<<< HEAD:docs/ref/django-admin.txt
.. django-admin:: test
Runs tests for all installed models. See :ref:`topics-testing` for more
information.
+=======
+>>>>>>> master:docs/ref/django-admin.txt
testserver <fixture fixture ...>
--------------------------------
@@ -935,6 +983,7 @@ Common options
The following options are not available on every commands, but they are
common to a number of commands.
+<<<<<<< HEAD:docs/ref/django-admin.txt
.. django-admin-option:: --database
.. versionadded:: 1.2
@@ -959,6 +1008,8 @@ directives::
django-admin.py dumpdata --exclude=auth --exclude=contenttypes
+=======
+>>>>>>> master:docs/ref/django-admin.txt
.. django-admin-option:: --locale
Use the ``--locale`` or ``-l`` option to specify the locale to process.
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index 11cb821b98..4443c93b61 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -145,6 +145,54 @@ The default number of seconds to cache a page when the caching middleware or
``cache_page()`` decorator is used.
.. setting:: CSRF_COOKIE_NAME
+<<<<<<< HEAD:docs/ref/settings.txt
+=======
+
+CSRF_COOKIE_NAME
+----------------
+
+.. versionadded:: 1.2
+
+Default: ``'csrftoken'``
+
+The name of the cookie to use for the CSRF authentication token. This can be whatever you
+want. See :ref:`ref-contrib-csrf`.
+
+.. setting:: CSRF_COOKIE_DOMAIN
+
+CSRF_COOKIE_DOMAIN
+------------------
+
+.. versionadded:: 1.2
+
+Default: ``None``
+
+The domain to be used when setting the CSRF cookie. This can be useful for
+allowing cross-subdomain requests to be exluded from the normal cross site
+request forgery protection. It should be set to a string such as
+``".lawrence.com"`` to allow a POST request from a form on one subdomain to be
+accepted by accepted by a view served from another subdomain.
+
+.. setting:: CSRF_FAILURE_VIEW
+
+CSRF_FAILURE_VIEW
+-----------------
+
+.. versionadded:: 1.2
+
+Default: ``'django.views.csrf.csrf_failure'``
+
+A dotted path to the view function to be used when an incoming request
+is rejected by the CSRF protection. The function should have this signature::
+
+ def csrf_failure(request, reason="")
+
+where ``reason`` is a short message (intended for developers or logging, not for
+end users) indicating the reason the request was rejected. See
+:ref:`ref-contrib-csrf`.
+
+.. setting:: DATABASE_ENGINE
+>>>>>>> master:docs/ref/settings.txt
CSRF_COOKIE_NAME
----------------
diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
index 6d6abd1098..bf2d9e899c 100644
--- a/docs/ref/templates/builtins.txt
+++ b/docs/ref/templates/builtins.txt
@@ -313,6 +313,9 @@ displayed by the ``{{ athlete_list|length }}`` variable.
As you can see, the ``if`` tag can take an optional ``{% else %}`` clause that
will be displayed if the test fails.
+Boolean operators
+^^^^^^^^^^^^^^^^^
+
``if`` tags may use ``and``, ``or`` or ``not`` to test a number of variables or
to negate a given variable::
@@ -338,24 +341,153 @@ to negate a given variable::
There are some athletes and absolutely no coaches.
{% endif %}
-``if`` tags don't allow ``and`` and ``or`` clauses within the same tag, because
-the order of logic would be ambiguous. For example, this is invalid::
+.. versionchanged:: 1.2
+
+Use of both ``and`` and ``or`` clauses within the same tag is allowed, with
+``and`` having higher precedence than ``or`` e.g.::
{% if athlete_list and coach_list or cheerleader_list %}
-If you need to combine ``and`` and ``or`` to do advanced logic, just use nested
-``if`` tags. For example::
+will be interpreted like:
- {% if athlete_list %}
- {% if coach_list or cheerleader_list %}
- We have athletes, and either coaches or cheerleaders!
- {% endif %}
+.. code-block:: python
+
+ if (athlete_list and coach_list) or cheerleader_list
+
+Use of actual brackets in the ``if`` tag is invalid syntax. If you need them to
+indicate precedence, you should use nested ``if`` tags.
+
+.. versionadded:: 1.2
+
+
+``if`` tags may also use the operators ``==``, ``!=``, ``<``, ``>``,
+``<=``, ``>=`` and ``in`` which work as follows:
+
+
+``==`` operator
+^^^^^^^^^^^^^^^
+
+Equality. Example::
+
+ {% if somevar == "x" %}
+ This appears if variable somevar equals the string "x"
+ {% endif %}
+
+``!=`` operator
+^^^^^^^^^^^^^^^
+
+Inequality. Example::
+
+ {% if somevar != "x" %}
+ This appears if variable somevar does not equal the string "x",
+ or if somevar is not found in the context
+ {% endif %}
+
+``<`` operator
+^^^^^^^^^^^^^^
+
+Less than. Example::
+
+ {% if somevar < 100 %}
+ This appears if variable somevar is less than 100.
+ {% endif %}
+
+``>`` operator
+^^^^^^^^^^^^^^
+
+Greater than. Example::
+
+ {% if somevar > 0 %}
+ This appears if variable somevar is greater than 0.
+ {% endif %}
+
+``<=`` operator
+^^^^^^^^^^^^^^^
+
+Less than or equal to. Example::
+
+ {% if somevar <= 100 %}
+ This appears if variable somevar is less than 100 or equal to 100.
+ {% endif %}
+
+``>=`` operator
+^^^^^^^^^^^^^^^
+
+Greater than or equal to. Example::
+
+ {% if somevar >= 1 %}
+ This appears if variable somevar is greater than 1 or equal to 1.
+ {% endif %}
+
+``in`` operator
+^^^^^^^^^^^^^^^
+
+Contained within. This operator is supported by many Python containers to test
+whether the given value is in the container. The following are some examples of
+how ``x in y`` will be interpreted::
+
+ {% if "bc" in "abcdef" %}
+ This appears since "bc" is a substring of "abcdef"
+ {% endif %}
+
+ {% if "hello" in greetings %}
+ If greetings is a list or set, one element of which is the string
+ "hello", this will appear.
{% endif %}
-Multiple uses of the same logical operator are fine, as long as you use the
-same operator. For example, this is valid::
+ {% if user in users %}
+ If users is a QuerySet, this will appear if user is an
+ instance that belongs to the QuerySet.
+ {% endif %}
+
+
+The comparison operators cannot be 'chained' like in Python or in mathematical
+notation. For example, instead of using::
+
+ {% if a > b > c %} (WRONG)
+
+you should use::
+
+ {% if a > b and b > c %}
+
+
+Filters
+^^^^^^^
+
+You can also use filters in the ``if`` expression. For example::
+
+ {% if messages|length >= 100 %}
+ You have lots of messages today!
+ {% endif %}
+
+Complex expressions
+^^^^^^^^^^^^^^^^^^^
+
+All of the above can be combined to form complex expressions. For such
+expressions, it can be important to know how the operators are grouped when the
+expression is evaluated - that is, the precedence rules. The precedence of the
+operators, from lowest to highest, is as follows:
+
+ * ``or``
+ * ``and``
+ * ``not``
+ * ``in``
+ * ``==``, ``!=``, ``<``, ``>``,``<=``, ``>=``
+
+(This follows Python exactly). So, for example, the following complex if tag:
+
+ {% if a == b or c == d and e %}
+
+...will be interpreted as:
+
+.. code-block:: python
+
+ (a == b) or ((c == d) and e)
+
+If you need different precedence, you will need to use nested if tags. Sometimes
+that is better for clarity anyway, for the sake of those who do not know the
+precedence rules.
- {% if athlete_list or coach_list or parent_list or teacher_list %}
.. templatetag:: ifchanged
@@ -427,6 +559,9 @@ You cannot check for equality with Python objects such as ``True`` or
``False``. If you need to test if something is true or false, use the ``if``
tag instead.
+.. versionadded:: 1.2
+ An alternative to the ``ifequal`` tag is to use the :ttag:`if` tag and the ``==`` operator.
+
.. templatetag:: ifnotequal
ifnotequal
@@ -434,6 +569,9 @@ ifnotequal
Just like ``ifequal``, except it tests that the two arguments are not equal.
+.. versionadded:: 1.2
+ An alternative to the ``ifnotequal`` tag is to use the :ttag:`if` tag and the ``!=`` operator.
+
.. templatetag:: include
include
diff --git a/docs/releases/1.2.txt b/docs/releases/1.2.txt
index 2f4b82fc33..3b3d1bcabd 100644
--- a/docs/releases/1.2.txt
+++ b/docs/releases/1.2.txt
@@ -42,6 +42,18 @@ changes that developers must be aware of:
* All of the CSRF has moved from contrib to core (with backwards compatible
imports in the old locations, which are deprecated).
+<<<<<<< HEAD:docs/releases/1.2.txt
+=======
+:ttag:`if` tag changes
+----------------------
+
+Due to new features in the :ttag:`if` template tag, it no longer accepts 'and',
+'or' and 'not' as valid **variable** names. Previously that worked in some
+cases even though these strings were normally treated as keywords. Now, the
+keyword status is always enforced, and template code like ``{% if not %}`` or
+``{% if and %}`` will throw a TemplateSyntaxError.
+
+>>>>>>> master:docs/releases/1.2.txt
``LazyObject``
--------------
@@ -67,6 +79,7 @@ changes:
__members__ = property(lambda self: self.__dir__())
+<<<<<<< HEAD:docs/releases/1.2.txt
Specifying databases
--------------------
@@ -208,6 +221,8 @@ connection, you should be able to upgrade by renaming
database specific conversions, then you will need to provide an
implementation ``get_db_prep_*`` that uses the ``connection``
argument to resolve database-specific values.
+=======
+>>>>>>> master:docs/releases/1.2.txt
.. _deprecated-features-1.2:
@@ -338,6 +353,7 @@ replaces the deprecated user message API and allows you to temporarily store
messages in one request and retrieve them for display in a subsequent request
(usually the next one).
+<<<<<<< HEAD:docs/releases/1.2.txt
Support for multiple databases
------------------------------
@@ -346,3 +362,38 @@ Django 1.2 adds the ability to use :ref:`more than one database
issued at a specific database with the `using()` method on
querysets; individual objects can be saved to a specific database
by providing a ``using`` argument when you save the instance.
+=======
+'Smart' if tag
+--------------
+
+The :ttag:`if` tag has been upgraded to be much more powerful. First, support
+for comparison operators has been added. No longer will you have to type:
+
+.. code-block:: html+django
+
+ {% ifnotequal a b %}
+ ...
+ {% endifnotequal %}
+
+...as you can now do:
+
+.. code-block:: html+django
+
+ {% if a != b %}
+ ...
+ {% endif %}
+
+The operators supported are ``==``, ``!=``, ``<``, ``>``, ``<=``, ``>=`` and
+``in``, all of which work like the Python operators, in addition to ``and``,
+``or`` and ``not`` which were already supported.
+
+Also, filters may now be used in the ``if`` expression. For example:
+
+.. code-block:: html+django
+
+ <div
+ {% if user.email|lower == message.recipient|lower %}
+ class="highlight"
+ {% endif %}
+ >{{ message }}</div>
+>>>>>>> master:docs/releases/1.2.txt
diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt
index ebd31e4e20..b8d34e5198 100644
--- a/docs/topics/auth.txt
+++ b/docs/topics/auth.txt
@@ -202,28 +202,51 @@ Methods
:meth:`~django.contrib.auth.models.User.set_unusable_password()` has
been called for this user.
- .. method:: models.User.get_group_permissions()
+ .. method:: models.User.get_group_permissions(obj=None)
Returns a list of permission strings that the user has, through his/her
groups.
- .. method:: models.User.get_all_permissions()
+ .. versionadded:: 1.2
+
+ If ``obj`` is passed in, only returns the group permissions for
+ this specific object.
+
+ .. method:: models.User.get_all_permissions(obj=None)
Returns a list of permission strings that the user has, both through
group and user permissions.
- .. method:: models.User.has_perm(perm)
+ .. versionadded:: 1.2
+
+ If ``obj`` is passed in, only returns the permissions for this
+ specific object.
+
+ .. method:: models.User.has_perm(perm, obj=None)
Returns ``True`` if the user has the specified permission, where perm is
in the format ``"<app label>.<permission codename>"``.
If the user is inactive, this method will always return ``False``.
- .. method:: models.User.has_perms(perm_list)
+ .. versionadded:: 1.2
+
+ If ``obj`` is passed in, this method won't check for a permission for
+ the model, but for this specific object.
+
+ .. method:: models.User.has_perms(perm_list, obj=None)
Returns ``True`` if the user has each of the specified permissions,
where each perm is in the format
``"<app label>.<permission codename>"``. If the user is inactive,
this method will always return ``False``.
+<<<<<<< HEAD:docs/topics/auth.txt
+=======
+
+ .. versionadded:: 1.2
+
+ If ``obj`` is passed in, this method won't check for permissions for
+ the model, but for the specific object.
+>>>>>>> master:docs/topics/auth.txt
.. method:: models.User.has_module_perms(package_name)
@@ -1521,3 +1544,24 @@ A full authorization implementation can be found in
the ``auth_permission`` table most of the time.
.. _django/contrib/auth/backends.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/backends.py
+
+Handling object permissions
+---------------------------
+
+Django's permission framework has a foundation for object permissions, though
+there is no implementation for it in the core. That means that checking for
+object permissions will always return ``False`` or an empty list (depending on
+the check performed).
+
+To enable object permissions in your own
+:ref:`authentication backend <ref-authentication-backends>` you'll just have
+to allow passing an ``obj`` parameter to the permission methods and set the
+``supports_objects_permissions`` class attribute to ``True``.
+
+A nonexistent ``supports_objects_permissions`` will raise a hidden
+``PendingDeprecationWarning`` if used in Django 1.2. In Django 1.3, this
+warning will be upgraded to a ``DeprecationWarning``, which will be displayed
+loudly. Additionally ``supports_objects_permissions`` will be set to ``False``.
+Django 1.4 will assume that every backend supports object permissions and
+won't check for the existence of ``supports_objects_permissions``, which
+means not supporting ``obj`` as a parameter will raise a ``TypeError``.
diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt
index 1c8b63c61e..41cb94a56d 100644
--- a/docs/topics/templates.txt
+++ b/docs/topics/templates.txt
@@ -187,8 +187,8 @@ tags:
<li>{{ athlete.name }}</li>
{% endfor %}
</ul>
-
- :ttag:`if` and :ttag:`else`
+
+ :ttag:`if` and ``else``
Evaluates a variable, and if that variable is "true" the contents of the
block are displayed::
@@ -200,20 +200,15 @@ tags:
In the above, if ``athlete_list`` is not empty, the number of athletes
will be displayed by the ``{{ athlete_list|length }}`` variable.
-
- :ttag:`ifequal` and :ttag:`ifnotequal`
- Display some contents if two arguments are or are not equal. For example::
- {% ifequal athlete.name coach.name %}
- ...
- {% endifequal %}
+ You can also use filters and various operators in the ``if`` tag::
- Or::
+ {% if athlete_list|length > 1 %}
+ Team: {% for athlete in athlete_list %} ... {% endfor %}
+ {% else %}
+ Athlete: {{ athlete_list.0.name }}
+ {% endif %}
- {% ifnotequal athlete.name "Joe" %}
- ...
- {% endifnotequal %}
-
:ttag:`block` and :ttag:`extends`
Set up `template inheritance`_ (see below), a powerful way
of cutting down on "boilerplate" in templates.
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index 8607589289..167498ac37 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -610,6 +610,12 @@ class AdminViewStringPrimaryKeyTest(TestCase):
def tearDown(self):
self.client.logout()
+ def test_get_history_view(self):
+ "Retrieving the history for the object using urlencoded form of primary key should work"
+ response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/history/' % quote(self.pk))
+ self.assertContains(response, escape(self.pk))
+ self.failUnlessEqual(response.status_code, 200)
+
def test_get_change_view(self):
"Retrieving the object using urlencoded form of primary key should work"
response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(self.pk))
diff --git a/tests/regressiontests/auth_backends/__init__.py b/tests/regressiontests/auth_backends/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
--- a/tests/regressiontests/auth_backends/__init__.py
+++ /dev/null
diff --git a/tests/regressiontests/auth_backends/models.py b/tests/regressiontests/auth_backends/models.py
deleted file mode 100644
index e69de29bb2..0000000000
--- a/tests/regressiontests/auth_backends/models.py
+++ /dev/null
diff --git a/tests/regressiontests/auth_backends/tests.py b/tests/regressiontests/auth_backends/tests.py
deleted file mode 100644
index d22f0bf939..0000000000
--- a/tests/regressiontests/auth_backends/tests.py
+++ /dev/null
@@ -1,78 +0,0 @@
-try:
- set
-except NameError:
- from sets import Set as set # Python 2.3 fallback
-
-__test__ = {'API_TESTS': """
->>> from django.contrib.auth.models import User, Group, Permission, AnonymousUser
->>> from django.contrib.contenttypes.models import ContentType
-
-# No Permissions assigned yet, should return False except for superuser
-
->>> user = User.objects.create_user('test', 'test@example.com', 'test')
->>> user.has_perm("auth.test")
-False
->>> user.is_staff=True
->>> user.save()
->>> user.has_perm("auth.test")
-False
->>> user.is_superuser=True
->>> user.save()
->>> user.has_perm("auth.test")
-True
->>> user.is_staff = False
->>> user.is_superuser = False
->>> user.save()
->>> user.has_perm("auth.test")
-False
->>> content_type=ContentType.objects.get_for_model(Group)
->>> perm = Permission.objects.create(name="test", content_type=content_type, codename="test")
->>> user.user_permissions.add(perm)
->>> user.save()
-
-# reloading user to purge the _perm_cache
-
->>> user = User.objects.get(username="test")
->>> user.get_all_permissions() == set([u'auth.test'])
-True
->>> user.get_group_permissions() == set([])
-True
->>> user.has_module_perms("Group")
-False
->>> user.has_module_perms("auth")
-True
->>> perm = Permission.objects.create(name="test2", content_type=content_type, codename="test2")
->>> user.user_permissions.add(perm)
->>> user.save()
->>> perm = Permission.objects.create(name="test3", content_type=content_type, codename="test3")
->>> user.user_permissions.add(perm)
->>> user.save()
->>> user = User.objects.get(username="test")
->>> user.get_all_permissions() == set([u'auth.test2', u'auth.test', u'auth.test3'])
-True
->>> user.has_perm('test')
-False
->>> user.has_perm('auth.test')
-True
->>> user.has_perms(['auth.test2', 'auth.test3'])
-True
->>> perm = Permission.objects.create(name="test_group", content_type=content_type, codename="test_group")
->>> group = Group.objects.create(name='test_group')
->>> group.permissions.add(perm)
->>> group.save()
->>> user.groups.add(group)
->>> user = User.objects.get(username="test")
->>> exp = set([u'auth.test2', u'auth.test', u'auth.test3', u'auth.test_group'])
->>> user.get_all_permissions() == exp
-True
->>> user.get_group_permissions() == set([u'auth.test_group'])
-True
->>> user.has_perms(['auth.test3', 'auth.test_group'])
-True
-
->>> user = AnonymousUser()
->>> user.has_perm('test')
-False
->>> user.has_perms(['auth.test2', 'auth.test3'])
-False
-"""}
diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py
index ad00f079ba..9d6007e67d 100644
--- a/tests/regressiontests/backends/tests.py
+++ b/tests/regressiontests/backends/tests.py
@@ -1,7 +1,11 @@
# -*- coding: utf-8 -*-
# Unit and doctests for specific database backends.
import unittest
+<<<<<<< HEAD:tests/regressiontests/backends/tests.py
from django.db import backend, connection, DEFAULT_DB_ALIAS
+=======
+from django.db import backend, connection
+>>>>>>> master:tests/regressiontests/backends/tests.py
from django.db.backends.signals import connection_created
from django.conf import settings
@@ -10,7 +14,11 @@ class Callproc(unittest.TestCase):
def test_dbms_session(self):
# If the backend is Oracle, test that we can call a standard
# stored procedure through our cursor wrapper.
+<<<<<<< HEAD:tests/regressiontests/backends/tests.py
if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle':
+=======
+ if settings.DATABASE_ENGINE == 'oracle':
+>>>>>>> master:tests/regressiontests/backends/tests.py
convert_unicode = backend.convert_unicode
cursor = connection.cursor()
cursor.callproc(convert_unicode('DBMS_SESSION.SET_IDENTIFIER'),
@@ -24,7 +32,11 @@ class LongString(unittest.TestCase):
def test_long_string(self):
# If the backend is Oracle, test that we can save a text longer
# than 4000 chars and read it properly
+<<<<<<< HEAD:tests/regressiontests/backends/tests.py
if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle':
+=======
+ if settings.DATABASE_ENGINE == 'oracle':
+>>>>>>> master:tests/regressiontests/backends/tests.py
c = connection.cursor()
c.execute('CREATE TABLE ltext ("TEXT" NCLOB)')
long_str = ''.join([unicode(x) for x in xrange(4000)])
diff --git a/tests/regressiontests/templates/smartif.py b/tests/regressiontests/templates/smartif.py
new file mode 100644
index 0000000000..0114753830
--- /dev/null
+++ b/tests/regressiontests/templates/smartif.py
@@ -0,0 +1,46 @@
+import unittest
+from django.template.smartif import IfParser, Literal
+
+class SmartIfTests(unittest.TestCase):
+
+ def assertCalcEqual(self, expected, tokens):
+ self.assertEqual(expected, IfParser(tokens).parse().eval({}))
+
+ # We only test things here that are difficult to test elsewhere
+ # Many other tests are found in the main tests for builtin template tags
+ # Test parsing via the printed parse tree
+ def test_not(self):
+ var = IfParser(["not", False]).parse()
+ self.assertEqual("(not (literal False))", repr(var))
+ self.assert_(var.eval({}))
+
+ self.assertFalse(IfParser(["not", True]).parse().eval({}))
+
+ def test_or(self):
+ var = IfParser([True, "or", False]).parse()
+ self.assertEqual("(or (literal True) (literal False))", repr(var))
+ self.assert_(var.eval({}))
+
+ def test_in(self):
+ list_ = [1,2,3]
+ self.assertCalcEqual(True, [1, 'in', list_])
+ self.assertCalcEqual(False, [1, 'in', None])
+ self.assertCalcEqual(False, [None, 'in', list_])
+
+ def test_precedence(self):
+ # (False and False) or True == True <- we want this one, like Python
+ # False and (False or True) == False
+ self.assertCalcEqual(True, [False, 'and', False, 'or', True])
+
+ # True or (False and False) == True <- we want this one, like Python
+ # (True or False) and False == False
+ self.assertCalcEqual(True, [True, 'or', False, 'and', False])
+
+ # (1 or 1) == 2 -> False
+ # 1 or (1 == 2) -> True <- we want this one
+ self.assertCalcEqual(True, [1, 'or', 1, '==', 2])
+
+ self.assertCalcEqual(True, [True, '==', True, 'or', True, '==', False])
+
+ self.assertEqual("(or (and (== (literal 1) (literal 2)) (literal 3)) (literal 4))",
+ repr(IfParser([1, '==', 2, 'and', 3, 'or', 4]).parse()))
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index 9c01b492e3..c29c53ae44 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -24,6 +24,7 @@ from context import context_tests
from custom import custom_filters
from parser import filter_parsing, variable_parsing
from unicode import unicode_tests
+from smartif import *
try:
from loaders import *
@@ -534,6 +535,27 @@ class Templates(unittest.TestCase):
'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
'if-tag03': ("{% if foo %}yes{% else %}no{% endif %}", {}, "no"),
+ # Filters
+ 'if-tag-filter01': ("{% if foo|length == 5 %}yes{% else %}no{% endif %}", {'foo': 'abcde'}, "yes"),
+ 'if-tag-filter02': ("{% if foo|upper == 'ABC' %}yes{% else %}no{% endif %}", {}, "no"),
+
+ # Equality
+ 'if-tag-eq01': ("{% if foo == bar %}yes{% else %}no{% endif %}", {}, "yes"),
+ 'if-tag-eq02': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1}, "no"),
+ 'if-tag-eq03': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1, 'bar': 1}, "yes"),
+ 'if-tag-eq04': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1, 'bar': 2}, "no"),
+ 'if-tag-eq05': ("{% if foo == '' %}yes{% else %}no{% endif %}", {}, "no"),
+
+ # Comparison
+ 'if-tag-gt-01': ("{% if 2 > 1 %}yes{% else %}no{% endif %}", {}, "yes"),
+ 'if-tag-gt-02': ("{% if 1 > 1 %}yes{% else %}no{% endif %}", {}, "no"),
+ 'if-tag-gte-01': ("{% if 1 >= 1 %}yes{% else %}no{% endif %}", {}, "yes"),
+ 'if-tag-gte-02': ("{% if 1 >= 2 %}yes{% else %}no{% endif %}", {}, "no"),
+ 'if-tag-lt-01': ("{% if 1 < 2 %}yes{% else %}no{% endif %}", {}, "yes"),
+ 'if-tag-lt-02': ("{% if 1 < 1 %}yes{% else %}no{% endif %}", {}, "no"),
+ 'if-tag-lte-01': ("{% if 1 <= 1 %}yes{% else %}no{% endif %}", {}, "yes"),
+ 'if-tag-lte-02': ("{% if 2 <= 1 %}yes{% else %}no{% endif %}", {}, "no"),
+
# AND
'if-tag-and01': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
'if-tag-and02': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
@@ -554,14 +576,13 @@ class Templates(unittest.TestCase):
'if-tag-or07': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True}, 'yes'),
'if-tag-or08': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'bar': True}, 'yes'),
- # TODO: multiple ORs
+ # multiple ORs
+ 'if-tag-or09': ("{% if foo or bar or baz %}yes{% else %}no{% endif %}", {'baz': True}, 'yes'),
# NOT
'if-tag-not01': ("{% if not foo %}no{% else %}yes{% endif %}", {'foo': True}, 'yes'),
- 'if-tag-not02': ("{% if not %}yes{% else %}no{% endif %}", {'foo': True}, 'no'),
- 'if-tag-not03': ("{% if not %}yes{% else %}no{% endif %}", {'not': True}, 'yes'),
- 'if-tag-not04': ("{% if not not %}no{% else %}yes{% endif %}", {'not': True}, 'yes'),
- 'if-tag-not05': ("{% if not not %}no{% else %}yes{% endif %}", {}, 'no'),
+ 'if-tag-not02': ("{% if not not foo %}no{% else %}yes{% endif %}", {'foo': True}, 'no'),
+ # not03 to not05 removed, now TemplateSyntaxErrors
'if-tag-not06': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {}, 'no'),
'if-tag-not07': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
@@ -599,12 +620,21 @@ class Templates(unittest.TestCase):
'if-tag-not34': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
'if-tag-not35': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
- # AND and OR raises a TemplateSyntaxError
- 'if-tag-error01': ("{% if foo or bar and baz %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, template.TemplateSyntaxError),
+ # Various syntax errors
+ 'if-tag-error01': ("{% if %}yes{% endif %}", {}, template.TemplateSyntaxError),
'if-tag-error02': ("{% if foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
'if-tag-error03': ("{% if foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
'if-tag-error04': ("{% if not foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
'if-tag-error05': ("{% if not foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+ 'if-tag-error06': ("{% if abc def %}yes{% endif %}", {}, template.TemplateSyntaxError),
+ 'if-tag-error07': ("{% if not %}yes{% endif %}", {}, template.TemplateSyntaxError),
+ 'if-tag-error08': ("{% if and %}yes{% endif %}", {}, template.TemplateSyntaxError),
+ 'if-tag-error09': ("{% if or %}yes{% endif %}", {}, template.TemplateSyntaxError),
+ 'if-tag-error10': ("{% if == %}yes{% endif %}", {}, template.TemplateSyntaxError),
+ 'if-tag-error11': ("{% if 1 == %}yes{% endif %}", {}, template.TemplateSyntaxError),
+ 'if-tag-error12': ("{% if a not b %}yes{% endif %}", {}, template.TemplateSyntaxError),
+
+ # Additional, more precise parsing tests are in SmartIfTests
### IFCHANGED TAG #########################################################
'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', {'num': (1,2,3)}, '123'),