1
0
mirror of https://github.com/django/django.git synced 2025-07-04 17:59:13 +00:00

boulder-oracle-sprint: Merged to [5462]

git-svn-id: http://code.djangoproject.com/svn/django/branches/boulder-oracle-sprint@5463 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Boulder Sprinters 2007-06-11 15:53:42 +00:00
parent 5e8eef19dc
commit 0f36cbec13
23 changed files with 496 additions and 85 deletions

View File

@ -73,6 +73,7 @@ answer newbie questions, and generally made Django that much better:
Michal Chruszcz <troll@pld-linux.org> Michal Chruszcz <troll@pld-linux.org>
Ian Clelland <clelland@gmail.com> Ian Clelland <clelland@gmail.com>
crankycoder@gmail.com crankycoder@gmail.com
Pete Crosier <pete.crosier@gmail.com>
Matt Croydon <http://www.postneo.com/> Matt Croydon <http://www.postneo.com/>
flavio.curella@gmail.com flavio.curella@gmail.com
Jure Cuhalev <gandalf@owca.info> Jure Cuhalev <gandalf@owca.info>
@ -130,6 +131,7 @@ answer newbie questions, and generally made Django that much better:
junzhang.jn@gmail.com junzhang.jn@gmail.com
Antti Kaihola <http://akaihola.blogspot.com/> Antti Kaihola <http://akaihola.blogspot.com/>
Ben Dean Kawamura <ben.dean.kawamura@gmail.com> Ben Dean Kawamura <ben.dean.kawamura@gmail.com>
ian.g.kelly@gmail.com
Garth Kidd <http://www.deadlybloodyserious.com/> Garth Kidd <http://www.deadlybloodyserious.com/>
kilian <kilian.cavalotti@lip6.fr> kilian <kilian.cavalotti@lip6.fr>
Sune Kirkeby <http://ibofobi.dk/> Sune Kirkeby <http://ibofobi.dk/>
@ -169,6 +171,7 @@ answer newbie questions, and generally made Django that much better:
mitakummaa@gmail.com mitakummaa@gmail.com
mmarshall mmarshall
Eric Moritz <http://eric.themoritzfamily.com/> Eric Moritz <http://eric.themoritzfamily.com/>
mrmachine <real.human@mrmachine.net>
Robin Munn <http://www.geekforgod.com/> Robin Munn <http://www.geekforgod.com/>
Robert Myers <myer0052@gmail.com> Robert Myers <myer0052@gmail.com>
Nebojša Dorđević Nebojša Dorđević
@ -234,6 +237,7 @@ answer newbie questions, and generally made Django that much better:
wangchun <yaohua2000@gmail.com> wangchun <yaohua2000@gmail.com>
Dan Watson <http://theidioteque.net/> Dan Watson <http://theidioteque.net/>
Chris Wesseling <Chris.Wesseling@cwi.nl> Chris Wesseling <Chris.Wesseling@cwi.nl>
James Wheare <django@sparemint.com>
charly.wilhelm@gmail.com charly.wilhelm@gmail.com
Rachel Willmer <http://www.willmer.com/kb/> Rachel Willmer <http://www.willmer.com/kb/>
Gary Wilson <gary.wilson@gmail.com> Gary Wilson <gary.wilson@gmail.com>

View File

@ -1 +1,8 @@
VERSION = (0, 97, 'pre') VERSION = (0, 97, 'pre')
def get_version():
"Returns the version as a human-format string."
v = '.'.join([str(i) for i in VERSION[:-1]])
if VERSION[-1]:
v += '-' + VERSION[-1]
return v

View File

@ -241,7 +241,8 @@ TRANSACTIONS_MANAGED = False
# The User-Agent string to use when checking for URL validity through the # The User-Agent string to use when checking for URL validity through the
# isExistingURL validator. # isExistingURL validator.
URL_VALIDATOR_USER_AGENT = "Django/0.96pre (http://www.djangoproject.com)" from django import get_version
URL_VALIDATOR_USER_AGENT = "Django/%s (http://www.djangoproject.com)" % get_version()
############## ##############
# MIDDLEWARE # # MIDDLEWARE #

View File

@ -2202,10 +2202,11 @@ msgstr[0] "dag"
msgstr[1] "dagen" msgstr[1] "dagen"
#: utils/timesince.py:16 #: utils/timesince.py:16
# In the timesince context it is stilistically wrong to use the plural for hour in Dutch.
msgid "hour" msgid "hour"
msgid_plural "hours" msgid_plural "hours"
msgstr[0] "uur" msgstr[0] "uur"
msgstr[1] "uren" msgstr[1] "uur"
#: utils/timesince.py:17 #: utils/timesince.py:17
msgid "minute" msgid "minute"

View File

@ -3,14 +3,17 @@
import django import django
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
import os, re, shutil, sys, textwrap
from optparse import OptionParser from optparse import OptionParser
from django.utils import termcolors from django.utils import termcolors
import os, re, shutil, sys, textwrap
# For Python 2.3 # For Python 2.3
if not hasattr(__builtins__, 'set'): if not hasattr(__builtins__, 'set'):
from sets import Set as set from sets import Set as set
# For backwards compatibility: get_version() used to be in this module.
get_version = django.get_version
MODULE_TEMPLATE = ''' {%% if perms.%(app)s.%(addperm)s or perms.%(app)s.%(changeperm)s %%} MODULE_TEMPLATE = ''' {%% if perms.%(app)s.%(addperm)s or perms.%(app)s.%(changeperm)s %%}
<tr> <tr>
<th>{%% if perms.%(app)s.%(changeperm)s %%}<a href="%(app)s/%(mod)s/">{%% endif %%}%(name)s{%% if perms.%(app)s.%(changeperm)s %%}</a>{%% endif %%}</th> <th>{%% if perms.%(app)s.%(changeperm)s %%}<a href="%(app)s/%(mod)s/">{%% endif %%}%(name)s{%% if perms.%(app)s.%(changeperm)s %%}</a>{%% endif %%}</th>
@ -97,14 +100,6 @@ def _get_sequence_list():
# field as the field to which it points. # field as the field to which it points.
get_rel_data_type = lambda f: (f.get_internal_type() in ('AutoField', 'PositiveIntegerField', 'PositiveSmallIntegerField')) and 'IntegerField' or f.get_internal_type() get_rel_data_type = lambda f: (f.get_internal_type() in ('AutoField', 'PositiveIntegerField', 'PositiveSmallIntegerField')) and 'IntegerField' or f.get_internal_type()
def get_version():
"Returns the version as a human-format string."
from django import VERSION
v = '.'.join([str(i) for i in VERSION[:-1]])
if VERSION[-1]:
v += '-' + VERSION[-1]
return v
def get_sql_create(app): def get_sql_create(app):
"Returns a list of the CREATE TABLE SQL statements for the given app." "Returns a list of the CREATE TABLE SQL statements for the given app."
from django.db import get_creation_module, models from django.db import get_creation_module, models

