Kaydet (Commit) 7ff5580d authored tarafından Russell Keith-Magee's avatar Russell Keith-Magee

Fixed #14389, #9666 -- Started the migration path to make the first argument to…

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
üst 591ad8af
{% extends "admin/base_site.html" %}
{% load i18n admin_modify adminmedia %}
{% load url from future %}
{% block extrahead %}{{ block.super }}
{% url admin:jsi18n as jsi18nurl %}
{% url 'admin:jsi18n' as jsi18nurl %}
<script type="text/javascript" src="{{ jsi18nurl|default:"../../../../jsi18n/" }}"></script>
{% endblock %}
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %}
......
<!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 %}>
<head>
<title>{% block title %}{% endblock %}</title>
......@@ -28,18 +28,18 @@
{% trans 'Welcome,' %}
<strong>{% filter force_escape %}{% firstof user.first_name user.username %}{% endfilter %}</strong>.
{% block userlinks %}
{% url django-admindocs-docroot as docsroot %}
{% url 'django-admindocs-docroot' as docsroot %}
{% if docsroot %}
<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> /
{% endif %}
{% url admin:password_change as password_change_url %}
{% url 'admin:password_change' as password_change_url %}
{% if password_change_url %}
<a href="{{ password_change_url }}">
{% else %}
<a href="{{ root_path }}password_change/">
{% endif %}
{% trans 'Change password' %}</a> /
{% url admin:logout as logout_url %}
{% url 'admin:logout' as logout_url %}
{% if logout_url %}
<a href="{{ logout_url }}">
{% else %}
......
{% extends "admin/base_site.html" %}
{% load adminmedia admin_list i18n %}
{% load url from future %}
{% block extrastyle %}
{{ block.super }}
<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" />
{% endif %}
{% 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>
{% endif %}
{{ media.css }}
......@@ -41,11 +41,11 @@
<a href="../../">
{% trans "Home" %}
</a>
&rsaquo;
&rsaquo;
<a href="../">
{{ app_label|capfirst }}
</a>
&rsaquo;
&rsaquo;
{{ cl.opts.verbose_name_plural|capfirst }}
</div>
{% endblock %}
......@@ -84,7 +84,7 @@
</div>
{% endif %}
{% endblock %}
<form id="changelist-form" action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %}>{% csrf_token %}
{% if cl.formset %}
{{ cl.formset.management_form }}
......
{% extends "admin/base_site.html" %}
{% 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 title %}{% trans 'Password change successful' %}{% endblock %}
......
{% extends "admin/base_site.html" %}
{% 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 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 title %}{% trans 'Password change' %}{% endblock %}
......
{% 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" %}
{% blocktrans %}for your user account at {{ site_name }}{% endblocktrans %}.
{% trans "Please go to the following page and choose a new password:" %}
{% 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 %}
{% trans "Your username, in case you've forgotten:" %} {{ user.username }}
......
......@@ -290,24 +290,30 @@ def include_is_allowed(filepath):
return False
class SsiNode(Node):
def __init__(self, filepath, parsed):
self.filepath, self.parsed = filepath, parsed
def __init__(self, filepath, parsed, legacy_filepath=True):
self.filepath = filepath
self.parsed = parsed
self.legacy_filepath = legacy_filepath
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:
return "[Didn't have permission to include file]"
else:
return '' # Fail silently for invalid includes.
try:
fp = open(self.filepath, 'r')
fp = open(filepath, 'r')
output = fp.read()
fp.close()
except IOError:
output = ''
if self.parsed:
try:
t = Template(output, name=self.filepath)
t = Template(output, name=filepath)
return t.render(context)
except TemplateSyntaxError, e:
if settings.DEBUG:
......@@ -356,8 +362,9 @@ class TemplateTagNode(Node):
return self.mapping.get(self.tagtype, '')
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.legacy_view_name = legacy_view_name
self.args = args
self.kwargs = kwargs
self.asvar = asvar
......@@ -365,22 +372,27 @@ class URLNode(Node):
def render(self, context):
from django.core.urlresolvers import reverse, NoReverseMatch
args = [arg.resolve(context) for arg in self.args]
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()])
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
# relative to what we guess is the "main" app. If they both fail,
# re-raise the NoReverseMatch unless we're using the
# {% url ... as var %} construct in which cause return nothing.
url = ''
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:
if settings.SETTINGS_MODULE:
project_name = settings.SETTINGS_MODULE.split('.')[0]
try:
url = reverse(project_name + '.' + self.view_name,
args=args, kwargs=kwargs, current_app=context.current_app)
url = reverse(project_name + '.' + view_name,
args=args, kwargs=kwargs,
current_app=context.current_app)
except NoReverseMatch:
if self.asvar is None:
# 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 %}
"""
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()
parsed = False
if len(bits) not in (2, 3):
......@@ -933,7 +950,7 @@ def ssi(parser, token):
else:
raise TemplateSyntaxError("Second (optional) argument to %s tag"
" must be 'parsed'" % bits[0])
return SsiNode(bits[1], parsed)
return SsiNode(bits[1], parsed, legacy_filepath=True)
ssi = register.tag(ssi)
#@register.tag
......@@ -945,16 +962,44 @@ def load(parser, token):
``django/templatetags/news/photos.py``::
{% load news.photos %}
Can also be used to load an individual tag/filter from
a library::
{% load byline from news %}
"""
bits = token.contents.split()
for taglib in bits[1:]:
# add the library to the parser
if len(bits) >= 4 and bits[-2] == "from":
try:
taglib = bits[-1]
lib = get_library(taglib)
parser.add_library(lib)
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:]:
# add the library to the parser
try:
lib = get_library(taglib)
parser.add_library(lib)
except InvalidTemplateLibrary, e:
raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
(taglib, e))
return LoadNode()
load = register.tag(load)
......@@ -1140,6 +1185,11 @@ def url(parser, token):
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()
if len(bits) < 2:
raise TemplateSyntaxError("'%s' takes at least one argument"
......@@ -1196,7 +1246,7 @@ def url(parser, token):
else:
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)
#@register.tag
......
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)
......@@ -131,6 +131,11 @@ their deprecation, as per the :ref:`Django deprecation policy
been deprecated in favor of the
: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
* ``django.views.defaults.shortcut()``. This function has been moved
to ``django.contrib.contenttypes.views.shortcut()`` as part of the
......
......@@ -657,6 +657,17 @@ load
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.
.. templatetag:: now
......@@ -838,6 +849,30 @@ Note that if you use ``{% ssi %}``, you'll need to define
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
......@@ -955,10 +990,37 @@ here's what it looks like::
{% 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
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
widthratio
......
......@@ -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
turned into an empty placeholder class, and will be removed entirely
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.
unicode: {{ user }}
{% load url from future %}unicode: {{ user }}
id: {{ user.id }}
username: {{ user.username }}
url: {% url userpage user %}
url: {% url 'userpage' user %}
......@@ -3,8 +3,9 @@ from django.template import Template, Context
from django.http import HttpResponse
def inner_view(request):
content = Template('{% url outer as outer_url %}outer:{{ outer_url }},'
'{% url inner as inner_url %}inner:{{ inner_url }}').render(Context())
content = Template('{% load url from future %}'
'{% url "outer" as outer_url %}outer:{{ outer_url }},'
'{% url "inner" as inner_url %}inner:{{ inner_url }}').render(Context())
return HttpResponse(content)
urlpatterns = patterns('',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment