From c88113683d6538dbc2f779ff57fd1ea20211abec Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 12 Dec 2009 02:10:28 +0000 Subject: [PATCH] [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 --- django/conf/global_settings.py | 3 + django/conf/locale/pl/LC_MESSAGES/django.po | 196 ++++++++++++++---- django/contrib/admin/options.py | 2 +- django/contrib/auth/__init__.py | 7 + django/contrib/auth/backends.py | 2 + django/contrib/auth/models.py | 57 +++-- django/contrib/auth/tests/__init__.py | 1 + django/contrib/auth/tests/auth_backends.py | 149 +++++++++++++ django/contrib/gis/tests/relatedapp/tests.py | 11 + django/core/management/commands/dumpdata.py | 5 + django/db/backends/oracle/base.py | 4 + django/db/models/base.py | 10 + django/db/models/fields/related.py | 23 ++ django/db/models/manager.py | 3 + django/db/models/query.py | 8 + django/db/models/sql/query.py | 104 ++++++++++ django/forms/models.py | 4 + django/template/defaulttags.py | 101 ++++----- django/template/smartif.py | 192 +++++++++++++++++ docs/internals/deprecation.txt | 18 ++ docs/ref/databases.txt | 3 + docs/ref/django-admin.txt | 51 +++++ docs/ref/settings.txt | 48 +++++ docs/ref/templates/builtins.txt | 160 +++++++++++++- docs/releases/1.2.txt | 51 +++++ docs/topics/auth.txt | 52 ++++- docs/topics/templates.txt | 21 +- tests/regressiontests/admin_views/tests.py | 6 + .../regressiontests/auth_backends/__init__.py | 0 tests/regressiontests/auth_backends/models.py | 0 tests/regressiontests/auth_backends/tests.py | 78 ------- tests/regressiontests/backends/tests.py | 12 ++ tests/regressiontests/templates/smartif.py | 46 ++++ tests/regressiontests/templates/tests.py | 44 +++- 34 files changed, 1239 insertions(+), 233 deletions(-) create mode 100644 django/contrib/auth/tests/auth_backends.py create mode 100644 django/template/smartif.py delete mode 100644 tests/regressiontests/auth_backends/__init__.py delete mode 100644 tests/regressiontests/auth_backends/models.py delete mode 100644 tests/regressiontests/auth_backends/tests.py create mode 100644 tests/regressiontests/templates/smartif.py 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 \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 change " "password form." @@ -1361,19 +1459,19 @@ msgstr "" "Użyj '[algo]$[salt]$[hexdigest]' lub formularza zmiany " "hasł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 "" @@ -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 ` 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 ` 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 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 --------------------------- .. 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 -------------------------------- @@ -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 ----------------------------- +.. django-admin:: test +>>>>>>> master:docs/ref/django-admin.txt + +test +----------------------------- + +<<<<<<< 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 -------------------------------- @@ -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 %} -Multiple uses of the same logical operator are fine, as long as you use the -same operator. For example, this is valid:: +``!=`` 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 %} + + {% 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 + +
{{ message }}
+>>>>>>> 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 ``"."``. 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 ``"."``. 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 ` 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:
  • {{ athlete.name }}
  • {% endfor %} - - :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 diff --git a/tests/regressiontests/auth_backends/models.py b/tests/regressiontests/auth_backends/models.py deleted file mode 100644 index e69de29bb2..0000000000 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'),