View File

@ -238,22 +238,27 @@ def get_sql_sequence_reset(style, model_list):
from django.db import models from django.db import models
output = [] output = []
for model in model_list: for model in model_list:
# Use `coalesce` to set the sequence for each model to the max pk value if there are records,
# or 1 if there are none. Set the `is_called` property (the third argument to `setval`) to true
# if there are records (as the max pk value is already in use), otherwise set it to false.
for f in model._meta.fields: for f in model._meta.fields:
if isinstance(f, models.AutoField): if isinstance(f, models.AutoField):
output.append("%s setval('%s', (%s max(%s) %s %s));" % \ output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
(style.SQL_KEYWORD('SELECT'), (style.SQL_KEYWORD('SELECT'),
style.SQL_FIELD(quote_name('%s_%s_seq' % (model._meta.db_table, f.column))), style.SQL_FIELD(quote_name('%s_%s_seq' % (model._meta.db_table, f.column))),
style.SQL_KEYWORD('SELECT'),
style.SQL_FIELD(quote_name(f.column)), style.SQL_FIELD(quote_name(f.column)),
style.SQL_FIELD(quote_name(f.column)),
style.SQL_KEYWORD('IS NOT'),
style.SQL_KEYWORD('FROM'), style.SQL_KEYWORD('FROM'),
style.SQL_TABLE(quote_name(model._meta.db_table)))) style.SQL_TABLE(quote_name(model._meta.db_table))))
break # Only one AutoField is allowed per model, so don't bother continuing. break # Only one AutoField is allowed per model, so don't bother continuing.
for f in model._meta.many_to_many: for f in model._meta.many_to_many:
output.append("%s setval('%s', (%s max(%s) %s %s));" % \ output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
(style.SQL_KEYWORD('SELECT'), (style.SQL_KEYWORD('SELECT'),
style.SQL_FIELD(quote_name('%s_id_seq' % f.m2m_db_table())), style.SQL_FIELD(quote_name('%s_id_seq' % f.m2m_db_table())),
style.SQL_KEYWORD('SELECT'),
style.SQL_FIELD(quote_name('id')), style.SQL_FIELD(quote_name('id')),
style.SQL_FIELD(quote_name('id')),
style.SQL_KEYWORD('IS NOT'),
style.SQL_KEYWORD('FROM'), style.SQL_KEYWORD('FROM'),
style.SQL_TABLE(f.m2m_db_table()))) style.SQL_TABLE(f.m2m_db_table())))
return output return output

View File

@ -195,22 +195,27 @@ def get_sql_sequence_reset(style, model_list):
from django.db import models from django.db import models
output = [] output = []
for model in model_list: for model in model_list:
# Use `coalesce` to set the sequence for each model to the max pk value if there are records,
# or 1 if there are none. Set the `is_called` property (the third argument to `setval`) to true
# if there are records (as the max pk value is already in use), otherwise set it to false.
for f in model._meta.fields: for f in model._meta.fields:
if isinstance(f, models.AutoField): if isinstance(f, models.AutoField):
output.append("%s setval('%s', (%s max(%s) %s %s));" % \ output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
(style.SQL_KEYWORD('SELECT'), (style.SQL_KEYWORD('SELECT'),
style.SQL_FIELD(quote_name('%s_%s_seq' % (model._meta.db_table, f.column))), style.SQL_FIELD(quote_name('%s_%s_seq' % (model._meta.db_table, f.column))),
style.SQL_KEYWORD('SELECT'),
style.SQL_FIELD(quote_name(f.column)), style.SQL_FIELD(quote_name(f.column)),
style.SQL_FIELD(quote_name(f.column)),
style.SQL_KEYWORD('IS NOT'),
style.SQL_KEYWORD('FROM'), style.SQL_KEYWORD('FROM'),
style.SQL_TABLE(quote_name(model._meta.db_table)))) style.SQL_TABLE(quote_name(model._meta.db_table))))
break # Only one AutoField is allowed per model, so don't bother continuing. break # Only one AutoField is allowed per model, so don't bother continuing.
for f in model._meta.many_to_many: for f in model._meta.many_to_many:
output.append("%s setval('%s', (%s max(%s) %s %s));" % \ output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
(style.SQL_KEYWORD('SELECT'), (style.SQL_KEYWORD('SELECT'),
style.SQL_FIELD(quote_name('%s_id_seq' % f.m2m_db_table())), style.SQL_FIELD(quote_name('%s_id_seq' % f.m2m_db_table())),
style.SQL_KEYWORD('SELECT'),
style.SQL_FIELD(quote_name('id')), style.SQL_FIELD(quote_name('id')),
style.SQL_FIELD(quote_name('id')),
style.SQL_KEYWORD('IS NOT'),
style.SQL_KEYWORD('FROM'), style.SQL_KEYWORD('FROM'),
style.SQL_TABLE(f.m2m_db_table()))) style.SQL_TABLE(f.m2m_db_table())))
return output return output

