Fixed #14389, #9666 -- Started the migration path to make the first argument to url and ssi template tags syntactically consistent with other tags. Thanks to Sean Brant for the draft patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14643 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Russell Keith-Magee 2010-11-20 06:22:28 +00:00
parent 591ad8afbf
commit 7ff5580d95
15 changed files with 424 additions and 65 deletions

View File

@ -1,7 +1,8 @@
{% extends "admin/base_site.html" %} {% extends "admin/base_site.html" %}
{% load i18n admin_modify adminmedia %} {% load i18n admin_modify adminmedia %}
{% load url from future %}
{% block extrahead %}{{ block.super }} {% block extrahead %}{{ block.super }}
{% url admin:jsi18n as jsi18nurl %} {% url 'admin:jsi18n' as jsi18nurl %}
<script type="text/javascript" src="{{ jsi18nurl|default:"../../../../jsi18n/" }}"></script> <script type="text/javascript" src="{{ jsi18nurl|default:"../../../../jsi18n/" }}"></script>
{% endblock %} {% endblock %}
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %} {% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %}

View File

@ -1,4 +1,4 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> {% load url from future %}<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="{{ LANGUAGE_CODE|default:"en-us" }}" xml:lang="{{ LANGUAGE_CODE|default:"en-us" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}> <html xmlns="http://www.w3.org/1999/xhtml" lang="{{ LANGUAGE_CODE|default:"en-us" }}" xml:lang="{{ LANGUAGE_CODE|default:"en-us" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
<head> <head>
<title>{% block title %}{% endblock %}</title> <title>{% block title %}{% endblock %}</title>
@ -28,18 +28,18 @@
{% trans 'Welcome,' %} {% trans 'Welcome,' %}
<strong>{% filter force_escape %}{% firstof user.first_name user.username %}{% endfilter %}</strong>. <strong>{% filter force_escape %}{% firstof user.first_name user.username %}{% endfilter %}</strong>.
{% block userlinks %} {% block userlinks %}
{% url django-admindocs-docroot as docsroot %} {% url 'django-admindocs-docroot' as docsroot %}
{% if docsroot %} {% if docsroot %}
<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> / <a href="{{ docsroot }}">{% trans 'Documentation' %}</a> /
{% endif %} {% endif %}
{% url admin:password_change as password_change_url %} {% url 'admin:password_change' as password_change_url %}
{% if password_change_url %} {% if password_change_url %}
<a href="{{ password_change_url }}"> <a href="{{ password_change_url }}">
{% else %} {% else %}
<a href="{{ root_path }}password_change/"> <a href="{{ root_path }}password_change/">
{% endif %} {% endif %}
{% trans 'Change password' %}</a> / {% trans 'Change password' %}</a> /
{% url admin:logout as logout_url %} {% url 'admin:logout' as logout_url %}
{% if logout_url %} {% if logout_url %}
<a href="{{ logout_url }}"> <a href="{{ logout_url }}">
{% else %} {% else %}

View File

@ -1,6 +1,6 @@
{% extends "admin/base_site.html" %} {% extends "admin/base_site.html" %}
{% load adminmedia admin_list i18n %} {% load adminmedia admin_list i18n %}
{% load url from future %}
{% block extrastyle %} {% block extrastyle %}
{{ block.super }} {{ block.super }}
<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/changelists.css" /> <link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/changelists.css" />
@ -8,7 +8,7 @@
<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" /> <link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />
{% endif %} {% endif %}
{% if cl.formset or action_form %} {% if cl.formset or action_form %}
{% url admin:jsi18n as jsi18nurl %} {% url 'admin:jsi18n' as jsi18nurl %}
<script type="text/javascript" src="{{ jsi18nurl|default:'../../jsi18n/' }}"></script> <script type="text/javascript" src="{{ jsi18nurl|default:'../../jsi18n/' }}"></script>
{% endif %} {% endif %}
{{ media.css }} {{ media.css }}

View File

@ -1,6 +1,7 @@
{% extends "admin/base_site.html" %} {% extends "admin/base_site.html" %}
{% load i18n %} {% load i18n %}
{% block userlinks %}{% url django-admindocs-docroot as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> / {% endif %}{% trans 'Change password' %} / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %} {% load url from future %}
{% block userlinks %}{% url 'django-admindocs-docroot' as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> / {% endif %}{% trans 'Change password' %} / <a href="../../logout/">{% trans 'Log out' %}</a>{% endblock %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %} {% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %}
{% block title %}{% trans 'Password change successful' %}{% endblock %} {% block title %}{% trans 'Password change successful' %}{% endblock %}

View File

@ -1,7 +1,8 @@
{% extends "admin/base_site.html" %} {% extends "admin/base_site.html" %}
{% load i18n adminmedia %} {% load i18n adminmedia %}
{% load url from future %}
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %} {% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %}
{% block userlinks %}{% url django-admindocs-docroot as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> / {% endif %} {% trans 'Change password' %} / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %} {% block userlinks %}{% url 'django-admindocs-docroot' as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> / {% endif %} {% trans 'Change password' %} / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %} {% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %}
{% block title %}{% trans 'Password change' %}{% endblock %} {% block title %}{% trans 'Password change' %}{% endblock %}

