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

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

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

View File

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

View File

@ -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 <django@icomputing.pl>\n"
"Last-Translator: Jarek Zgoda <jarek.zgoda@gmail.com>\n"
"Language-Team: Polish <pl@li.org>\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 <a href=\"password/\">change "
"password form</a>."
@ -1168,19 +1176,19 @@ msgstr ""
"Użyj '[algo]$[salt]$[hexdigest]' lub <a href=\"password/\">formularza zmiany "
"hasła</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."

View File

@ -1,12 +1,13 @@
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.forms import 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:
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()

View File

@ -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
<Site: example.com>
>>> isinstance(s, Site)
True
>>> s.delete()
>>> Site.objects.get_current()

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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',

View File

@ -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

View File

@ -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,6 +121,7 @@ class RelatedField(object):
def do_related_class(self, other, cls):
self.set_attributes_from_rel()
related = RelatedObject(other, cls, self)
if not cls._meta.abstract:
self.contribute_to_related_class(other, related)
def get_db_prep_lookup(self, lookup_type, value):

View File

@ -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():

View File

@ -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):
@ -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

View File

@ -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).
while 1:
join = self.alias_map[alias]
if col == join[RHS_JOIN_COL]:
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,13 +999,15 @@ 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]:
if col != join[RHS_JOIN_COL]:
break
self.unref_alias(alias)
alias = join[LHS_ALIAS]
col = join[LHS_JOIN_COL]
@ -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':
if lookup_type != 'isnull':
if final > 1:
for alias in join_list:
if self.alias_map[alias] == self.LOUTER:
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:

View File

@ -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]

View File

@ -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,7 +453,7 @@ def cycle(parser, token):
<tr class="{% cycle rowcolors %}">...</tr>
<tr class="{% cycle rowcolors %}">...</tr>
You can use any number of values, seperated by spaces. Commas can also
You can use any number of values, separated by spaces. Commas can also
be used to separate values; if a comma is used, the cycle values are
interpreted as literal strings.
"""
@ -462,7 +463,7 @@ def cycle(parser, token):
# 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 }}

View File

@ -1,4 +1,4 @@
from django.template import Library, Node, TemplateSyntaxError
from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist
from django.template import resolve_variable
from django.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)

View File

@ -343,3 +343,34 @@ class FileDict(dict):
d = dict(self, content='<omitted>')
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

View File

@ -76,19 +76,19 @@ 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
safe_input = isinstance(text, SafeData)
words = word_split_re.split(force_unicode(text))
@ -97,29 +97,29 @@ 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 = '<a href="%s"%s>%s</a>' % (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 = '<a href="mailto:%s">%s</a>' % (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 = '<a href="%s"%s>%s</a>' % (url, nofollow_attr, trimmed)
words[i] = mark_safe('%s%s%s' % (lead, middle, trail))
elif autoescape and not safe_input:
else:
if safe_input:
words[i] = mark_safe(word)
elif autoescape:
words[i] = escape(word)
elif safe_input:
words[i] = mark_safe(word)

View File

@ -336,6 +336,17 @@ template tag to uniquely identify the cache fragment::
It's perfectly fine to specify more than one argument to identify the fragment.
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
=======================

View File

@ -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
* 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
* 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.
*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``.
mapped, put them in ``tests/urls.py``.
To run tests for just one contrib app (e.g. ``markup``), use the same
method as above::

View File

@ -382,7 +382,7 @@ Pickling QuerySets
If you pickle_ a ``QuerySet``, this will also force all the results to be
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)

View File

@ -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

View File

@ -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)
<Comment: Save $ on V1agr@>
>>> post.attached_link_set.create(content='The Web framework for perfectionists with deadlines.', url='http://www.djangoproject.com/')
<Link: The Web framework for perfectionists with deadlines.>
# The Post model doesn't have an attribute called 'attached_%(class)s_set'.
>>> getattr(post, 'attached_%(class)s_set')
Traceback (most recent call last):
...
AttributeError: 'Post' object has no attribute 'attached_%(class)s_set'
# The Place/Restaurant/ItalianRestaurant models all exist as independent
# models. However, the subclasses also have transparent access to the fields of
# their ancestors.
# Create a couple of Places.
>>> p1 = Place(name='Master Shakes', address='666 W. Jersey')

View File

@ -125,4 +125,12 @@ Init from sequence of tuples
>>> d = FileDict({'other-key': 'once upon a time...'})
>>> 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'
"""

View File

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

View File

@ -2,6 +2,8 @@
Regression tests for Model inheritance behaviour.
"""
import datetime
from django.db import models
class Place(models.Model):
@ -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)]
"""}

View File

@ -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')
[<Item: one>, <Item: two>, <Item: one>, <Item: two>, <Item: four>]
# 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")
[<Report: r1>, <Report: r2>, <Report: r3>]
When there are multiple paths to a table from another table, we have to be
careful not to accidentally reuse an inappropriate join when using
select_related(). We used to return the parent's Detail record here by mistake.
>>> d1 = Detail.objects.create(data="d1")
>>> d2 = Detail.objects.create(data="d2")
>>> m1 = Member.objects.create(name="m1", details=d1)
>>> m2 = Member.objects.create(name="m2", details=d2)
>>> c1 = Child.objects.create(person=m2, parent=m1)
>>> obj = m1.children.select_related("person__details")[0]
>>> obj.person.details.data
u'd2'
Bug #7076 -- excluding shouldn't eliminate NULL entries.
>>> Item.objects.exclude(modified=time1).order_by('name')
[<Item: four>, <Item: three>, <Item: two>]
>>> Tag.objects.exclude(parent__name=t1.name)
[<Tag: t1>, <Tag: t4>, <Tag: t5>]
Bug #7181 -- ordering by related tables should accomodate nullable fields (this
test is a little tricky, since NULL ordering is database dependent. Instead, we
just count the number of results).
>>> len(Tag.objects.order_by('parent__name'))
5
Bug #7107 -- this shouldn't create an infinite loop.
>>> Valid.objects.all()
[]
Empty querysets can be merged with others.
>>> Note.objects.none() | Note.objects.all()
[<Note: n1>, <Note: n2>, <Note: n3>]
>>> Note.objects.all() | Note.objects.none()
[<Note: n1>, <Note: n2>, <Note: n3>]
>>> Note.objects.none() & Note.objects.all()
[]
>>> Note.objects.all() & Note.objects.none()
[]
"""}

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
"""
Test cases for the template loaders
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,13 +36,14 @@ 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
Creates a mock egg with a list of resources.
name: The name of the module
name: The name of the module.
resources: A dictionary of resources. Keys are the names and values the the data.
"""
egg = imp.new_module(name)
@ -49,7 +51,6 @@ def create_egg(name, resources):
egg._resources = resources
sys.modules[name] = egg
class EggLoader(unittest.TestCase):
def setUp(self):
pkg_resources._provider_factories[MockLoader] = MockProvider
@ -87,6 +88,5 @@ class EggLoader(unittest.TestCase):
settings.INSTALLED_APPS = []
self.assertRaises(TemplateDoesNotExist, lts_egg, "y.html")
if __name__ == "__main__":
unittest.main()

View File

@ -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"),