From c8da0874c78ed4c6e1ad08cc78228799a333f76c Mon Sep 17 00:00:00 2001 From: Brian Rosner Date: Thu, 26 Jun 2008 15:42:33 +0000 Subject: [PATCH] newforms-admin: Merged from trunk up to [7766]. git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@7770 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + django/conf/locale/pl/LC_MESSAGES/django.mo | Bin 66404 -> 66481 bytes django/conf/locale/pl/LC_MESSAGES/django.po | 226 +++++++++--------- django/contrib/auth/views.py | 14 +- django/contrib/sites/tests.py | 4 +- django/core/servers/basehttp.py | 2 +- django/db/backends/__init__.py | 1 - django/db/backends/oracle/base.py | 1 - django/db/backends/oracle/creation.py | 12 +- django/db/models/fields/__init__.py | 4 +- django/db/models/fields/related.py | 9 +- django/db/models/options.py | 11 +- django/db/models/query.py | 36 ++- django/db/models/sql/query.py | 72 ++++-- django/db/models/sql/subqueries.py | 8 +- django/template/defaulttags.py | 24 +- django/templatetags/cache.py | 25 +- django/utils/datastructures.py | 31 +++ django/utils/html.py | 52 ++-- docs/cache.txt | 11 + docs/contributing.txt | 79 +++--- docs/db-api.txt | 8 +- docs/model-api.txt | 4 +- tests/modeltests/model_inheritance/models.py | 45 +++- tests/regressiontests/datastructures/tests.py | 8 + tests/regressiontests/defaultfilters/tests.py | 6 +- .../model_inheritance_regress/models.py | 22 +- tests/regressiontests/queries/models.py | 95 +++++++- tests/regressiontests/templates/loaders.py | 26 +- tests/regressiontests/templates/tests.py | 79 +++--- 30 files changed, 589 insertions(+), 327 deletions(-) diff --git a/AUTHORS b/AUTHORS index 4966384776..bfaf6db6ca 100644 --- a/AUTHORS +++ b/AUTHORS @@ -385,6 +385,7 @@ answer newbie questions, and generally made Django that much better: Wang Chun Filip Wasilewski Dan Watson + Joel Watts Chris Wesseling James Wheare charly.wilhelm@gmail.com diff --git a/django/conf/locale/pl/LC_MESSAGES/django.mo b/django/conf/locale/pl/LC_MESSAGES/django.mo index b82290dd046b42c672080d1f2504321e9e9b5e61..5cdabb1d11e5de84f399cb0bcf2860da6c65ce25 100644 GIT binary patch delta 21149 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 diff --git a/django/conf/locale/pl/LC_MESSAGES/django.po b/django/conf/locale/pl/LC_MESSAGES/django.po index d084d90688..6f73bb45d0 100644 --- a/django/conf/locale/pl/LC_MESSAGES/django.po +++ b/django/conf/locale/pl/LC_MESSAGES/django.po @@ -5,9 +5,9 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-04-14 18:02+0200\n" +"POT-Creation-Date: 2008-06-24 07:36+0200\n" "PO-Revision-Date: 2008-02-25 15:53+0100\n" -"Last-Translator: Piotr Lewandowski \n" +"Last-Translator: Jarek Zgoda \n" "Language-Team: Polish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -16,191 +16,199 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%" "100<10 || n%100>=20) ? 1 : 2);\n" -#: conf/global_settings.py:39 +#: conf/global_settings.py:44 msgid "Arabic" msgstr "Arabski" -#: conf/global_settings.py:40 +#: conf/global_settings.py:45 msgid "Bengali" msgstr "Bengalski" -#: conf/global_settings.py:41 +#: conf/global_settings.py:46 msgid "Bulgarian" msgstr "Bułgarski" -#: conf/global_settings.py:42 +#: conf/global_settings.py:47 msgid "Catalan" msgstr "Kataloński" -#: conf/global_settings.py:43 +#: conf/global_settings.py:48 msgid "Czech" msgstr "Czeski" -#: conf/global_settings.py:44 +#: conf/global_settings.py:49 msgid "Welsh" msgstr "Walijski" -#: conf/global_settings.py:45 +#: conf/global_settings.py:50 msgid "Danish" msgstr "Duński" -#: conf/global_settings.py:46 +#: conf/global_settings.py:51 msgid "German" msgstr "Niemiecki" -#: conf/global_settings.py:47 +#: conf/global_settings.py:52 msgid "Greek" msgstr "Grecki" -#: conf/global_settings.py:48 +#: conf/global_settings.py:53 msgid "English" msgstr "Angielski" -#: conf/global_settings.py:49 +#: conf/global_settings.py:54 msgid "Spanish" msgstr "Hiszpański" -#: conf/global_settings.py:50 +#: conf/global_settings.py:55 +msgid "Estonian" +msgstr "Estoński" + +#: conf/global_settings.py:56 msgid "Argentinean Spanish" msgstr "Hiszpański argentyński" -#: conf/global_settings.py:51 +#: conf/global_settings.py:57 msgid "Basque" msgstr "Baskijski" -#: conf/global_settings.py:52 +#: conf/global_settings.py:58 msgid "Persian" msgstr "Perski" -#: conf/global_settings.py:53 +#: conf/global_settings.py:59 msgid "Finnish" msgstr "Fiński" -#: conf/global_settings.py:54 +#: conf/global_settings.py:60 msgid "French" msgstr "Francuski" -#: conf/global_settings.py:55 +#: conf/global_settings.py:61 msgid "Irish" msgstr "Irlandzki" -#: conf/global_settings.py:56 +#: conf/global_settings.py:62 msgid "Galician" msgstr "Galicyjski" -#: conf/global_settings.py:57 +#: conf/global_settings.py:63 msgid "Hungarian" msgstr "Węgierski" -#: conf/global_settings.py:58 +#: conf/global_settings.py:64 msgid "Hebrew" msgstr "Hebrajski" -#: conf/global_settings.py:59 +#: conf/global_settings.py:65 msgid "Croatian" msgstr "Chorwacki" -#: conf/global_settings.py:60 +#: conf/global_settings.py:66 msgid "Icelandic" msgstr "Islandzki" -#: conf/global_settings.py:61 +#: conf/global_settings.py:67 msgid "Italian" msgstr "Włoski" -#: conf/global_settings.py:62 +#: conf/global_settings.py:68 msgid "Japanese" msgstr "Japoński" -#: conf/global_settings.py:63 +#: conf/global_settings.py:69 msgid "Georgian" msgstr "Gruziński" -#: conf/global_settings.py:64 +#: conf/global_settings.py:70 msgid "Korean" msgstr "Koreański" -#: conf/global_settings.py:65 +#: conf/global_settings.py:71 msgid "Khmer" msgstr "Khmerski" -#: conf/global_settings.py:66 +#: conf/global_settings.py:72 msgid "Kannada" msgstr "Kannada" -#: conf/global_settings.py:67 +#: conf/global_settings.py:73 msgid "Latvian" msgstr "Łotewski" -#: conf/global_settings.py:68 +#: conf/global_settings.py:74 +msgid "Lithuanian" +msgstr "Litewski" + +#: conf/global_settings.py:75 msgid "Macedonian" msgstr "Macedoński" -#: conf/global_settings.py:69 +#: conf/global_settings.py:76 msgid "Dutch" msgstr "Holenderski" -#: conf/global_settings.py:70 +#: conf/global_settings.py:77 msgid "Norwegian" msgstr "Norweski" -#: conf/global_settings.py:71 +#: conf/global_settings.py:78 msgid "Polish" msgstr "Polski" -#: conf/global_settings.py:72 +#: conf/global_settings.py:79 msgid "Portugese" msgstr "Portugalski" -#: conf/global_settings.py:73 +#: conf/global_settings.py:80 msgid "Brazilian Portuguese" msgstr "Brazylijski portugalski" -#: conf/global_settings.py:74 +#: conf/global_settings.py:81 msgid "Romanian" msgstr "Rumuński" -#: conf/global_settings.py:75 +#: conf/global_settings.py:82 msgid "Russian" msgstr "Rosyjski" -#: conf/global_settings.py:76 +#: conf/global_settings.py:83 msgid "Slovak" msgstr "Słowacki" -#: conf/global_settings.py:77 +#: conf/global_settings.py:84 msgid "Slovenian" msgstr "Słoweński" -#: conf/global_settings.py:78 +#: conf/global_settings.py:85 msgid "Serbian" msgstr "Serbski" -#: conf/global_settings.py:79 +#: conf/global_settings.py:86 msgid "Swedish" msgstr "Szwedzki" -#: conf/global_settings.py:80 +#: conf/global_settings.py:87 msgid "Tamil" msgstr "Tamilski" -#: conf/global_settings.py:81 +#: conf/global_settings.py:88 msgid "Telugu" msgstr "Telugu" -#: conf/global_settings.py:82 +#: conf/global_settings.py:89 msgid "Turkish" msgstr "Turecki" -#: conf/global_settings.py:83 +#: conf/global_settings.py:90 msgid "Ukrainian" msgstr "Ukraiński" -#: conf/global_settings.py:84 +#: conf/global_settings.py:91 msgid "Simplified Chinese" msgstr "Uproszczony chiński" -#: conf/global_settings.py:85 +#: conf/global_settings.py:92 msgid "Traditional Chinese" msgstr "Chiński tradycyjny" @@ -417,7 +425,7 @@ msgstr "" #: contrib/admin/templates/admin/delete_confirmation.html:25 msgid "Yes, I'm sure" -msgstr "Tak, usuń" +msgstr "Tak, na pewno" #: contrib/admin/templates/admin/filter.html:2 #, python-format @@ -824,15 +832,15 @@ msgstr "" "Twoja przeglądarka nie chce akceptować ciasteczek. Zmień jej ustawienia i " "spróbuj ponownie." -#: contrib/admin/views/decorators.py:90 -msgid "Usernames cannot contain the '@' character." -msgstr "Nazwy użytkowników nie mogą zawierać znaków '@'." - -#: contrib/admin/views/decorators.py:92 +#: contrib/admin/views/decorators.py:89 #, python-format msgid "Your e-mail address is not your username. Try '%s' instead." msgstr "Twój adres e-mail to nie jest twój login. Spróbuj '%s'." +#: contrib/admin/views/decorators.py:93 +msgid "Usernames cannot contain the '@' character." +msgstr "Nazwy użytkowników nie mogą zawierać znaków '@'." + #: contrib/admin/views/doc.py:48 contrib/admin/views/doc.py:50 #: contrib/admin/views/doc.py:52 msgid "tag:" @@ -1063,7 +1071,7 @@ msgstr "Zaznacz %s" msgid "Select %s to change" msgstr "Zaznacz %s aby zmienić" -#: contrib/admin/views/main.py:784 +#: contrib/admin/views/main.py:765 msgid "Database error" msgstr "Błąd bazy danych" @@ -1128,15 +1136,15 @@ msgstr "uprawnienia" msgid "group" msgstr "grupa" -#: contrib/auth/models.py:98 contrib/auth/models.py:141 +#: contrib/auth/models.py:98 contrib/auth/models.py:148 msgid "groups" msgstr "grupy" -#: contrib/auth/models.py:131 +#: contrib/auth/models.py:138 msgid "username" msgstr "użytkownik" -#: contrib/auth/models.py:131 +#: contrib/auth/models.py:138 msgid "" "Required. 30 characters or fewer. Alphanumeric characters only (letters, " "digits and underscores)." @@ -1144,23 +1152,23 @@ msgstr "" "Wymagane. 30 znaków lub mniej. Tylko znaki alfanumeryczne (litery, cyfry i " "podkreślenia)." -#: contrib/auth/models.py:132 +#: contrib/auth/models.py:139 msgid "first name" msgstr "Imię" -#: contrib/auth/models.py:133 +#: contrib/auth/models.py:140 msgid "last name" msgstr "Nazwisko" -#: contrib/auth/models.py:134 +#: contrib/auth/models.py:141 msgid "e-mail address" msgstr "adres e-mail" -#: contrib/auth/models.py:135 +#: contrib/auth/models.py:142 msgid "password" msgstr "hasło" -#: contrib/auth/models.py:135 +#: contrib/auth/models.py:142 msgid "" "Use '[algo]$[salt]$[hexdigest]' or use the change " "password form." @@ -1168,19 +1176,19 @@ msgstr "" "Użyj '[algo]$[salt]$[hexdigest]' lub formularza zmiany " "hasła." -#: contrib/auth/models.py:136 +#: contrib/auth/models.py:143 msgid "staff status" msgstr "w zespole" -#: contrib/auth/models.py:136 +#: contrib/auth/models.py:143 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:137 +#: contrib/auth/models.py:144 msgid "active" msgstr "aktywny" -#: contrib/auth/models.py:137 +#: contrib/auth/models.py:144 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -1188,11 +1196,11 @@ msgstr "" "Oznacza czy użytkownika należy uważać za aktywnego. Odznacz tozamiast usuwać " "konta." -#: contrib/auth/models.py:138 +#: contrib/auth/models.py:145 msgid "superuser status" msgstr "Główny Administrator" -#: contrib/auth/models.py:138 +#: contrib/auth/models.py:145 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." @@ -1200,15 +1208,15 @@ msgstr "" "Oznacza, że ten użytkownik ma wszystkie uprawnienia bez jawnego " "przypisywania ich." -#: contrib/auth/models.py:139 +#: contrib/auth/models.py:146 msgid "last login" msgstr "ostatnio zalogowany" -#: contrib/auth/models.py:140 +#: contrib/auth/models.py:147 msgid "date joined" msgstr "data przyłączenia" -#: contrib/auth/models.py:142 +#: contrib/auth/models.py:149 msgid "" "In addition to the permissions manually assigned, this user will also get " "all permissions granted to each group he/she is in." @@ -1216,39 +1224,39 @@ msgstr "" "Oprócz uprawnień przypisanych bezpośrednio użytkownikowi otrzyma on " "uprawnienia grup, do których należy." -#: contrib/auth/models.py:143 +#: contrib/auth/models.py:150 msgid "user permissions" msgstr "uprawnienia użytkownika" -#: contrib/auth/models.py:147 +#: contrib/auth/models.py:154 msgid "user" msgstr "użytkownik" -#: contrib/auth/models.py:148 +#: contrib/auth/models.py:155 msgid "users" msgstr "użytkownicy" -#: contrib/auth/models.py:154 +#: contrib/auth/models.py:161 msgid "Personal info" msgstr "Dane osobowe" -#: contrib/auth/models.py:155 +#: contrib/auth/models.py:162 msgid "Permissions" msgstr "Uprawnienia" -#: contrib/auth/models.py:156 +#: contrib/auth/models.py:163 msgid "Important dates" msgstr "Ważne daty" -#: contrib/auth/models.py:157 +#: contrib/auth/models.py:164 msgid "Groups" msgstr "Grupy" -#: contrib/auth/models.py:316 +#: contrib/auth/models.py:323 msgid "message" msgstr "wiadomość" -#: contrib/auth/views.py:47 +#: contrib/auth/views.py:48 msgid "Logged out" msgstr "Wylogowany" @@ -3516,23 +3524,23 @@ msgstr "przekieruj" msgid "redirects" msgstr "przekierowania" -#: contrib/sessions/models.py:41 +#: contrib/sessions/models.py:45 msgid "session key" msgstr "klucz sesji" -#: contrib/sessions/models.py:42 +#: contrib/sessions/models.py:47 msgid "session data" msgstr "data sesji" -#: contrib/sessions/models.py:43 +#: contrib/sessions/models.py:48 msgid "expire date" msgstr "data wygaśnięcia sesji" -#: contrib/sessions/models.py:48 +#: contrib/sessions/models.py:53 msgid "session" msgstr "sesja" -#: contrib/sessions/models.py:49 +#: contrib/sessions/models.py:54 msgid "sessions" msgstr "sesje" @@ -3617,7 +3625,7 @@ msgstr "Rok nie może być wcześniejszy niż 1900." msgid "Invalid date: %s" msgstr "Niepoprawna data: %s" -#: core/validators.py:156 db/models/fields/__init__.py:527 +#: core/validators.py:156 db/models/fields/__init__.py:548 msgid "Enter a valid date in YYYY-MM-DD format." msgstr "Proszę wpisać poprawną datę w formacie RRRR-MM-DD." @@ -3625,7 +3633,7 @@ msgstr "Proszę wpisać poprawną datę w formacie RRRR-MM-DD." msgid "Enter a valid time in HH:MM format." msgstr "Proszę wpisać poprawną godzinę w formacie HH:MM." -#: core/validators.py:165 db/models/fields/__init__.py:604 +#: core/validators.py:165 db/models/fields/__init__.py:625 msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." msgstr "Wprowadź poprawną datę i godzinę w formacie RRRR-MM-DD GG:MM." @@ -3888,60 +3896,60 @@ msgstr "" msgid "%(object)s with this %(type)s already exists for the given %(field)s." msgstr "%(object)s z %(type)s już istnieje dla %(field)s." -#: db/models/fields/__init__.py:52 +#: db/models/fields/__init__.py:51 #, python-format msgid "%(optname)s with this %(fieldname)s already exists." msgstr "Już istnieje %(optname)s z %(fieldname)s." -#: db/models/fields/__init__.py:161 db/models/fields/__init__.py:327 -#: db/models/fields/__init__.py:759 db/models/fields/__init__.py:770 +#: db/models/fields/__init__.py:176 db/models/fields/__init__.py:348 +#: db/models/fields/__init__.py:780 db/models/fields/__init__.py:791 #: newforms/fields.py:46 oldforms/__init__.py:374 msgid "This field is required." msgstr "To pole jest wymagane." -#: db/models/fields/__init__.py:427 +#: db/models/fields/__init__.py:448 msgid "This value must be an integer." msgstr "Ta wartość musi być liczbą całkowitą." -#: db/models/fields/__init__.py:466 +#: db/models/fields/__init__.py:487 msgid "This value must be either True or False." msgstr "" "Ta wartość musi być wartością logiczną (True, False - prawda lub fałsz)." -#: db/models/fields/__init__.py:490 +#: db/models/fields/__init__.py:511 msgid "This field cannot be null." msgstr "To pole nie może być puste." -#: db/models/fields/__init__.py:668 +#: db/models/fields/__init__.py:689 msgid "This value must be a decimal number." msgstr "Ta wartość musi być liczbą dziesiętną." -#: db/models/fields/__init__.py:779 +#: db/models/fields/__init__.py:800 msgid "Enter a valid filename." msgstr "Wpisz poprawną nazwę pliku." -#: db/models/fields/__init__.py:960 +#: db/models/fields/__init__.py:981 msgid "This value must be either None, True or False." msgstr "" "Ta wartość musi być jedną z None (nic), True (prawda) lub False (fałsz)." -#: db/models/fields/related.py:93 +#: db/models/fields/related.py:94 #, python-format msgid "Please enter a valid %s." msgstr "Proszę wpisać poprawne %s." -#: db/models/fields/related.py:701 +#: db/models/fields/related.py:746 msgid "Separate multiple IDs with commas." msgstr "Oddziel identyfikatory przecinkami." -#: db/models/fields/related.py:703 +#: db/models/fields/related.py:748 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." -#: db/models/fields/related.py:750 +#: db/models/fields/related.py:795 #, python-format msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." msgid_plural "" @@ -4003,11 +4011,11 @@ msgstr "Upewnij się, że jest nie więcej niż %s miejsc po przecinku." msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Upewnij się, że jest nie więcej niż %s miejsc przed przecinkiem." -#: newforms/fields.py:263 newforms/fields.py:751 +#: newforms/fields.py:263 newforms/fields.py:750 msgid "Enter a valid date." msgstr "Wpisz poprawną datę." -#: newforms/fields.py:296 newforms/fields.py:752 +#: newforms/fields.py:296 newforms/fields.py:751 msgid "Enter a valid time." msgstr "Wpisz poprawną godzinę." @@ -4031,25 +4039,25 @@ msgstr "Wpisz poprawny URL." msgid "This URL appears to be a broken link." msgstr "Ten odnośnik jest nieprawidłowy." -#: newforms/fields.py:560 newforms/models.py:299 +#: newforms/fields.py:559 newforms/models.py:305 msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Wybierz poprawną wartość. Podana nie jest jednym z dostępnych wyborów." -#: newforms/fields.py:599 +#: newforms/fields.py:598 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" "Wybierz poprawną wartość. %(value)s nie jest jednym z dostępnych wyborów." -#: newforms/fields.py:600 newforms/fields.py:662 newforms/models.py:371 +#: newforms/fields.py:599 newforms/fields.py:661 newforms/models.py:372 msgid "Enter a list of values." msgstr "Podaj listę wartości." -#: newforms/fields.py:780 +#: newforms/fields.py:779 msgid "Enter a valid IPv4 address." msgstr "Wprowadź poprawny adres IPv4." -#: newforms/models.py:372 +#: newforms/models.py:373 #, python-format msgid "Select a valid choice. %s is not one of the available choices." msgstr "Wybierz poprawną wartość. %s nie jest jednym z dostępnych wyborów." diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 0886d12d02..0a52240631 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -1,12 +1,13 @@ +from django.contrib.auth import REDIRECT_FIELD_NAME +from django.contrib.auth.decorators import login_required from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import PasswordResetForm, PasswordChangeForm, AdminPasswordChangeForm from django.core.exceptions import PermissionDenied from django.shortcuts import render_to_response, get_object_or_404 -from django.template import RequestContext from django.contrib.sites.models import Site, RequestSite from django.http import HttpResponseRedirect -from django.contrib.auth.decorators import login_required -from django.contrib.auth import REDIRECT_FIELD_NAME +from django.template import RequestContext +from django.utils.http import urlquote from django.utils.html import escape from django.utils.translation import ugettext as _ from django.contrib.auth.models import User @@ -62,7 +63,7 @@ def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_N if not login_url: from django.conf import settings login_url = settings.LOGIN_URL - return HttpResponseRedirect('%s?%s=%s' % (login_url, redirect_field_name, next)) + return HttpResponseRedirect('%s?%s=%s' % (login_url, urlquote(redirect_field_name), urlquote(next))) def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html', email_template_name='registration/password_reset_email.html', @@ -73,7 +74,10 @@ def password_reset(request, is_admin_site=False, template_name='registration/pas if is_admin_site: form.save(domain_override=request.META['HTTP_HOST']) else: - form.save(email_template_name=email_template_name) + if Site._meta.installed: + form.save(email_template_name=email_template_name) + else: + form.save(domain_override=RequestSite(request).domain, email_template_name=email_template_name) return HttpResponseRedirect('%sdone/' % request.path) else: form = password_reset_form() diff --git a/django/contrib/sites/tests.py b/django/contrib/sites/tests.py index d2ec331eca..5c38ee1b6d 100644 --- a/django/contrib/sites/tests.py +++ b/django/contrib/sites/tests.py @@ -2,8 +2,8 @@ >>> # Make sure that get_current() does not return a deleted Site object. >>> from django.contrib.sites.models import Site >>> s = Site.objects.get_current() ->>> s - +>>> isinstance(s, Site) +True >>> s.delete() >>> Site.objects.get_current() diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py index 05f8756655..5bdf08b8ca 100644 --- a/django/core/servers/basehttp.py +++ b/django/core/servers/basehttp.py @@ -17,7 +17,7 @@ import urllib from django.utils.http import http_date __version__ = "0.1" -__all__ = ['WSGIServer','WSGIRequestHandler','demo_app'] +__all__ = ['WSGIServer','WSGIRequestHandler'] server_version = "WSGIServer/" + __version__ sys_version = "Python/" + sys.version.split()[0] diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 63a6435bdb..7a4e46a7d7 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -45,7 +45,6 @@ class BaseDatabaseFeatures(object): autoindexes_primary_keys = True inline_fk_references = True needs_datetime_string_cast = True - needs_upper_for_iops = False supports_constraints = True supports_tablespaces = False uses_case_insensitive_names = False diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index a1a9fb1c73..1df6bac069 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -27,7 +27,6 @@ class DatabaseFeatures(BaseDatabaseFeatures): allows_unique_and_pk = False # Suppress UNIQUE/PK for Oracle (ORA-02259) empty_fetchmany_value = () needs_datetime_string_cast = False - needs_upper_for_iops = True supports_tablespaces = True uses_case_insensitive_names = True uses_custom_query_class = True diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index f4ada55ac6..219686789f 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -5,9 +5,13 @@ from django.core import management # types, as strings. Column-type strings can contain format strings; they'll # be interpolated against the values of Field.__dict__ before being output. # If a column type is set to None, it won't be included in the output. +# +# Any format strings starting with "qn_" are quoted before being used in the +# output (the "qn_" prefix is stripped before the lookup is performed. + DATA_TYPES = { 'AutoField': 'NUMBER(11)', - 'BooleanField': 'NUMBER(1) CHECK (%(column)s IN (0,1))', + 'BooleanField': 'NUMBER(1) CHECK (%(qn_column)s IN (0,1))', 'CharField': 'NVARCHAR2(%(max_length)s)', 'CommaSeparatedIntegerField': 'VARCHAR2(%(max_length)s)', 'DateField': 'DATE', @@ -19,11 +23,11 @@ DATA_TYPES = { 'ImageField': 'NVARCHAR2(%(max_length)s)', 'IntegerField': 'NUMBER(11)', 'IPAddressField': 'VARCHAR2(15)', - 'NullBooleanField': 'NUMBER(1) CHECK ((%(column)s IN (0,1)) OR (%(column)s IS NULL))', + 'NullBooleanField': 'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(column)s IS NULL))', 'OneToOneField': 'NUMBER(11)', 'PhoneNumberField': 'VARCHAR2(20)', - 'PositiveIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)', - 'PositiveSmallIntegerField': 'NUMBER(11) CHECK (%(column)s >= 0)', + 'PositiveIntegerField': 'NUMBER(11) CHECK (%(qn_column)s >= 0)', + 'PositiveSmallIntegerField': 'NUMBER(11) CHECK (%(qn_column)s >= 0)', 'SlugField': 'NVARCHAR2(50)', 'SmallIntegerField': 'NUMBER(11)', 'TextField': 'NCLOB', diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 8a51193a01..d906d5bdda 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -16,6 +16,7 @@ from django.core import validators from django import oldforms from django import newforms as forms from django.core.exceptions import ObjectDoesNotExist +from django.utils.datastructures import DictWrapper from django.utils.functional import curry from django.utils.itercompat import tee from django.utils.text import capfirst @@ -153,8 +154,9 @@ class Field(object): # mapped to one of the built-in Django field types. In this case, you # can implement db_type() instead of get_internal_type() to specify # exactly which wacky database column type you want to use. + data = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_") try: - return get_creation_module().DATA_TYPES[self.get_internal_type()] % self.__dict__ + return get_creation_module().DATA_TYPES[self.get_internal_type()] % data except KeyError: return None diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 1c0466f97f..5ae94e02de 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -103,13 +103,15 @@ class RelatedField(object): if hasattr(sup, 'contribute_to_class'): sup.contribute_to_class(cls, name) + + if not cls._meta.abstract and self.rel.related_name: + self.rel.related_name = self.rel.related_name % {'class': cls.__name__.lower()} + other = self.rel.to if isinstance(other, basestring): add_lazy_relation(cls, self, other) else: self.do_related_class(other, cls) - if not cls._meta.abstract and self.rel.related_name: - self.rel.related_name = self.rel.related_name % {'class': cls.__name__.lower()} def set_attributes_from_rel(self): self.name = self.name or (self.rel.to._meta.object_name.lower() + '_' + self.rel.to._meta.pk.name) @@ -119,7 +121,8 @@ class RelatedField(object): def do_related_class(self, other, cls): self.set_attributes_from_rel() related = RelatedObject(other, cls, self) - self.contribute_to_related_class(other, related) + if not cls._meta.abstract: + self.contribute_to_related_class(other, related) def get_db_prep_lookup(self, lookup_type, value): # If we are doing a lookup on a Related Field, we must be diff --git a/django/db/models/options.py b/django/db/models/options.py index c78ab1cd48..1ac24fb311 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -273,14 +273,17 @@ class Options(object): """ Initialises the field name -> field object mapping. """ - cache = dict([(f.name, (f, m, True, False)) for f, m in - self.get_fields_with_model()]) - for f, model in self.get_m2m_with_model(): - cache[f.name] = (f, model, True, True) + cache = {} + # We intentionally handle related m2m objects first so that symmetrical + # m2m accessor names can be overridden, if necessary. for f, model in self.get_all_related_m2m_objects_with_model(): cache[f.field.related_query_name()] = (f, model, False, True) for f, model in self.get_all_related_objects_with_model(): cache[f.field.related_query_name()] = (f, model, False, False) + for f, model in self.get_m2m_with_model(): + cache[f.name] = (f, model, True, True) + for f, model in self.get_fields_with_model(): + cache[f.name] = (f, model, True, False) if self.order_with_respect_to: cache['_order'] = OrderWrt(), None, True, False if app_cache_ready(): diff --git a/django/db/models/query.py b/django/db/models/query.py index 8714cffb7f..98caaf004c 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -36,7 +36,7 @@ class CollectedObjects(object): """ Adds an item. model is the class of the object being added, - pk is the primary key, obj is the object itself, + pk is the primary key, obj is the object itself, parent_model is the model of the parent object that this object was reached through, nullable should be True if this relation is nullable. @@ -77,7 +77,7 @@ class CollectedObjects(object): def ordered_keys(self): """ - Returns the models in the order that they should be + Returns the models in the order that they should be dealth with i.e. models with no dependencies first. """ dealt_with = SortedDict() @@ -92,7 +92,7 @@ class CollectedObjects(object): found = True if not found: raise CyclicDependency("There is a cyclic dependency of items to be processed.") - + return dealt_with.keys() def unordered_keys(self): @@ -218,6 +218,8 @@ class QuerySet(object): def __and__(self, other): self._merge_sanity_check(other) + if isinstance(other, EmptyQuerySet): + return other._clone() combined = self._clone() combined.query.combine(other.query, sql.AND) return combined @@ -225,6 +227,8 @@ class QuerySet(object): def __or__(self, other): self._merge_sanity_check(other) combined = self._clone() + if isinstance(other, EmptyQuerySet): + return combined combined.query.combine(other.query, sql.OR) return combined @@ -488,7 +492,9 @@ class QuerySet(object): and usually it will be more natural to use other methods. """ if isinstance(filter_obj, Q) or hasattr(filter_obj, 'add_to_query'): - return self._filter_or_exclude(None, filter_obj) + clone = self._clone() + clone.query.add_q(filter_obj) + return clone else: return self._filter_or_exclude(None, **filter_obj) @@ -583,11 +589,11 @@ class QuerySet(object): def _merge_sanity_check(self, other): """ - Checks that we are merging two comparable queryset classes. + Checks that we are merging two comparable queryset classes. By default + this does nothing, but see the ValuesQuerySet for an example of where + it's useful. """ - if self.__class__ is not other.__class__: - raise TypeError("Cannot merge querysets of different types ('%s' and '%s'." - % (self.__class__.__name__, other.__class__.__name__)) + pass class ValuesQuerySet(QuerySet): def __init__(self, *args, **kwargs): @@ -599,7 +605,7 @@ class ValuesQuerySet(QuerySet): # names of the model fields to select. def iterator(self): - if (not self.extra_names and + if (not self.extra_names and len(self.field_names) != len(self.model._meta.fields)): self.query.trim_extra_select(self.extra_names) names = self.query.extra_select.keys() + self.field_names @@ -688,9 +694,9 @@ class DateQuerySet(QuerySet): """ self.query = self.query.clone(klass=sql.DateQuery, setup=True) self.query.select = [] - self.query.add_date_select(self._field.column, self._kind, self._order) + self.query.add_date_select(self._field, self._kind, self._order) if self._field.null: - self.query.add_filter(('%s__isnull' % self._field.name, True)) + self.query.add_filter(('%s__isnull' % self._field.name, False)) def _clone(self, klass=None, setup=False, **kwargs): c = super(DateQuerySet, self)._clone(klass, False, **kwargs) @@ -705,6 +711,12 @@ class EmptyQuerySet(QuerySet): super(EmptyQuerySet, self).__init__(model, query) self._result_cache = [] + def __and__(self, other): + return self._clone() + + def __or__(self, other): + return other._clone() + def count(self): return 0 @@ -773,7 +785,7 @@ def delete_objects(seen_objs): except CyclicDependency: # if there is a cyclic dependency, we cannot in general delete # the objects. However, if an appropriate transaction is set - # up, or if the database is lax enough, it will succeed. + # up, or if the database is lax enough, it will succeed. # So for now, we go ahead and try anway. ordered_classes = seen_objs.unordered_keys() diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 3044882a86..e8d10bc55b 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -610,6 +610,10 @@ class Query(object): alias = joins[-1] col = target.column + # Must use left outer joins for nullable fields. + for join in joins: + self.promote_alias(join) + # If we get to this point and the field is a relation to another model, # append the default ordering for that model. if field.rel and len(joins) > 1 and opts.ordering: @@ -631,8 +635,10 @@ class Query(object): # We have to do the same "final join" optimisation as in # add_filter, since the final column might not otherwise be part of # the select set (so we can't order on it). - join = self.alias_map[alias] - if col == join[RHS_JOIN_COL]: + while 1: + join = self.alias_map[alias] + if col != join[RHS_JOIN_COL]: + break self.unref_alias(alias) alias = join[LHS_ALIAS] col = join[LHS_JOIN_COL] @@ -679,12 +685,16 @@ class Query(object): for the join to contain NULL values on the left. If 'unconditional' is False, the join is only promoted if it is nullable, otherwise it is always promoted. + + Returns True if the join was promoted. """ if ((unconditional or self.alias_map[alias][NULLABLE]) and self.alias_map[alias] != self.LOUTER): data = list(self.alias_map[alias]) data[JOIN_TYPE] = self.LOUTER self.alias_map[alias] = tuple(data) + return True + return False def change_aliases(self, change_map): """ @@ -826,6 +836,10 @@ class Query(object): if not always_create: for alias in self.join_map.get(t_ident, ()): if alias not in exclusions: + if lhs_table and not self.alias_refcount[self.alias_map[alias][LHS_ALIAS]]: + # The LHS of this join tuple is no longer part of the + # query, so skip this possibility. + continue self.ref_alias(alias) if promote: self.promote_alias(alias) @@ -985,20 +999,22 @@ class Query(object): col = target.column alias = join_list[-1] - if final > 1: + while final > 1: # An optimization: if the final join is against the same column as # we are comparing against, we can go back one step in the join - # chain and compare against the lhs of the join instead. The result - # (potentially) involves one less table join. + # chain and compare against the lhs of the join instead (and then + # repeat the optimization). The result, potentially, involves less + # table joins. join = self.alias_map[alias] - if col == join[RHS_JOIN_COL]: - self.unref_alias(alias) - alias = join[LHS_ALIAS] - col = join[LHS_JOIN_COL] - join_list = join_list[:-1] - final -= 1 - if final == penultimate: - penultimate = last.pop() + if col != join[RHS_JOIN_COL]: + break + self.unref_alias(alias) + alias = join[LHS_ALIAS] + col = join[LHS_JOIN_COL] + join_list = join_list[:-1] + final -= 1 + if final == penultimate: + penultimate = last.pop() if (lookup_type == 'isnull' and value is True and not negate and final > 1): @@ -1033,17 +1049,27 @@ class Query(object): self.promote_alias(table) self.where.add((alias, col, field, lookup_type, value), connector) + if negate: for alias in join_list: self.promote_alias(alias) - if final > 1 and lookup_type != 'isnull': - for alias in join_list: - if self.alias_map[alias] == self.LOUTER: - j_col = self.alias_map[alias][RHS_JOIN_COL] - entry = Node([(alias, j_col, None, 'isnull', True)]) - entry.negate() - self.where.add(entry, AND) - break + if lookup_type != 'isnull': + if final > 1: + for alias in join_list: + if self.alias_map[alias][JOIN_TYPE] == self.LOUTER: + j_col = self.alias_map[alias][RHS_JOIN_COL] + entry = Node([(alias, j_col, None, 'isnull', True)]) + entry.negate() + self.where.add(entry, AND) + break + elif not (lookup_type == 'in' and not value): + # Leaky abstraction artifact: We have to specifically + # exclude the "foo__in=[]" case from this handling, because + # it's short-circuited in the Where class. + entry = Node([(alias, col, field, 'isnull', True)]) + entry.negate() + self.where.add(entry, AND) + if can_reuse is not None: can_reuse.update(join_list) @@ -1294,10 +1320,12 @@ class Query(object): final_alias = join[LHS_ALIAS] col = join[LHS_JOIN_COL] joins = joins[:-1] + promote = False for join in joins[1:]: # Only nullable aliases are promoted, so we don't end up # doing unnecessary left outer joins here. - self.promote_alias(join) + if self.promote_alias(join, promote): + promote = True self.select.append((final_alias, col)) self.select_fields.append(field) except MultiJoin: diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py index 28436abede..0bb741d706 100644 --- a/django/db/models/sql/subqueries.py +++ b/django/db/models/sql/subqueries.py @@ -357,12 +357,14 @@ class DateQuery(Query): date = typecast_timestamp(str(date)) yield date - def add_date_select(self, column, lookup_type, order='ASC'): + def add_date_select(self, field, lookup_type, order='ASC'): """ Converts the query into a date extraction query. """ - alias = self.join((None, self.model._meta.db_table, None, None)) - select = Date((alias, column), lookup_type, + result = self.setup_joins([field.name], self.get_meta(), + self.get_initial_alias(), False) + alias = result[3][-1] + select = Date((alias, field.column), lookup_type, self.connection.ops.date_trunc_sql) self.select = [select] self.select_fields = [None] diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index cf3b35b4cc..01c43ee86f 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -39,12 +39,11 @@ class CommentNode(Node): class CycleNode(Node): def __init__(self, cyclevars, variable_name=None): - self.cycle_iter = itertools_cycle(cyclevars) + self.cycle_iter = itertools_cycle([Variable(v) for v in cyclevars]) self.variable_name = variable_name def render(self, context): - value = self.cycle_iter.next() - value = Variable(value).resolve(context) + value = self.cycle_iter.next().resolve(context) if self.variable_name: context[self.variable_name] = value return value @@ -162,10 +161,12 @@ class IfChangedNode(Node): self.nodelist = nodelist self._last_seen = None self._varlist = map(Variable, varlist) + self._id = str(id(self)) def render(self, context): - if 'forloop' in context and context['forloop']['first']: + if 'forloop' in context and self._id not in context['forloop']: self._last_seen = None + context['forloop'][self._id] = 1 try: if self._varlist: # Consider multiple parameters. This automatically behaves @@ -452,17 +453,17 @@ def cycle(parser, token): ... ... - You can use any number of values, seperated by spaces. Commas can also + You can use any number of values, separated by spaces. Commas can also be used to separate values; if a comma is used, the cycle values are interpreted as literal strings. """ # Note: This returns the exact same node on each {% cycle name %} call; # that is, the node object returned from {% cycle a b c as name %} and the - # one returned from {% cycle name %} are the exact same object. This + # one returned from {% cycle name %} are the exact same object. This # shouldn't cause problems (heh), but if it does, now you know. # - # Ugly hack warning: this stuffs the named template dict into parser so + # Ugly hack warning: This stuffs the named template dict into parser so # that names are only unique within each template (as opposed to using # a global variable, which would make cycle names have to be unique across # *all* templates. @@ -481,8 +482,7 @@ def cycle(parser, token): # {% cycle foo %} case. name = args[1] if not hasattr(parser, '_namedCycleNodes'): - raise TemplateSyntaxError("No named cycles in template." - " '%s' is not defined" % name) + raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name) if not name in parser._namedCycleNodes: raise TemplateSyntaxError("Named cycle '%s' does not exist" % name) return parser._namedCycleNodes[name] @@ -682,8 +682,10 @@ ifnotequal = register.tag(ifnotequal) def do_if(parser, token): """ The ``{% if %}`` tag evaluates a variable, and if that variable is "true" - (i.e. exists, is not empty, and is not a false boolean value) the contents - of the block are output:: + (i.e., exists, is not empty, and is not a false boolean value), the + contents of the block are output: + + :: {% if athlete_list %} Number of athletes: {{ athlete_list|count }} diff --git a/django/templatetags/cache.py b/django/templatetags/cache.py index 4436b15d6e..07bd590fba 100644 --- a/django/templatetags/cache.py +++ b/django/templatetags/cache.py @@ -1,4 +1,4 @@ -from django.template import Library, Node, TemplateSyntaxError +from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist from django.template import resolve_variable from django.core.cache import cache from django.utils.encoding import force_unicode @@ -6,20 +6,27 @@ from django.utils.encoding import force_unicode register = Library() class CacheNode(Node): - def __init__(self, nodelist, expire_time, fragment_name, vary_on): + def __init__(self, nodelist, expire_time_var, fragment_name, vary_on): self.nodelist = nodelist - self.expire_time = expire_time + self.expire_time_var = Variable(expire_time_var) self.fragment_name = fragment_name self.vary_on = vary_on def render(self, context): + try: + expire_time = self.expire_time_var.resolve(context) + except VariableDoesNotExist: + raise TemplateSyntaxError('"cache" tag got an unknkown variable: %r' % self.expire_time_var.var) + try: + expire_time = int(expire_time) + except (ValueError, TypeError): + raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time) # Build a unicode key for this fragment and all vary-on's. - cache_key = u':'.join([self.fragment_name] + \ - [force_unicode(resolve_variable(var, context)) for var in self.vary_on]) + cache_key = u':'.join([self.fragment_name] + [force_unicode(resolve_variable(var, context)) for var in self.vary_on]) value = cache.get(cache_key) if value is None: value = self.nodelist.render(context) - cache.set(cache_key, value, self.expire_time) + cache.set(cache_key, value, expire_time) return value def do_cache(parser, token): @@ -48,10 +55,6 @@ def do_cache(parser, token): tokens = token.contents.split() if len(tokens) < 3: raise TemplateSyntaxError(u"'%r' tag requires at least 2 arguments." % tokens[0]) - try: - expire_time = int(tokens[1]) - except ValueError: - raise TemplateSyntaxError(u"First argument to '%r' must be an integer (got '%s')." % (tokens[0], tokens[1])) - return CacheNode(nodelist, expire_time, tokens[2], tokens[3:]) + return CacheNode(nodelist, tokens[1], tokens[2], tokens[3:]) register.tag('cache', do_cache) diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 4c278c0d8e..21a72f2d1e 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -343,3 +343,34 @@ class FileDict(dict): d = dict(self, content='') return dict.__repr__(d) return dict.__repr__(self) + +class DictWrapper(dict): + """ + Wraps accesses to a dictionary so that certain values (those starting with + the specified prefix) are passed through a function before being returned. + The prefix is removed before looking up the real value. + + Used by the SQL construction code to ensure that values are correctly + quoted before being used. + """ + def __init__(self, data, func, prefix): + super(DictWrapper, self).__init__(data) + self.func = func + self.prefix = prefix + + def __getitem__(self, key): + """ + Retrieves the real value after stripping the prefix string (if + present). If the prefix is present, pass the value through self.func + before returning, otherwise return the raw value. + """ + if key.startswith(self.prefix): + use_func = True + key = key[len(self.prefix):] + else: + use_func = False + value = super(DictWrapper, self).__getitem__(key) + if use_func: + return self.func(value) + return value + diff --git a/django/utils/html.py b/django/utils/html.py index 07e4f0d3f4..747af52879 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -76,20 +76,20 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False): """ Converts any URLs in text into clickable links. - Works on http://, https://, and www. links. Links can have trailing - punctuation (periods, commas, close-parens) and leading punctuation - (opening parens) and it'll still do the right thing. + Works on http://, https://, www. links and links ending in .org, .net or + .com. Links can have trailing punctuation (periods, commas, close-parens) + and leading punctuation (opening parens) and it'll still do the right + thing. If trim_url_limit is not None, the URLs in link text longer than this limit will truncated to trim_url_limit-3 characters and appended with an elipsis. If nofollow is True, the URLs in link text will get a rel="nofollow" attribute. + + If autoescape is True, the link text and URLs will get autoescaped. """ - if autoescape: - trim_url = lambda x, limit=trim_url_limit: conditional_escape(limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x) - else: - trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x + trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x safe_input = isinstance(text, SafeData) words = word_split_re.split(force_unicode(text)) nofollow_attr = nofollow and ' rel="nofollow"' or '' @@ -97,30 +97,30 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False): match = punctuation_re.match(word) if match: lead, middle, trail = match.groups() - if safe_input: - middle = mark_safe(middle) - if middle.startswith('www.') or ('@' not in middle and not (middle.startswith('http://') or middle.startswith('https://')) and \ - len(middle) > 0 and middle[0] in string.ascii_letters + string.digits and \ - (middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))): - middle = 'http://%s' % middle + # Make URL we want to point to. + url = None if middle.startswith('http://') or middle.startswith('https://'): url = urlquote(middle, safe='/&=:;#?+*') - if autoescape and not safe_input: - url = escape(url) - trimmed_url = trim_url(middle) - middle = '%s' % (url, nofollow_attr, - trimmed_url) - elif '@' in middle and not middle.startswith('www.') and \ - not ':' in middle and simple_email_re.match(middle): - if autoescape: - middle = conditional_escape(middle) - middle = '%s' % (middle, middle) - if lead + middle + trail != word: + elif middle.startswith('www.') or ('@' not in middle and \ + len(middle) > 0 and middle[0] in string.ascii_letters + string.digits and \ + (middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))): + url = urlquote('http://%s' % middle, safe='/&=:;#?+*') + elif '@' in middle and not ':' in middle and simple_email_re.match(middle): + url = 'mailto:%s' % middle + nofollow_attr = '' + # Make link. + if url: + trimmed = trim_url(middle) if autoescape and not safe_input: lead, trail = escape(lead), escape(trail) + url, trimmed = escape(url), escape(trimmed) + middle = '%s' % (url, nofollow_attr, trimmed) words[i] = mark_safe('%s%s%s' % (lead, middle, trail)) - elif autoescape and not safe_input: - words[i] = escape(word) + else: + if safe_input: + words[i] = mark_safe(word) + elif autoescape: + words[i] = escape(word) elif safe_input: words[i] = mark_safe(word) elif autoescape: diff --git a/docs/cache.txt b/docs/cache.txt index e7e1cdd791..3318b2ad4a 100644 --- a/docs/cache.txt +++ b/docs/cache.txt @@ -336,6 +336,17 @@ template tag to uniquely identify the cache fragment:: It's perfectly fine to specify more than one argument to identify the fragment. Simply pass as many arguments to ``{% cache %}`` as you need. +The cache timeout can be a template variable, as long as the template variable +resolves to an integer value. For example, if the template variable +``my_timeout`` is set to the value ``600``, then the following two examples are +equivalent:: + + {% cache 600 sidebar %} ... {% endcache %} + {% cache my_timeout sidebar %} ... {% endcache %} + +This feature is useful in avoiding repetition in templates. You can set the +timeout in a variable, in one place, and just reuse that value. + The low-level cache API ======================= diff --git a/docs/contributing.txt b/docs/contributing.txt index b73e3601bc..686c440c96 100644 --- a/docs/contributing.txt +++ b/docs/contributing.txt @@ -238,14 +238,14 @@ Since a picture is worth a thousand words, let's start there: We've got two official roles here: - * Core developers: people with commit access who make the big decisions + * Core developers: people with commit access who make the big decisions and write the bulk of the code. * Ticket triagers: trusted community members with a proven history of working with the Django community. As a result of this history, they have been entrusted by the core developers to make some of the smaller decisions about tickets. - + Second, note the five triage stages: 1. A ticket starts as "Unreviewed", meaning that nobody has examined @@ -254,7 +254,7 @@ Second, note the five triage stages: 2. "Design decision needed" means "this concept requires a design decision," which should be discussed either in the ticket comments or on `django-developers`_. The "Design decision needed" step will generally - only be used for feature requests. It can also be used for issues + only be used for feature requests. It can also be used for issues that *might* be bugs, depending on opinion or interpretation. Obvious bugs (such as crashes, incorrect query results, or non-compliance with a standard) skip this step and move straight to "Accepted". @@ -317,7 +317,7 @@ A ticket can be resolved in a number of ways: tickets, we keep all the discussion in one place, which helps everyone. "worksforme" - Used when the the ticket doesn't contain enough detail to replicate + Used when the the ticket doesn't contain enough detail to replicate the original bug. If you believe that the ticket was closed in error -- because you're @@ -332,50 +332,49 @@ reopen tickets that have been marked as "wontfix" by core developers. Triage by the general community ------------------------------- -Although the Core Developers and Ticket Triagers make the big decisions in -the ticket triage process, there is also a lot that general community +Although the core developers and ticket triagers make the big decisions in +the ticket triage process, there's also a lot that general community members can do to help the triage process. In particular, you can help out by: - * Closing "Unreviewed" tickets as "invalid", "worksforme", or "duplicate". + * Closing "Unreviewed" tickets as "invalid", "worksforme" or "duplicate." - * Promoting "Unreviewed" tickets to "Design Decision Required" if there - is a design decision that needs to be made, or "Accepted" if they are - an obvious bug. + * Promoting "Unreviewed" tickets to "Design decision needed" if a design + decision needs to be made, or "Accepted" in case of obvious bugs. - * Correcting the "Needs Tests", "Needs documentation", or "Has Patch" flags + * Correcting the "Needs tests", "Needs documentation", or "Has patch" flags for tickets where they are incorrectly set. - + * Checking that old tickets are still valid. If a ticket hasn't seen any activity in a long time, it's possible that the problem has been - fixed, but the ticket hasn't been closed. + fixed but the ticket hasn't yet been closed. - * Contact the owners of tickets that have been claimed, but have not seen - any recent activity. If the owner doesn't respond after a week or so, + * Contacting the owners of tickets that have been claimed but have not seen + any recent activity. If the owner doesn't respond after a week or so, remove the owner's claim on the ticket. * Identifying trends and themes in the tickets. If there a lot of bug reports - about a particular part of Django, it possibly indicates that we need - to consider refactoring that part of the code. If a trend is emerging, - you should raise it for discussion (referencing the relevant tickets) - on `django-developers`_. + about a particular part of Django, it may indicate we should consider + refactoring that part of the code. If a trend is emerging, you should + raise it for discussion (referencing the relevant tickets) on + `django-developers`_. -However, we do ask that as a general community member working in the -ticket database: +However, we do ask the following of all general community members working in +the ticket database: - * Please **don't** close tickets as "wontfix". The core developers will - make the final determination of the fate of a ticket, usually after + * Please **don't** close tickets as "wontfix." The core developers will + make the final determination of the fate of a ticket, usually after consultation with the community. - - * Please **don't** promote tickets to "Ready for checkin" unless they are - *trivial* changes - for example, spelling mistakes or - broken links in documentation. - * Please **don't** reverse a decision that has been made by a core - developer. If you disagree with a discussion that has been made, + * Please **don't** promote tickets to "Ready for checkin" unless they are + *trivial* changes -- for example, spelling mistakes or broken links in + documentation. + + * Please **don't** reverse a decision that has been made by a core + developer. If you disagree with a discussion that has been made, please post a message to `django-developers`_. * Please be conservative in your actions. If you're unsure if you should - be making a change, don't make the change - leave a comment with your + be making a change, don't make the change -- leave a comment with your concerns on the ticket, or post a message to `django-developers`_. Submitting and maintaining translations @@ -739,8 +738,8 @@ If you're using another backend: deleted when the tests are finished. This means your user account needs permission to execute ``CREATE DATABASE``. -If you want to run the full suite of tests, there are a number of dependencies that -you should install: +If you want to run the full suite of tests, you'll need to install a number of +dependencies: * PyYAML_ * Markdown_ @@ -748,10 +747,8 @@ you should install: * Docutils_ * setuptools_ -Of these dependencies, setuptools_ is the only dependency that is required - if -setuptools_ is not installed, you will get import errors when running one of -the template tests. The tests using the other libraries will be skipped if the -dependency can't be found. +Each of these dependencies is optional. If you're missing any of them, the +associated tests will be skipped. .. _PyYAML: http://pyyaml.org/wiki/PyYAML .. _Markdown: http://pypi.python.org/pypi/Markdown/1.7 @@ -773,13 +770,13 @@ for generic relations and internationalization, type:: Contrib apps ------------ -Tests for apps in ``django/contrib/`` go in their respective directories, -in a ``tests.py`` file. (You can split the tests over multiple modules -by using a ``tests`` folder in the normal Python way). +Tests for apps in ``django/contrib/`` go in their respective directories under +``django/contrib/``, in a ``tests.py`` file. (You can split the tests over +multiple modules by using a ``tests`` directory in the normal Python way.) For the tests to be found, a ``models.py`` file must exist (it doesn't -have to have anything in it). If you have URLs that need to be -mapped, you must add them in ``tests/urls.py``. +have to have anything in it). If you have URLs that need to be +mapped, put them in ``tests/urls.py``. To run tests for just one contrib app (e.g. ``markup``), use the same method as above:: diff --git a/docs/db-api.txt b/docs/db-api.txt index 3202bd4f9f..f80d63797a 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -382,7 +382,7 @@ Pickling QuerySets If you pickle_ a ``QuerySet``, this will also force all the results to be loaded into memory prior to pickling. This is because pickling is usually used -as a precursor to caching and when the cached queryset is reloaded, you want +as a precursor to caching and when the cached ``QuerySet`` is reloaded, you want the results to already be present. This means that when you unpickle a ``QuerySet``, it contains the results at the moment it was pickled, rather than the results that are currently in the database. @@ -2040,7 +2040,7 @@ automatically saved to the database. One-to-one relationships ------------------------ -One-to-one relationships are very similar to Many-to-one relationships. +One-to-one relationships are very similar to many-to-one relationships. If you define a OneToOneField on your model, instances of that model will have access to the related object via a simple attribute of the model. @@ -2053,8 +2053,8 @@ For example:: ed = EntryDetail.objects.get(id=2) ed.entry # Returns the related Entry object. -The difference comes in reverse queries. The related model in a One-to-one -relationship also has access to a ``Manager`` object; however, that ``Manager`` +The difference comes in "reverse" queries. The related model in a one-to-one +relationship also has access to a ``Manager`` object, but that ``Manager`` represents a single object, rather than a collection of objects:: e = Entry.objects.get(id=2) diff --git a/docs/model-api.txt b/docs/model-api.txt index 6dc2d0835a..02a9fd2d95 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -1544,7 +1544,7 @@ the ``url`` function):: 'django.views.generic.list_detail.object_detail', name='people_view'), -and then using that name to perform the reverse URL resolution instead +...and then using that name to perform the reverse URL resolution instead of the view name:: from django.db.models import permalink @@ -1553,7 +1553,7 @@ of the view name:: return ('people_view', [str(self.id)]) get_absolute_url = permalink(get_absolute_url) -More details on named URL patterns can be found in `URL dispatch documentation`_. +More details on named URL patterns are in the `URL dispatch documentation`_. .. _URL dispatch documentation: ../url_dispatch/#naming-url-patterns diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py index 7c737b6bd1..9842cb166b 100644 --- a/tests/modeltests/model_inheritance/models.py +++ b/tests/modeltests/model_inheritance/models.py @@ -38,6 +38,29 @@ class Student(CommonInfo): class Meta: pass +# +# Abstract base classes with related models +# + +class Post(models.Model): + title = models.CharField(max_length=50) + +class Attachment(models.Model): + post = models.ForeignKey(Post, related_name='attached_%(class)s_set') + content = models.TextField() + + class Meta: + abstract = True + + def __unicode__(self): + return self.content + +class Comment(Attachment): + is_spam = models.BooleanField() + +class Link(Attachment): + url = models.URLField() + # # Multi-table inheritance # @@ -128,9 +151,25 @@ Traceback (most recent call last): ... AttributeError: type object 'CommonInfo' has no attribute 'objects' -# The Place/Restaurant/ItalianRestaurant models, on the other hand, all exist -# as independent models. However, the subclasses also have transparent access -# to the fields of their ancestors. +# Create a Post +>>> post = Post(title='Lorem Ipsum') +>>> post.save() + +# The Post model has distinct accessors for the Comment and Link models. +>>> post.attached_comment_set.create(content='Save $ on V1agr@', is_spam=True) + +>>> post.attached_link_set.create(content='The Web framework for perfectionists with deadlines.', url='http://www.djangoproject.com/') + + +# The Post model doesn't have an attribute called 'attached_%(class)s_set'. +>>> getattr(post, 'attached_%(class)s_set') +Traceback (most recent call last): + ... +AttributeError: 'Post' object has no attribute 'attached_%(class)s_set' + +# The Place/Restaurant/ItalianRestaurant models all exist as independent +# models. However, the subclasses also have transparent access to the fields of +# their ancestors. # Create a couple of Places. >>> p1 = Place(name='Master Shakes', address='666 W. Jersey') diff --git a/tests/regressiontests/datastructures/tests.py b/tests/regressiontests/datastructures/tests.py index b51b4b1233..d6141b09ce 100644 --- a/tests/regressiontests/datastructures/tests.py +++ b/tests/regressiontests/datastructures/tests.py @@ -125,4 +125,12 @@ Init from sequence of tuples >>> d = FileDict({'other-key': 'once upon a time...'}) >>> repr(d) "{'other-key': 'once upon a time...'}" + +### DictWrapper ############################################################# + +>>> f = lambda x: "*%s" % x +>>> d = DictWrapper({'a': 'a'}, f, 'xx_') +>>> "Normal: %(a)s. Modified: %(xx_a)s" % d +'Normal: a. Modified: *a' + """ diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py index 62403935d4..b56c33a652 100644 --- a/tests/regressiontests/defaultfilters/tests.py +++ b/tests/regressiontests/defaultfilters/tests.py @@ -150,7 +150,7 @@ u'fran%C3%A7ois%20%26%20jill' u'http://short.com/' >>> urlizetrunc(u'http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=', 20) -u'http://www.google....' +u'http://www.google...' >>> urlizetrunc('http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=', 20) u'http://www.google...' @@ -174,10 +174,10 @@ u'http://google.com' u'http://google.com/' >>> urlize('www.google.com') -u'http://www.google.com' +u'www.google.com' >>> urlize('djangoproject.org') -u'http://djangoproject.org' +u'djangoproject.org' >>> urlize('info@djangoproject.org') u'info@djangoproject.org' diff --git a/tests/regressiontests/model_inheritance_regress/models.py b/tests/regressiontests/model_inheritance_regress/models.py index 8801715a0c..33e2e0e4f6 100644 --- a/tests/regressiontests/model_inheritance_regress/models.py +++ b/tests/regressiontests/model_inheritance_regress/models.py @@ -2,6 +2,8 @@ Regression tests for Model inheritance behaviour. """ +import datetime + from django.db import models class Place(models.Model): @@ -10,7 +12,7 @@ class Place(models.Model): class Meta: ordering = ('name',) - + def __unicode__(self): return u"%s the place" % self.name @@ -35,11 +37,17 @@ class ParkingLot(Place): def __unicode__(self): return u"%s the parking lot" % self.name +class Parent(models.Model): + created = models.DateTimeField(default=datetime.datetime.now) + +class Child(Parent): + name = models.CharField(max_length=10) + __test__ = {'API_TESTS':""" # Regression for #7350, #7202 -# Check that when you create a Parent object with a specific reference to an existent -# child instance, saving the Parent doesn't duplicate the child. -# This behaviour is only activated during a raw save - it is mostly relevant to +# Check that when you create a Parent object with a specific reference to an +# existent child instance, saving the Parent doesn't duplicate the child. This +# behaviour is only activated during a raw save - it is mostly relevant to # deserialization, but any sort of CORBA style 'narrow()' API would require a # similar approach. @@ -117,4 +125,10 @@ __test__ = {'API_TESTS':""" >>> [sorted(d.items()) for d in dicts] [[('name', u"Guido's All New House of Pasta"), ('serves_gnocchi', False), ('serves_hot_dogs', False)]] +# Regressions tests for #7105: dates() queries should be able to use fields +# from the parent model as easily as the child. +>>> obj = Child.objects.create(name='child', created=datetime.datetime(2008, 6, 26, 17, 0, 0)) +>>> Child.objects.dates('created', 'month') +[datetime.datetime(2008, 6, 1, 0, 0)] + """} diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py index aa78d6583a..c02ad73998 100644 --- a/tests/regressiontests/queries/models.py +++ b/tests/regressiontests/queries/models.py @@ -45,6 +45,7 @@ class Author(models.Model): class Item(models.Model): name = models.CharField(max_length=10) created = models.DateTimeField() + modified = models.DateTimeField(blank=True, null=True) tags = models.ManyToManyField(Tag, blank=True, null=True) creator = models.ForeignKey(Author) note = models.ForeignKey(Note) @@ -57,7 +58,7 @@ class Item(models.Model): class Report(models.Model): name = models.CharField(max_length=10) - creator = models.ForeignKey(Author, to_field='num') + creator = models.ForeignKey(Author, to_field='num', null=True) def __unicode__(self): return self.name @@ -89,6 +90,15 @@ class Number(models.Model): def __unicode__(self): return unicode(self.num) +# Symmetrical m2m field with a normal field using the reverse accesor name +# ("valid"). +class Valid(models.Model): + valid = models.CharField(max_length=10) + parent = models.ManyToManyField('self') + + class Meta: + ordering = ['valid'] + # Some funky cross-linked models for testing a couple of infinite recursion # cases. class X(models.Model): @@ -121,12 +131,12 @@ class LoopZ(models.Model): class CustomManager(models.Manager): def get_query_set(self): qs = super(CustomManager, self).get_query_set() - return qs.filter(is_public=True, tag__name='t1') + return qs.filter(public=True, tag__name='t1') class ManagedModel(models.Model): data = models.CharField(max_length=10) tag = models.ForeignKey(Tag) - is_public = models.BooleanField(default=True) + public = models.BooleanField(default=True) objects = CustomManager() normal_manager = models.Manager() @@ -134,6 +144,24 @@ class ManagedModel(models.Model): def __unicode__(self): return self.data +# An inter-related setup with multiple paths from Child to Detail. +class Detail(models.Model): + data = models.CharField(max_length=10) + +class MemberManager(models.Manager): + def get_query_set(self): + return super(MemberManager, self).get_query_set().select_related("details") + +class Member(models.Model): + name = models.CharField(max_length=10) + details = models.OneToOneField(Detail, primary_key=True) + + objects = MemberManager() + +class Child(models.Model): + person = models.OneToOneField(Member, primary_key=True) + parent = models.ForeignKey(Member, related_name="children") + __test__ = {'API_TESTS':""" >>> t1 = Tag(name='t1') @@ -174,7 +202,7 @@ by 'info'. Helps detect some problems later. >>> time2 = datetime.datetime(2007, 12, 19, 21, 0, 0) >>> time3 = datetime.datetime(2007, 12, 20, 22, 25, 0) >>> time4 = datetime.datetime(2007, 12, 20, 21, 0, 0) ->>> i1 = Item(name='one', created=time1, creator=a1, note=n3) +>>> i1 = Item(name='one', created=time1, modified=time1, creator=a1, note=n3) >>> i1.save() >>> i1.tags = [t1, t2] >>> i2 = Item(name='two', created=time2, creator=a2, note=n2) @@ -190,6 +218,8 @@ by 'info'. Helps detect some problems later. >>> r1.save() >>> r2 = Report(name='r2', creator=a3) >>> r2.save() +>>> r3 = Report(name='r3') +>>> r3.save() Ordering by 'rank' gives us rank2, rank1, rank3. Ordering by the Meta.ordering will be rank3, rank2, rank1. @@ -478,7 +508,7 @@ FieldError: Infinite loop caused by ordering. # Ordering by a many-valued attribute (e.g. a many-to-many or reverse # ForeignKey) is legal, but the results might not make sense. That isn't # Django's problem. Garbage in, garbage out. ->>> Item.objects.all().order_by('tags', 'id') +>>> Item.objects.filter(tags__isnull=False).order_by('tags', 'id') [, , , , ] # If we replace the default ordering, Django adjusts the required tables @@ -627,6 +657,10 @@ Bug #7087 -- dates with extra select columns >>> Item.objects.dates('created', 'day').extra(select={'a': 1}) [datetime.datetime(2007, 12, 19, 0, 0), datetime.datetime(2007, 12, 20, 0, 0)] +Bug #7155 -- nullable dates +>>> Item.objects.dates('modified', 'day') +[datetime.datetime(2007, 12, 19, 0, 0)] + Test that parallel iterators work. >>> qs = Tag.objects.all() @@ -705,8 +739,57 @@ More twisted cases, involving nested negations. Bug #7095 Updates that are filtered on the model being updated are somewhat tricky to get in MySQL. This exercises that case. ->>> mm = ManagedModel.objects.create(data='mm1', tag=t1, is_public=True) +>>> mm = ManagedModel.objects.create(data='mm1', tag=t1, public=True) >>> ManagedModel.objects.update(data='mm') +A values() or values_list() query across joined models must use outer joins +appropriately. +>>> Report.objects.values_list("creator__extra__info", flat=True).order_by("name") +[u'e1', u'e2', None] + +Similarly for select_related(), joins beyond an initial nullable join must +use outer joins so that all results are included. +>>> Report.objects.select_related("creator", "creator__extra").order_by("name") +[, , ] + +When there are multiple paths to a table from another table, we have to be +careful not to accidentally reuse an inappropriate join when using +select_related(). We used to return the parent's Detail record here by mistake. + +>>> d1 = Detail.objects.create(data="d1") +>>> d2 = Detail.objects.create(data="d2") +>>> m1 = Member.objects.create(name="m1", details=d1) +>>> m2 = Member.objects.create(name="m2", details=d2) +>>> c1 = Child.objects.create(person=m2, parent=m1) +>>> obj = m1.children.select_related("person__details")[0] +>>> obj.person.details.data +u'd2' + +Bug #7076 -- excluding shouldn't eliminate NULL entries. +>>> Item.objects.exclude(modified=time1).order_by('name') +[, , ] +>>> Tag.objects.exclude(parent__name=t1.name) +[, , ] + +Bug #7181 -- ordering by related tables should accomodate nullable fields (this +test is a little tricky, since NULL ordering is database dependent. Instead, we +just count the number of results). +>>> len(Tag.objects.order_by('parent__name')) +5 + +Bug #7107 -- this shouldn't create an infinite loop. +>>> Valid.objects.all() +[] + +Empty querysets can be merged with others. +>>> Note.objects.none() | Note.objects.all() +[, , ] +>>> Note.objects.all() | Note.objects.none() +[, , ] +>>> Note.objects.none() & Note.objects.all() +[] +>>> Note.objects.all() & Note.objects.none() +[] + """} diff --git a/tests/regressiontests/templates/loaders.py b/tests/regressiontests/templates/loaders.py index 82e3c622d1..db37116b94 100644 --- a/tests/regressiontests/templates/loaders.py +++ b/tests/regressiontests/templates/loaders.py @@ -1,6 +1,7 @@ -# -*- coding: utf-8 -*- """ Test cases for the template loaders + +Note: This test requires setuptools! """ from django.conf import settings @@ -17,7 +18,7 @@ import StringIO from django.template import TemplateDoesNotExist from django.template.loaders.eggs import load_template_source as lts_egg -#Mock classes and objects for pkg_resources functions +# Mock classes and objects for pkg_resources functions. class MockProvider(pkg_resources.NullProvider): def __init__(self, module): pkg_resources.NullProvider.__init__(self, module) @@ -35,25 +36,25 @@ class MockProvider(pkg_resources.NullProvider): def _get(self, path): return self.module._resources[path].read() -class MockLoader(object): pass +class MockLoader(object): + pass def create_egg(name, resources): """ - Creates a mock egg with a list of resources - - name: The name of the module - resources: A dictionary of resources. Keys are the names and values the the data. + Creates a mock egg with a list of resources. + + name: The name of the module. + resources: A dictionary of resources. Keys are the names and values the the data. """ egg = imp.new_module(name) egg.__loader__ = MockLoader() egg._resources = resources sys.modules[name] = egg - class EggLoader(unittest.TestCase): def setUp(self): pkg_resources._provider_factories[MockLoader] = MockProvider - + self.empty_egg = create_egg("egg_empty", {}) self.egg_1 = create_egg("egg_1", { 'templates/y.html' : StringIO.StringIO("y"), @@ -61,7 +62,7 @@ class EggLoader(unittest.TestCase): }) self._old_installed_apps = settings.INSTALLED_APPS settings.INSTALLED_APPS = [] - + def tearDown(self): settings.INSTALLED_APPS = self._old_installed_apps @@ -74,19 +75,18 @@ class EggLoader(unittest.TestCase): "Template loading fails if the template is not in the egg" settings.INSTALLED_APPS = ['egg_1'] self.assertRaises(TemplateDoesNotExist, lts_egg, "not-existing.html") - + def test_existing(self): "A template can be loaded from an egg" settings.INSTALLED_APPS = ['egg_1'] contents, template_name = lts_egg("y.html") self.assertEqual(contents, "y") self.assertEqual(template_name, "egg:egg_1:templates/y.html") - + def test_not_installed(self): "Loading an existent template from an egg not included in INSTALLED_APPS should fail" settings.INSTALLED_APPS = [] self.assertRaises(TemplateDoesNotExist, lts_egg, "y.html") - if __name__ == "__main__": unittest.main() diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 089a6d312d..186b8aacb5 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -20,7 +20,10 @@ from django.utils.tzinfo import LocalTimezone from unicode import unicode_tests from context import context_tests -from loaders import * +try: + from loaders import * +except ImportError: + pass # If setuptools isn't installed, that's fine. Just move on. import filters @@ -132,8 +135,7 @@ class Templates(unittest.TestCase): # Quickly check that we aren't accidentally using a name in both # template and filter tests. - overlapping_names = [name for name in filter_tests if name in - template_tests] + overlapping_names = [name for name in filter_tests if name in template_tests] assert not overlapping_names, 'Duplicate test name(s): %s' % ', '.join(overlapping_names) template_tests.update(filter_tests) @@ -156,7 +158,7 @@ class Templates(unittest.TestCase): # Turn TEMPLATE_DEBUG off, because tests assume that. old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False - # Set TEMPLATE_STRING_IF_INVALID to a known string + # Set TEMPLATE_STRING_IF_INVALID to a known string. old_invalid = settings.TEMPLATE_STRING_IF_INVALID expected_invalid_str = 'INVALID' @@ -539,13 +541,14 @@ class Templates(unittest.TestCase): 'if-tag-error05': ("{% if not foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError), ### IFCHANGED TAG ######################################################### - 'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,2,3) }, '123'), - 'ifchanged02': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,3) }, '13'), - 'ifchanged03': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,1) }, '1'), - 'ifchanged04': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 2, 3), 'numx': (2, 2, 2)}, '122232'), - 'ifchanged05': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (1, 2, 3)}, '1123123123'), - 'ifchanged06': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2)}, '1222'), - 'ifchanged07': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% for y in numy %}{% ifchanged %}{{ y }}{% endifchanged %}{% endfor %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2), 'numy': (3, 3, 3)}, '1233323332333'), + 'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', {'num': (1,2,3)}, '123'), + 'ifchanged02': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', {'num': (1,1,3)}, '13'), + 'ifchanged03': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', {'num': (1,1,1)}, '1'), + 'ifchanged04': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', {'num': (1, 2, 3), 'numx': (2, 2, 2)}, '122232'), + 'ifchanged05': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', {'num': (1, 1, 1), 'numx': (1, 2, 3)}, '1123123123'), + 'ifchanged06': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', {'num': (1, 1, 1), 'numx': (2, 2, 2)}, '1222'), + 'ifchanged07': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% for y in numy %}{% ifchanged %}{{ y }}{% endifchanged %}{% endfor %}{% endfor %}{% endfor %}', {'num': (1, 1, 1), 'numx': (2, 2, 2), 'numy': (3, 3, 3)}, '1233323332333'), + 'ifchanged08': ('{% for data in datalist %}{% for c,d in data %}{% if c %}{% ifchanged %}{{ d }}{% endifchanged %}{% endif %}{% endfor %}{% endfor %}', {'datalist': [[(1, 'a'), (1, 'a'), (0, 'b'), (1, 'c')], [(0, 'a'), (1, 'c'), (1, 'd'), (1, 'd'), (0, 'e')]]}, 'accd'), # Test one parameter given to ifchanged. 'ifchanged-param01': ('{% for n in num %}{% ifchanged n %}..{% endifchanged %}{{ n }}{% endfor %}', { 'num': (1,2,3) }, '..1..2..3'), @@ -860,40 +863,46 @@ class Templates(unittest.TestCase): ### NOW TAG ######################################################## # Simple case - 'now01' : ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)), + 'now01': ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)), # Check parsing of escaped and special characters - 'now02' : ('{% now "j "n" Y"%}', {}, template.TemplateSyntaxError), - # 'now03' : ('{% now "j \"n\" Y"%}', {}, str(datetime.now().day) + '"' + str(datetime.now().month) + '"' + str(datetime.now().year)), - # 'now04' : ('{% now "j \nn\n Y"%}', {}, str(datetime.now().day) + '\n' + str(datetime.now().month) + '\n' + str(datetime.now().year)) + 'now02': ('{% now "j "n" Y"%}', {}, template.TemplateSyntaxError), + # 'now03': ('{% now "j \"n\" Y"%}', {}, str(datetime.now().day) + '"' + str(datetime.now().month) + '"' + str(datetime.now().year)), + # 'now04': ('{% now "j \nn\n Y"%}', {}, str(datetime.now().day) + '\n' + str(datetime.now().month) + '\n' + str(datetime.now().year)) ### URL TAG ######################################################## # Successes - 'url01' : ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'), - 'url02' : ('{% url regressiontests.templates.views.client_action client.id, action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'), - 'url03' : ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'), - 'url04' : ('{% url named.client client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'), - 'url05' : (u'{% url метка_оператора v %}', {'v': u'Ω'}, - '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'), + 'url01': ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'), + 'url02': ('{% url regressiontests.templates.views.client_action client.id, action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'), + 'url03': ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'), + 'url04': ('{% url named.client client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'), + 'url05': (u'{% url метка_оператора v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'), # Failures - 'url-fail01' : ('{% url %}', {}, template.TemplateSyntaxError), - 'url-fail02' : ('{% url no_such_view %}', {}, ''), - 'url-fail03' : ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''), + 'url-fail01': ('{% url %}', {}, template.TemplateSyntaxError), + 'url-fail02': ('{% url no_such_view %}', {}, ''), + 'url-fail03': ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''), ### CACHE TAG ###################################################### - 'cache01' : ('{% load cache %}{% cache -1 test %}cache01{% endcache %}', {}, 'cache01'), - 'cache02' : ('{% load cache %}{% cache -1 test %}cache02{% endcache %}', {}, 'cache02'), - 'cache03' : ('{% load cache %}{% cache 2 test %}cache03{% endcache %}', {}, 'cache03'), - 'cache04' : ('{% load cache %}{% cache 2 test %}cache04{% endcache %}', {}, 'cache03'), - 'cache05' : ('{% load cache %}{% cache 2 test foo %}cache05{% endcache %}', {'foo': 1}, 'cache05'), - 'cache06' : ('{% load cache %}{% cache 2 test foo %}cache06{% endcache %}', {'foo': 2}, 'cache06'), - 'cache07' : ('{% load cache %}{% cache 2 test foo %}cache06{% endcache %}', {'foo': 1}, 'cache05'), + 'cache01': ('{% load cache %}{% cache -1 test %}cache01{% endcache %}', {}, 'cache01'), + 'cache02': ('{% load cache %}{% cache -1 test %}cache02{% endcache %}', {}, 'cache02'), + 'cache03': ('{% load cache %}{% cache 2 test %}cache03{% endcache %}', {}, 'cache03'), + 'cache04': ('{% load cache %}{% cache 2 test %}cache04{% endcache %}', {}, 'cache03'), + 'cache05': ('{% load cache %}{% cache 2 test foo %}cache05{% endcache %}', {'foo': 1}, 'cache05'), + 'cache06': ('{% load cache %}{% cache 2 test foo %}cache06{% endcache %}', {'foo': 2}, 'cache06'), + 'cache07': ('{% load cache %}{% cache 2 test foo %}cache07{% endcache %}', {'foo': 1}, 'cache05'), - # Raise exception if we dont have at least 2 args, first one integer. - 'cache08' : ('{% load cache %}{% cache %}{% endcache %}', {}, template.TemplateSyntaxError), - 'cache09' : ('{% load cache %}{% cache 1 %}{% endcache %}', {}, template.TemplateSyntaxError), - 'cache10' : ('{% load cache %}{% cache foo bar %}{% endcache %}', {}, template.TemplateSyntaxError), + # Allow first argument to be a variable. + 'cache08': ('{% load cache %}{% cache time test foo %}cache08{% endcache %}', {'foo': 2, 'time': 2}, 'cache06'), + 'cache09': ('{% load cache %}{% cache time test foo %}cache09{% endcache %}', {'foo': 3, 'time': -1}, 'cache09'), + 'cache10': ('{% load cache %}{% cache time test foo %}cache10{% endcache %}', {'foo': 3, 'time': -1}, 'cache10'), + + # Raise exception if we don't have at least 2 args, first one integer. + 'cache11': ('{% load cache %}{% cache %}{% endcache %}', {}, template.TemplateSyntaxError), + 'cache12': ('{% load cache %}{% cache 1 %}{% endcache %}', {}, template.TemplateSyntaxError), + 'cache13': ('{% load cache %}{% cache foo bar %}{% endcache %}', {}, template.TemplateSyntaxError), + 'cache14': ('{% load cache %}{% cache foo bar %}{% endcache %}', {'foo': 'fail'}, template.TemplateSyntaxError), + 'cache15': ('{% load cache %}{% cache foo bar %}{% endcache %}', {'foo': []}, template.TemplateSyntaxError), ### AUTOESCAPE TAG ############################################## 'autoescape-tag01': ("{% autoescape off %}hello{% endautoescape %}", {}, "hello"),