View File

@ -1,10 +1,10 @@
{% load i18n %}{% autoescape off %} {% load i18n %}{% load url from future %}{% autoescape off %}
{% trans "You're receiving this e-mail because you requested a password reset" %} {% trans "You're receiving this e-mail because you requested a password reset" %}
{% blocktrans %}for your user account at {{ site_name }}{% endblocktrans %}. {% blocktrans %}for your user account at {{ site_name }}{% endblocktrans %}.
{% trans "Please go to the following page and choose a new password:" %} {% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %} {% block reset_link %}
{{ protocol }}://{{ domain }}{% url django.contrib.auth.views.password_reset_confirm uidb36=uid token=token %} {{ protocol }}://{{ domain }}{% url 'django.contrib.auth.views.password_reset_confirm' uidb36=uid token=token %}
{% endblock %} {% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.username }} {% trans "Your username, in case you've forgotten:" %} {{ user.username }}

View File

@ -290,24 +290,30 @@ def include_is_allowed(filepath):
return False return False
class SsiNode(Node): class SsiNode(Node):
def __init__(self, filepath, parsed): def __init__(self, filepath, parsed, legacy_filepath=True):
self.filepath, self.parsed = filepath, parsed self.filepath = filepath
self.parsed = parsed
self.legacy_filepath = legacy_filepath
def render(self, context): def render(self, context):
if not include_is_allowed(self.filepath): filepath = self.filepath
if not self.legacy_filepath:
filepath = filepath.resolve(context)
if not include_is_allowed(filepath):
if settings.DEBUG: if settings.DEBUG:
return "[Didn't have permission to include file]" return "[Didn't have permission to include file]"
else: else:
return '' # Fail silently for invalid includes. return '' # Fail silently for invalid includes.
try: try:
fp = open(self.filepath, 'r') fp = open(filepath, 'r')
output = fp.read() output = fp.read()
fp.close() fp.close()
except IOError: except IOError:
output = '' output = ''
if self.parsed: if self.parsed:
try: try:
t = Template(output, name=self.filepath) t = Template(output, name=filepath)
return t.render(context) return t.render(context)
except TemplateSyntaxError, e: except TemplateSyntaxError, e:
if settings.DEBUG: if settings.DEBUG:
@ -356,8 +362,9 @@ class TemplateTagNode(Node):
return self.mapping.get(self.tagtype, '') return self.mapping.get(self.tagtype, '')
class URLNode(Node): class URLNode(Node):
def __init__(self, view_name, args, kwargs, asvar): def __init__(self, view_name, args, kwargs, asvar, legacy_view_name=True):
self.view_name = view_name self.view_name = view_name
self.legacy_view_name = legacy_view_name
self.args = args self.args = args
self.kwargs = kwargs self.kwargs = kwargs
self.asvar = asvar self.asvar = asvar
@ -368,19 +375,24 @@ class URLNode(Node):
kwargs = dict([(smart_str(k, 'ascii'), v.resolve(context)) kwargs = dict([(smart_str(k, 'ascii'), v.resolve(context))
for k, v in self.kwargs.items()]) for k, v in self.kwargs.items()])
view_name = self.view_name
if not self.legacy_view_name:
view_name = view_name.resolve(context)
# Try to look up the URL twice: once given the view name, and again # Try to look up the URL twice: once given the view name, and again
# relative to what we guess is the "main" app. If they both fail, # relative to what we guess is the "main" app. If they both fail,
# re-raise the NoReverseMatch unless we're using the # re-raise the NoReverseMatch unless we're using the
# {% url ... as var %} construct in which cause return nothing. # {% url ... as var %} construct in which cause return nothing.
url = '' url = ''
try: try:
url = reverse(self.view_name, args=args, kwargs=kwargs, current_app=context.current_app) url = reverse(view_name, args=args, kwargs=kwargs, current_app=context.current_app)
except NoReverseMatch, e: except NoReverseMatch, e:
if settings.SETTINGS_MODULE: if settings.SETTINGS_MODULE:
project_name = settings.SETTINGS_MODULE.split('.')[0] project_name = settings.SETTINGS_MODULE.split('.')[0]
try: try:
url = reverse(project_name + '.' + self.view_name, url = reverse(project_name + '.' + view_name,
args=args, kwargs=kwargs, current_app=context.current_app) args=args, kwargs=kwargs,
current_app=context.current_app)
except NoReverseMatch: except NoReverseMatch:
if self.asvar is None: if self.asvar is None:
# Re-raise the original exception, not the one with # Re-raise the original exception, not the one with
@ -922,6 +934,11 @@ def ssi(parser, token):
{% ssi /home/html/ljworld.com/includes/right_generic.html parsed %} {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
""" """
import warnings
warnings.warn('The syntax for the ssi template tag is changing. Load the `ssi` tag from the `future` tag library to start using the new behavior.',
category=PendingDeprecationWarning)
bits = token.contents.split() bits = token.contents.split()
parsed = False parsed = False
if len(bits) not in (2, 3): if len(bits) not in (2, 3):
@ -933,7 +950,7 @@ def ssi(parser, token):
else: else:
raise TemplateSyntaxError("Second (optional) argument to %s tag" raise TemplateSyntaxError("Second (optional) argument to %s tag"
" must be 'parsed'" % bits[0]) " must be 'parsed'" % bits[0])
return SsiNode(bits[1], parsed) return SsiNode(bits[1], parsed, legacy_filepath=True)
ssi = register.tag(ssi) ssi = register.tag(ssi)
#@register.tag #@register.tag
@ -945,8 +962,36 @@ def load(parser, token):
``django/templatetags/news/photos.py``:: ``django/templatetags/news/photos.py``::
{% load news.photos %} {% load news.photos %}
Can also be used to load an individual tag/filter from
a library::
{% load byline from news %}
""" """
bits = token.contents.split() bits = token.contents.split()
if len(bits) >= 4 and bits[-2] == "from":
try:
taglib = bits[-1]
lib = get_library(taglib)
except InvalidTemplateLibrary, e:
raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
(taglib, e))
else:
temp_lib = Library()
for name in bits[1:-2]:
if name in lib.tags:
temp_lib.tags[name] = lib.tags[name]
# a name could be a tag *and* a filter, so check for both
if name in lib.filters:
temp_lib.filters[name] = lib.filters[name]
elif name in lib.filters:
temp_lib.filters[name] = lib.filters[name]
else:
raise TemplateSyntaxError("'%s' is not a valid tag or filter in tag library '%s'" %
(name, taglib))
parser.add_library(temp_lib)
else:
for taglib in bits[1:]: for taglib in bits[1:]:
# add the library to the parser # add the library to the parser
try: try:
@ -1140,6 +1185,11 @@ def url(parser, token):
The URL will look like ``/clients/client/123/``. The URL will look like ``/clients/client/123/``.
""" """
import warnings
warnings.warn('The syntax for the url template tag is changing. Load the `url` tag from the `future` tag library to start using the new behavior.',
category=PendingDeprecationWarning)
bits = token.split_contents() bits = token.split_contents()
if len(bits) < 2: if len(bits) < 2:
raise TemplateSyntaxError("'%s' takes at least one argument" raise TemplateSyntaxError("'%s' takes at least one argument"
@ -1196,7 +1246,7 @@ def url(parser, token):
else: else:
args.append(parser.compile_filter(value)) args.append(parser.compile_filter(value))
return URLNode(viewname, args, kwargs, asvar) return URLNode(viewname, args, kwargs, asvar, legacy_view_name=True)
url = register.tag(url) url = register.tag(url)
#@register.tag #@register.tag

View File

@ -0,0 +1,99 @@
from django.conf import settings
from django.template import Library, Node, Template, TemplateSyntaxError
from django.template.defaulttags import kwarg_re, include_is_allowed, SsiNode, URLNode
from django.utils.encoding import smart_str
register = Library()
@register.tag
def ssi(parser, token):
"""
Outputs the contents of a given file into the page.
Like a simple "include" tag, the ``ssi`` tag includes the contents
of another file -- which must be specified using an absolute path --
in the current page::
{% ssi "/home/html/ljworld.com/includes/right_generic.html" %}
If the optional "parsed" parameter is given, the contents of the included
file are evaluated as template code, with the current context::
{% ssi "/home/html/ljworld.com/includes/right_generic.html" parsed %}
"""
bits = token.contents.split()
parsed = False
if len(bits) not in (2, 3):
raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
" the file to be included")
if len(bits) == 3:
if bits[2] == 'parsed':
parsed = True
else:
raise TemplateSyntaxError("Second (optional) argument to %s tag"
" must be 'parsed'" % bits[0])
filepath = parser.compile_filter(bits[1])
return SsiNode(filepath, parsed, legacy_filepath=False)
@register.tag
def url(parser, token):
"""
Returns an absolute URL matching given view with its parameters.
This is a way to define links that aren't tied to a particular URL
configuration::
{% url "path.to.some_view" arg1 arg2 %}
or
{% url "path.to.some_view" name1=value1 name2=value2 %}
The first argument is a path to a view. It can be an absolute python path
or just ``app_name.view_name`` without the project name if the view is
located inside the project. Other arguments are comma-separated values
that will be filled in place of positional and keyword arguments in the
URL. All arguments for the URL should be present.
For example if you have a view ``app_name.client`` taking client's id and
the corresponding line in a URLconf looks like this::
('^client/(\d+)/$', 'app_name.client')
and this app's URLconf is included into the project's URLconf under some
path::
('^clients/', include('project_name.app_name.urls'))
then in a template you can create a link for a certain client like this::
{% url "app_name.client" client.id %}
The URL will look like ``/clients/client/123/``.
"""
bits = token.split_contents()
if len(bits) < 2:
raise TemplateSyntaxError("'%s' takes at least one argument"
" (path to a view)" % bits[0])
viewname = parser.compile_filter(bits[1])
args = []
kwargs = {}
asvar = None
bits = bits[2:]
if len(bits) >= 2 and bits[-2] == 'as':
asvar = bits[-1]
bits = bits[:-2]
if len(bits):
for bit in bits:
match = kwarg_re.match(bit)
if not match:
raise TemplateSyntaxError("Malformed arguments to url tag")
name, value = match.groups()
if name:
kwargs[name] = parser.compile_filter(value)
else:
args.append(parser.compile_filter(value))
return URLNode(viewname, args, kwargs, asvar, legacy_view_name=False)

View File

@ -131,6 +131,11 @@ their deprecation, as per the :ref:`Django deprecation policy
been deprecated in favor of the been deprecated in favor of the
:class:`~django.contrib.staticfiles.handlers.StaticFilesHandler`. :class:`~django.contrib.staticfiles.handlers.StaticFilesHandler`.
* The :ttag:`url` and :ttag:`ssi` template tags will be
modified so that the first argument to each tag is a
template variable, not an implied string. The new-style
behavior is provided in the ``future`` template tag library.
* 2.0 * 2.0
* ``django.views.defaults.shortcut()``. This function has been moved * ``django.views.defaults.shortcut()``. This function has been moved
to ``django.contrib.contenttypes.views.shortcut()`` as part of the to ``django.contrib.contenttypes.views.shortcut()`` as part of the

View File

@ -657,6 +657,17 @@ load
Load a custom template tag set. Load a custom template tag set.
For example, the following template would load all the tags and filters registered
in ``somelibrary`` and ``otherlibrary``::
{% load somelibrary otherlibrary %}
You can also selectively load individual templates or tags from a library, using
the ``from`` argument. In this example, the template tags/filters named ``foo``
and ``bar`` will be loaded from ``somelibrary``::
{% load foo bar from somelibrary %}
See :doc:`Custom tag and filter libraries </howto/custom-template-tags>` for more information. See :doc:`Custom tag and filter libraries </howto/custom-template-tags>` for more information.
.. templatetag:: now .. templatetag:: now
@ -838,6 +849,30 @@ Note that if you use ``{% ssi %}``, you'll need to define
See also: ``{% include %}``. See also: ``{% include %}``.
.. admonition:: Forwards compatibility
.. versionchanged:: 1.3
In Django 1.5, the behavior of the :ttag:`ssi` template tag will
change, with the the first argument being made into a context
variable, rather than being a special case unquoted constant. This
will allow the :ttag:`ssi` tag to use a context variable as the
value of the page to be included.
In order to provide a forwards compatibility path, Django 1.3
provides a future compatibility library -- ``future`` -- that
implements the new behavior. To use this library, add a
:ttag:`load` call at the top of any template using the :ttag:`ssi`
tag, and wrap the first argument to the :ttag:`ssi` tag in quotes.
For example::
{% load ssi from future %}
{% ssi '/home/html/ljworld.com/includes/right_generic.html' %}
In Django 1.5, the unquoted constant behavior will be replaced
with the behavior provided by the ``future`` tag library.
Existing templates be migrated to use the new syntax.
.. templatetag:: templatetag .. templatetag:: templatetag
templatetag templatetag
@ -955,10 +990,37 @@ here's what it looks like::
{% url path.to.view arg,arg2 %} {% url path.to.view arg,arg2 %}
{% url path.to.view arg, arg2 %} {% url path.to.view arg, arg2 %}
This syntax doesn't support the use of literal commas, or or equals This syntax doesn't support the use of literal commas, or equals
signs. Did we mention you shouldn't use this syntax in any new signs. Did we mention you shouldn't use this syntax in any new
projects? projects?
.. admonition:: Forwards compatibility
.. versionchanged:: 1.3
In Django 1.5, the behavior of the :ttag:`url` template tag will
change, with the the first argument being made into a context
variable, rather than being a special case unquoted constant. This
will allow the :ttag:`url` tag to use a context variable as the
value of the URL name to be reversed.
In order to provide a forwards compatibility path, Django 1.3
provides a future compatibility library -- ``future`` -- that
implements the new behavior. To use this library, add a
:ttag:`load` call at the top of any template using the :ttag:`url`
tag, and wrap the first argument to the :ttag:`url` tag in quotes.
For example::
{% load url from future %}
{% url 'myapp:view-name' %}
The new library also drops support for the comma syntax for
separating arguments to the :ttag:`url` template tag.
In Django 1.5, the old behavior will be replaced with the behavior
provided by the ``future`` tag library. Existing templates be
migrated to use the new syntax.
.. templatetag:: widthratio .. templatetag:: widthratio
widthratio widthratio

View File

@ -344,3 +344,44 @@ and Ctrl-C test termination) have been made redundant. In view of this
redundancy, :class:`~django.test.simple.DjangoTestRunner` has been redundancy, :class:`~django.test.simple.DjangoTestRunner` has been
turned into an empty placeholder class, and will be removed entirely turned into an empty placeholder class, and will be removed entirely
in Django 1.5. in Django 1.5.
Changes to :ttag:`url` and :ttag:`ssi`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Most template tags will allow you to pass in either constants or
variables as arguments -- for example::
{% extends "base.html" %}
allows you to specify a base template as a constant, but if you have a
context variable ``templ`` that contains the value ``base.html``::
{% extends templ %}
is also legal.
However, due to an accident of history, the :ttag:`url` and
:ttag:`ssi` are different. These tags use the second, quoteless
syntax, but interpret the argument as a constant. This means it isn't
possible to use a context variable as the target of a :ttag:`url` and
:ttag:`ssi` tag.
Django 1.3 marks the start of the process to correct this historical
accident. Django 1.3 adds a new template library -- ``future`` -- that
provides alternate implementations of the :ttag:`url` and :ttag:`ssi`
template tags. This ``future`` library implement behavior that makes
the handling of the first argument consistent with the handling of all
other variables. So, an existing template that contains::
{% url sample %}
should be replaced with::
{% load url from future %}
{% url 'sample' %}
The tags implementing the old behavior have been deprecated, and in
Django 1.5, the old behavior will be replaced with the new behavior.
To ensure compatibility with future versions of Django, existing
templates should be modified to use the new ``future`` libraries and
syntax.

View File

@ -1,4 +1,4 @@
unicode: {{ user }} {% load url from future %}unicode: {{ user }}
id: {{ user.id }} id: {{ user.id }}
username: {{ user.username }} username: {{ user.username }}
url: {% url userpage user %} url: {% url 'userpage' user %}

View File

@ -0,0 +1 @@
This is for testing an ssi include. {{ test }}

View File

@ -51,7 +51,12 @@ class EchoNode(template.Node):
def do_echo(parser, token): def do_echo(parser, token):
return EchoNode(token.contents.split()[1:]) return EchoNode(token.contents.split()[1:])
def do_upper(value):
return value.upper()
register.tag("echo", do_echo) register.tag("echo", do_echo)
register.tag("other_echo", do_echo)
register.filter("upper", do_upper)
template.libraries['testtags'] = register template.libraries['testtags'] = register
@ -354,6 +359,10 @@ class Templates(unittest.TestCase):
old_invalid = settings.TEMPLATE_STRING_IF_INVALID old_invalid = settings.TEMPLATE_STRING_IF_INVALID
expected_invalid_str = 'INVALID' expected_invalid_str = 'INVALID'
#Set ALLOWED_INCLUDE_ROOTS so that ssi works.
old_allowed_include_roots = settings.ALLOWED_INCLUDE_ROOTS
settings.ALLOWED_INCLUDE_ROOTS = os.path.dirname(os.path.abspath(__file__))
# Warm the URL reversing cache. This ensures we don't pay the cost # Warm the URL reversing cache. This ensures we don't pay the cost
# warming the cache during one of the tests. # warming the cache during one of the tests.
urlresolvers.reverse('regressiontests.templates.views.client_action', urlresolvers.reverse('regressiontests.templates.views.client_action',
@ -416,6 +425,7 @@ class Templates(unittest.TestCase):
deactivate() deactivate()
settings.TEMPLATE_DEBUG = old_td settings.TEMPLATE_DEBUG = old_td
settings.TEMPLATE_STRING_IF_INVALID = old_invalid settings.TEMPLATE_STRING_IF_INVALID = old_invalid
settings.ALLOWED_INCLUDE_ROOTS = old_allowed_include_roots
self.assertEqual(failures, [], "Tests failed:\n%s\n%s" % self.assertEqual(failures, [], "Tests failed:\n%s\n%s" %
('-'*70, ("\n%s\n" % ('-'*70)).join(failures))) ('-'*70, ("\n%s\n" % ('-'*70)).join(failures)))
@ -1056,6 +1066,19 @@ class Templates(unittest.TestCase):
'inheritance40': ("{% extends 'inheritance33' %}{% block opt %}new{{ block.super }}{% endblock %}", {'optional': 1}, '1new23'), 'inheritance40': ("{% extends 'inheritance33' %}{% block opt %}new{{ block.super }}{% endblock %}", {'optional': 1}, '1new23'),
'inheritance41': ("{% extends 'inheritance36' %}{% block opt %}new{{ block.super }}{% endblock %}", {'numbers': '123'}, '_new1_new2_new3_'), 'inheritance41': ("{% extends 'inheritance36' %}{% block opt %}new{{ block.super }}{% endblock %}", {'numbers': '123'}, '_new1_new2_new3_'),
### LOADING TAG LIBRARIES #################################################
# {% load %} tag, importing individual tags
'load1': ("{% load echo from testtags %}{% echo this that theother %}", {}, 'this that theother'),
'load2': ("{% load echo other_echo from testtags %}{% echo this that theother %} {% other_echo and another thing %}", {}, 'this that theother and another thing'),
'load3': ("{% load echo upper from testtags %}{% echo this that theother %} {{ statement|upper }}", {'statement': 'not shouting'}, 'this that theother NOT SHOUTING'),
# {% load %} tag errors
'load4': ("{% load echo other_echo bad_tag from testtags %}", {}, template.TemplateSyntaxError),
'load5': ("{% load echo other_echo bad_tag from %}", {}, template.TemplateSyntaxError),
'load6': ("{% load from testtags %}", {}, template.TemplateSyntaxError),
'load7': ("{% load echo from bad_library %}", {}, template.TemplateSyntaxError),
### I18N ################################################################## ### I18N ##################################################################
# {% spaceless %} tag # {% spaceless %} tag
@ -1179,6 +1202,30 @@ class Templates(unittest.TestCase):
'{% endfor %},' + \ '{% endfor %},' + \
'{% endfor %}', '{% endfor %}',
{}, ''), {}, ''),
### SSI TAG ########################################################
# Test normal behavior
'old-ssi01': ('{%% ssi %s %%}' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates', 'ssi_include.html'), {}, 'This is for testing an ssi include. {{ test }}\n'),
'old-ssi02': ('{%% ssi %s %%}' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'not_here'), {}, ''),
# Test parsed output
'old-ssi06': ('{%% ssi %s parsed %%}' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates', 'ssi_include.html'), {'test': 'Look ma! It parsed!'}, 'This is for testing an ssi include. Look ma! It parsed!\n'),
'old-ssi07': ('{%% ssi %s parsed %%}' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'not_here'), {'test': 'Look ma! It parsed!'}, ''),
# Future compatibility
# Test normal behavior
'ssi01': ('{%% load ssi from future %%}{%% ssi "%s" %%}' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates', 'ssi_include.html'), {}, 'This is for testing an ssi include. {{ test }}\n'),
'ssi02': ('{%% load ssi from future %%}{%% ssi "%s" %%}' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'not_here'), {}, ''),
'ssi03': ("{%% load ssi from future %%}{%% ssi '%s' %%}" % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'not_here'), {}, ''),
# Test passing as a variable
'ssi04': ('{% load ssi from future %}{% ssi ssi_file %}', {'ssi_file': os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates', 'ssi_include.html')}, 'This is for testing an ssi include. {{ test }}\n'),
'ssi05': ('{% load ssi from future %}{% ssi ssi_file %}', {'ssi_file': 'no_file'}, ''),
# Test parsed output
'ssi06': ('{%% load ssi from future %%}{%% ssi "%s" parsed %%}' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates', 'ssi_include.html'), {'test': 'Look ma! It parsed!'}, 'This is for testing an ssi include. Look ma! It parsed!\n'),
'ssi07': ('{%% load ssi from future %%}{%% ssi "%s" parsed %%}' % os.path.join(os.path.dirname(os.path.abspath(__file__)), 'not_here'), {'test': 'Look ma! It parsed!'}, ''),
### TEMPLATETAG TAG ####################################################### ### TEMPLATETAG TAG #######################################################
'templatetag01': ('{% templatetag openblock %}', {}, '{%'), 'templatetag01': ('{% templatetag openblock %}', {}, '{%'),
@ -1244,41 +1291,91 @@ class Templates(unittest.TestCase):
'legacyurl16a': ("{% url regressiontests.templates.views.client_action action='update',id='1' %}", {}, '/url_tag/client/1/update/'), 'legacyurl16a': ("{% url regressiontests.templates.views.client_action action='update',id='1' %}", {}, '/url_tag/client/1/update/'),
'legacyurl17': ('{% url regressiontests.templates.views.client_action client_id=client.my_id,action=action %}', {'client': {'my_id': 1}, 'action': 'update'}, '/url_tag/client/1/update/'), 'legacyurl17': ('{% url regressiontests.templates.views.client_action client_id=client.my_id,action=action %}', {'client': {'my_id': 1}, 'action': 'update'}, '/url_tag/client/1/update/'),
'url01': ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'), 'old-url01': ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),
'url02': ('{% url regressiontests.templates.views.client_action id=client.id action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'), 'old-url02': ('{% url regressiontests.templates.views.client_action id=client.id action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'url02a': ('{% url regressiontests.templates.views.client_action client.id "update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'), 'old-url02a': ('{% url regressiontests.templates.views.client_action client.id "update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'url02b': ("{% url regressiontests.templates.views.client_action id=client.id action='update' %}", {'client': {'id': 1}}, '/url_tag/client/1/update/'), 'old-url02b': ("{% url regressiontests.templates.views.client_action id=client.id action='update' %}", {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'url02c': ("{% url regressiontests.templates.views.client_action client.id 'update' %}", {'client': {'id': 1}}, '/url_tag/client/1/update/'), 'old-url02c': ("{% url regressiontests.templates.views.client_action client.id 'update' %}", {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'url03': ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'), 'old-url03': ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'),
'url04': ('{% url named.client client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'), 'old-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/'), 'old-url05': (u'{% url метка_оператора v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
'url06': (u'{% url метка_оператора_2 tag=v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'), 'old-url06': (u'{% url метка_оператора_2 tag=v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
'url07': (u'{% url regressiontests.templates.views.client2 tag=v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'), 'old-url07': (u'{% url regressiontests.templates.views.client2 tag=v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
'url08': (u'{% url метка_оператора v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'), 'old-url08': (u'{% url метка_оператора v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
'url09': (u'{% url метка_оператора_2 tag=v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'), 'old-url09': (u'{% url метка_оператора_2 tag=v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
'url10': ('{% url regressiontests.templates.views.client_action id=client.id action="two words" %}', {'client': {'id': 1}}, '/url_tag/client/1/two%20words/'), 'old-url10': ('{% url regressiontests.templates.views.client_action id=client.id action="two words" %}', {'client': {'id': 1}}, '/url_tag/client/1/two%20words/'),
'url11': ('{% url regressiontests.templates.views.client_action id=client.id action="==" %}', {'client': {'id': 1}}, '/url_tag/client/1/==/'), 'old-url11': ('{% url regressiontests.templates.views.client_action id=client.id action="==" %}', {'client': {'id': 1}}, '/url_tag/client/1/==/'),
'url12': ('{% url regressiontests.templates.views.client_action id=client.id action="," %}', {'client': {'id': 1}}, '/url_tag/client/1/,/'), 'old-url12': ('{% url regressiontests.templates.views.client_action id=client.id action="," %}', {'client': {'id': 1}}, '/url_tag/client/1/,/'),
'url13': ('{% url regressiontests.templates.views.client_action id=client.id action=arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'), 'old-url13': ('{% url regressiontests.templates.views.client_action id=client.id action=arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'),
'url14': ('{% url regressiontests.templates.views.client_action client.id arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'), 'old-url14': ('{% url regressiontests.templates.views.client_action client.id arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'),
'url15': ('{% url regressiontests.templates.views.client_action 12 "test" %}', {}, '/url_tag/client/12/test/'), 'old-url15': ('{% url regressiontests.templates.views.client_action 12 "test" %}', {}, '/url_tag/client/12/test/'),
'url18': ('{% url regressiontests.templates.views.client "1,2" %}', {}, '/url_tag/client/1,2/'), 'old-url18': ('{% url regressiontests.templates.views.client "1,2" %}', {}, '/url_tag/client/1,2/'),
# Failures # Failures
'url-fail01': ('{% url %}', {}, template.TemplateSyntaxError), 'old-url-fail01': ('{% url %}', {}, template.TemplateSyntaxError),
'url-fail02': ('{% url no_such_view %}', {}, urlresolvers.NoReverseMatch), 'old-url-fail02': ('{% url no_such_view %}', {}, urlresolvers.NoReverseMatch),
'url-fail03': ('{% url regressiontests.templates.views.client %}', {}, urlresolvers.NoReverseMatch), 'old-url-fail03': ('{% url regressiontests.templates.views.client %}', {}, urlresolvers.NoReverseMatch),
'url-fail04': ('{% url view id, %}', {}, template.TemplateSyntaxError), 'old-url-fail04': ('{% url view id, %}', {}, template.TemplateSyntaxError),
'url-fail05': ('{% url view id= %}', {}, template.TemplateSyntaxError), 'old-url-fail05': ('{% url view id= %}', {}, template.TemplateSyntaxError),
'url-fail06': ('{% url view a.id=id %}', {}, template.TemplateSyntaxError), 'old-url-fail06': ('{% url view a.id=id %}', {}, template.TemplateSyntaxError),
'url-fail07': ('{% url view a.id!id %}', {}, template.TemplateSyntaxError), 'old-url-fail07': ('{% url view a.id!id %}', {}, template.TemplateSyntaxError),
'url-fail08': ('{% url view id="unterminatedstring %}', {}, template.TemplateSyntaxError), 'old-url-fail08': ('{% url view id="unterminatedstring %}', {}, template.TemplateSyntaxError),
'url-fail09': ('{% url view id=", %}', {}, template.TemplateSyntaxError), 'old-url-fail09': ('{% url view id=", %}', {}, template.TemplateSyntaxError),
# {% url ... as var %} # {% url ... as var %}
'url-asvar01': ('{% url regressiontests.templates.views.index as url %}', {}, ''), 'old-url-asvar01': ('{% url regressiontests.templates.views.index as url %}', {}, ''),
'url-asvar02': ('{% url regressiontests.templates.views.index as url %}{{ url }}', {}, '/url_tag/'), 'old-url-asvar02': ('{% url regressiontests.templates.views.index as url %}{{ url }}', {}, '/url_tag/'),
'url-asvar03': ('{% url no_such_view as url %}{{ url }}', {}, ''), 'old-url-asvar03': ('{% url no_such_view as url %}{{ url }}', {}, ''),
# forward compatibility
# Successes
'url01': ('{% load url from future %}{% url "regressiontests.templates.views.client" client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),
'url02': ('{% load url from future %}{% url "regressiontests.templates.views.client_action" id=client.id action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'url02a': ('{% load url from future %}{% url "regressiontests.templates.views.client_action" client.id "update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'url02b': ("{% load url from future %}{% url 'regressiontests.templates.views.client_action' id=client.id action='update' %}", {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'url02c': ("{% load url from future %}{% url 'regressiontests.templates.views.client_action' client.id 'update' %}", {'client': {'id': 1}}, '/url_tag/client/1/update/'),
'url03': ('{% load url from future %}{% url "regressiontests.templates.views.index" %}', {}, '/url_tag/'),
'url04': ('{% load url from future %}{% url "named.client" client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'),
'url05': (u'{% load url from future %}{% url "метка_оператора" v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
'url06': (u'{% load url from future %}{% url "метка_оператора_2" tag=v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
'url07': (u'{% load url from future %}{% url "regressiontests.templates.views.client2" tag=v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
'url08': (u'{% load url from future %}{% url "метка_оператора" v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
'url09': (u'{% load url from future %}{% url "метка_оператора_2" tag=v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'),
'url10': ('{% load url from future %}{% url "regressiontests.templates.views.client_action" id=client.id action="two words" %}', {'client': {'id': 1}}, '/url_tag/client/1/two%20words/'),
'url11': ('{% load url from future %}{% url "regressiontests.templates.views.client_action" id=client.id action="==" %}', {'client': {'id': 1}}, '/url_tag/client/1/==/'),
'url12': ('{% load url from future %}{% url "regressiontests.templates.views.client_action" id=client.id action="," %}', {'client': {'id': 1}}, '/url_tag/client/1/,/'),
'url13': ('{% load url from future %}{% url "regressiontests.templates.views.client_action" id=client.id action=arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'),
'url14': ('{% load url from future %}{% url "regressiontests.templates.views.client_action" client.id arg|join:"-" %}', {'client': {'id': 1}, 'arg':['a','b']}, '/url_tag/client/1/a-b/'),
'url15': ('{% load url from future %}{% url "regressiontests.templates.views.client_action" 12 "test" %}', {}, '/url_tag/client/12/test/'),
'url18': ('{% load url from future %}{% url "regressiontests.templates.views.client" "1,2" %}', {}, '/url_tag/client/1,2/'),
'url19': ('{% load url from future %}{% url named_url client.id %}', {'named_url': 'regressiontests.templates.views.client', 'client': {'id': 1}}, '/url_tag/client/1/'),
# Failures
'url-fail01': ('{% load url from future %}{% url %}', {}, template.TemplateSyntaxError),
'url-fail02': ('{% load url from future %}{% url "no_such_view" %}', {}, urlresolvers.NoReverseMatch),
'url-fail03': ('{% load url from future %}{% url "regressiontests.templates.views.client" %}', {}, urlresolvers.NoReverseMatch),
'url-fail04': ('{% load url from future %}{% url "view" id, %}', {}, template.TemplateSyntaxError),
'url-fail05': ('{% load url from future %}{% url "view" id= %}', {}, template.TemplateSyntaxError),
'url-fail06': ('{% load url from future %}{% url "view" a.id=id %}', {}, template.TemplateSyntaxError),
'url-fail07': ('{% load url from future %}{% url "view" a.id!id %}', {}, template.TemplateSyntaxError),
'url-fail08': ('{% load url from future %}{% url "view" id="unterminatedstring %}', {}, template.TemplateSyntaxError),
'url-fail09': ('{% load url from future %}{% url "view" id=", %}', {}, template.TemplateSyntaxError),
'url-fail11': ('{% load url from future %}{% url named_url %}', {}, urlresolvers.NoReverseMatch),
'url-fail12': ('{% load url from future %}{% url named_url %}', {'named_url': 'no_such_view'}, urlresolvers.NoReverseMatch),
'url-fail13': ('{% load url from future %}{% url named_url %}', {'named_url': 'regressiontests.templates.views.client'}, urlresolvers.NoReverseMatch),
'url-fail14': ('{% load url from future %}{% url named_url id, %}', {'named_url': 'view'}, template.TemplateSyntaxError),
'url-fail15': ('{% load url from future %}{% url named_url id= %}', {'named_url': 'view'}, template.TemplateSyntaxError),
'url-fail16': ('{% load url from future %}{% url named_url a.id=id %}', {'named_url': 'view'}, template.TemplateSyntaxError),
'url-fail17': ('{% load url from future %}{% url named_url a.id!id %}', {'named_url': 'view'}, template.TemplateSyntaxError),
'url-fail18': ('{% load url from future %}{% url named_url id="unterminatedstring %}', {'named_url': 'view'}, template.TemplateSyntaxError),
'url-fail19': ('{% load url from future %}{% url named_url id=", %}', {'named_url': 'view'}, template.TemplateSyntaxError),
# {% url ... as var %}
'url-asvar01': ('{% load url from future %}{% url "regressiontests.templates.views.index" as url %}', {}, ''),
'url-asvar02': ('{% load url from future %}{% url "regressiontests.templates.views.index" as url %}{{ url }}', {}, '/url_tag/'),
'url-asvar03': ('{% load url from future %}{% url "no_such_view" as url %}{{ url }}', {}, ''),
### CACHE TAG ###################################################### ### CACHE TAG ######################################################
'cache01': ('{% load cache %}{% cache -1 test %}cache01{% endcache %}', {}, 'cache01'), 'cache01': ('{% load cache %}{% cache -1 test %}cache01{% endcache %}', {}, 'cache01'),

View File

@ -3,8 +3,9 @@ from django.template import Template, Context
from django.http import HttpResponse from django.http import HttpResponse
def inner_view(request): def inner_view(request):
content = Template('{% url outer as outer_url %}outer:{{ outer_url }},' content = Template('{% load url from future %}'
'{% url inner as inner_url %}inner:{{ inner_url }}').render(Context()) '{% url "outer" as outer_url %}outer:{{ outer_url }},'
'{% url "inner" as inner_url %}inner:{{ inner_url }}').render(Context())
return HttpResponse(content) return HttpResponse(content)
urlpatterns = patterns('', urlpatterns = patterns('',