View File

@ -92,7 +92,7 @@ def typecast_boolean(s):
return str(s)[0].lower() == 't' return str(s)[0].lower() == 't'
def typecast_decimal(s): def typecast_decimal(s):
if s is None: if s is None or s == '':
return None return None
return decimal.Decimal(s) return decimal.Decimal(s)

View File

@ -5,6 +5,15 @@ from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG
from django.template import get_library, Library, InvalidTemplateLibrary from django.template import get_library, Library, InvalidTemplateLibrary
from django.conf import settings from django.conf import settings
import sys import sys
import re
if not hasattr(__builtins__, 'reversed'):
# For Python 2.3.
# From http://www.python.org/doc/current/tut/node11.html
def reversed(data):
for index in xrange(len(data)-1, -1, -1):
yield data[index]
register = Library() register = Library()
@ -61,8 +70,8 @@ class FirstOfNode(Node):
return '' return ''
class ForNode(Node): class ForNode(Node):
def __init__(self, loopvar, sequence, reversed, nodelist_loop): def __init__(self, loopvars, sequence, reversed, nodelist_loop):
self.loopvar, self.sequence = loopvar, sequence self.loopvars, self.sequence = loopvars, sequence
self.reversed = reversed self.reversed = reversed
self.nodelist_loop = nodelist_loop self.nodelist_loop = nodelist_loop
@ -72,7 +81,7 @@ class ForNode(Node):
else: else:
reversed = '' reversed = ''
return "<For Node: for %s in %s, tail_len: %d%s>" % \ return "<For Node: for %s in %s, tail_len: %d%s>" % \
(self.loopvar, self.sequence, len(self.nodelist_loop), reversed) (', '.join( self.loopvars ), self.sequence, len(self.nodelist_loop), reversed)
def __iter__(self): def __iter__(self):
for node in self.nodelist_loop: for node in self.nodelist_loop:
@ -102,11 +111,8 @@ class ForNode(Node):
values = list(values) values = list(values)
len_values = len(values) len_values = len(values)
if self.reversed: if self.reversed:
# From http://www.python.org/doc/current/tut/node11.html values = reversed(values)
def reverse(data): unpack = len(self.loopvars) > 1
for index in range(len(data)-1, -1, -1):
yield data[index]
values = reverse(values)
for i, item in enumerate(values): for i, item in enumerate(values):
context['forloop'] = { context['forloop'] = {
# shortcuts for current loop iteration number # shortcuts for current loop iteration number
@ -120,9 +126,20 @@ class ForNode(Node):
'last': (i == len_values - 1), 'last': (i == len_values - 1),
'parentloop': parentloop, 'parentloop': parentloop,
} }
context[self.loopvar] = item if unpack:
# If there are multiple loop variables, unpack the item into them.
context.update(dict(zip(self.loopvars, item)))
else:
context[self.loopvars[0]] = item
for node in self.nodelist_loop: for node in self.nodelist_loop:
nodelist.append(node.render(context)) nodelist.append(node.render(context))
if unpack:
# The loop variables were pushed on to the context so pop them
# off again. This is necessary because the tag lets the length
# of loopvars differ to the length of each set of items and we
# don't want to leave any vars from the previous loop on the
# context.
context.pop()
context.pop() context.pop()
return nodelist.render(context) return nodelist.render(context)
@ -486,7 +503,7 @@ def do_filter(parser, token):
nodelist = parser.parse(('endfilter',)) nodelist = parser.parse(('endfilter',))
parser.delete_first_token() parser.delete_first_token()
return FilterNode(filter_expr, nodelist) return FilterNode(filter_expr, nodelist)
filter = register.tag("filter", do_filter) do_filter = register.tag("filter", do_filter)
#@register.tag #@register.tag
def firstof(parser, token): def firstof(parser, token):
@ -530,9 +547,15 @@ def do_for(parser, token):
{% endfor %} {% endfor %}
</ul> </ul>
You can also loop over a list in reverse by using You can loop over a list in reverse by using
``{% for obj in list reversed %}``. ``{% for obj in list reversed %}``.
You can also unpack multiple values from a two-dimensional array::
{% for key,value in dict.items %}
{{ key }}: {{ value }}
{% endfor %}
The for loop sets a number of variables available within the loop: The for loop sets a number of variables available within the loop:
========================== ================================================ ========================== ================================================
@ -552,18 +575,23 @@ def do_for(parser, token):
""" """
bits = token.contents.split() bits = token.contents.split()
if len(bits) == 5 and bits[4] != 'reversed': if len(bits) < 4:
raise TemplateSyntaxError, "'for' statements with five words should end in 'reversed': %s" % token.contents raise TemplateSyntaxError, "'for' statements should have at least four words: %s" % token.contents
if len(bits) not in (4, 5):
raise TemplateSyntaxError, "'for' statements should have either four or five words: %s" % token.contents reversed = bits[-1] == 'reversed'
if bits[2] != 'in': in_index = reversed and -3 or -2
raise TemplateSyntaxError, "'for' statement must contain 'in' as the second word: %s" % token.contents if bits[in_index] != 'in':
loopvar = bits[1] raise TemplateSyntaxError, "'for' statements should use the format 'for x in y': %s" % token.contents
sequence = parser.compile_filter(bits[3])
reversed = (len(bits) == 5) loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
for var in loopvars:
if not var or ' ' in var:
raise TemplateSyntaxError, "'for' tag received an invalid argument: %s" % token.contents
sequence = parser.compile_filter(bits[in_index+1])
nodelist_loop = parser.parse(('endfor',)) nodelist_loop = parser.parse(('endfor',))
parser.delete_first_token() parser.delete_first_token()
return ForNode(loopvar, sequence, reversed, nodelist_loop) return ForNode(loopvars, sequence, reversed, nodelist_loop)
do_for = register.tag("for", do_for) do_for = register.tag("for", do_for)
def do_ifequal(parser, token, negate): def do_ifequal(parser, token, negate):

View File

@ -2,7 +2,7 @@ from django.conf import settings
from django.template import Template, Context, TemplateDoesNotExist from django.template import Template, Context, TemplateDoesNotExist
from django.utils.html import escape from django.utils.html import escape
from django.http import HttpResponseServerError, HttpResponseNotFound from django.http import HttpResponseServerError, HttpResponseNotFound
import os, re import os, re, sys
HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST') HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST')
@ -131,6 +131,8 @@ def technical_500_response(request, exc_type, exc_value, tb):
'request': request, 'request': request,
'request_protocol': request.is_secure() and "https" or "http", 'request_protocol': request.is_secure() and "https" or "http",
'settings': get_safe_settings(), 'settings': get_safe_settings(),
'sys_executable' : sys.executable,
'sys_version_info' : '%d.%d.%d' % sys.version_info[0:3],
'template_info': template_info, 'template_info': template_info,
'template_does_not_exist': template_does_not_exist, 'template_does_not_exist': template_does_not_exist,
'loader_debug_info': loader_debug_info, 'loader_debug_info': loader_debug_info,
@ -334,6 +336,14 @@ TECHNICAL_500_TEMPLATE = """
<th>Exception Location:</th> <th>Exception Location:</th>
<td>{{ lastframe.filename|escape }} in {{ lastframe.function|escape }}, line {{ lastframe.lineno }}</td> <td>{{ lastframe.filename|escape }} in {{ lastframe.function|escape }}, line {{ lastframe.lineno }}</td>
</tr> </tr>
<tr>
<th>Python Executable:</th>
<td>{{ sys_executable|escape }}</td>
</tr>
<tr>
<th>Python Version:</th>
<td>{{ sys_version_info }}</td>
</tr>
</table> </table>
</div> </div>
{% if template_does_not_exist %} {% if template_does_not_exist %}

View File

@ -6,8 +6,9 @@ Django aims to follow Python's `"batteries included" philosophy`_. It ships
with a variety of extra, optional tools that solve common Web-development with a variety of extra, optional tools that solve common Web-development
problems. problems.
This code lives in ``django/contrib`` in the Django distribution. Here's a This code lives in ``django/contrib`` in the Django distribution. This document
rundown of the packages in ``contrib``: gives a rundown of the packages in ``contrib``, along with any dependencies
those packages have.
.. admonition:: Note .. admonition:: Note
@ -26,6 +27,8 @@ The automatic Django administrative interface. For more information, see
.. _Tutorial 2: ../tutorial02/ .. _Tutorial 2: ../tutorial02/
Requires the auth_ and contenttypes_ contrib packages to be installed.
auth auth
==== ====
@ -144,6 +147,8 @@ See the `flatpages documentation`_.
.. _flatpages documentation: ../flatpages/ .. _flatpages documentation: ../flatpages/
Requires the sites_ contrib package to be installed as well.
localflavor localflavor
=========== ===========

View File

@ -730,7 +730,7 @@ Django developers are currently discussing.
Default permissions Default permissions
------------------- -------------------
Three basic permissions -- add, create and delete -- are automatically created Three basic permissions -- add, change and delete -- are automatically created
for each Django model that has a ``class Admin`` set. Behind the scenes, these for each Django model that has a ``class Admin`` set. Behind the scenes, these
permissions are added to the ``auth_permission`` database table when you run permissions are added to the ``auth_permission`` database table when you run
``manage.py syncdb``. ``manage.py syncdb``.

View File

@ -134,6 +134,15 @@ the database until you explicitly call ``save()``.
The ``save()`` method has no return value. The ``save()`` method has no return value.
Updating ``ForeignKey`` fields works exactly the same way; simply assign an
object of the right type to the field in question::
joe = Author.objects.create(name="Joe")
entry.author = joe
entry.save()
Django will complain if you try to assign an object of the wrong type.
How Django knows to UPDATE vs. INSERT How Django knows to UPDATE vs. INSERT
------------------------------------- -------------------------------------
@ -379,7 +388,7 @@ The lookup parameters (``**kwargs``) should be in the format described in
`Field lookups`_ below. Multiple parameters are joined via ``AND`` in the `Field lookups`_ below. Multiple parameters are joined via ``AND`` in the
underlying SQL statement, and the whole thing is enclosed in a ``NOT()``. underlying SQL statement, and the whole thing is enclosed in a ``NOT()``.
This example excludes all entries whose ``pub_date`` is the current date/time This example excludes all entries whose ``pub_date`` is later than 2005-1-3
AND whose ``headline`` is "Hello":: AND whose ``headline`` is "Hello"::
Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello') Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')
@ -389,8 +398,8 @@ In SQL terms, that evaluates to::
SELECT ... SELECT ...
WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello') WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello')
This example excludes all entries whose ``pub_date`` is the current date/time This example excludes all entries whose ``pub_date`` is later than 2005-1-3
OR whose ``headline`` is "Hello":: AND whose headline is NOT "Hello"::
Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3)).exclude(headline='Hello') Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3)).exclude(headline='Hello')
@ -1229,8 +1238,8 @@ whose ``headline`` contains ``'Lennon'``::
Blog.objects.filter(entry__headline__contains='Lennon') Blog.objects.filter(entry__headline__contains='Lennon')
Escaping parenthesis and underscores in LIKE statements Escaping percent signs and underscores in LIKE statements
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The field lookups that equate to ``LIKE`` SQL statements (``iexact``, The field lookups that equate to ``LIKE`` SQL statements (``iexact``,
``contains``, ``icontains``, ``startswith``, ``istartswith``, ``endswith`` ``contains``, ``icontains``, ``startswith``, ``istartswith``, ``endswith``

View File

@ -203,7 +203,7 @@ This is probably the most common case, if you're using Django's admin site::
DocumentRoot /home/user/public_html DocumentRoot /home/user/public_html
Alias /media /home/user/python/django/contrib/admin/media Alias /media /home/user/python/django/contrib/admin/media
RewriteEngine On RewriteEngine On
RewriteRule ^/(media.*)$ /$1 [QSA,L] RewriteRule ^/(media.*)$ /$1 [QSA,L,PT]
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^/(.*)$ /mysite.fcgi/$1 [QSA,L] RewriteRule ^/(.*)$ /mysite.fcgi/$1 [QSA,L]
</VirtualHost> </VirtualHost>

162
docs/man/django-admin.1 Normal file
View File

@ -0,0 +1,162 @@
.TH "django-admin.py" "1" "June 2007" "Django Project" ""
.SH "NAME"
django\-admin.py \- Utility script for the Django web framework
.SH "SYNOPSIS"
.B django\-admin.py
.I <action>
.B [options]
.sp
.SH "DESCRIPTION"
This utility script provides commands for creation and maintenance of Django
projects and apps.
.sp
With the exception of
.BI startproject,
all commands listed below can also be performed with the
.BI manage.py
script found at the top level of each Django project directory.
.sp
.SH "ACTIONS"
.TP
.BI "adminindex [" "appname ..." "]"
Prints the admin\-index template snippet for the given app name(s).
.TP
.BI "createcachetable [" "tablename" "]"
Creates the table needed to use the SQL cache backend
.TP
.B dbshell
Runs the command\-line client for the current
.BI DATABASE_ENGINE.
.TP
.B diffsettings
Displays differences between the current
.B settings.py
and Django's default settings. Settings that don't appear in the defaults are
followed by "###".
.TP
.B inspectdb
Introspects the database tables in the database specified in settings.py and outputs a Django
model module.
.TP
.BI "install [" "appname ..." "]"
Executes
.B sqlall
for the given app(s) in the current database.
.TP
.BI "reset [" "appname ..." "]"
Executes
.B sqlreset
for the given app(s) in the current database.
.TP
.BI "runfcgi [" "KEY=val" "] [" "KEY=val" "] " "..."
Runs this project as a FastCGI application. Requires flup. Use
.B runfcgi help
for help on the KEY=val pairs.
.TP
.BI "runserver [" "\-\-noreload" "] [" "\-\-adminmedia=ADMIN_MEDIA_PATH" "] [" "port|ipaddr:port" "]"
Starts a lightweight Web server for development.
.TP
.BI "shell [" "\-\-plain" "]"
Runs a Python interactive interpreter. Tries to use IPython, if it's available.
The
.BI \-\-plain
option forces the use of the standard Python interpreter even when IPython is
installed.
.TP
.BI "sql [" "appname ..." "]"
Prints the CREATE TABLE SQL statements for the given app name(s).
.TP
.BI "sqlall [" "appname ..." "]"
Prints the CREATE TABLE, initial\-data and CREATE INDEX SQL statements for the
given model module name(s).
.TP
.BI "sqlclear [" "appname ..." "]"
Prints the DROP TABLE SQL statements for the given app name(s).
.TP
.BI "sqlindexes [" "appname ..." "]"
Prints the CREATE INDEX SQL statements for the given model module name(s).
.TP
.BI "sqlinitialdata [" "appname ..." "]"
Prints the initial INSERT SQL statements for the given app name(s).
.TP
.BI "sqlreset [" "appname ..." "]"
Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given app
name(s).
.TP
.BI "sqlsequencereset [" "appname ..." "]"
Prints the SQL statements for resetting PostgreSQL sequences for the
given app name(s).
.TP
.BI "startapp [" "appname" "]"
Creates a Django app directory structure for the given app name in
the current directory.
.TP
.BI "startproject [" "projectname" "]"
Creates a Django project directory structure for the given project name
in the current directory.
.TP
.BI syncdb
Creates the database tables for all apps in INSTALLED_APPS whose tables
haven't already been created.
.TP
.BI "test [" "\-\-verbosity" "] [" "appname ..." "]"
Runs the test suite for the specified applications, or the entire project if
no apps are specified
.TP
.BI validate
Validates all installed models.
.SH "OPTIONS"
.TP
.I \-\-version
Show program's version number and exit.
.TP
.I \-h, \-\-help
Show this help message and exit.
.TP
.I \-\-settings=SETTINGS
Python path to settings module, e.g. "myproject.settings.main". If
this isn't provided, the DJANGO_SETTINGS_MODULE environment variable
will be used.
.TP
.I \-\-pythonpath=PYTHONPATH
Lets you manually add a directory the Python path,
e.g. "/home/djangoprojects/myproject".
.TP
.I \-\-plain
Use plain Python, not IPython, for the "shell" command.
.TP
.I \-\-noinput
Do not prompt the user for input.
.TP
.I \-\-noreload
Disable the development server's auto\-reloader.
.TP
.I \-\-verbosity=VERBOSITY
Verbosity level: 0=minimal output, 1=normal output, 2=all output.
.TP
.I \-\-adminmedia=ADMIN_MEDIA_PATH
Specifies the directory from which to serve admin media when using the development server.
.SH "ENVIRONMENT"
.TP
.I DJANGO_SETTINGS_MODULE
In the absence of the
.BI \-\-settings
option, this environment variable defines the settings module to be read.
It should be in Python-import form, e.g. "myproject.settings".
.SH "SEE ALSO"
Full descriptions of all these options, with examples, as well as documentation
for the rest of the Django framework, can be found on the Django site:
.sp
.I http://www.djangoproject.com/documentation/
.sp
or in the distributed documentation.
.SH "AUTHORS/CREDITS"
Originally developed at World Online in Lawrence, Kansas, USA. Refer to the
AUTHORS file in the Django distribution for contributors.
.sp
.SH "LICENSE"
New BSD license. For the full license text refer to the LICENSE file in the
Django distribution.

View File

@ -447,6 +447,11 @@ and doesn't give a 404 response).
The admin represents this as an ``<input type="text">`` (a single-line input). The admin represents this as an ``<input type="text">`` (a single-line input).
``URLField`` takes an optional argument, ``maxlength``, the maximum length (in
characters) of the field. The maxlength is enforced at the database level and
in Django's validation. If you don't specify ``maxlength``, a default of 200
is used.
``USStateField`` ``USStateField``
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
@ -1877,14 +1882,15 @@ rows. Example::
row = cursor.fetchone() row = cursor.fetchone()
return row return row
``connection`` and ``cursor`` simply use the standard `Python DB-API`_. If ``connection`` and ``cursor`` mostly implement the standard `Python DB-API`_
you're not familiar with the Python DB-API, note that the SQL statement in (except when it comes to `transaction handling`_). If you're not familiar with
``cursor.execute()`` uses placeholders, ``"%s"``, rather than adding parameters the Python DB-API, note that the SQL statement in ``cursor.execute()`` uses
directly within the SQL. If you use this technique, the underlying database placeholders, ``"%s"``, rather than adding parameters directly within the SQL.
library will automatically add quotes and escaping to your parameter(s) as If you use this technique, the underlying database library will automatically
necessary. (Also note that Django expects the ``"%s"`` placeholder, *not* the add quotes and escaping to your parameter(s) as necessary. (Also note that
``"?"`` placeholder, which is used by the SQLite Python bindings. This is for Django expects the ``"%s"`` placeholder, *not* the ``"?"`` placeholder, which is
the sake of consistency and sanity.) used by the SQLite Python bindings. This is for the sake of consistency and
sanity.)
A final note: If all you want to do is a custom ``WHERE`` clause, you can just A final note: If all you want to do is a custom ``WHERE`` clause, you can just
just the ``where``, ``tables`` and ``params`` arguments to the standard lookup just the ``where``, ``tables`` and ``params`` arguments to the standard lookup
@ -1892,6 +1898,7 @@ API. See `Other lookup options`_.
.. _Python DB-API: http://www.python.org/peps/pep-0249.html .. _Python DB-API: http://www.python.org/peps/pep-0249.html
.. _Other lookup options: ../db-api/#extra-params-select-where-tables .. _Other lookup options: ../db-api/#extra-params-select-where-tables
.. _transaction handling: ../transactions/
Overriding default model methods Overriding default model methods
-------------------------------- --------------------------------

View File

@ -51,9 +51,17 @@ whereas ``<Location>`` points at places in the URL structure of a Web site.
``<Directory>`` would be meaningless here. ``<Directory>`` would be meaningless here.
Also, if you've manually altered your ``PYTHONPATH`` to put your Django project Also, if you've manually altered your ``PYTHONPATH`` to put your Django project
on it, you'll need to tell mod_python:: on it, you'll need to tell mod_python:
PythonPath "['/path/to/project'] + sys.path" .. parsed-literal::
<Location "/mysite/">
SetHandler python-program
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE mysite.settings
PythonDebug On
**PythonPath "['/path/to/project'] + sys.path"**
</Location>
.. caution:: .. caution::

View File

@ -299,12 +299,19 @@ required. In this example, the data dictionary doesn't include a value for the
In this above example, the ``cleaned_data`` value for ``nick_name`` is set to an In this above example, the ``cleaned_data`` value for ``nick_name`` is set to an
empty string, because ``nick_name`` is ``CharField``, and ``CharField``\s treat empty string, because ``nick_name`` is ``CharField``, and ``CharField``\s treat
empty values as an empty string. Each field type knows what its "blank" value empty values as an empty string. Each field type knows what its "blank" value
is -- e.g., for ``DateField``, it's ``None`` instead of the empty string. is -- e.g., for ``DateField``, it's ``None`` instead of the empty string. For
full details on each field's behavior in this case, see the "Empty value" note
for each field in the "Built-in ``Field`` classes" section below.
You can write code to perform validation for particular form fields (based on
their name) or for the form as a whole (considering combinations of various
fields). More information about this is in the `Custom form and field
validation`_ section, below.
Behavior of unbound forms Behavior of unbound forms
~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~
It's meaningless to request "clean" data in a form with no data, but, for the It's meaningless to request "cleaned" data in a form with no data, but, for the
record, here's what happens with unbound forms:: record, here's what happens with unbound forms::
>>> f = ContactForm() >>> f = ContactForm()
@ -606,8 +613,13 @@ Using forms in views and templates
---------------------------------- ----------------------------------
Let's put this all together and use the ``ContactForm`` example in a Django Let's put this all together and use the ``ContactForm`` example in a Django
view and template. This example view displays the contact form by default and view and template.
validates/processes it if accessed via a POST request::
Simple view example
~~~~~~~~~~~~~~~~~~~
This example view displays the contact form by default and validates/processes
it if accessed via a POST request::
def contact(request): def contact(request):
if request.method == 'POST': if request.method == 'POST':
@ -619,12 +631,12 @@ validates/processes it if accessed via a POST request::
form = ContactForm() form = ContactForm()
return render_to_response('contact.html', {'form': form}) return render_to_response('contact.html', {'form': form})
Simple template output Simple template example
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~
The template, ``contact.html``, is responsible for displaying the form as HTML. The template in the above view example, ``contact.html``, is responsible for
To do this, we can use the techniques outlined in the "Outputting forms as HTML" displaying the form as HTML. To do this, we can use the techniques outlined in
section above. the "Outputting forms as HTML" section above.
The simplest way to display a form's HTML is to use the variable on its own, The simplest way to display a form's HTML is to use the variable on its own,
like this:: like this::
@ -677,7 +689,7 @@ The easiest way is to iterate over the form's fields, with
This iteration technique is useful if you want to apply the same HTML This iteration technique is useful if you want to apply the same HTML
formatting to each field, or if you don't know the names of the form fields formatting to each field, or if you don't know the names of the form fields
ahead of time. Note that the fields will be listed in the order in which ahead of time. Note that the fields will be iterated over in the order in which
they're defined in the ``Form`` class. they're defined in the ``Form`` class.
Alternatively, you can arrange the form's fields explicitly, by name. Do that Alternatively, you can arrange the form's fields explicitly, by name. Do that
@ -701,7 +713,10 @@ For example::
Subclassing forms Subclassing forms
----------------- -----------------
If you subclass a custom ``Form`` class, the resulting ``Form`` class will If you have multiple ``Form`` classes that share fields, you can use
subclassing to remove redundancy.
When you subclass a custom ``Form`` class, the resulting subclass will
include all fields of the parent class(es), followed by the fields you define include all fields of the parent class(es), followed by the fields you define
in the subclass. in the subclass.
@ -1202,6 +1217,114 @@ custom ``Field`` classes. To do this, just create a subclass of
mentioned above (``required``, ``label``, ``initial``, ``widget``, mentioned above (``required``, ``label``, ``initial``, ``widget``,
``help_text``). ``help_text``).
Custom form and field validation
---------------------------------
Form validation happens when the data is cleaned. If you want to customise
this process, there are various places you can change, each one serving a
different purpose. Thee types of cleaning methods are run during form
processing. These are normally executed when you call the ``is_valid()``
method on a form. There are other things that can kick of cleaning and
validation (accessing the ``errors`` attribute or calling ``full_clean()``
directly), but normally they won't be needed.
In general, any cleaning method can raise ``ValidationError`` if there is a
problem with the data it is processing, passing the relevant error message to
the ``ValidationError`` constructor. If no ``ValidationError`` is raised, the
method should return the cleaned (normalised) data as a Python object.
If you detect multiple errors during a cleaning method and wish to signal all
of them to the form submittor, it is possible to pass a list of errors to the
``ValidationError`` constructor.
The three types of cleaning methods are:
* The ``clean()`` method on a Field subclass. This is responsible
for cleaning the data in a way that is generic for that type of field.
For example, a FloatField will turn the data into a Python ``float`` or
raise a ``ValidationError``.
* The ``clean_<fieldname>()`` method in a form subclass -- where
``<fieldname>`` is replaced with the name of the form field attribute.
This method does any cleaning that is specific to that particular
attribute, unrelated to the type of field that it is. This method is not
passed any parameters. You will need to look up the value of the field
in ``self.cleaned_data`` and remember that it will be a Python object
at this point, not the original string submitted in the form (it will be
in ``cleaned_data`` because the general field ``clean()`` method, above,
has already cleaned the data once).
For example, if you wanted to validate that the contents of a
``CharField`` called ``serialnumber`` was unique,
``clean_serialnumber()`` would be the right place to do this. You don't
need a specific field (it's just a ``CharField``), but you want a
formfield-specific piece of validation and, possibly,
cleaning/normalizing the data.
* The Form subclass's ``clean()`` method. This method can perform
any validation that requires access to multiple fields from the form at
once. This is where you might put in things to check that if field ``A``
is supplied, field ``B`` must contain a valid email address and the
like. The data that this method returns is the final ``cleaned_data``
attribute for the form, so don't forget to return the full list of
cleaned data if you override this method (by default, ``Form.clean()``
just returns ``self.cleaned_data``).
Note that any errors raised by your ``Form.clean()`` override will not
be associated with any field in particular. They go into a special
"field" (called ``__all__``, which you can access via the
``non_field_errors()`` method if you need to.
These methods are run in the order given above, one field at a time. That is,
for each field in the form (in the order they are declared in the form
definition), the ``Field.clean()`` method (or it's override) is run, then
``clean_<fieldname>()``. Finally, once those two methods are run for every
field, the ``Form.clean()`` method, or it's override, is executed.
As mentioned above, any of these methods can raise a ``ValidationError``. For
any field, if the ``Field.clean()`` method raises a ``ValidationError``, any
field-specific cleaning method is not called. However, the cleaning methods
for all remaining fields are still executed.
The ``clean()`` method for the ``Form`` class or subclass is always run. If
that method raises a ``ValidationError``, ``cleaned_data`` will be an empty
dictionary.
The previous paragraph means that if you are overriding ``Form.clean()``, you
should iterate through ``self.cleaned_data.items()``, possibly considering the
``_errors`` dictionary attribute on the form as well. In this way, you will
already know which fields have passed thei individual validation requirements.
A simple example
~~~~~~~~~~~~~~~~
Here's a simple example of a custom field that validates its input is a string
containing comma-separated e-mail addresses, with at least one address. We'll
keep it simple and assume e-mail validation is contained in a function called
``is_valid_email()``. The full class::
from django import newforms as forms
class MultiEmailField(forms.Field):
def clean(self, value):
emails = value.split(',')
for email in emails:
if not is_valid_email(email):
raise forms.ValidationError('%s is not a valid e-mail address.' % email)
if not emails:
raise forms.ValidationError('Enter at least one e-mail address.')
return emails
Let's alter the ongoing ``ContactForm`` example to demonstrate how you'd use
this in a form. Simply use ``MultiEmailField`` instead of ``forms.EmailField``,
like so::
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField()
senders = MultiEmailField()
cc_myself = forms.BooleanField()
Generating forms for models Generating forms for models
=========================== ===========================

View File

@ -91,9 +91,12 @@ Filters can be "chained." The output of one filter is applied to the next.
``{{ text|escape|linebreaks }}`` is a common idiom for escaping text contents, ``{{ text|escape|linebreaks }}`` is a common idiom for escaping text contents,
then converting line breaks to ``<p>`` tags. then converting line breaks to ``<p>`` tags.
Some filters take arguments. A filter argument looks like this: Some filters take arguments. A filter argument looks like this: ``{{
``{{ bio|truncatewords:"30" }}``. This will display the first 30 words of the bio|truncatewords:30 }}``. This will display the first 30 words of the ``bio``
``bio`` variable. Filter arguments always are in double quotes. variable.
Filter arguments that contain spaces must be quoted; for example, to join a list
with commas and spaced you'd use ``{{ list|join:", " }}``.
The `Built-in filter reference`_ below describes all the built-in filters. The `Built-in filter reference`_ below describes all the built-in filters.
@ -444,7 +447,7 @@ for
~~~ ~~~
Loop over each item in an array. For example, to display a list of athletes Loop over each item in an array. For example, to display a list of athletes
given ``athlete_list``:: provided in ``athlete_list``::
<ul> <ul>
{% for athlete in athlete_list %} {% for athlete in athlete_list %}
@ -452,7 +455,25 @@ given ``athlete_list``::
{% endfor %} {% endfor %}
</ul> </ul>
You can also loop over a list in reverse by using ``{% for obj in list reversed %}``. You can loop over a list in reverse by using ``{% for obj in list reversed %}``.
**New in Django development version**
If you need to loop over a list of lists, you can unpack the values
in eachs sub-list into a set of known names. For example, if your context contains
a list of (x,y) coordinates called ``points``, you could use the following
to output the list of points::
{% for x, y in points %}
There is a point at {{ x }},{{ y }}
{% endfor %}
This can also be useful if you need to access the items in a dictionary.
For example, if your context contained a dictionary ``data``, the following
would display the keys and values of the dictionary::
{% for key, value in data.items %}
{{ key }}: {{ value }}
{% endfor %}
The for loop sets a number of variables available within the loop: The for loop sets a number of variables available within the loop:

View File

@ -21,3 +21,8 @@ done
# Make sure we match foo.pyo and foo.pyc along with foo.py (but only once each) # Make sure we match foo.pyo and foo.pyc along with foo.py (but only once each)
sed -e "/\.py[co]$/d" -e "s/\.py$/.py*/" DIRS FILES >INSTALLED_FILES sed -e "/\.py[co]$/d" -e "s/\.py$/.py*/" DIRS FILES >INSTALLED_FILES
mkdir -p ${RPM_BUILD_ROOT}/%{_mandir}/man1/
cp docs/man/* ${RPM_BUILD_ROOT}/%{_mandir}/man1/
cat << EOF >> INSTALLED_FILES
%doc %{_mandir}/man1/*"
EOF

View File

@ -327,10 +327,11 @@ def streamTest(format, self):
string_data = serializers.serialize(format, [obj], indent=2) string_data = serializers.serialize(format, [obj], indent=2)
# Check that the two are the same # Check that the two are the same
self.assertEqual(string_data, stream.buffer()) self.assertEqual(string_data, stream.getvalue())
stream.close() stream.close()
for format in serializers.get_serializer_formats(): for format in serializers.get_serializer_formats():
setattr(SerializerTests, 'test_'+format+'_serializer', curry(serializerTest, format)) setattr(SerializerTests, 'test_'+format+'_serializer', curry(serializerTest, format))
setattr(SerializerTests, 'test_'+format+'_serializer_fields', curry(fieldsTest, format)) setattr(SerializerTests, 'test_'+format+'_serializer_fields', curry(fieldsTest, format))
setattr(SerializerTests, 'test_'+format+'_serializer_stream', curry(fieldsTest, format)) if format != 'python':
setattr(SerializerTests, 'test_'+format+'_serializer_stream', curry(streamTest, format))

View File

@ -289,6 +289,20 @@ class Templates(unittest.TestCase):
'for-tag-vars02': ("{% for val in values %}{{ forloop.counter0 }}{% endfor %}", {"values": [6, 6, 6]}, "012"), 'for-tag-vars02': ("{% for val in values %}{{ forloop.counter0 }}{% endfor %}", {"values": [6, 6, 6]}, "012"),
'for-tag-vars03': ("{% for val in values %}{{ forloop.revcounter }}{% endfor %}", {"values": [6, 6, 6]}, "321"), 'for-tag-vars03': ("{% for val in values %}{{ forloop.revcounter }}{% endfor %}", {"values": [6, 6, 6]}, "321"),
'for-tag-vars04': ("{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}", {"values": [6, 6, 6]}, "210"), 'for-tag-vars04': ("{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}", {"values": [6, 6, 6]}, "210"),
'for-tag-unpack01': ("{% for key,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
'for-tag-unpack03': ("{% for key, value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
'for-tag-unpack04': ("{% for key , value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
'for-tag-unpack05': ("{% for key ,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
'for-tag-unpack06': ("{% for key value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError),
'for-tag-unpack07': ("{% for key,,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError),
'for-tag-unpack08': ("{% for key,value, in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError),
# Ensure that a single loopvar doesn't truncate the list in val.
'for-tag-unpack09': ("{% for val in items %}{{ val.0 }}:{{ val.1 }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
# Otherwise, silently truncate if the length of loopvars differs to the length of each set of items.
'for-tag-unpack10': ("{% for x,y in items %}{{ x }}:{{ y }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2, 'orange'))}, "one:1/two:2/"),
'for-tag-unpack11': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, ("one:1,/two:2,/", "one:1,INVALID/two:2,INVALID/")),
'for-tag-unpack12': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2))}, ("one:1,carrot/two:2,/", "one:1,carrot/two:2,INVALID/")),
'for-tag-unpack13': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2, 'cheese'))}, ("one:1,carrot/two:2,cheese/", "one:1,carrot/two:2,cheese/")),
### IF TAG ################################################################ ### IF TAG ################################################################
'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"), 'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),