mirror of https://github.com/django/django.git
Implemented auto-escaping of variable output in templates. Fully controllable by template authors and it's possible to write filters and templates that simulataneously work in both auto-escaped and non-auto-escaped environments if you need to. Fixed #2359
See documentation in templates.txt and templates_python.txt for how everything works. Backwards incompatible if you're inserting raw HTML output via template variables. Based on an original design from Simon Willison and with debugging help from Michael Radziej. git-svn-id: http://code.djangoproject.com/svn/django/trunk@6671 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
babfe78494
commit
356662cf74
|
@ -9,6 +9,8 @@ certain test -- e.g. being a DateField or ForeignKey.
|
|||
from django.db import models
|
||||
from django.utils.encoding import smart_unicode, iri_to_uri
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
import datetime
|
||||
|
||||
class FilterSpec(object):
|
||||
|
@ -39,7 +41,7 @@ class FilterSpec(object):
|
|||
def output(self, cl):
|
||||
t = []
|
||||
if self.has_output():
|
||||
t.append(_(u'<h3>By %s:</h3>\n<ul>\n') % self.title())
|
||||
t.append(_(u'<h3>By %s:</h3>\n<ul>\n') % escape(self.title()))
|
||||
|
||||
for choice in self.choices(cl):
|
||||
t.append(u'<li%s><a href="%s">%s</a></li>\n' % \
|
||||
|
@ -47,7 +49,7 @@ class FilterSpec(object):
|
|||
iri_to_uri(choice['query_string']),
|
||||
choice['display']))
|
||||
t.append('</ul>\n\n')
|
||||
return "".join(t)
|
||||
return mark_safe("".join(t))
|
||||
|
||||
class RelatedFilterSpec(FilterSpec):
|
||||
def __init__(self, f, request, params, model):
|
||||
|
|
|
@ -3,6 +3,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.encoding import smart_unicode
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
ADDITION = 1
|
||||
CHANGE = 2
|
||||
|
@ -49,4 +50,4 @@ class LogEntry(models.Model):
|
|||
Returns the admin URL to edit the object represented by this log entry.
|
||||
This is relative to the Django admin index page.
|
||||
"""
|
||||
return u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, self.object_id)
|
||||
return mark_safe(u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, self.object_id))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "admin/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ title|escape }} | {% trans 'Django site admin' %}{% endblock %}
|
||||
{% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %}
|
||||
|
||||
{% block branding %}
|
||||
<h1 id="site-name">{% trans 'Django administration' %}</h1>
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
{% block breadcrumbs %}{% if not is_popup %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="../../../">{% trans "Home" %}</a> ›
|
||||
<a href="../">{{ opts.verbose_name_plural|capfirst|escape }}</a> ›
|
||||
{% if add %}{% trans "Add" %} {{ opts.verbose_name|escape }}{% else %}{{ original|truncatewords:"18"|escape }}{% endif %}
|
||||
<a href="../">{{ opts.verbose_name_plural|capfirst }}</a> ›
|
||||
{% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
|
||||
</div>
|
||||
{% endif %}{% endblock %}
|
||||
{% block content %}<div id="content-main">
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{% if show %}
|
||||
<div class="xfull">
|
||||
<ul class="toplinks">
|
||||
{% if back %}<li class="date-back"><a href="{{ back.link }}">‹ {{ back.title|escape }}</a></li>{% endif %}
|
||||
{% if back %}<li class="date-back"><a href="{{ back.link }}">‹ {{ back.title }}</a></li>{% endif %}
|
||||
{% for choice in choices %}
|
||||
<li> {% if choice.link %}<a href="{{ choice.link }}">{% endif %}{{ choice.title|escape }}{% if choice.link %}</a>{% endif %}</li>
|
||||
<li> {% if choice.link %}<a href="{{ choice.link }}">{% endif %}{{ choice.title }}{% if choice.link %}</a>{% endif %}</li>
|
||||
{% endfor %}
|
||||
</ul><br class="clear" />
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="../../../../">{% trans "Home" %}</a> ›
|
||||
<a href="../../">{{ opts.verbose_name_plural|capfirst|escape }}</a> ›
|
||||
<a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> ›
|
||||
<a href="../">{{ object|escape|truncatewords:"18" }}</a> ›
|
||||
{% trans 'Delete' %}
|
||||
</div>
|
||||
|
@ -13,7 +13,7 @@
|
|||
<p>{% blocktrans with object|escape as escaped_object %}Deleting the {{ object_name }} '{{ escaped_object }}' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}</p>
|
||||
<ul>
|
||||
{% for obj in perms_lacking %}
|
||||
<li>{{ obj|escape }}</li>
|
||||
<li>{{ obj }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% load admin_modify %}
|
||||
<fieldset class="module aligned">
|
||||
{% for fcw in bound_related_object.form_field_collection_wrappers %}
|
||||
<h2>{{ bound_related_object.relation.opts.verbose_name|capfirst|escape }} #{{ forloop.counter }}</h2>
|
||||
<h2>{{ bound_related_object.relation.opts.verbose_name|capfirst }} #{{ forloop.counter }}</h2>
|
||||
{% if bound_related_object.show_url %}{% if fcw.obj.original %}
|
||||
<p><a href="/r/{{ fcw.obj.original.content_type_id }}/{{ fcw.obj.original.id }}/">View on site</a></p>
|
||||
{% endif %}{% endif %}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{% load admin_modify %}
|
||||
<fieldset class="module">
|
||||
<h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst|escape }}</h2><table>
|
||||
<h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst }}</h2><table>
|
||||
<thead><tr>
|
||||
{% for fw in bound_related_object.field_wrapper_list %}
|
||||
{% if fw.needs_header %}
|
||||
<th{{ fw.header_class_attribute }}>{{ fw.field.verbose_name|capfirst|escape }}</th>
|
||||
<th{{ fw.header_class_attribute }}>{{ fw.field.verbose_name|capfirst }}</th>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tr></thead>
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
{% for model in app.models %}
|
||||
<tr>
|
||||
{% if model.perms.change %}
|
||||
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name|escape }}</a></th>
|
||||
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th>
|
||||
{% else %}
|
||||
<th scope="row">{{ model.name|escape }}</th>
|
||||
<th scope="row">{{ model.name }}</th>
|
||||
{% endif %}
|
||||
|
||||
{% if model.perms.add %}
|
||||
|
@ -58,7 +58,7 @@
|
|||
{% else %}
|
||||
<ul class="actionlist">
|
||||
{% for entry in admin_log %}
|
||||
<li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{% filter capfirst|escape %}{% trans entry.content_type.name %}{% endfilter %}</span></li>
|
||||
<li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %}</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> › {{ title|escape }}</div>{% endblock %}
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> › {{ title }}</div>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs"><a href="../../../../">{% trans 'Home' %}</a> › <a href="../../">{{ module_name|escape }}</a> › <a href="../">{{ object|escape|truncatewords:"18" }}</a> › {% trans 'History' %}</div>
|
||||
<div class="breadcrumbs"><a href="../../../../">{% trans 'Home' %}</a> › <a href="../../">{{ module_name }}</a> › <a href="../">{{ object|truncatewords:"18" }}</a> › {% trans 'History' %}</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
@ -23,8 +23,8 @@
|
|||
{% for action in action_list %}
|
||||
<tr>
|
||||
<th scope="row">{{ action.action_time|date:_("DATE_WITH_TIME_FULL") }}</th>
|
||||
<td>{{ action.user.username }}{% if action.user.first_name %} ({{ action.user.first_name|escape }} {{ action.user.last_name|escape }}){% endif %}</td>
|
||||
<td>{{ action.change_message|escape }}</td>
|
||||
<td>{{ action.user.username }}{% if action.user.first_name %} ({{ action.user.first_name }} {{ action.user.last_name }}){% endif %}</td>
|
||||
<td>{{ action.change_message }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
|
@ -6,6 +6,6 @@
|
|||
{% paginator_number cl i %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{{ cl.result_count }} {% ifequal cl.result_count 1 %}{{ cl.opts.verbose_name|escape }}{% else %}{{ cl.opts.verbose_name_plural|escape }}{% endifequal %}
|
||||
{{ cl.result_count }} {% ifequal cl.result_count 1 %}{{ cl.opts.verbose_name|escape }}{% else %}{{ cl.opts.verbose_name_plural }}{% endifequal %}
|
||||
{% if show_all_url %} <a href="{{ show_all_url }}" class="showall">{% trans 'Show all' %}</a>{% endif %}
|
||||
</p>
|
||||
|
|
|
@ -8,16 +8,16 @@
|
|||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> › <a href="../../">Documentation</a> › <a href="../">Models</a> › {{ name|escape }}</div>{% endblock %}
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> › <a href="../../">Documentation</a> › <a href="../">Models</a> › {{ name }}</div>{% endblock %}
|
||||
|
||||
{% block title %}Model: {{ name|escape }}{% endblock %}
|
||||
{% block title %}Model: {{ name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="content-main">
|
||||
<h1>{{ summary|escape }}</h1>
|
||||
<h1>{{ summary }}</h1>
|
||||
|
||||
{% if description %}
|
||||
<p>{% filter escape|linebreaksbr %}{% trans description %}{% endfilter %}</p>
|
||||
<p>{% filter linebreaksbr %}{% trans description %}{% endfilter %}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="module">
|
||||
|
|
|
@ -15,6 +15,6 @@
|
|||
{{ bound_field.original_value }}
|
||||
{% endif %}
|
||||
{% if bound_field.raw_id_admin %}
|
||||
{% if bound_field.existing_display %} <strong>{{ bound_field.existing_display|truncatewords:"14"|escape }}</strong>{% endif %}
|
||||
{% if bound_field.existing_display %} <strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
{% if add %}{% include "widget/foreign.html" %}{% endif %}
|
||||
{% if change %}{% if bound_field.existing_display %} <strong>{{ bound_field.existing_display|truncatewords:"14"|escape }}</strong>{% endif %}{% endif %}
|
||||
{% if change %}{% if bound_field.existing_display %} <strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}{% endif %}
|
||||
|
|
|
@ -4,8 +4,9 @@ from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR,
|
|||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import models
|
||||
from django.utils import dateformat
|
||||
from django.utils.html import escape
|
||||
from django.utils.html import escape, conditional_escape
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import get_date_formats, get_partial_date_formats, ugettext as _
|
||||
from django.utils.encoding import smart_unicode, smart_str, force_unicode
|
||||
from django.template import Library
|
||||
|
@ -19,9 +20,9 @@ def paginator_number(cl,i):
|
|||
if i == DOT:
|
||||
return u'... '
|
||||
elif i == cl.page_num:
|
||||
return u'<span class="this-page">%d</span> ' % (i+1)
|
||||
return mark_safe(u'<span class="this-page">%d</span> ' % (i+1))
|
||||
else:
|
||||
return u'<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.pages-1 and ' class="end"' or ''), i+1)
|
||||
return mark_safe(u'<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.pages-1 and ' class="end"' or ''), i+1))
|
||||
paginator_number = register.simple_tag(paginator_number)
|
||||
|
||||
def pagination(cl):
|
||||
|
@ -117,7 +118,7 @@ def result_headers(cl):
|
|||
|
||||
def _boolean_icon(field_val):
|
||||
BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'}
|
||||
return u'<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)
|
||||
return mark_safe(u'<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val))
|
||||
|
||||
def items_for_result(cl, result):
|
||||
first = True
|
||||
|
@ -193,10 +194,10 @@ def items_for_result(cl, result):
|
|||
# Convert the pk to something that can be used in Javascript.
|
||||
# Problem cases are long ints (23L) and non-ASCII strings.
|
||||
result_id = repr(force_unicode(getattr(result, pk)))[1:]
|
||||
yield (u'<%s%s><a href="%s"%s>%s</a></%s>' % \
|
||||
(table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), result_repr, table_tag))
|
||||
yield mark_safe(u'<%s%s><a href="%s"%s>%s</a></%s>' % \
|
||||
(table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), conditional_escape(result_repr), table_tag))
|
||||
else:
|
||||
yield (u'<td%s>%s</td>' % (row_class, result_repr))
|
||||
yield mark_safe(u'<td%s>%s</td>' % (row_class, conditional_escape(result_repr)))
|
||||
|
||||
def results(cl):
|
||||
for res in cl.result_list:
|
||||
|
@ -220,7 +221,7 @@ def date_hierarchy(cl):
|
|||
day_lookup = cl.params.get(day_field)
|
||||
year_month_format, month_day_format = get_partial_date_formats()
|
||||
|
||||
link = lambda d: cl.get_query_string(d, [field_generic])
|
||||
link = lambda d: mark_safe(cl.get_query_string(d, [field_generic]))
|
||||
|
||||
if year_lookup and month_lookup and day_lookup:
|
||||
day = datetime.date(int(year_lookup), int(month_lookup), int(day_lookup))
|
||||
|
|
|
@ -3,6 +3,8 @@ from django.contrib.admin.views.main import AdminBoundField
|
|||
from django.template import loader
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.encoding import force_unicode
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.html import escape
|
||||
from django.db import models
|
||||
from django.db.models.fields import Field
|
||||
from django.db.models.related import BoundRelatedObject
|
||||
|
@ -32,7 +34,8 @@ def include_admin_script(script_path):
|
|||
"""
|
||||
if not absolute_url_re.match(script_path):
|
||||
script_path = '%s%s' % (settings.ADMIN_MEDIA_PREFIX, script_path)
|
||||
return u'<script type="text/javascript" src="%s"></script>' % script_path
|
||||
return mark_safe(u'<script type="text/javascript" src="%s"></script>'
|
||||
% script_path)
|
||||
include_admin_script = register.simple_tag(include_admin_script)
|
||||
|
||||
def submit_row(context):
|
||||
|
@ -63,8 +66,10 @@ def field_label(bound_field):
|
|||
class_names.append('inline')
|
||||
colon = ":"
|
||||
class_str = class_names and u' class="%s"' % u' '.join(class_names) or u''
|
||||
return u'<label for="%s"%s>%s%s</label> ' % (bound_field.element_id, class_str, \
|
||||
force_unicode(capfirst(bound_field.field.verbose_name)), colon)
|
||||
return mark_safe(u'<label for="%s"%s>%s%s</label> ' %
|
||||
(bound_field.element_id, class_str,
|
||||
escape(force_unicode(capfirst(bound_field.field.verbose_name))),
|
||||
colon))
|
||||
field_label = register.simple_tag(field_label)
|
||||
|
||||
class FieldWidgetNode(template.Node):
|
||||
|
@ -193,15 +198,15 @@ def auto_populated_field_script(auto_pop_fields, change = False):
|
|||
' var e = document.getElementById("id_%s");' \
|
||||
' if(!e._changed) { e.value = URLify(%s, %s);} }; ' % (
|
||||
f, field.name, add_values, field.max_length))
|
||||
return u''.join(t)
|
||||
return mark_safe(u''.join(t))
|
||||
auto_populated_field_script = register.simple_tag(auto_populated_field_script)
|
||||
|
||||
def filter_interface_script_maybe(bound_field):
|
||||
f = bound_field.field
|
||||
if f.rel and isinstance(f.rel, models.ManyToManyRel) and f.rel.filter_interface:
|
||||
return u'<script type="text/javascript">addEvent(window, "load", function(e) {' \
|
||||
return mark_safe(u'<script type="text/javascript">addEvent(window, "load", function(e) {' \
|
||||
' SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % (
|
||||
f.name, f.verbose_name.replace('"', '\\"'), f.rel.filter_interface-1, settings.ADMIN_MEDIA_PREFIX)
|
||||
f.name, escape(f.verbose_name.replace('"', '\\"')), f.rel.filter_interface-1, settings.ADMIN_MEDIA_PREFIX))
|
||||
else:
|
||||
return ''
|
||||
filter_interface_script_maybe = register.simple_tag(filter_interface_script_maybe)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from django import template
|
||||
from django.db.models import get_models
|
||||
from django.utils.encoding import force_unicode
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
@ -38,7 +39,7 @@ class AdminApplistNode(template.Node):
|
|||
if True in perms.values():
|
||||
model_list.append({
|
||||
'name': force_unicode(capfirst(m._meta.verbose_name_plural)),
|
||||
'admin_url': u'%s/%s/' % (force_unicode(app_label), m.__name__.lower()),
|
||||
'admin_url': mark_safe(u'%s/%s/' % (force_unicode(app_label), m.__name__.lower())),
|
||||
'perms': perms,
|
||||
})
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import re
|
||||
from email.Parser import HeaderParser
|
||||
from email.Errors import HeaderParseError
|
||||
from django.utils.safestring import mark_safe
|
||||
try:
|
||||
import docutils.core
|
||||
import docutils.nodes
|
||||
|
@ -66,7 +67,7 @@ def parse_rst(text, default_reference_context, thing_being_parsed=None, link_bas
|
|||
parts = docutils.core.publish_parts(text, source_path=thing_being_parsed,
|
||||
destination_path=None, writer_name='html',
|
||||
settings_overrides=overrides)
|
||||
return parts['fragment']
|
||||
return mark_safe(parts['fragment'])
|
||||
|
||||
#
|
||||
# reST roles
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.contrib.auth.models import User
|
|||
from django.contrib.auth import authenticate, login
|
||||
from django.shortcuts import render_to_response
|
||||
from django.utils.translation import ugettext_lazy, ugettext as _
|
||||
from django.utils.safestring import mark_safe
|
||||
import base64, datetime, md5
|
||||
import cPickle as pickle
|
||||
|
||||
|
@ -22,7 +23,7 @@ def _display_login_form(request, error_message=''):
|
|||
post_data = _encode_post_data({})
|
||||
return render_to_response('admin/login.html', {
|
||||
'title': _('Log in'),
|
||||
'app_path': request.path,
|
||||
'app_path': mark_safe(request.path),
|
||||
'post_data': post_data,
|
||||
'error_message': error_message
|
||||
}, context_instance=template.RequestContext(request))
|
||||
|
|
|
@ -10,6 +10,7 @@ from django.core import urlresolvers
|
|||
from django.contrib.admin import utils
|
||||
from django.contrib.sites.models import Site
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.safestring import mark_safe
|
||||
import inspect, os, re
|
||||
|
||||
# Exclude methods starting with these strings from documentation
|
||||
|
@ -29,7 +30,7 @@ def bookmarklets(request):
|
|||
# Hack! This couples this view to the URL it lives at.
|
||||
admin_root = request.path[:-len('doc/bookmarklets/')]
|
||||
return render_to_response('admin_doc/bookmarklets.html', {
|
||||
'admin_url': "%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), admin_root),
|
||||
'admin_url': mark_safe("%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), admin_root)),
|
||||
}, context_instance=RequestContext(request))
|
||||
bookmarklets = staff_member_required(bookmarklets)
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ from django.utils.html import escape
|
|||
from django.utils.text import capfirst, get_text_list
|
||||
from django.utils.encoding import force_unicode, smart_str
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.safestring import mark_safe
|
||||
import operator
|
||||
|
||||
try:
|
||||
|
@ -136,7 +137,9 @@ class AdminBoundField(object):
|
|||
self._repr_filled = False
|
||||
|
||||
if field.rel:
|
||||
self.related_url = u'../../../%s/%s/' % (field.rel.to._meta.app_label, field.rel.to._meta.object_name.lower())
|
||||
self.related_url = mark_safe(u'../../../%s/%s/'
|
||||
% (field.rel.to._meta.app_label,
|
||||
field.rel.to._meta.object_name.lower()))
|
||||
|
||||
def original_value(self):
|
||||
if self.original:
|
||||
|
@ -216,7 +219,7 @@ def render_change_form(model, manipulator, context, add=False, change=False, for
|
|||
'javascript_imports': get_javascript_imports(opts, auto_populated_fields, field_sets),
|
||||
'ordered_objects': ordered_objects,
|
||||
'inline_related_objects': inline_related_objects,
|
||||
'form_url': form_url,
|
||||
'form_url': mark_safe(form_url),
|
||||
'opts': opts,
|
||||
'content_type_id': ContentType.objects.get_for_model(model).id,
|
||||
}
|
||||
|
@ -436,12 +439,14 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
|
|||
if related.field.rel.edit_inline or not related.opts.admin:
|
||||
# Don't display link to edit, because it either has no
|
||||
# admin or is edited inline.
|
||||
nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj), []])
|
||||
nh(deleted_objects, current_depth, [mark_safe(u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj)), []])
|
||||
else:
|
||||
# Display a link to the admin page.
|
||||
nh(deleted_objects, current_depth, [u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
|
||||
(force_unicode(capfirst(related.opts.verbose_name)), related.opts.app_label, related.opts.object_name.lower(),
|
||||
sub_obj._get_pk_val(), sub_obj), []])
|
||||
nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="../../../../%s/%s/%s/">%s</a>' %
|
||||
(escape(force_unicode(capfirst(related.opts.verbose_name))),
|
||||
related.opts.app_label,
|
||||
related.opts.object_name.lower(),
|
||||
sub_obj._get_pk_val(), sub_obj)), []])
|
||||
_get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
|
||||
else:
|
||||
has_related_objs = False
|
||||
|
@ -453,8 +458,8 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
|
|||
nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), escape(sub_obj)), []])
|
||||
else:
|
||||
# Display a link to the admin page.
|
||||
nh(deleted_objects, current_depth, [u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
|
||||
(force_unicode(capfirst(related.opts.verbose_name)), related.opts.app_label, related.opts.object_name.lower(), sub_obj._get_pk_val(), escape(sub_obj)), []])
|
||||
nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
|
||||
(escape(force_unicode(capfirst(related.opts.verbose_name))), related.opts.app_label, related.opts.object_name.lower(), sub_obj._get_pk_val(), escape(sub_obj))), []])
|
||||
_get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
|
||||
# If there were related objects, and the user doesn't have
|
||||
# permission to delete them, add the missing perm to perms_needed.
|
||||
|
@ -485,9 +490,9 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current
|
|||
else:
|
||||
# Display a link to the admin page.
|
||||
nh(deleted_objects, current_depth, [
|
||||
(_('One or more %(fieldname)s in %(name)s:') % {'fieldname': force_unicode(related.field.verbose_name), 'name': force_unicode(related.opts.verbose_name)}) + \
|
||||
mark_safe((_('One or more %(fieldname)s in %(name)s:') % {'fieldname': escape(force_unicode(related.field.verbose_name)), 'name': escape(force_unicode(related.opts.verbose_name))}) + \
|
||||
(u' <a href="../../../../%s/%s/%s/">%s</a>' % \
|
||||
(related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(sub_obj))), []])
|
||||
(related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(sub_obj)))), []])
|
||||
# If there were related objects, and the user doesn't have
|
||||
# permission to change them, add the missing perm to perms_needed.
|
||||
if related.opts.admin and has_related_objs:
|
||||
|
@ -507,7 +512,7 @@ def delete_stage(request, app_label, model_name, object_id):
|
|||
|
||||
# Populate deleted_objects, a data structure of all related objects that
|
||||
# will also be deleted.
|
||||
deleted_objects = [u'%s: <a href="../../%s/">%s</a>' % (force_unicode(capfirst(opts.verbose_name)), force_unicode(object_id), escape(obj)), []]
|
||||
deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), force_unicode(object_id), escape(obj))), []]
|
||||
perms_needed = set()
|
||||
_get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1)
|
||||
|
||||
|
@ -604,7 +609,7 @@ class ChangeList(object):
|
|||
del p[k]
|
||||
elif v is not None:
|
||||
p[k] = v
|
||||
return '?' + '&'.join([u'%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20')
|
||||
return mark_safe('?' + '&'.join([u'%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20'))
|
||||
|
||||
def get_results(self, request):
|
||||
paginator = ObjectPaginator(self.query_set, self.lookup_opts.admin.list_per_page)
|
||||
|
|
|
@ -7,11 +7,12 @@ against request forgeries from other sites.
|
|||
"""
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponseForbidden
|
||||
from django.utils.safestring import mark_safe
|
||||
import md5
|
||||
import re
|
||||
import itertools
|
||||
|
||||
_ERROR_MSG = '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>'
|
||||
_ERROR_MSG = mark_safe('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>')
|
||||
|
||||
_POST_FORM_RE = \
|
||||
re.compile(r'(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
|
||||
|
@ -82,10 +83,10 @@ class CsrfMiddleware(object):
|
|||
itertools.repeat(''))
|
||||
def add_csrf_field(match):
|
||||
"""Returns the matched <form> tag plus the added <input> element"""
|
||||
return match.group() + "<div style='display:none;'>" + \
|
||||
return mark_safe(match.group() + "<div style='display:none;'>" + \
|
||||
"<input type='hidden' " + idattributes.next() + \
|
||||
" name='csrfmiddlewaretoken' value='" + csrf_token + \
|
||||
"' /></div>"
|
||||
"' /></div>")
|
||||
|
||||
# Modify any POST forms
|
||||
response.content = _POST_FORM_RE.sub(add_csrf_field, response.content)
|
||||
|
|
|
@ -8,6 +8,7 @@ from django.utils import dateformat
|
|||
from django.utils.text import capfirst
|
||||
from django.utils.translation import get_date_formats
|
||||
from django.utils.encoding import smart_unicode, smart_str, iri_to_uri
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.db.models.query import QuerySet
|
||||
|
||||
EMPTY_VALUE = '(None)'
|
||||
|
@ -28,7 +29,7 @@ class EasyModel(object):
|
|||
return self.site.registry[self.model]
|
||||
|
||||
def url(self):
|
||||
return '%s%s/%s/' % (self.site.root_url, self.model._meta.app_label, self.model._meta.module_name)
|
||||
return mark_safe('%s%s/%s/' % (self.site.root_url, self.model._meta.app_label, self.model._meta.module_name))
|
||||
|
||||
def objects(self, **kwargs):
|
||||
return self.get_query_set().filter(**kwargs)
|
||||
|
@ -68,9 +69,9 @@ class EasyField(object):
|
|||
|
||||
def url(self):
|
||||
if self.field.choices:
|
||||
return '%s%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name)
|
||||
return mark_safe('%s%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name))
|
||||
elif self.field.rel:
|
||||
return '%s%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name)
|
||||
return mark_safe('%s%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name))
|
||||
|
||||
class EasyChoice(object):
|
||||
def __init__(self, easy_model, field, value, label):
|
||||
|
@ -81,7 +82,7 @@ class EasyChoice(object):
|
|||
return smart_str(u'<EasyChoice for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
|
||||
|
||||
def url(self):
|
||||
return '%s%s/%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.field.name, iri_to_uri(self.value))
|
||||
return mark_safe('%s%s/%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.field.name, iri_to_uri(self.value)))
|
||||
|
||||
class EasyInstance(object):
|
||||
def __init__(self, easy_model, instance):
|
||||
|
@ -184,14 +185,14 @@ class EasyInstanceField(object):
|
|||
if self.field.rel.to in self.model.model_list:
|
||||
lst = []
|
||||
for value in self.values():
|
||||
url = '%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, iri_to_uri(value._get_pk_val()))
|
||||
url = mark_safe('%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, iri_to_uri(value._get_pk_val())))
|
||||
lst.append((smart_unicode(value), url))
|
||||
else:
|
||||
lst = [(value, None) for value in self.values()]
|
||||
elif self.field.choices:
|
||||
lst = []
|
||||
for value in self.values():
|
||||
url = '%s%s/%s/fields/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name, iri_to_uri(self.raw_value))
|
||||
url = mark_safe('%s%s/%s/fields/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name, iri_to_uri(self.raw_value)))
|
||||
lst.append((value, url))
|
||||
elif isinstance(self.field, models.URLField):
|
||||
val = self.values()[0]
|
||||
|
|
|
@ -5,8 +5,9 @@ from django.contrib.databrowse.sites import DatabrowsePlugin
|
|||
from django.shortcuts import render_to_response
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.translation import get_date_formats
|
||||
from django.views.generic import date_based
|
||||
from django.utils.encoding import force_unicode
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.views.generic import date_based
|
||||
import datetime
|
||||
import time
|
||||
|
||||
|
@ -29,16 +30,17 @@ class CalendarPlugin(DatabrowsePlugin):
|
|||
fields = self.field_dict(model)
|
||||
if not fields:
|
||||
return u''
|
||||
return u'<p class="filter"><strong>View calendar by:</strong> %s</p>' % \
|
||||
u', '.join(['<a href="calendars/%s/">%s</a>' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])
|
||||
return mark_safe(u'<p class="filter"><strong>View calendar by:</strong> %s</p>' % \
|
||||
u', '.join(['<a href="calendars/%s/">%s</a>' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()]))
|
||||
|
||||
def urls(self, plugin_name, easy_instance_field):
|
||||
if isinstance(easy_instance_field.field, models.DateField):
|
||||
return [u'%s%s/%s/%s/%s/%s/' % (easy_instance_field.model.url(),
|
||||
return [mark_safe(u'%s%s/%s/%s/%s/%s/' % (
|
||||
easy_instance_field.model.url(),
|
||||
plugin_name, easy_instance_field.field.name,
|
||||
easy_instance_field.raw_value.year,
|
||||
easy_instance_field.raw_value.strftime('%b').lower(),
|
||||
easy_instance_field.raw_value.day)]
|
||||
easy_instance_field.raw_value.day))]
|
||||
|
||||
def model_view(self, request, model_databrowse, url):
|
||||
self.model, self.site = model_databrowse.model, model_databrowse.site
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.contrib.databrowse.sites import DatabrowsePlugin
|
|||
from django.shortcuts import render_to_response
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.encoding import smart_str, force_unicode
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.views.generic import date_based
|
||||
import datetime
|
||||
import time
|
||||
|
@ -32,15 +33,16 @@ class FieldChoicePlugin(DatabrowsePlugin):
|
|||
fields = self.field_dict(model)
|
||||
if not fields:
|
||||
return u''
|
||||
return u'<p class="filter"><strong>View by:</strong> %s</p>' % \
|
||||
u', '.join(['<a href="fields/%s/">%s</a>' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])
|
||||
return mark_safe(u'<p class="filter"><strong>View by:</strong> %s</p>' % \
|
||||
u', '.join(['<a href="fields/%s/">%s</a>' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()]))
|
||||
|
||||
def urls(self, plugin_name, easy_instance_field):
|
||||
if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values():
|
||||
field_value = smart_str(easy_instance_field.raw_value)
|
||||
return [u'%s%s/%s/%s/' % (easy_instance_field.model.url(),
|
||||
return [mark_safe(u'%s%s/%s/%s/' % (
|
||||
easy_instance_field.model.url(),
|
||||
plugin_name, easy_instance_field.field.name,
|
||||
urllib.quote(field_value, safe=''))]
|
||||
urllib.quote(field_value, safe='')))]
|
||||
|
||||
def model_view(self, request, model_databrowse, url):
|
||||
self.model, self.site = model_databrowse.model, model_databrowse.site
|
||||
|
|
|
@ -2,6 +2,7 @@ from django import http
|
|||
from django.db import models
|
||||
from django.contrib.databrowse.datastructures import EasyModel, EasyChoice
|
||||
from django.shortcuts import render_to_response
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
class AlreadyRegistered(Exception):
|
||||
pass
|
||||
|
@ -60,7 +61,7 @@ class ModelDatabrowse(object):
|
|||
|
||||
def main_view(self, request):
|
||||
easy_model = EasyModel(self.site, self.model)
|
||||
html_snippets = u'\n'.join([p.model_index_html(request, self.model, self.site) for p in self.plugins.values()])
|
||||
html_snippets = mark_safe(u'\n'.join([p.model_index_html(request, self.model, self.site) for p in self.plugins.values()]))
|
||||
return render_to_response('databrowse/model_detail.html', {
|
||||
'model': easy_model,
|
||||
'root_url': self.site.root_url,
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.shortcuts import get_object_or_404
|
|||
from django.http import HttpResponse
|
||||
from django.conf import settings
|
||||
from django.core.xheaders import populate_xheaders
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
DEFAULT_TEMPLATE = 'flatpages/default.html'
|
||||
|
||||
|
@ -30,6 +31,13 @@ def flatpage(request, url):
|
|||
t = loader.select_template((f.template_name, DEFAULT_TEMPLATE))
|
||||
else:
|
||||
t = loader.get_template(DEFAULT_TEMPLATE)
|
||||
|
||||
# To avoid having to always use the "|safe" filter in flatpage templates,
|
||||
# mark the title and content as already safe (since they are raw HTML
|
||||
# content in the first place).
|
||||
f.title = mark_safe(f.title)
|
||||
f.content = mark_safe(f.content)
|
||||
|
||||
c = RequestContext(request, {
|
||||
'flatpage': f,
|
||||
})
|
||||
|
|
|
@ -21,6 +21,7 @@ def ordinal(value):
|
|||
if value % 100 in (11, 12, 13): # special case
|
||||
return u"%d%s" % (value, t[0])
|
||||
return u'%d%s' % (value, t[value % 10])
|
||||
ordinal.is_safe = True
|
||||
register.filter(ordinal)
|
||||
|
||||
def intcomma(value):
|
||||
|
@ -34,6 +35,7 @@ def intcomma(value):
|
|||
return new
|
||||
else:
|
||||
return intcomma(new)
|
||||
intcomma.is_safe = True
|
||||
register.filter(intcomma)
|
||||
|
||||
def intword(value):
|
||||
|
@ -55,6 +57,7 @@ def intword(value):
|
|||
new_value = value / 1000000000000.0
|
||||
return ungettext('%(value).1f trillion', '%(value).1f trillion', new_value) % {'value': new_value}
|
||||
return value
|
||||
intword.is_safe = False
|
||||
register.filter(intword)
|
||||
|
||||
def apnumber(value):
|
||||
|
@ -69,6 +72,7 @@ def apnumber(value):
|
|||
if not 0 < value < 10:
|
||||
return value
|
||||
return (_('one'), _('two'), _('three'), _('four'), _('five'), _('six'), _('seven'), _('eight'), _('nine'))[value-1]
|
||||
apnumber.is_safe = True
|
||||
register.filter(apnumber)
|
||||
|
||||
def naturalday(value, arg=None):
|
||||
|
|
|
@ -17,6 +17,7 @@ silently fail and return the un-marked-up text.
|
|||
from django import template
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import smart_str, force_unicode
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
@ -28,7 +29,8 @@ def textile(value):
|
|||
raise template.TemplateSyntaxError, "Error in {% textile %} filter: The Python textile library isn't installed."
|
||||
return force_unicode(value)
|
||||
else:
|
||||
return force_unicode(textile.textile(smart_str(value), encoding='utf-8', output='utf-8'))
|
||||
return mark_safe(force_unicode(textile.textile(smart_str(value), encoding='utf-8', output='utf-8')))
|
||||
textile.is_safe = True
|
||||
|
||||
def markdown(value):
|
||||
try:
|
||||
|
@ -38,7 +40,8 @@ def markdown(value):
|
|||
raise template.TemplateSyntaxError, "Error in {% markdown %} filter: The Python markdown library isn't installed."
|
||||
return force_unicode(value)
|
||||
else:
|
||||
return force_unicode(markdown.markdown(smart_str(value)))
|
||||
return mark_safe(force_unicode(markdown.markdown(smart_str(value))))
|
||||
markdown.is_safe = True
|
||||
|
||||
def restructuredtext(value):
|
||||
try:
|
||||
|
@ -50,7 +53,8 @@ def restructuredtext(value):
|
|||
else:
|
||||
docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {})
|
||||
parts = publish_parts(source=smart_str(value), writer_name="html4css1", settings_overrides=docutils_settings)
|
||||
return force_unicode(parts["fragment"])
|
||||
return mark_safe(force_unicode(parts["fragment"]))
|
||||
restructuredtext.is_safe = True
|
||||
|
||||
register.filter(textile)
|
||||
register.filter(markdown)
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
# Quick tests for the markup templatetags (django.contrib.markup)
|
||||
|
||||
from django.template import Template, Context, add_to_builtins
|
||||
import re
|
||||
import unittest
|
||||
|
||||
from django.template import Template, Context, add_to_builtins
|
||||
from django.utils.html import escape
|
||||
|
||||
add_to_builtins('django.contrib.markup.templatetags.markup')
|
||||
|
||||
class Templates(unittest.TestCase):
|
||||
|
@ -24,7 +26,7 @@ Paragraph 2 with "quotes" and @code@"""
|
|||
|
||||
<p>Paragraph 2 with “quotes” and <code>code</code></p>""")
|
||||
else:
|
||||
self.assertEqual(rendered, textile_content)
|
||||
self.assertEqual(rendered, escape(textile_content))
|
||||
|
||||
def test_markdown(self):
|
||||
try:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
{% autoescape off %}<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
{% spaceless %}
|
||||
{% for url in urlset %}
|
||||
|
@ -11,3 +11,4 @@
|
|||
{% endfor %}
|
||||
{% endspaceless %}
|
||||
</urlset>
|
||||
{% endautoescape %}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
{% autoescape off %}<?xml version="1.0" encoding="UTF-8"?>
|
||||
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
{% for location in sitemaps %}<sitemap><loc>{{ location|escape }}</loc></sitemap>{% endfor %}
|
||||
</sitemapindex>
|
||||
{% endautoescape %}
|
||||
|
|
|
@ -7,6 +7,7 @@ from copy import deepcopy
|
|||
from django.utils.datastructures import SortedDict
|
||||
from django.utils.html import escape
|
||||
from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from fields import Field
|
||||
from widgets import TextInput, Textarea
|
||||
|
@ -118,7 +119,8 @@ class BaseForm(StrAndUnicode):
|
|||
output.append(error_row % force_unicode(bf_errors))
|
||||
if bf.label:
|
||||
label = escape(force_unicode(bf.label))
|
||||
# Only add the suffix if the label does not end in punctuation.
|
||||
# Only add the suffix if the label does not end in
|
||||
# punctuation.
|
||||
if self.label_suffix:
|
||||
if label[-1] not in ':?.!':
|
||||
label += self.label_suffix
|
||||
|
@ -136,11 +138,14 @@ class BaseForm(StrAndUnicode):
|
|||
str_hidden = u''.join(hidden_fields)
|
||||
if output:
|
||||
last_row = output[-1]
|
||||
# Chop off the trailing row_ender (e.g. '</td></tr>') and insert the hidden fields.
|
||||
# Chop off the trailing row_ender (e.g. '</td></tr>') and
|
||||
# insert the hidden fields.
|
||||
output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
|
||||
else: # If there aren't any rows in the output, just append the hidden fields.
|
||||
else:
|
||||
# If there aren't any rows in the output, just append the
|
||||
# hidden fields.
|
||||
output.append(str_hidden)
|
||||
return u'\n'.join(output)
|
||||
return mark_safe(u'\n'.join(output))
|
||||
|
||||
def as_table(self):
|
||||
"Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
|
||||
|
@ -303,7 +308,7 @@ class BoundField(StrAndUnicode):
|
|||
if id_:
|
||||
attrs = attrs and flatatt(attrs) or ''
|
||||
contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, contents)
|
||||
return contents
|
||||
return mark_safe(contents)
|
||||
|
||||
def _is_hidden(self):
|
||||
"Returns True if this BoundField's widget is hidden."
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from django.utils.html import escape
|
||||
from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode
|
||||
from django.utils.functional import Promise
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
def flatatt(attrs):
|
||||
"""
|
||||
|
@ -22,7 +23,9 @@ class ErrorDict(dict, StrAndUnicode):
|
|||
|
||||
def as_ul(self):
|
||||
if not self: return u''
|
||||
return u'<ul class="errorlist">%s</ul>' % ''.join([u'<li>%s%s</li>' % (k, force_unicode(v)) for k, v in self.items()])
|
||||
return mark_safe(u'<ul class="errorlist">%s</ul>'
|
||||
% ''.join([u'<li>%s%s</li>' % (k, force_unicode(v))
|
||||
for k, v in self.items()]))
|
||||
|
||||
def as_text(self):
|
||||
return u'\n'.join([u'* %s\n%s' % (k, u'\n'.join([u' * %s' % force_unicode(i) for i in v])) for k, v in self.items()])
|
||||
|
@ -36,7 +39,8 @@ class ErrorList(list, StrAndUnicode):
|
|||
|
||||
def as_ul(self):
|
||||
if not self: return u''
|
||||
return u'<ul class="errorlist">%s</ul>' % ''.join([u'<li>%s</li>' % force_unicode(e) for e in self])
|
||||
return mark_safe(u'<ul class="errorlist">%s</ul>'
|
||||
% ''.join([u'<li>%s</li>' % force_unicode(e) for e in self]))
|
||||
|
||||
def as_text(self):
|
||||
if not self: return u''
|
||||
|
|
|
@ -14,6 +14,7 @@ from django.utils.datastructures import MultiValueDict
|
|||
from django.utils.html import escape
|
||||
from django.utils.translation import ugettext
|
||||
from django.utils.encoding import StrAndUnicode, force_unicode
|
||||
from django.utils.safestring import mark_safe
|
||||
from util import flatatt
|
||||
|
||||
__all__ = (
|
||||
|
@ -86,8 +87,10 @@ class Input(Widget):
|
|||
def render(self, name, value, attrs=None):
|
||||
if value is None: value = ''
|
||||
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
|
||||
if value != '': final_attrs['value'] = force_unicode(value) # Only add the 'value' attribute if a value is non-empty.
|
||||
return u'<input%s />' % flatatt(final_attrs)
|
||||
if value != '':
|
||||
# Only add the 'value' attribute if a value is non-empty.
|
||||
final_attrs['value'] = force_unicode(value)
|
||||
return mark_safe(u'<input%s />' % flatatt(final_attrs))
|
||||
|
||||
class TextInput(Input):
|
||||
input_type = 'text'
|
||||
|
@ -120,7 +123,9 @@ class MultipleHiddenInput(HiddenInput):
|
|||
def render(self, name, value, attrs=None, choices=()):
|
||||
if value is None: value = []
|
||||
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
|
||||
return u'\n'.join([(u'<input%s />' % flatatt(dict(value=force_unicode(v), **final_attrs))) for v in value])
|
||||
return mark_safe(u'\n'.join([(u'<input%s />' %
|
||||
flatatt(dict(value=force_unicode(v), **final_attrs)))
|
||||
for v in value]))
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
if isinstance(data, MultiValueDict):
|
||||
|
@ -149,7 +154,8 @@ class Textarea(Widget):
|
|||
if value is None: value = ''
|
||||
value = force_unicode(value)
|
||||
final_attrs = self.build_attrs(attrs, name=name)
|
||||
return u'<textarea%s>%s</textarea>' % (flatatt(final_attrs), escape(value))
|
||||
return mark_safe(u'<textarea%s>%s</textarea>' % (flatatt(final_attrs),
|
||||
escape(value)))
|
||||
|
||||
class DateTimeInput(Input):
|
||||
input_type = 'text'
|
||||
|
@ -183,8 +189,9 @@ class CheckboxInput(Widget):
|
|||
if result:
|
||||
final_attrs['checked'] = 'checked'
|
||||
if value not in ('', True, False, None):
|
||||
final_attrs['value'] = force_unicode(value) # Only add the 'value' attribute if a value is non-empty.
|
||||
return u'<input%s />' % flatatt(final_attrs)
|
||||
# Only add the 'value' attribute if a value is non-empty.
|
||||
final_attrs['value'] = force_unicode(value)
|
||||
return mark_safe(u'<input%s />' % flatatt(final_attrs))
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
if name not in data:
|
||||
|
@ -205,13 +212,14 @@ class Select(Widget):
|
|||
if value is None: value = ''
|
||||
final_attrs = self.build_attrs(attrs, name=name)
|
||||
output = [u'<select%s>' % flatatt(final_attrs)]
|
||||
str_value = force_unicode(value) # Normalize to string.
|
||||
# Normalize to string.
|
||||
str_value = force_unicode(value)
|
||||
for option_value, option_label in chain(self.choices, choices):
|
||||
option_value = force_unicode(option_value)
|
||||
selected_html = (option_value == str_value) and u' selected="selected"' or ''
|
||||
output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(force_unicode(option_label))))
|
||||
output.append(u'</select>')
|
||||
return u'\n'.join(output)
|
||||
return mark_safe(u'\n'.join(output))
|
||||
|
||||
class NullBooleanSelect(Select):
|
||||
"""
|
||||
|
@ -248,7 +256,7 @@ class SelectMultiple(Widget):
|
|||
selected_html = (option_value in str_values) and ' selected="selected"' or ''
|
||||
output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(force_unicode(option_label))))
|
||||
output.append(u'</select>')
|
||||
return u'\n'.join(output)
|
||||
return mark_safe(u'\n'.join(output))
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
if isinstance(data, MultiValueDict):
|
||||
|
@ -269,7 +277,8 @@ class RadioInput(StrAndUnicode):
|
|||
self.index = index
|
||||
|
||||
def __unicode__(self):
|
||||
return u'<label>%s %s</label>' % (self.tag(), self.choice_label)
|
||||
return mark_safe(u'<label>%s %s</label>' % (self.tag(),
|
||||
self.choice_label))
|
||||
|
||||
def is_checked(self):
|
||||
return self.value == self.choice_value
|
||||
|
@ -280,7 +289,7 @@ class RadioInput(StrAndUnicode):
|
|||
final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
|
||||
if self.is_checked():
|
||||
final_attrs['checked'] = 'checked'
|
||||
return u'<input%s />' % flatatt(final_attrs)
|
||||
return mark_safe(u'<input%s />' % flatatt(final_attrs))
|
||||
|
||||
class RadioFieldRenderer(StrAndUnicode):
|
||||
"""
|
||||
|
@ -304,7 +313,8 @@ class RadioFieldRenderer(StrAndUnicode):
|
|||
|
||||
def render(self):
|
||||
"""Outputs a <ul> for this set of radio fields."""
|
||||
return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % force_unicode(w) for w in self])
|
||||
return mark_safe(u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>'
|
||||
% force_unicode(w) for w in self]))
|
||||
|
||||
class RadioSelect(Select):
|
||||
|
||||
|
@ -341,7 +351,8 @@ class CheckboxSelectMultiple(SelectMultiple):
|
|||
has_id = attrs and 'id' in attrs
|
||||
final_attrs = self.build_attrs(attrs, name=name)
|
||||
output = [u'<ul>']
|
||||
str_values = set([force_unicode(v) for v in value]) # Normalize to strings.
|
||||
# Normalize to strings
|
||||
str_values = set([force_unicode(v) for v in value])
|
||||
for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
|
||||
# If an ID attribute was given, add a numeric index as a suffix,
|
||||
# so that the checkboxes don't all have the same ID attribute.
|
||||
|
@ -352,7 +363,7 @@ class CheckboxSelectMultiple(SelectMultiple):
|
|||
rendered_cb = cb.render(name, option_value)
|
||||
output.append(u'<li><label>%s %s</label></li>' % (rendered_cb, escape(force_unicode(option_label))))
|
||||
output.append(u'</ul>')
|
||||
return u'\n'.join(output)
|
||||
return mark_safe(u'\n'.join(output))
|
||||
|
||||
def id_for_label(self, id_):
|
||||
# See the comment for RadioSelect.id_for_label()
|
||||
|
@ -450,3 +461,4 @@ class SplitDateTimeWidget(MultiWidget):
|
|||
if value:
|
||||
return [value.date(), value.time().replace(microsecond=0)]
|
||||
return [None, None]
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from django.core import validators
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext, ungettext
|
||||
from django.utils.encoding import smart_unicode, force_unicode
|
||||
|
@ -189,9 +190,9 @@ class FormFieldWrapper(object):
|
|||
|
||||
def html_error_list(self):
|
||||
if self.errors():
|
||||
return '<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])
|
||||
return mark_safe('<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()]))
|
||||
else:
|
||||
return ''
|
||||
return mark_safe('')
|
||||
|
||||
def get_id(self):
|
||||
return self.formfield.get_id()
|
||||
|
@ -226,7 +227,7 @@ class FormFieldCollection(FormFieldWrapper):
|
|||
return bool(len(self.errors()))
|
||||
|
||||
def html_combined_error_list(self):
|
||||
return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])
|
||||
return mark_safe(''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')]))
|
||||
|
||||
class InlineObjectCollection(object):
|
||||
"An object that acts like a sparse list of form field collections."
|
||||
|
@ -418,9 +419,9 @@ class TextField(FormField):
|
|||
max_length = u''
|
||||
if self.max_length:
|
||||
max_length = u'maxlength="%s" ' % self.max_length
|
||||
return u'<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
|
||||
return mark_safe(u'<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
|
||||
(self.input_type, self.get_id(), self.__class__.__name__, self.is_required and u' required' or '',
|
||||
self.field_name, self.length, escape(data), max_length)
|
||||
self.field_name, self.length, escape(data), max_length))
|
||||
|
||||
def html2python(data):
|
||||
return data
|
||||
|
@ -442,9 +443,9 @@ class LargeTextField(TextField):
|
|||
def render(self, data):
|
||||
if data is None:
|
||||
data = ''
|
||||
return u'<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
|
||||
return mark_safe(u'<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
|
||||
(self.get_id(), self.__class__.__name__, self.is_required and u' required' or u'',
|
||||
self.field_name, self.rows, self.cols, escape(data))
|
||||
self.field_name, self.rows, self.cols, escape(data)))
|
||||
|
||||
class HiddenField(FormField):
|
||||
def __init__(self, field_name, is_required=False, validator_list=None, max_length=None):
|
||||
|
@ -453,8 +454,8 @@ class HiddenField(FormField):
|
|||
self.validator_list = validator_list[:]
|
||||
|
||||
def render(self, data):
|
||||
return u'<input type="hidden" id="%s" name="%s" value="%s" />' % \
|
||||
(self.get_id(), self.field_name, escape(data))
|
||||
return mark_safe(u'<input type="hidden" id="%s" name="%s" value="%s" />' % \
|
||||
(self.get_id(), self.field_name, escape(data)))
|
||||
|
||||
class CheckboxField(FormField):
|
||||
def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False):
|
||||
|
@ -468,9 +469,9 @@ class CheckboxField(FormField):
|
|||
checked_html = ''
|
||||
if data or (data is '' and self.checked_by_default):
|
||||
checked_html = ' checked="checked"'
|
||||
return u'<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
|
||||
return mark_safe(u'<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
|
||||
(self.get_id(), self.__class__.__name__,
|
||||
self.field_name, checked_html)
|
||||
self.field_name, checked_html))
|
||||
|
||||
def html2python(data):
|
||||
"Convert value from browser ('on' or '') to a Python boolean"
|
||||
|
@ -502,7 +503,7 @@ class SelectField(FormField):
|
|||
selected_html = u' selected="selected"'
|
||||
output.append(u' <option value="%s"%s>%s</option>' % (escape(value), selected_html, force_unicode(escape(display_name))))
|
||||
output.append(u' </select>')
|
||||
return u'\n'.join(output)
|
||||
return mark_safe(u'\n'.join(output))
|
||||
|
||||
def isValidChoice(self, data, form):
|
||||
str_data = smart_unicode(data)
|
||||
|
@ -556,7 +557,7 @@ class RadioSelectField(FormField):
|
|||
output = [u'<ul%s>' % (self.ul_class and u' class="%s"' % self.ul_class or u'')]
|
||||
output.extend([u'<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist])
|
||||
output.append(u'</ul>')
|
||||
return u''.join(output)
|
||||
return mark_safe(u''.join(output))
|
||||
def __iter__(self):
|
||||
for d in self.datalist:
|
||||
yield d
|
||||
|
@ -571,11 +572,11 @@ class RadioSelectField(FormField):
|
|||
datalist.append({
|
||||
'value': value,
|
||||
'name': display_name,
|
||||
'field': u'<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
|
||||
(self.get_id() + u'_' + unicode(i), self.field_name, value, selected_html),
|
||||
'label': u'<label for="%s">%s</label>' % \
|
||||
'field': mark_safe(u'<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
|
||||
(self.get_id() + u'_' + unicode(i), self.field_name, value, selected_html)),
|
||||
'label': mark_safe(u'<label for="%s">%s</label>' % \
|
||||
(self.get_id() + u'_' + unicode(i), display_name),
|
||||
})
|
||||
)})
|
||||
return RadioFieldRenderer(datalist, self.ul_class)
|
||||
|
||||
def isValidChoice(self, data, form):
|
||||
|
@ -614,7 +615,7 @@ class SelectMultipleField(SelectField):
|
|||
selected_html = u' selected="selected"'
|
||||
output.append(u' <option value="%s"%s>%s</option>' % (escape(value), selected_html, force_unicode(escape(choice))))
|
||||
output.append(u' </select>')
|
||||
return u'\n'.join(output)
|
||||
return mark_safe(u'\n'.join(output))
|
||||
|
||||
def isValidChoice(self, field_data, all_data):
|
||||
# data is something like ['1', '2', '3']
|
||||
|
@ -667,7 +668,7 @@ class CheckboxSelectMultipleField(SelectMultipleField):
|
|||
(self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html,
|
||||
self.get_id() + escape(value), choice))
|
||||
output.append(u'</ul>')
|
||||
return u'\n'.join(output)
|
||||
return mark_safe(u'\n'.join(output))
|
||||
|
||||
####################
|
||||
# FILE UPLOADS #
|
||||
|
@ -688,8 +689,8 @@ class FileUploadField(FormField):
|
|||
raise validators.CriticalValidationError, ugettext("The submitted file is empty.")
|
||||
|
||||
def render(self, data):
|
||||
return u'<input type="file" id="%s" class="v%s" name="%s" />' % \
|
||||
(self.get_id(), self.__class__.__name__, self.field_name)
|
||||
return mark_safe(u'<input type="file" id="%s" class="v%s" name="%s" />' % \
|
||||
(self.get_id(), self.__class__.__name__, self.field_name))
|
||||
|
||||
def html2python(data):
|
||||
if data is None:
|
||||
|
|
|
@ -57,6 +57,8 @@ from django.utils.functional import curry, Promise
|
|||
from django.utils.text import smart_split
|
||||
from django.utils.encoding import smart_unicode, force_unicode
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
|
||||
from django.utils.html import escape
|
||||
|
||||
__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
|
||||
|
||||
|
@ -595,7 +597,16 @@ class FilterExpression(object):
|
|||
arg_vals.append(arg)
|
||||
else:
|
||||
arg_vals.append(arg.resolve(context))
|
||||
obj = func(obj, *arg_vals)
|
||||
if getattr(func, 'needs_autoescape', False):
|
||||
new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
|
||||
else:
|
||||
new_obj = func(obj, *arg_vals)
|
||||
if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
|
||||
obj = mark_safe(new_obj)
|
||||
elif isinstance(obj, EscapeData):
|
||||
obj = mark_for_escaping(new_obj)
|
||||
else:
|
||||
obj = new_obj
|
||||
return obj
|
||||
|
||||
def args_check(name, func, provided):
|
||||
|
@ -637,7 +648,7 @@ def resolve_variable(path, context):
|
|||
"""
|
||||
Returns the resolved variable, which may contain attribute syntax, within
|
||||
the given context.
|
||||
|
||||
|
||||
Deprecated; use the Variable class instead.
|
||||
"""
|
||||
return Variable(path).resolve(context)
|
||||
|
@ -647,7 +658,7 @@ class Variable(object):
|
|||
A template variable, resolvable against a given context. The variable may be
|
||||
a hard-coded string (if it begins and ends with single or double quote
|
||||
marks)::
|
||||
|
||||
|
||||
>>> c = {'article': {'section':'News'}}
|
||||
>>> Variable('article.section').resolve(c)
|
||||
u'News'
|
||||
|
@ -662,25 +673,25 @@ class Variable(object):
|
|||
|
||||
(The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, var):
|
||||
self.var = var
|
||||
self.literal = None
|
||||
self.lookups = None
|
||||
|
||||
|
||||
try:
|
||||
# First try to treat this variable as a number.
|
||||
#
|
||||
# Note that this could cause an OverflowError here that we're not
|
||||
# Note that this could cause an OverflowError here that we're not
|
||||
# catching. Since this should only happen at compile time, that's
|
||||
# probably OK.
|
||||
self.literal = float(var)
|
||||
|
||||
|
||||
# So it's a float... is it an int? If the original value contained a
|
||||
# dot or an "e" then it was a float, not an int.
|
||||
if '.' not in var and 'e' not in var.lower():
|
||||
self.literal = int(self.literal)
|
||||
|
||||
|
||||
# "2." is invalid
|
||||
if var.endswith('.'):
|
||||
raise ValueError
|
||||
|
@ -691,12 +702,12 @@ class Variable(object):
|
|||
# we're also dealing with a literal.
|
||||
if var[0] in "\"'" and var[0] == var[-1]:
|
||||
self.literal = var[1:-1]
|
||||
|
||||
|
||||
else:
|
||||
# Otherwise we'll set self.lookups so that resolve() knows we're
|
||||
# dealing with a bonafide variable
|
||||
self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
|
||||
|
||||
|
||||
def resolve(self, context):
|
||||
"""Resolve this variable against a given context."""
|
||||
if self.lookups is not None:
|
||||
|
@ -705,18 +716,18 @@ class Variable(object):
|
|||
else:
|
||||
# We're dealing with a literal, so it's already been "resolved"
|
||||
return self.literal
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %r>" % (self.__class__.__name__, self.var)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.var
|
||||
|
||||
def _resolve_lookup(self, context):
|
||||
"""
|
||||
Performs resolution of a real variable (i.e. not a literal) against the
|
||||
given context.
|
||||
|
||||
given context.
|
||||
|
||||
As indicated by the method's name, this method is an implementation
|
||||
detail and shouldn't be called by external code. Use Variable.resolve()
|
||||
instead.
|
||||
|
@ -757,14 +768,7 @@ class Variable(object):
|
|||
current = settings.TEMPLATE_STRING_IF_INVALID
|
||||
else:
|
||||
raise
|
||||
|
||||
if isinstance(current, (basestring, Promise)):
|
||||
try:
|
||||
current = force_unicode(current)
|
||||
except UnicodeDecodeError:
|
||||
# Failing to convert to unicode can happen sometimes (e.g. debug
|
||||
# tracebacks). So we allow it in this particular instance.
|
||||
pass
|
||||
|
||||
return current
|
||||
|
||||
class Node(object):
|
||||
|
@ -838,16 +842,31 @@ class VariableNode(Node):
|
|||
return "<Variable Node: %s>" % self.filter_expression
|
||||
|
||||
def render(self, context):
|
||||
return self.filter_expression.resolve(context)
|
||||
try:
|
||||
output = force_unicode(self.filter_expression.resolve(context))
|
||||
except UnicodeDecodeError:
|
||||
# Unicode conversion can fail sometimes for reasons out of our
|
||||
# control (e.g. exception rendering). In that case, we fail quietly.
|
||||
return ''
|
||||
if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
|
||||
return force_unicode(escape(output))
|
||||
else:
|
||||
return force_unicode(output)
|
||||
|
||||
class DebugVariableNode(VariableNode):
|
||||
def render(self, context):
|
||||
try:
|
||||
return self.filter_expression.resolve(context)
|
||||
output = force_unicode(self.filter_expression.resolve(context))
|
||||
except TemplateSyntaxError, e:
|
||||
if not hasattr(e, 'source'):
|
||||
e.source = self.source
|
||||
raise
|
||||
except UnicodeDecodeError:
|
||||
return ''
|
||||
if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
|
||||
return escape(output)
|
||||
else:
|
||||
return output
|
||||
|
||||
def generic_tag_compiler(params, defaults, name, node_class, parser, token):
|
||||
"Returns a template.Node subclass."
|
||||
|
@ -961,7 +980,8 @@ class Library(object):
|
|||
else:
|
||||
t = get_template(file_name)
|
||||
self.nodelist = t.nodelist
|
||||
return self.nodelist.render(context_class(dict))
|
||||
return self.nodelist.render(context_class(dict,
|
||||
autoescape=context.autoescape))
|
||||
|
||||
compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
|
||||
compile_func.__doc__ = func.__doc__
|
||||
|
|
|
@ -9,9 +9,11 @@ class ContextPopException(Exception):
|
|||
|
||||
class Context(object):
|
||||
"A stack container for variable context"
|
||||
def __init__(self, dict_=None):
|
||||
|
||||
def __init__(self, dict_=None, autoescape=True):
|
||||
dict_ = dict_ or {}
|
||||
self.dicts = [dict_]
|
||||
self.autoescape = autoescape
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self.dicts)
|
||||
|
@ -97,3 +99,4 @@ class RequestContext(Context):
|
|||
processors = tuple(processors)
|
||||
for processor in get_standard_processors() + processors:
|
||||
self.update(processor(request))
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ from django.template import Variable, Library
|
|||
from django.conf import settings
|
||||
from django.utils.translation import ugettext, ungettext
|
||||
from django.utils.encoding import force_unicode, iri_to_uri
|
||||
from django.utils.safestring import mark_safe, SafeData
|
||||
|
||||
register = Library()
|
||||
|
||||
|
@ -29,6 +30,9 @@ def stringfilter(func):
|
|||
# Include a reference to the real function (used to check original
|
||||
# arguments by the template parser).
|
||||
_dec._decorated_function = getattr(func, '_decorated_function', func)
|
||||
for attr in ('is_safe', 'needs_autoescape'):
|
||||
if hasattr(func, attr):
|
||||
setattr(_dec, attr, getattr(func, attr))
|
||||
return _dec
|
||||
|
||||
###################
|
||||
|
@ -39,17 +43,20 @@ def stringfilter(func):
|
|||
def addslashes(value):
|
||||
"""Adds slashes - useful for passing strings to JavaScript, for example."""
|
||||
return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
|
||||
addslashes.is_safe = True
|
||||
addslashes = stringfilter(addslashes)
|
||||
|
||||
def capfirst(value):
|
||||
"""Capitalizes the first character of the value."""
|
||||
return value and value[0].upper() + value[1:]
|
||||
capfirst.is_safe=True
|
||||
capfirst = stringfilter(capfirst)
|
||||
|
||||
def fix_ampersands(value):
|
||||
"""Replaces ampersands with ``&`` entities."""
|
||||
from django.utils.html import fix_ampersands
|
||||
return fix_ampersands(value)
|
||||
fix_ampersands.is_safe=True
|
||||
fix_ampersands = stringfilter(fix_ampersands)
|
||||
|
||||
def floatformat(text, arg=-1):
|
||||
|
@ -90,31 +97,39 @@ def floatformat(text, arg=-1):
|
|||
return force_unicode(f)
|
||||
m = f - int(f)
|
||||
if not m and d < 0:
|
||||
return u'%d' % int(f)
|
||||
return mark_safe(u'%d' % int(f))
|
||||
else:
|
||||
formatstr = u'%%.%df' % abs(d)
|
||||
return formatstr % f
|
||||
return mark_safe(formatstr % f)
|
||||
floatformat.is_safe = True
|
||||
|
||||
def iriencode(value):
|
||||
"""Escapes an IRI value for use in a URL."""
|
||||
return force_unicode(iri_to_uri(value))
|
||||
iriencode = stringfilter(iriencode)
|
||||
|
||||
def linenumbers(value):
|
||||
def linenumbers(value, autoescape=None):
|
||||
"""Displays text with line numbers."""
|
||||
from django.utils.html import escape
|
||||
lines = value.split(u'\n')
|
||||
# Find the maximum width of the line count, for use with zero padding
|
||||
# string format command.
|
||||
# string format command
|
||||
width = unicode(len(unicode(len(lines))))
|
||||
for i, line in enumerate(lines):
|
||||
lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line))
|
||||
return u'\n'.join(lines)
|
||||
if not autoescape or isinstance(value, SafeData):
|
||||
for i, line in enumerate(lines):
|
||||
lines[i] = (u"%0" + width + u"d. %s") % (i + 1, line)
|
||||
else:
|
||||
for i, line in enumerate(lines):
|
||||
lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line))
|
||||
return mark_safe(u'\n'.join(lines))
|
||||
linenumbers.is_safe = True
|
||||
linenumbers.needs_autoescape = True
|
||||
linenumbers = stringfilter(linenumbers)
|
||||
|
||||
def lower(value):
|
||||
"""Converts a string into all lowercase."""
|
||||
return value.lower()
|
||||
lower.is_safe = True
|
||||
lower = stringfilter(lower)
|
||||
|
||||
def make_list(value):
|
||||
|
@ -125,6 +140,7 @@ def make_list(value):
|
|||
For a string, it's a list of characters.
|
||||
"""
|
||||
return list(value)
|
||||
make_list.is_safe = False
|
||||
make_list = stringfilter(make_list)
|
||||
|
||||
def slugify(value):
|
||||
|
@ -135,7 +151,8 @@ def slugify(value):
|
|||
import unicodedata
|
||||
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
|
||||
value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
|
||||
return re.sub('[-\s]+', '-', value)
|
||||
return mark_safe(re.sub('[-\s]+', '-', value))
|
||||
slugify.is_safe = True
|
||||
slugify = stringfilter(slugify)
|
||||
|
||||
def stringformat(value, arg):
|
||||
|
@ -152,10 +169,12 @@ def stringformat(value, arg):
|
|||
return (u"%" + unicode(arg)) % value
|
||||
except (ValueError, TypeError):
|
||||
return u""
|
||||
stringformat.is_safe = True
|
||||
|
||||
def title(value):
|
||||
"""Converts a string into titlecase."""
|
||||
return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
|
||||
title.is_safe = True
|
||||
title = stringfilter(title)
|
||||
|
||||
def truncatewords(value, arg):
|
||||
|
@ -170,6 +189,7 @@ def truncatewords(value, arg):
|
|||
except ValueError: # Invalid literal for int().
|
||||
return value # Fail silently.
|
||||
return truncate_words(value, length)
|
||||
truncatewords.is_safe = True
|
||||
truncatewords = stringfilter(truncatewords)
|
||||
|
||||
def truncatewords_html(value, arg):
|
||||
|
@ -184,23 +204,28 @@ def truncatewords_html(value, arg):
|
|||
except ValueError: # invalid literal for int()
|
||||
return value # Fail silently.
|
||||
return truncate_html_words(value, length)
|
||||
truncatewords_html.is_safe = True
|
||||
truncatewords_html = stringfilter(truncatewords_html)
|
||||
|
||||
def upper(value):
|
||||
"""Converts a string into all uppercase."""
|
||||
return value.upper()
|
||||
upper.is_safe = False
|
||||
upper = stringfilter(upper)
|
||||
|
||||
def urlencode(value):
|
||||
"""Escapes a value for use in a URL."""
|
||||
from django.utils.http import urlquote
|
||||
return urlquote(value)
|
||||
urlencode.is_safe = False
|
||||
urlencode = stringfilter(urlencode)
|
||||
|
||||
def urlize(value):
|
||||
def urlize(value, autoescape=None):
|
||||
"""Converts URLs in plain text into clickable links."""
|
||||
from django.utils.html import urlize
|
||||
return urlize(value, nofollow=True)
|
||||
return mark_safe(urlize(value, nofollow=True, autoescape=autoescape))
|
||||
urlize.is_safe=True
|
||||
urlize.needs_autoescape = True
|
||||
urlize = stringfilter(urlize)
|
||||
|
||||
def urlizetrunc(value, limit):
|
||||
|
@ -211,12 +236,14 @@ def urlizetrunc(value, limit):
|
|||
Argument: Length to truncate URLs to.
|
||||
"""
|
||||
from django.utils.html import urlize
|
||||
return urlize(value, trim_url_limit=int(limit), nofollow=True)
|
||||
return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True))
|
||||
urlizetrunc.is_safe = True
|
||||
urlizetrunc = stringfilter(urlizetrunc)
|
||||
|
||||
def wordcount(value):
|
||||
"""Returns the number of words."""
|
||||
return len(value.split())
|
||||
wordcount.is_safe = False
|
||||
wordcount = stringfilter(wordcount)
|
||||
|
||||
def wordwrap(value, arg):
|
||||
|
@ -227,6 +254,7 @@ def wordwrap(value, arg):
|
|||
"""
|
||||
from django.utils.text import wrap
|
||||
return wrap(value, int(arg))
|
||||
wordwrap.is_safe = True
|
||||
wordwrap = stringfilter(wordwrap)
|
||||
|
||||
def ljust(value, arg):
|
||||
|
@ -236,6 +264,7 @@ def ljust(value, arg):
|
|||
Argument: field size.
|
||||
"""
|
||||
return value.ljust(int(arg))
|
||||
ljust.is_safe = True
|
||||
ljust = stringfilter(ljust)
|
||||
|
||||
def rjust(value, arg):
|
||||
|
@ -245,16 +274,24 @@ def rjust(value, arg):
|
|||
Argument: field size.
|
||||
"""
|
||||
return value.rjust(int(arg))
|
||||
rjust.is_safe = True
|
||||
rjust = stringfilter(rjust)
|
||||
|
||||
def center(value, arg):
|
||||
"""Centers the value in a field of a given width."""
|
||||
return value.center(int(arg))
|
||||
center.is_safe = True
|
||||
center = stringfilter(center)
|
||||
|
||||
def cut(value, arg):
|
||||
"""Removes all values of arg from the given string."""
|
||||
return value.replace(arg, u'')
|
||||
"""
|
||||
Removes all values of arg from the given string.
|
||||
"""
|
||||
safe = isinstance(value, SafeData)
|
||||
value = value.replace(arg, u'')
|
||||
if safe and arg != ';':
|
||||
return mark_safe(value)
|
||||
return value
|
||||
cut = stringfilter(cut)
|
||||
|
||||
###################
|
||||
|
@ -262,29 +299,60 @@ cut = stringfilter(cut)
|
|||
###################
|
||||
|
||||
def escape(value):
|
||||
"Escapes a string's HTML"
|
||||
from django.utils.html import escape
|
||||
return escape(value)
|
||||
"""
|
||||
Marks the value as a string that should not be auto-escaped.
|
||||
"""
|
||||
from django.utils.safestring import mark_for_escaping
|
||||
return mark_for_escaping(value)
|
||||
escape.is_safe = True
|
||||
escape = stringfilter(escape)
|
||||
|
||||
def linebreaks(value):
|
||||
def force_escape(value):
|
||||
"""
|
||||
Escapes a string's HTML. This returns a new string containing the escaped
|
||||
characters (as opposed to "escape", which marks the content for later
|
||||
possible escaping).
|
||||
"""
|
||||
from django.utils.html import escape
|
||||
return mark_safe(escape(value))
|
||||
escape = stringfilter(escape)
|
||||
force_escape.is_safe = True
|
||||
|
||||
def linebreaks(value, autoescape=None):
|
||||
"""
|
||||
Replaces line breaks in plain text with appropriate HTML; a single
|
||||
newline becomes an HTML line break (``<br />``) and a new line
|
||||
followed by a blank line becomes a paragraph break (``</p>``).
|
||||
"""
|
||||
from django.utils.html import linebreaks
|
||||
return linebreaks(value)
|
||||
autoescape = autoescape and not isinstance(value, SafeData)
|
||||
return mark_safe(linebreaks(value, autoescape))
|
||||
linebreaks.is_safe = True
|
||||
linebreaks.needs_autoescape = True
|
||||
linebreaks = stringfilter(linebreaks)
|
||||
|
||||
def linebreaksbr(value):
|
||||
def linebreaksbr(value, autoescape=None):
|
||||
"""
|
||||
Converts all newlines in a piece of plain text to HTML line breaks
|
||||
(``<br />``).
|
||||
"""
|
||||
return value.replace('\n', '<br />')
|
||||
if autoescape and not isinstance(value, SafeData):
|
||||
from django.utils.html import escape
|
||||
value = escape(value)
|
||||
return mark_safe(value.replace('\n', '<br />'))
|
||||
linebreaksbr.is_safe = True
|
||||
linebreaksbr.needs_autoescape = True
|
||||
linebreaksbr = stringfilter(linebreaksbr)
|
||||
|
||||
def safe(value):
|
||||
"""
|
||||
Marks the value as a string that should not be auto-escaped.
|
||||
"""
|
||||
from django.utils.safestring import mark_safe
|
||||
return mark_safe(value)
|
||||
safe.is_safe = True
|
||||
safe = stringfilter(safe)
|
||||
|
||||
def removetags(value, tags):
|
||||
"""Removes a space separated list of [X]HTML tags from the output."""
|
||||
tags = [re.escape(tag) for tag in tags.split()]
|
||||
|
@ -294,12 +362,14 @@ def removetags(value, tags):
|
|||
value = starttag_re.sub(u'', value)
|
||||
value = endtag_re.sub(u'', value)
|
||||
return value
|
||||
removetags.is_safe = True
|
||||
removetags = stringfilter(removetags)
|
||||
|
||||
def striptags(value):
|
||||
"""Strips all [X]HTML tags."""
|
||||
from django.utils.html import strip_tags
|
||||
return strip_tags(value)
|
||||
striptags.is_safe = True
|
||||
striptags = stringfilter(striptags)
|
||||
|
||||
###################
|
||||
|
@ -315,6 +385,7 @@ def dictsort(value, arg):
|
|||
decorated = [(var_resolve(item), item) for item in value]
|
||||
decorated.sort()
|
||||
return [item[1] for item in decorated]
|
||||
dictsort.is_safe = False
|
||||
|
||||
def dictsortreversed(value, arg):
|
||||
"""
|
||||
|
@ -326,6 +397,7 @@ def dictsortreversed(value, arg):
|
|||
decorated.sort()
|
||||
decorated.reverse()
|
||||
return [item[1] for item in decorated]
|
||||
dictsortreversed.is_safe = False
|
||||
|
||||
def first(value):
|
||||
"""Returns the first item in a list."""
|
||||
|
@ -333,25 +405,36 @@ def first(value):
|
|||
return value[0]
|
||||
except IndexError:
|
||||
return u''
|
||||
first.is_safe = True
|
||||
|
||||
def join(value, arg):
|
||||
"""Joins a list with a string, like Python's ``str.join(list)``."""
|
||||
try:
|
||||
return arg.join(map(force_unicode, value))
|
||||
data = arg.join(map(force_unicode, value))
|
||||
except AttributeError: # fail silently but nicely
|
||||
return value
|
||||
safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData),
|
||||
value, True)
|
||||
if safe_args:
|
||||
return mark_safe(data)
|
||||
else:
|
||||
return data
|
||||
join.is_safe = True
|
||||
|
||||
def length(value):
|
||||
"""Returns the length of the value - useful for lists."""
|
||||
return len(value)
|
||||
length.is_safe = True
|
||||
|
||||
def length_is(value, arg):
|
||||
"""Returns a boolean of whether the value's length is the argument."""
|
||||
return len(value) == int(arg)
|
||||
length_is.is_safe = True
|
||||
|
||||
def random(value):
|
||||
"""Returns a random item from the list."""
|
||||
return random_module.choice(value)
|
||||
random.is_safe = True
|
||||
|
||||
def slice_(value, arg):
|
||||
"""
|
||||
|
@ -372,8 +455,9 @@ def slice_(value, arg):
|
|||
|
||||
except (ValueError, TypeError):
|
||||
return value # Fail silently.
|
||||
slice_.is_safe = True
|
||||
|
||||
def unordered_list(value):
|
||||
def unordered_list(value, autoescape=None):
|
||||
"""
|
||||
Recursively takes a self-nested list and returns an HTML unordered list --
|
||||
WITHOUT opening and closing <ul> tags.
|
||||
|
@ -394,6 +478,11 @@ def unordered_list(value):
|
|||
</ul>
|
||||
</li>
|
||||
"""
|
||||
if autoescape:
|
||||
from django.utils.html import conditional_escape
|
||||
escaper = conditional_escape
|
||||
else:
|
||||
escaper = lambda x: x
|
||||
def convert_old_style_list(list_):
|
||||
"""
|
||||
Converts old style lists to the new easier to understand format.
|
||||
|
@ -443,12 +532,14 @@ def unordered_list(value):
|
|||
sublist = _helper(sublist_item, tabs+1)
|
||||
sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
|
||||
indent, indent)
|
||||
output.append('%s<li>%s%s</li>' % (indent, force_unicode(title),
|
||||
sublist))
|
||||
output.append('%s<li>%s%s</li>' % (indent,
|
||||
escaper(force_unicode(title)), sublist))
|
||||
i += 1
|
||||
return '\n'.join(output)
|
||||
value, converted = convert_old_style_list(value)
|
||||
return _helper(value)
|
||||
return mark_safe(_helper(value))
|
||||
unordered_list.is_safe = True
|
||||
unordered_list.needs_autoescape = True
|
||||
|
||||
###################
|
||||
# INTEGERS #
|
||||
|
@ -457,6 +548,7 @@ def unordered_list(value):
|
|||
def add(value, arg):
|
||||
"""Adds the arg to the value."""
|
||||
return int(value) + int(arg)
|
||||
add.is_safe = False
|
||||
|
||||
def get_digit(value, arg):
|
||||
"""
|
||||
|
@ -476,6 +568,7 @@ def get_digit(value, arg):
|
|||
return int(str(value)[-arg])
|
||||
except IndexError:
|
||||
return 0
|
||||
get_digit.is_safe = False
|
||||
|
||||
###################
|
||||
# DATES #
|
||||
|
@ -489,6 +582,7 @@ def date(value, arg=None):
|
|||
if arg is None:
|
||||
arg = settings.DATE_FORMAT
|
||||
return format(value, arg)
|
||||
date.is_safe = False
|
||||
|
||||
def time(value, arg=None):
|
||||
"""Formats a time according to the given format."""
|
||||
|
@ -498,6 +592,7 @@ def time(value, arg=None):
|
|||
if arg is None:
|
||||
arg = settings.TIME_FORMAT
|
||||
return time_format(value, arg)
|
||||
time.is_safe = False
|
||||
|
||||
def timesince(value, arg=None):
|
||||
"""Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
|
||||
|
@ -507,6 +602,7 @@ def timesince(value, arg=None):
|
|||
if arg:
|
||||
return timesince(arg, value)
|
||||
return timesince(value)
|
||||
timesince.is_safe = False
|
||||
|
||||
def timeuntil(value, arg=None):
|
||||
"""Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
|
||||
|
@ -517,6 +613,7 @@ def timeuntil(value, arg=None):
|
|||
if arg:
|
||||
return timesince(arg, value)
|
||||
return timesince(datetime.now(), value)
|
||||
timeuntil.is_safe = False
|
||||
|
||||
###################
|
||||
# LOGIC #
|
||||
|
@ -525,16 +622,19 @@ def timeuntil(value, arg=None):
|
|||
def default(value, arg):
|
||||
"""If value is unavailable, use given default."""
|
||||
return value or arg
|
||||
default.is_safe = False
|
||||
|
||||
def default_if_none(value, arg):
|
||||
"""If value is None, use given default."""
|
||||
if value is None:
|
||||
return arg
|
||||
return value
|
||||
default_if_none.is_safe = False
|
||||
|
||||
def divisibleby(value, arg):
|
||||
"""Returns True if the value is devisible by the argument."""
|
||||
return int(value) % int(arg) == 0
|
||||
divisibleby.is_safe = False
|
||||
|
||||
def yesno(value, arg=None):
|
||||
"""
|
||||
|
@ -566,6 +666,7 @@ def yesno(value, arg=None):
|
|||
if value:
|
||||
return yes
|
||||
return no
|
||||
yesno.is_safe = False
|
||||
|
||||
###################
|
||||
# MISC #
|
||||
|
@ -588,29 +689,30 @@ def filesizeformat(bytes):
|
|||
if bytes < 1024 * 1024 * 1024:
|
||||
return ugettext("%.1f MB") % (bytes / (1024 * 1024))
|
||||
return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
|
||||
filesizeformat.is_safe = True
|
||||
|
||||
def pluralize(value, arg=u's'):
|
||||
"""
|
||||
Returns a plural suffix if the value is not 1. By default, 's' is used as
|
||||
the suffix:
|
||||
|
||||
* If value is 0, vote{{ value|plurlize }} displays "0 votes".
|
||||
* If value is 1, vote{{ value|plurlize }} displays "1 vote".
|
||||
* If value is 2, vote{{ value|plurlize }} displays "2 votes".
|
||||
* If value is 0, vote{{ value|pluralize }} displays "0 votes".
|
||||
* If value is 1, vote{{ value|pluralize }} displays "1 vote".
|
||||
* If value is 2, vote{{ value|pluralize }} displays "2 votes".
|
||||
|
||||
If an argument is provided, that string is used instead:
|
||||
|
||||
* If value is 0, class{{ value|plurlize:"es" }} displays "0 classes".
|
||||
* If value is 1, class{{ value|plurlize:"es" }} displays "1 class".
|
||||
* If value is 2, class{{ value|plurlize:"es" }} displays "2 classes".
|
||||
* If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".
|
||||
* If value is 1, class{{ value|pluralize:"es" }} displays "1 class".
|
||||
* If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".
|
||||
|
||||
If the provided argument contains a comma, the text before the comma is
|
||||
used for the singular case and the text after the comma is used for the
|
||||
plural case:
|
||||
|
||||
* If value is 0, cand{{ value|plurlize:"y,ies" }} displays "0 candies".
|
||||
* If value is 1, cand{{ value|plurlize:"y,ies" }} displays "1 candy".
|
||||
* If value is 2, cand{{ value|plurlize:"y,ies" }} displays "2 candies".
|
||||
* If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".
|
||||
* If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".
|
||||
* If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".
|
||||
"""
|
||||
if not u',' in arg:
|
||||
arg = u',' + arg
|
||||
|
@ -631,11 +733,13 @@ def pluralize(value, arg=u's'):
|
|||
except TypeError: # len() of unsized object.
|
||||
pass
|
||||
return singular_suffix
|
||||
pluralize.is_safe = False
|
||||
|
||||
def phone2numeric(value):
|
||||
"""Takes a phone number and converts it in to its numerical equivalent."""
|
||||
from django.utils.text import phone2numeric
|
||||
return phone2numeric(value)
|
||||
phone2numeric.is_safe = True
|
||||
|
||||
def pprint(value):
|
||||
"""A wrapper around pprint.pprint -- for debugging, really."""
|
||||
|
@ -644,6 +748,7 @@ def pprint(value):
|
|||
return pformat(value)
|
||||
except Exception, e:
|
||||
return u"Error in formatting: %s" % force_unicode(e, errors="replace")
|
||||
pprint.is_safe = True
|
||||
|
||||
# Syntax: register.filter(name of filter, callback)
|
||||
register.filter(add)
|
||||
|
@ -662,6 +767,7 @@ register.filter(filesizeformat)
|
|||
register.filter(first)
|
||||
register.filter(fix_ampersands)
|
||||
register.filter(floatformat)
|
||||
register.filter(force_escape)
|
||||
register.filter(get_digit)
|
||||
register.filter(iriencode)
|
||||
register.filter(join)
|
||||
|
@ -679,6 +785,7 @@ register.filter(pprint)
|
|||
register.filter(removetags)
|
||||
register.filter(random)
|
||||
register.filter(rjust)
|
||||
register.filter(safe)
|
||||
register.filter('slice', slice_)
|
||||
register.filter(slugify)
|
||||
register.filter(stringformat)
|
||||
|
|
|
@ -14,9 +14,25 @@ from django.template import get_library, Library, InvalidTemplateLibrary
|
|||
from django.conf import settings
|
||||
from django.utils.encoding import smart_str, smart_unicode
|
||||
from django.utils.itercompat import groupby
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
register = Library()
|
||||
|
||||
class AutoEscapeControlNode(Node):
|
||||
"""Implements the actions of the autoescape tag."""
|
||||
def __init__(self, setting, nodelist):
|
||||
self.setting, self.nodelist = setting, nodelist
|
||||
|
||||
def render(self, context):
|
||||
old_setting = context.autoescape
|
||||
context.autoescape = self.setting
|
||||
output = self.nodelist.render(context)
|
||||
context.autoescape = old_setting
|
||||
if self.setting:
|
||||
return mark_safe(output)
|
||||
else:
|
||||
return output
|
||||
|
||||
class CommentNode(Node):
|
||||
def render(self, context):
|
||||
return ''
|
||||
|
@ -392,6 +408,22 @@ class WithNode(Node):
|
|||
context.pop()
|
||||
return output
|
||||
|
||||
#@register.tag
|
||||
def autoescape(parser, token):
|
||||
"""
|
||||
Force autoescape behaviour for this block.
|
||||
"""
|
||||
args = token.contents.split()
|
||||
if len(args) != 2:
|
||||
raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.")
|
||||
arg = args[1]
|
||||
if arg not in (u'on', u'off'):
|
||||
raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
|
||||
nodelist = parser.parse(('endautoescape',))
|
||||
parser.delete_first_token()
|
||||
return AutoEscapeControlNode((arg == 'on'), nodelist)
|
||||
autoescape = register.tag(autoescape)
|
||||
|
||||
#@register.tag
|
||||
def comment(parser, token):
|
||||
"""
|
||||
|
@ -492,12 +524,15 @@ def do_filter(parser, token):
|
|||
|
||||
Sample usage::
|
||||
|
||||
{% filter escape|lower %}
|
||||
{% filter force_escape|lower %}
|
||||
This text will be HTML-escaped, and will appear in lowercase.
|
||||
{% endfilter %}
|
||||
"""
|
||||
_, rest = token.contents.split(None, 1)
|
||||
filter_expr = parser.compile_filter("var|%s" % (rest))
|
||||
for func, unused in filter_expr.filters:
|
||||
if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
|
||||
raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__)
|
||||
nodelist = parser.parse(('endfilter',))
|
||||
parser.delete_first_token()
|
||||
return FilterNode(filter_expr, nodelist)
|
||||
|
|
|
@ -3,6 +3,7 @@ import urllib
|
|||
import datetime
|
||||
|
||||
from django.utils.functional import Promise
|
||||
from django.utils.safestring import SafeData, mark_safe
|
||||
|
||||
class DjangoUnicodeDecodeError(UnicodeDecodeError):
|
||||
def __init__(self, obj, *args):
|
||||
|
@ -51,7 +52,10 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
|
|||
else:
|
||||
s = unicode(str(s), encoding, errors)
|
||||
elif not isinstance(s, unicode):
|
||||
s = unicode(s, encoding, errors)
|
||||
# Note: We use .decode() here, instead of unicode(s, encoding,
|
||||
# errors), so that if s is a SafeString, it ends up being a
|
||||
# SafeUnicode at the end.
|
||||
s = s.decode(encoding, errors)
|
||||
except UnicodeDecodeError, e:
|
||||
raise DjangoUnicodeDecodeError(s, *e.args)
|
||||
return s
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import re
|
||||
import string
|
||||
|
||||
from django.utils.safestring import SafeData, mark_safe
|
||||
from django.utils.encoding import force_unicode
|
||||
from django.utils.functional import allow_lazy
|
||||
|
||||
|
@ -27,16 +28,28 @@ del x # Temporary variable
|
|||
|
||||
def escape(html):
|
||||
"Return the given HTML with ampersands, quotes and carets encoded."
|
||||
return force_unicode(html).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''')
|
||||
return mark_safe(force_unicode(html).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", '''))
|
||||
escape = allow_lazy(escape, unicode)
|
||||
|
||||
def linebreaks(value):
|
||||
"Convert newlines into <p> and <br />s."
|
||||
def conditional_escape(html):
|
||||
"""
|
||||
Similar to escape(), except that it doesn't operate on pre-escaped strings.
|
||||
"""
|
||||
if isinstance(html, SafeData):
|
||||
return html
|
||||
else:
|
||||
return escape(html)
|
||||
|
||||
def linebreaks(value, autoescape=False):
|
||||
"Converts newlines into <p> and <br />s"
|
||||
value = re.sub(r'\r\n|\r|\n', '\n', force_unicode(value)) # normalize newlines
|
||||
paras = re.split('\n{2,}', value)
|
||||
paras = [u'<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
|
||||
if autoescape:
|
||||
paras = [u'<p>%s</p>' % escape(p.strip()).replace('\n', '<br />') for p in paras]
|
||||
else:
|
||||
paras = [u'<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
|
||||
return u'\n\n'.join(paras)
|
||||
linebreaks = allow_lazy(linebreaks, unicode)
|
||||
linebreaks = allow_lazy(linebreaks, unicode)
|
||||
|
||||
def strip_tags(value):
|
||||
"Return the given HTML with all tags stripped."
|
||||
|
@ -58,7 +71,7 @@ def fix_ampersands(value):
|
|||
return unencoded_ampersands_re.sub('&', force_unicode(value))
|
||||
fix_ampersands = allow_lazy(fix_ampersands, unicode)
|
||||
|
||||
def urlize(text, trim_url_limit=None, nofollow=False):
|
||||
def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
|
||||
"""
|
||||
Convert any URLs in text into clickable links.
|
||||
|
||||
|
@ -72,13 +85,19 @@ def urlize(text, trim_url_limit=None, nofollow=False):
|
|||
If nofollow is True, the URLs in link text will get a rel="nofollow"
|
||||
attribute.
|
||||
"""
|
||||
trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
|
||||
if autoescape:
|
||||
trim_url = lambda x, limit=trim_url_limit: conditional_escape(limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x)
|
||||
else:
|
||||
trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
|
||||
safe_input = isinstance(text, SafeData)
|
||||
words = word_split_re.split(force_unicode(text))
|
||||
nofollow_attr = nofollow and ' rel="nofollow"' or ''
|
||||
for i, word in enumerate(words):
|
||||
match = punctuation_re.match(word)
|
||||
if match:
|
||||
lead, middle, trail = match.groups()
|
||||
if safe_input:
|
||||
middle = mark_safe(middle)
|
||||
if middle.startswith('www.') or ('@' not in middle and not middle.startswith('http://') and \
|
||||
len(middle) > 0 and middle[0] in string.letters + string.digits and \
|
||||
(middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))):
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
"""
|
||||
Functions for working with "safe strings": strings that can be displayed safely
|
||||
without further escaping in HTML. Marking something as a "safe string" means
|
||||
that the producer of the string has already turned characters that should not
|
||||
be interpreted by the HTML engine (e.g. '<') into the appropriate entities.
|
||||
"""
|
||||
from django.utils.functional import curry, Promise
|
||||
|
||||
class EscapeData(object):
|
||||
pass
|
||||
|
||||
class EscapeString(str, EscapeData):
|
||||
"""
|
||||
A string that should be HTML-escaped when output.
|
||||
"""
|
||||
pass
|
||||
|
||||
class EscapeUnicode(unicode, EscapeData):
|
||||
"""
|
||||
A unicode object that should be HTML-escaped when output.
|
||||
"""
|
||||
pass
|
||||
|
||||
class SafeData(object):
|
||||
pass
|
||||
|
||||
class SafeString(str, SafeData):
|
||||
"""
|
||||
A string subclass that has been specifically marked as "safe" (requires no
|
||||
further escaping) for HTML output purposes.
|
||||
"""
|
||||
def __add__(self, rhs):
|
||||
"""
|
||||
Concatenating a safe string with another safe string or safe unicode
|
||||
object is safe. Otherwise, the result is no longer safe.
|
||||
"""
|
||||
if isinstance(rhs, SafeUnicode):
|
||||
return SafeUnicode(self + rhs)
|
||||
elif isinstance(rhs, SafeString):
|
||||
return SafeString(self + rhs)
|
||||
else:
|
||||
return super(SafeString, self).__add__(rhs)
|
||||
|
||||
def __str__(self):
|
||||
return self
|
||||
|
||||
def _proxy_method(self, *args, **kwargs):
|
||||
"""
|
||||
Wrap a call to a normal unicode method up so that we return safe
|
||||
results. The method that is being wrapped is passed in the 'method'
|
||||
argument.
|
||||
"""
|
||||
method = kwargs.pop('method')
|
||||
data = method(self, *args, **kwargs)
|
||||
if isinstance(data, str):
|
||||
return SafeString(data)
|
||||
else:
|
||||
return SafeUnicode(data)
|
||||
|
||||
encode = curry(_proxy_method, method = str.encode)
|
||||
decode = curry(_proxy_method, method = str.decode)
|
||||
|
||||
class SafeUnicode(unicode, SafeData):
|
||||
"""
|
||||
A unicode subclass that has been specifically marked as "safe" for HTML
|
||||
output purposes.
|
||||
"""
|
||||
def __add__(self, rhs):
|
||||
"""
|
||||
Concatenating a safe unicode object with another safe string or safe
|
||||
unicode object is safe. Otherwise, the result is no longer safe.
|
||||
"""
|
||||
if isinstance(rhs, SafeData):
|
||||
return SafeUnicode(self + rhs)
|
||||
else:
|
||||
return super(SafeUnicode, self).__add__(rhs)
|
||||
|
||||
def _proxy_method(self, *args, **kwargs):
|
||||
"""
|
||||
Wrap a call to a normal unicode method up so that we return safe
|
||||
results. The method that is being wrapped is passed in the 'method'
|
||||
argument.
|
||||
"""
|
||||
method = kwargs.pop('method')
|
||||
data = method(self, *args, **kwargs)
|
||||
if isinstance(data, str):
|
||||
return SafeString(data)
|
||||
else:
|
||||
return SafeUnicode(data)
|
||||
|
||||
encode = curry(_proxy_method, method = unicode.encode)
|
||||
decode = curry(_proxy_method, method = unicode.decode)
|
||||
|
||||
def mark_safe(s):
|
||||
"""
|
||||
Explicitly mark a string as safe for (HTML) output purposes. The returned
|
||||
object can be used everywhere a string or unicode object is appropriate.
|
||||
|
||||
Can be called multiple times on a single string.
|
||||
"""
|
||||
if isinstance(s, SafeData):
|
||||
return s
|
||||
if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str):
|
||||
return SafeString(s)
|
||||
if isinstance(s, (unicode, Promise)):
|
||||
return SafeUnicode(s)
|
||||
return SafeString(str(s))
|
||||
|
||||
def mark_for_escaping(s):
|
||||
"""
|
||||
Explicitly mark a string as requiring HTML escaping upon output. Has no
|
||||
effect on SafeData subclasses.
|
||||
|
||||
Can be called multiple times on a single string (the resulting escaping is
|
||||
only applied once).
|
||||
"""
|
||||
if isinstance(s, (SafeData, EscapeData)):
|
||||
return s
|
||||
if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str):
|
||||
return EscapeString(s)
|
||||
if isinstance(s, (unicode, Promise)):
|
||||
return EscapeUnicode(s)
|
||||
return EscapeString(str(s))
|
||||
|
|
@ -333,7 +333,6 @@ TECHNICAL_500_TEMPLATE = """
|
|||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="summary">
|
||||
<h1>{{ exception_type }} at {{ request.path|escape }}</h1>
|
||||
<h2>{{ exception_value|escape }}</h2>
|
||||
|
@ -395,7 +394,7 @@ TECHNICAL_500_TEMPLATE = """
|
|||
<div id="template">
|
||||
<h2>Template error</h2>
|
||||
<p>In template <code>{{ template_info.name }}</code>, error at line <strong>{{ template_info.line }}</strong></p>
|
||||
<h3>{{ template_info.message|escape }}</h3>
|
||||
<h3>{{ template_info.message }}</h3>
|
||||
<table class="source{% if template_info.top %} cut-top{% endif %}{% ifnotequal template_info.bottom template_info.total %} cut-bottom{% endifnotequal %}">
|
||||
{% for source_line in template_info.source_lines %}
|
||||
{% ifequal source_line.0 template_info.line %}
|
||||
|
@ -413,6 +412,7 @@ TECHNICAL_500_TEMPLATE = """
|
|||
<h2>Traceback <span>(innermost last)</span></h2>
|
||||
<div class="commands"><a href="#" onclick="return switchPastebinFriendly(this);">Switch to copy-and-paste view</a></div>
|
||||
<br/>
|
||||
{% autoescape off %}
|
||||
<div id="browserTraceback">
|
||||
<ul class="traceback">
|
||||
{% for frame in frames %}
|
||||
|
@ -422,11 +422,11 @@ TECHNICAL_500_TEMPLATE = """
|
|||
{% if frame.context_line %}
|
||||
<div class="context" id="c{{ frame.id }}">
|
||||
{% if frame.pre_context %}
|
||||
<ol start="{{ frame.pre_context_lineno }}" class="pre-context" id="pre{{ frame.id }}">{% for line in frame.pre_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line|escape }}</li>{% endfor %}</ol>
|
||||
<ol start="{{ frame.pre_context_lineno }}" class="pre-context" id="pre{{ frame.id }}">{% for line in frame.pre_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line }}</li>{% endfor %}</ol>
|
||||
{% endif %}
|
||||
<ol start="{{ frame.lineno }}" class="context-line"><li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ frame.context_line|escape }} <span>...</span></li></ol>
|
||||
<ol start="{{ frame.lineno }}" class="context-line"><li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ frame.context_line }} <span>...</span></li></ol>
|
||||
{% if frame.post_context %}
|
||||
<ol start='{{ frame.lineno|add:"1" }}' class="post-context" id="post{{ frame.id }}">{% for line in frame.post_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line|escape }}</li>{% endfor %}</ol>
|
||||
<ol start='{{ frame.lineno|add:"1" }}' class="post-context" id="post{{ frame.id }}">{% for line in frame.post_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line }}</li>{% endfor %}</ol>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -446,7 +446,7 @@ TECHNICAL_500_TEMPLATE = """
|
|||
{% for var in frame.vars|dictsort:"0" %}
|
||||
<tr>
|
||||
<td>{{ var.0 }}</td>
|
||||
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
|
||||
<td class="code"><div>{{ var.1|pprint }}</div></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
@ -466,7 +466,7 @@ Traceback (most recent call last):<br/>
|
|||
{% for frame in frames %}
|
||||
File "{{ frame.filename }}" in {{ frame.function }}<br/>
|
||||
{% if frame.context_line %}
|
||||
{{ frame.lineno }}. {{ frame.context_line|escape }}<br/>
|
||||
{{ frame.lineno }}. {{ frame.context_line }}<br/>
|
||||
{% endif %}
|
||||
{% endfor %}<br/>
|
||||
{{ exception_type }} at {{ request.path|escape }}<br/>
|
||||
|
@ -476,6 +476,7 @@ Traceback (most recent call last):<br/>
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endautoescape %}
|
||||
</div>
|
||||
|
||||
<div id="requestinfo">
|
||||
|
@ -494,7 +495,7 @@ Traceback (most recent call last):<br/>
|
|||
{% for var in request.GET.items %}
|
||||
<tr>
|
||||
<td>{{ var.0 }}</td>
|
||||
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
|
||||
<td class="code"><div>{{ var.1|pprint }}</div></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
@ -516,7 +517,7 @@ Traceback (most recent call last):<br/>
|
|||
{% for var in request.POST.items %}
|
||||
<tr>
|
||||
<td>{{ var.0 }}</td>
|
||||
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
|
||||
<td class="code"><div>{{ var.1|pprint }}</div></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
@ -538,7 +539,7 @@ Traceback (most recent call last):<br/>
|
|||
{% for var in request.COOKIES.items %}
|
||||
<tr>
|
||||
<td>{{ var.0 }}</td>
|
||||
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
|
||||
<td class="code"><div>{{ var.1|pprint }}</div></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
@ -559,7 +560,7 @@ Traceback (most recent call last):<br/>
|
|||
{% for var in request.META.items|dictsort:"0" %}
|
||||
<tr>
|
||||
<td>{{ var.0 }}</td>
|
||||
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
|
||||
<td class="code"><div>{{ var.1|pprint }}</div></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
@ -578,7 +579,7 @@ Traceback (most recent call last):<br/>
|
|||
{% for var in settings.items|dictsort:"0" %}
|
||||
<tr>
|
||||
<td>{{ var.0 }}</td>
|
||||
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
|
||||
<td class="code"><div>{{ var.1|pprint }}</div></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
@ -593,7 +594,6 @@ Traceback (most recent call last):<br/>
|
|||
display a standard 500 page.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
@ -645,12 +645,12 @@ TECHNICAL_404_TEMPLATE = """
|
|||
</p>
|
||||
<ol>
|
||||
{% for pattern in urlpatterns %}
|
||||
<li>{{ pattern|escape }}</li>
|
||||
<li>{{ pattern }}</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
<p>The current URL, <code>{{ request_path|escape }}</code>, didn't match any of these.</p>
|
||||
{% else %}
|
||||
<p>{{ reason|escape }}</p>
|
||||
<p>{{ reason }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -299,6 +299,104 @@ it also defines the content that fills the hole in the *parent*. If there were
|
|||
two similarly-named ``{% block %}`` tags in a template, that template's parent
|
||||
wouldn't know which one of the blocks' content to use.
|
||||
|
||||
Automatic HTML escaping
|
||||
=======================
|
||||
|
||||
**New in Django development version**
|
||||
|
||||
A very real problem when creating HTML (and other) output using templates and
|
||||
variable substitution is the possibility of accidently inserting some variable
|
||||
value that affects the resulting HTML. For example, a template fragment such as
|
||||
::
|
||||
|
||||
Hello, {{ name }}.
|
||||
|
||||
seems like a harmless way to display the user's name. However, if you are
|
||||
displaying data that the user entered directly and they had entered their name as ::
|
||||
|
||||
<script>alert('hello')</script>
|
||||
|
||||
this would always display a Javascript alert box when the page was loaded.
|
||||
Similarly, if you were displaying some data generated by another process and it
|
||||
contained a '<' symbol, you couldn't just dump this straight into your HTML,
|
||||
because it would be treated as the start of an element. The effects of these
|
||||
sorts of problems can vary from merely annoying to allowing exploits via `Cross
|
||||
Site Scripting`_ (XSS) attacks.
|
||||
|
||||
.. _Cross Site Scripting: http://en.wikipedia.org/wiki/Cross-site_scripting
|
||||
|
||||
In order to provide some protection against these problems, Django
|
||||
provides automatic (but controllable) HTML escaping for data coming from
|
||||
tempate variables. Inside this tag, any data that comes from template
|
||||
variables is examined to see if it contains one of the five HTML characters
|
||||
(<, >, ', " and &) that often need escaping and those characters are converted
|
||||
to their respective HTML entities. It causes no harm if a character is
|
||||
converted to an entity when it doesn't need to be, so all five characters are
|
||||
always converted.
|
||||
|
||||
Since some variables will contain data that is *intended* to be rendered
|
||||
as HTML, template tag and filter writers can mark their output strings as
|
||||
requiring no further escaping. For example, the ``unordered_list`` filter is
|
||||
designed to return raw HTML and we want the template processor to simply
|
||||
display the results as returned, without applying any escaping. That is taken
|
||||
care of by the filter. The template author need do nothing special in that
|
||||
case.
|
||||
|
||||
By default, automatic HTML escaping is always applied. However, sometimes you
|
||||
will not want this to occur (for example, if you're using the templating
|
||||
system to create an email). To control automatic escaping inside your template,
|
||||
wrap the affected content in the ``autoescape`` tag, like so::
|
||||
|
||||
{% autoescape off %}
|
||||
Hello {{ name }}
|
||||
{% endautoescape %}
|
||||
|
||||
The auto-escaping tag passes its effect onto templates that extend the
|
||||
current one as well as templates included via the ``include`` tag, just like
|
||||
all block tags.
|
||||
|
||||
The ``autoescape`` tag takes either ``on`` or ``off`` as its argument. At times, you might want to force auto-escaping when it would otherwise be disabled. For example::
|
||||
|
||||
Auto-escaping is on by default. Hello {{ name }}
|
||||
|
||||
{% autoescape off %}
|
||||
This will not be auto-escaped: {{ data }}.
|
||||
|
||||
Nor this: {{ other_data }}
|
||||
{% autoescape on %}
|
||||
Auto-escaping applies again, {{ name }}
|
||||
{% endautoescape %}
|
||||
{% endautoescape %}
|
||||
|
||||
For individual variables, the ``safe`` filter can also be used to indicate
|
||||
that the contents should not be automatically escaped::
|
||||
|
||||
This will be escaped: {{ data }}
|
||||
This will not be escaped: {{ data|safe }}
|
||||
|
||||
Think of *safe* as shorthand for *safe from further escaping* or *can be
|
||||
safely interpreted as HTML*. In this example, if ``data`` contains ``'<a>'``,
|
||||
the output will be::
|
||||
|
||||
This will be escaped: <a>
|
||||
This will not be escaped: <a>
|
||||
|
||||
Generally, you won't need to worry about auto-escaping very much. View
|
||||
developers and custom filter authors need to think about when their data
|
||||
shouldn't be escaped and mark it appropriately. They are in a better position
|
||||
to know when that should happen than the template author, so it is their
|
||||
responsibility. By default, all output is escaped unless the template
|
||||
processor is explicitly told otherwise.
|
||||
|
||||
You should also note that if you are trying to write a template that might be
|
||||
used in situations where automatic escaping is enabled or disabled and you
|
||||
don't know which (such as when your template is included in other templates),
|
||||
you can safely write as if you were in an ``{% autoescape off %}`` situation.
|
||||
Scatter ``escape`` filters around for any variables that need escaping. When
|
||||
auto-escaping is on, these extra filters won't change the output -- any
|
||||
variables that use the ``escape`` filter do not have further automatic
|
||||
escaping applied to them.
|
||||
|
||||
Using the built-in reference
|
||||
============================
|
||||
|
||||
|
@ -374,6 +472,24 @@ available, and what they do.
|
|||
Built-in tag reference
|
||||
----------------------
|
||||
|
||||
autoescape
|
||||
~~~~~~~~~~
|
||||
|
||||
**New in Django development version**
|
||||
|
||||
Control the current auto-escaping behaviour. This tag takes either ``on`` or
|
||||
``off`` as an argument and that determines whether auto-escaping is in effect
|
||||
inside the block.
|
||||
|
||||
When auto-escaping is in effect, all variable content has HTML escaping applied
|
||||
to it before placing the result into the output (but after any filters have
|
||||
been applied). This is equivalent to manually applying the ``escape`` filter
|
||||
attached to each variable.
|
||||
|
||||
The only exceptions are variables that are already marked as 'safe' from
|
||||
escaping, either by the code that populated the variable, or because it has
|
||||
the ``safe`` or ``escape`` filters applied.
|
||||
|
||||
block
|
||||
~~~~~
|
||||
|
||||
|
@ -452,7 +568,7 @@ just like in variable syntax.
|
|||
|
||||
Sample usage::
|
||||
|
||||
{% filter escape|lower %}
|
||||
{% filter force_escape|lower %}
|
||||
This text will be HTML-escaped, and will appear in all lowercase.
|
||||
{% endfilter %}
|
||||
|
||||
|
@ -1076,6 +1192,10 @@ Returns true if the value is divisible by the argument.
|
|||
escape
|
||||
~~~~~~
|
||||
|
||||
**New in Django development version:** The behaviour of this filter has
|
||||
changed slightly in the development version (the affects are only applied
|
||||
once, after all other filters).
|
||||
|
||||
Escapes a string's HTML. Specifically, it makes these replacements:
|
||||
|
||||
* ``"&"`` to ``"&"``
|
||||
|
@ -1084,6 +1204,16 @@ Escapes a string's HTML. Specifically, it makes these replacements:
|
|||
* ``'"'`` (double quote) to ``'"'``
|
||||
* ``"'"`` (single quote) to ``'''``
|
||||
|
||||
The escaping is only applied when the string is output, so it does not matter
|
||||
where in a chained sequence of filters you put ``escape``: it will always be
|
||||
applied as though it were the last filter. If you want escaping to be applied
|
||||
immediately, use the ``force_escape`` filter.
|
||||
|
||||
Applying ``escape`` to a variable that would normally have auto-escaping
|
||||
applied to the result will only result in one round of escaping being done. So
|
||||
it is safe to use this function even in auto-escaping environments. If you want
|
||||
multiple escaping passes to be applied, use the ``force_escape`` filter.
|
||||
|
||||
filesizeformat
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -1140,6 +1270,17 @@ value Template Output
|
|||
Using ``floatformat`` with no argument is equivalent to using ``floatformat``
|
||||
with an argument of ``-1``.
|
||||
|
||||
force_escape
|
||||
~~~~~~~~~~~~
|
||||
|
||||
**New in Django development version**
|
||||
|
||||
Applies HTML escaping to a string (see the ``escape`` filter for details).
|
||||
This filter is applied *immediately* and returns a new, escaped string. This
|
||||
is useful in the rare cases where you need multiple escaping or want to apply
|
||||
other filters to the escaped results. Normally, you want to use the ``escape``
|
||||
filter.
|
||||
|
||||
get_digit
|
||||
~~~~~~~~~
|
||||
|
||||
|
@ -1264,6 +1405,12 @@ Right-aligns the value in a field of a given width.
|
|||
|
||||
**Argument:** field size
|
||||
|
||||
safe
|
||||
~~~~
|
||||
|
||||
Marks a string as not requiring further HTML escaping prior to output. When
|
||||
autoescaping is off, this filter has no effect.
|
||||
|
||||
slice
|
||||
~~~~~
|
||||
|
||||
|
|
|
@ -219,13 +219,13 @@ be replaced with the name of the invalid variable.
|
|||
|
||||
While ``TEMPLATE_STRING_IF_INVALID`` can be a useful debugging tool,
|
||||
it is a bad idea to turn it on as a 'development default'.
|
||||
|
||||
|
||||
Many templates, including those in the Admin site, rely upon the
|
||||
silence of the template system when a non-existent variable is
|
||||
encountered. If you assign a value other than ``''`` to
|
||||
``TEMPLATE_STRING_IF_INVALID``, you will experience rendering
|
||||
problems with these templates and sites.
|
||||
|
||||
|
||||
Generally, ``TEMPLATE_STRING_IF_INVALID`` should only be enabled
|
||||
in order to debug a specific template problem, then cleared
|
||||
once debugging is complete.
|
||||
|
@ -722,6 +722,95 @@ decorator instead::
|
|||
If you leave off the ``name`` argument, as in the second example above, Django
|
||||
will use the function's name as the filter name.
|
||||
|
||||
Filters and auto-escaping
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**New in Django development version**
|
||||
|
||||
When you are writing a custom filter, you need to give some thought to how
|
||||
this filter will interact with Django's auto-escaping behaviour. Firstly, you
|
||||
should realise that there are three types of strings that can be passed around
|
||||
inside the template code:
|
||||
|
||||
* raw strings are the native Python ``str`` or ``unicode`` types. On
|
||||
output, they are escaped if auto-escaping is in effect and presented
|
||||
unchanged, otherwise.
|
||||
|
||||
* "safe" strings are strings that are safe from further escaping at output
|
||||
time. Any necessary escaping has already been done. They are commonly used
|
||||
for output that contains raw HTML that is intended to be intrepreted on the
|
||||
client side.
|
||||
|
||||
Internally, these strings are of type ``SafeString`` or ``SafeUnicode``,
|
||||
although they share a common base class in ``SafeData``, so you can test
|
||||
for them using code like::
|
||||
|
||||
if isinstance(value, SafeData):
|
||||
# Do something with the "safe" string.
|
||||
|
||||
* strings which are marked as "needing escaping" are *always* escaped on
|
||||
output, regardless of whether they are in an ``autoescape`` block or not.
|
||||
These strings are only escaped once, however, even if auto-escaping
|
||||
applies. This type of string is internally represented by the types
|
||||
``EscapeString`` and ``EscapeUnicode``. You will not normally need to worry
|
||||
about these; they exist for the implementation of the ``escape`` filter.
|
||||
|
||||
Inside your filter, you will need to think about three areas in order to be
|
||||
auto-escaping compliant:
|
||||
|
||||
1. If your filter returns a string that is ready for direct output (it should
|
||||
be considered a "safe" string), you should call
|
||||
``django.utils.safestring.mark_safe()`` on the result prior to returning.
|
||||
This will turn the result into the appropriate ``SafeData`` type. This is
|
||||
often the case when you are returning raw HTML, for example.
|
||||
|
||||
2. If your filter is given a "safe" string, is it guaranteed to return a
|
||||
"safe" string? If so, set the ``is_safe`` attribute on the function to be
|
||||
``True``. For example, a filter that replaced a word consisting only of
|
||||
digits with the number spelt out in words is going to be
|
||||
safe-string-preserving, since it cannot introduce any of the five dangerous
|
||||
characters: <, >, ", ' or &. We can write::
|
||||
|
||||
@register.filter
|
||||
def convert_to_words(value):
|
||||
# ... implementation here ...
|
||||
return result
|
||||
|
||||
convert_to_words.is_safe = True
|
||||
|
||||
Note that this filter does not return a universally safe result (it does
|
||||
not return ``mark_safe(result)``) because if it is handed a raw string such
|
||||
as '<a>', this will need further escaping in an auto-escape environment.
|
||||
The ``is_safe`` attribute only talks about the the result when a safe
|
||||
string is passed into the filter.
|
||||
|
||||
3. Will your filter behave differently depending upon whether auto-escaping
|
||||
is currently in effect or not? This is normally a concern when you are
|
||||
returning mixed content (HTML elements mixed with user-supplied content).
|
||||
For example, the ``ordered_list`` filter that ships with Django needs to
|
||||
know whether to escape its content or not. It will always return a safe
|
||||
string. Since it returns raw HTML, we cannot apply escaping to the
|
||||
result -- it needs to be done in-situ.
|
||||
|
||||
For these cases, the filter function needs to be told what the current
|
||||
auto-escaping setting is. Set the ``needs_autoescape`` attribute on the
|
||||
filter to ``True`` and have your function take an extra argument called
|
||||
``autoescape`` with a default value of ``None``. When the filter is called,
|
||||
the ``autoescape`` keyword argument will be ``True`` if auto-escaping is in
|
||||
effect. For example, the ``unordered_list`` filter is written as::
|
||||
|
||||
def unordered_list(value, autoescape=None):
|
||||
# ... lots of code here ...
|
||||
|
||||
return mark_safe(...)
|
||||
|
||||
unordered_list.is_safe = True
|
||||
unordered_list.needs_autoescape = True
|
||||
|
||||
By default, both the ``is_safe`` and ``needs_autoescape`` attributes are
|
||||
``False``. You do not need to specify them if ``False`` is an acceptable
|
||||
value.
|
||||
|
||||
Writing custom template tags
|
||||
----------------------------
|
||||
|
||||
|
@ -840,6 +929,43 @@ Ultimately, this decoupling of compilation and rendering results in an
|
|||
efficient template system, because a template can render multiple context
|
||||
without having to be parsed multiple times.
|
||||
|
||||
Auto-escaping considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The output from template tags is not automatically run through the
|
||||
auto-escaping filters. However, there are still a couple of things you should
|
||||
keep in mind when writing a template tag:
|
||||
|
||||
If the ``render()`` function of your template stores the result in a context
|
||||
variable (rather than returning the result in a string), it should take care
|
||||
to call ``mark_safe()`` if appropriate. When the variable is ultimately
|
||||
rendered, it will be affected by the auto-escape setting in effect at the
|
||||
time, so content that should be safe from further escaping needs to be marked
|
||||
as such.
|
||||
|
||||
Also, if your template tag creates a new context for performing some
|
||||
sub-rendering, you should be careful to set the auto-escape attribute to the
|
||||
current context's value. The ``__init__`` method for the ``Context`` class
|
||||
takes a parameter called ``autoescape`` that you can use for this purpose. For
|
||||
example::
|
||||
|
||||
def render(self, context):
|
||||
# ...
|
||||
new_context = Context({'var': obj}, autoescape=context.autoescape)
|
||||
# ... Do something with new_context ...
|
||||
|
||||
This is not a very common situation, but it is sometimes useful, particularly
|
||||
if you are rendering a template yourself. For example::
|
||||
|
||||
def render(self, context):
|
||||
t = template.load_template('small_fragment.html')
|
||||
return t.render(Context({'var': obj}, autoescape=context.autoescape))
|
||||
|
||||
If we had neglected to pass in the current ``context.autoescape`` value to our
|
||||
new ``Context`` in this example, the results would have *always* been
|
||||
automatically escaped, which may not be the desired behaviour if the template
|
||||
tag is used inside a ``{% autoescape off %}`` block.
|
||||
|
||||
Registering the tag
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -917,7 +1043,7 @@ current context, available in the ``render`` method::
|
|||
def __init__(self, date_to_be_formatted, format_string):
|
||||
self.date_to_be_formatted = date_to_be_formatted
|
||||
self.format_string = format_string
|
||||
|
||||
|
||||
def render(self, context):
|
||||
try:
|
||||
actual_date = resolve_variable(self.date_to_be_formatted, context)
|
||||
|
@ -934,26 +1060,26 @@ format it accordingly.
|
|||
``template.resolve_variable()`` is still available, but has been deprecated
|
||||
in favor of a new ``template.Variable`` class. Using this class will usually
|
||||
be more efficient than calling ``template.resolve_variable``
|
||||
|
||||
|
||||
To use the ``Variable`` class, simply instantiate it with the name of the
|
||||
variable to be resolved, and then call ``variable.resolve(context)``. So,
|
||||
in the development version, the above example would be more correctly
|
||||
written as:
|
||||
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
|
||||
class FormatTimeNode(template.Node):
|
||||
def __init__(self, date_to_be_formatted, format_string):
|
||||
self.date_to_be_formatted = **Variable(date_to_be_formatted)**
|
||||
self.format_string = format_string
|
||||
|
||||
|
||||
def render(self, context):
|
||||
try:
|
||||
actual_date = **self.date_to_be_formatted.resolve(context)**
|
||||
return actual_date.strftime(self.format_string)
|
||||
except template.VariableDoesNotExist:
|
||||
return ''
|
||||
|
||||
|
||||
Changes are highlighted in bold.
|
||||
|
||||
Variable resolution will throw a ``VariableDoesNotExist`` exception if it cannot
|
||||
|
|
|
@ -194,10 +194,10 @@ u'a stri to be maled'
|
|||
>>> cut(u'a string to be mangled', 'strings')
|
||||
u'a string to be mangled'
|
||||
|
||||
>>> escape(u'<some html & special characters > here')
|
||||
>>> force_escape(u'<some html & special characters > here')
|
||||
u'<some html & special characters > here'
|
||||
|
||||
>>> escape(u'<some html & special characters > here ĐÅ€£')
|
||||
>>> force_escape(u'<some html & special characters > here ĐÅ€£')
|
||||
u'<some html & special characters > here \xc4\x90\xc3\x85\xe2\x82\xac\xc2\xa3'
|
||||
|
||||
>>> linebreaks(u'line 1')
|
||||
|
|
|
@ -1554,7 +1554,7 @@ does not have help text, nothing will be output.
|
|||
... </form>''')
|
||||
>>> print t.render(Context({'form': UserRegistration(auto_id=False)}))
|
||||
<form action="">
|
||||
<p>Username: <input type="text" name="username" maxlength="10" /><br />Good luck picking a username that doesn't already exist.</p>
|
||||
<p>Username: <input type="text" name="username" maxlength="10" /><br />Good luck picking a username that doesn't already exist.</p>
|
||||
<p>Password1: <input type="password" name="password1" /></p>
|
||||
<p>Password2: <input type="password" name="password2" /></p>
|
||||
<input type="submit" />
|
||||
|
|
|
@ -50,7 +50,7 @@ __test__ = {
|
|||
'localflavor_sk_tests': localflavor_sk_tests,
|
||||
'localflavor_uk_tests': localflavor_uk_tests,
|
||||
'localflavor_us_tests': localflavor_us_tests,
|
||||
'regressions_tests': regression_tests,
|
||||
'regression_tests': regression_tests,
|
||||
'util_tests': util_tests,
|
||||
'widgets_tests': widgets_tests,
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ from datetime import timedelta, date
|
|||
from django.template import Template, Context, add_to_builtins
|
||||
from django.utils.dateformat import DateFormat
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.html import escape
|
||||
|
||||
add_to_builtins('django.contrib.humanize.templatetags.humanize')
|
||||
|
||||
|
@ -15,7 +16,7 @@ class HumanizeTests(unittest.TestCase):
|
|||
test_content = test_list[index]
|
||||
t = Template('{{ test_content|%s }}' % method)
|
||||
rendered = t.render(Context(locals())).strip()
|
||||
self.assertEqual(rendered, result_list[index],
|
||||
self.assertEqual(rendered, escape(result_list[index]),
|
||||
msg="%s test failed, produced %s, should've produced %s" % (method, rendered, result_list[index]))
|
||||
|
||||
def test_ordinal(self):
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
# coding: utf-8
|
||||
"""
|
||||
Tests for template filters (as opposed to template tags).
|
||||
|
||||
The tests are hidden inside a function so that things like timestamps and
|
||||
timezones are only evaluated at the moment of execution and will therefore be
|
||||
consistent.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.utils.tzinfo import LocalTimezone
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
# RESULT SYNTAX --
|
||||
# 'template_name': ('template contents', 'context dict',
|
||||
# 'expected string output' or Exception class)
|
||||
def get_filter_tests():
|
||||
now = datetime.now()
|
||||
now_tz = datetime.now(LocalTimezone(now))
|
||||
return {
|
||||
# Default compare with datetime.now()
|
||||
'filter-timesince01' : ('{{ a|timesince }}', {'a': datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'),
|
||||
'filter-timesince02' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(days=1, minutes = 1)}, '1 day'),
|
||||
'filter-timesince03' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(hours=1, minutes=25, seconds = 10)}, '1 hour, 25 minutes'),
|
||||
|
||||
# Compare to a given parameter
|
||||
'filter-timesince04' : ('{{ a|timesince:b }}', {'a':now + timedelta(days=2), 'b':now + timedelta(days=1)}, '1 day'),
|
||||
'filter-timesince05' : ('{{ a|timesince:b }}', {'a':now + timedelta(days=2, minutes=1), 'b':now + timedelta(days=2)}, '1 minute'),
|
||||
|
||||
# Check that timezone is respected
|
||||
'filter-timesince06' : ('{{ a|timesince:b }}', {'a':now_tz + timedelta(hours=8), 'b':now_tz}, '8 hours'),
|
||||
|
||||
# Default compare with datetime.now()
|
||||
'filter-timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'),
|
||||
'filter-timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'),
|
||||
'filter-timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8 hours, 10 minutes'),
|
||||
|
||||
# Compare to a given parameter
|
||||
'filter-timeuntil04' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=1), 'b':now - timedelta(days=2)}, '1 day'),
|
||||
'filter-timeuntil05' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=2, minutes=1)}, '1 minute'),
|
||||
|
||||
'filter-addslash01': ("{% autoescape off %}{{ a|addslashes }} {{ b|addslashes }}{% endautoescape %}", {"a": "<a>'", "b": mark_safe("<a>'")}, ur"<a>\' <a>\'"),
|
||||
'filter-addslash02': ("{{ a|addslashes }} {{ b|addslashes }}", {"a": "<a>'", "b": mark_safe("<a>'")}, ur"<a>\' <a>\'"),
|
||||
|
||||
'filter-capfirst01': ("{% autoescape off %}{{ a|capfirst }} {{ b|capfirst }}{% endautoescape %}", {"a": "fred>", "b": mark_safe("fred>")}, u"Fred> Fred>"),
|
||||
'filter-capfirst02': ("{{ a|capfirst }} {{ b|capfirst }}", {"a": "fred>", "b": mark_safe("fred>")}, u"Fred> Fred>"),
|
||||
|
||||
# Note that applying fix_ampsersands in autoescape mode leads to
|
||||
# double escaping.
|
||||
'filter-fix_ampersands01': ("{% autoescape off %}{{ a|fix_ampersands }} {{ b|fix_ampersands }}{% endautoescape %}", {"a": "a&b", "b": mark_safe("a&b")}, u"a&b a&b"),
|
||||
'filter-fix_ampersands02': ("{{ a|fix_ampersands }} {{ b|fix_ampersands }}", {"a": "a&b", "b": mark_safe("a&b")}, u"a&amp;b a&b"),
|
||||
|
||||
'filter-floatformat01': ("{% autoescape off %}{{ a|floatformat }} {{ b|floatformat }}{% endautoescape %}", {"a": "1.42", "b": mark_safe("1.42")}, u"1.4 1.4"),
|
||||
'filter-floatformat02': ("{{ a|floatformat }} {{ b|floatformat }}", {"a": "1.42", "b": mark_safe("1.42")}, u"1.4 1.4"),
|
||||
|
||||
# The contents of "linenumbers" is escaped according to the current
|
||||
# autoescape setting.
|
||||
'filter-linenumbers01': ("{{ a|linenumbers }} {{ b|linenumbers }}", {"a": "one\n<two>\nthree", "b": mark_safe("one\n<two>\nthree")}, u"1. one\n2. <two>\n3. three 1. one\n2. <two>\n3. three"),
|
||||
'filter-linenumbers02': ("{% autoescape off %}{{ a|linenumbers }} {{ b|linenumbers }}{% endautoescape %}", {"a": "one\n<two>\nthree", "b": mark_safe("one\n<two>\nthree")}, u"1. one\n2. <two>\n3. three 1. one\n2. <two>\n3. three"),
|
||||
|
||||
'filter-lower01': ("{% autoescape off %}{{ a|lower }} {{ b|lower }}{% endautoescape %}", {"a": "Apple & banana", "b": mark_safe("Apple & banana")}, u"apple & banana apple & banana"),
|
||||
'filter-lower02': ("{{ a|lower }} {{ b|lower }}", {"a": "Apple & banana", "b": mark_safe("Apple & banana")}, u"apple & banana apple & banana"),
|
||||
|
||||
# The make_list filter can destroy existing escaping, so the results are
|
||||
# escaped.
|
||||
'filter-make_list01': ("{% autoescape off %}{{ a|make_list }}{% endautoescape %}", {"a": mark_safe("&")}, u"[u'&']"),
|
||||
'filter-make_list02': ("{{ a|make_list }}", {"a": mark_safe("&")}, u"[u'&']"),
|
||||
'filter-make_list03': ('{% autoescape off %}{{ a|make_list|stringformat:"s"|safe }}{% endautoescape %}', {"a": mark_safe("&")}, u"[u'&']"),
|
||||
'filter-make_list04': ('{{ a|make_list|stringformat:"s"|safe }}', {"a": mark_safe("&")}, u"[u'&']"),
|
||||
|
||||
# Running slugify on a pre-escaped string leads to odd behaviour,
|
||||
# but the result is still safe.
|
||||
'filter-slugify01': ("{% autoescape off %}{{ a|slugify }} {{ b|slugify }}{% endautoescape %}", {"a": "a & b", "b": mark_safe("a & b")}, u"a-b a-amp-b"),
|
||||
'filter-slugify02': ("{{ a|slugify }} {{ b|slugify }}", {"a": "a & b", "b": mark_safe("a & b")}, u"a-b a-amp-b"),
|
||||
|
||||
# Notice that escaping is applied *after* any filters, so the string
|
||||
# formatting here only needs to deal with pre-escaped characters.
|
||||
'filter-stringformat01': ('{% autoescape off %}.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.{% endautoescape %}', {"a": "a<b", "b": mark_safe("a<b")}, u". a<b. . a<b."),
|
||||
'filter-stringformat02': ('.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.', {"a": "a<b", "b": mark_safe("a<b")}, u". a<b. . a<b."),
|
||||
|
||||
# XXX No test for "title" filter; needs an actual object.
|
||||
|
||||
'filter-truncatewords01': ('{% autoescape off %}{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}{% endautoescape %}', {"a": "alpha & bravo", "b": mark_safe("alpha & bravo")}, u"alpha & ... alpha & ..."),
|
||||
'filter-truncatewords02': ('{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}', {"a": "alpha & bravo", "b": mark_safe("alpha & bravo")}, u"alpha & ... alpha & ..."),
|
||||
|
||||
# The "upper" filter messes up entities (which are case-sensitive),
|
||||
# so it's not safe for non-escaping purposes.
|
||||
'filter-upper01': ('{% autoescape off %}{{ a|upper }} {{ b|upper }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, u"A & B A & B"),
|
||||
'filter-upper02': ('{{ a|upper }} {{ b|upper }}', {"a": "a & b", "b": mark_safe("a & b")}, u"A & B A &AMP; B"),
|
||||
|
||||
'filter-urlize01': ('{% autoescape off %}{{ a|urlize }} {{ b|urlize }}{% endautoescape %}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, u'<a href="http://example.com/x=&y=" rel="nofollow">http://example.com/x=&y=</a> <a href="http://example.com?x=&y=" rel="nofollow">http://example.com?x=&y=</a>'),
|
||||
'filter-urlize02': ('{{ a|urlize }} {{ b|urlize }}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, u'<a href="http://example.com/x=&y=" rel="nofollow">http://example.com/x=&y=</a> <a href="http://example.com?x=&y=" rel="nofollow">http://example.com?x=&y=</a>'),
|
||||
'filter-urlize03': ('{% autoescape off %}{{ a|urlize }}{% endautoescape %}', {"a": mark_safe("a & b")}, 'a & b'),
|
||||
'filter-urlize04': ('{{ a|urlize }}', {"a": mark_safe("a & b")}, 'a & b'),
|
||||
|
||||
'filter-urlizetrunc01': ('{% autoescape off %}{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}{% endautoescape %}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, u'<a href="http://example.com/x=&y=" rel="nofollow">http:...</a> <a href="http://example.com?x=&y=" rel="nofollow">http:...</a>'),
|
||||
'filter-urlizetrunc02': ('{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, u'<a href="http://example.com/x=&y=" rel="nofollow">http:...</a> <a href="http://example.com?x=&y=" rel="nofollow">http:...</a>'),
|
||||
|
||||
'filter-wordcount01': ('{% autoescape off %}{{ a|wordcount }} {{ b|wordcount }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, "3 3"),
|
||||
'filter-wordcount02': ('{{ a|wordcount }} {{ b|wordcount }}', {"a": "a & b", "b": mark_safe("a & b")}, "3 3"),
|
||||
|
||||
'filter-wordwrap01': ('{% autoescape off %}{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, u"a &\nb a &\nb"),
|
||||
'filter-wordwrap02': ('{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}', {"a": "a & b", "b": mark_safe("a & b")}, u"a &\nb a &\nb"),
|
||||
|
||||
'filter-ljust01': ('{% autoescape off %}.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, u".a&b . .a&b ."),
|
||||
'filter-ljust02': ('.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, u".a&b . .a&b ."),
|
||||
|
||||
'filter-rjust01': ('{% autoescape off %}.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, u". a&b. . a&b."),
|
||||
'filter-rjust02': ('.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, u". a&b. . a&b."),
|
||||
|
||||
'filter-center01': ('{% autoescape off %}.{{ a|center:"5" }}. .{{ b|center:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, u". a&b . . a&b ."),
|
||||
'filter-center02': ('.{{ a|center:"5" }}. .{{ b|center:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, u". a&b . . a&b ."),
|
||||
|
||||
'filter-cut01': ('{% autoescape off %}{{ a|cut:"x" }} {{ b|cut:"x" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, u"&y &y"),
|
||||
'filter-cut02': ('{{ a|cut:"x" }} {{ b|cut:"x" }}', {"a": "x&y", "b": mark_safe("x&y")}, u"&y &y"),
|
||||
'filter-cut03': ('{% autoescape off %}{{ a|cut:"&" }} {{ b|cut:"&" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, u"xy xamp;y"),
|
||||
'filter-cut04': ('{{ a|cut:"&" }} {{ b|cut:"&" }}', {"a": "x&y", "b": mark_safe("x&y")}, u"xy xamp;y"),
|
||||
# Passing ';' to cut can break existing HTML entities, so those strings
|
||||
# are auto-escaped.
|
||||
'filter-cut05': ('{% autoescape off %}{{ a|cut:";" }} {{ b|cut:";" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, u"x&y x&y"),
|
||||
'filter-cut06': ('{{ a|cut:";" }} {{ b|cut:";" }}', {"a": "x&y", "b": mark_safe("x&y")}, u"x&y x&ampy"),
|
||||
|
||||
# The "escape" filter works the same whether autoescape is on or off,
|
||||
# but it has no effect on strings already marked as safe.
|
||||
'filter-escape01': ('{{ a|escape }} {{ b|escape }}', {"a": "x&y", "b": mark_safe("x&y")}, u"x&y x&y"),
|
||||
'filter-escape02': ('{% autoescape off %}{{ a|escape }} {{ b|escape }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y"),
|
||||
|
||||
# It is only applied once, regardless of the number of times it
|
||||
# appears in a chain.
|
||||
'filter-escape03': ('{% autoescape off %}{{ a|escape|escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"),
|
||||
'filter-escape04': ('{{ a|escape|escape }}', {"a": "x&y"}, u"x&y"),
|
||||
|
||||
# Force_escape is applied immediately. It can be used to provide
|
||||
# double-escaping, for example.
|
||||
'filter-force-escape01': ('{% autoescape off %}{{ a|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"),
|
||||
'filter-force-escape02': ('{{ a|force_escape }}', {"a": "x&y"}, u"x&y"),
|
||||
'filter-force-escape03': ('{% autoescape off %}{{ a|force_escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&amp;y"),
|
||||
'filter-force-escape04': ('{{ a|force_escape|force_escape }}', {"a": "x&y"}, u"x&amp;y"),
|
||||
|
||||
# Because the result of force_escape is "safe", an additional
|
||||
# escape filter has no effect.
|
||||
'filter-force-escape05': ('{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"),
|
||||
'filter-force-escape06': ('{{ a|force_escape|escape }}', {"a": "x&y"}, u"x&y"),
|
||||
'filter-force-escape07': ('{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"),
|
||||
'filter-force-escape07': ('{{ a|escape|force_escape }}', {"a": "x&y"}, u"x&y"),
|
||||
|
||||
# The contents in "linebreaks" and "linebreaksbr" are escaped
|
||||
# according to the current autoescape setting.
|
||||
'filter-linebreaks01': ('{{ a|linebreaks }} {{ b|linebreaks }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, u"<p>x&<br />y</p> <p>x&<br />y</p>"),
|
||||
'filter-linebreaks02': ('{% autoescape off %}{{ a|linebreaks }} {{ b|linebreaks }}{% endautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, u"<p>x&<br />y</p> <p>x&<br />y</p>"),
|
||||
|
||||
'filter-linebreaksbr01': ('{{ a|linebreaksbr }} {{ b|linebreaksbr }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, u"x&<br />y x&<br />y"),
|
||||
'filter-linebreaksbr02': ('{% autoescape off %}{{ a|linebreaksbr }} {{ b|linebreaksbr }}{% endautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, u"x&<br />y x&<br />y"),
|
||||
|
||||
'filter-safe01': ("{{ a }} -- {{ a|safe }}", {"a": u"<b>hello</b>"}, "<b>hello</b> -- <b>hello</b>"),
|
||||
'filter-safe02': ("{% autoescape off %}{{ a }} -- {{ a|safe }}{% endautoescape %}", {"a": "<b>hello</b>"}, u"<b>hello</b> -- <b>hello</b>"),
|
||||
|
||||
'filter-removetags01': ('{{ a|removetags:"a b" }} {{ b|removetags:"a b" }}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, u"x <p>y</p> x <p>y</p>"),
|
||||
'filter-removetags02': ('{% autoescape off %}{{ a|removetags:"a b" }} {{ b|removetags:"a b" }}{% endautoescape %}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, u"x <p>y</p> x <p>y</p>"),
|
||||
|
||||
'filter-striptags01': ('{{ a|striptags }} {{ b|striptags }}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x y x y"),
|
||||
'filter-striptags02': ('{% autoescape off %}{{ a|striptags }} {{ b|striptags }}{% endautoescape %}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x y x y"),
|
||||
|
||||
'filter-first01': ('{{ a|first }} {{ b|first }}', {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&b a&b"),
|
||||
'filter-first02': ('{% autoescape off %}{{ a|first }} {{ b|first }}{% endautoescape %}', {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&b a&b"),
|
||||
|
||||
'filter-random01': ('{{ a|random }} {{ b|random }}', {"a": ["a&b", "a&b"], "b": [mark_safe("a&b"), mark_safe("a&b")]}, "a&b a&b"),
|
||||
'filter-random02': ('{% autoescape off %}{{ a|random }} {{ b|random }}{% endautoescape %}', {"a": ["a&b", "a&b"], "b": [mark_safe("a&b"), mark_safe("a&b")]}, "a&b a&b"),
|
||||
|
||||
'filter-slice01': ('{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}', {"a": "a&b", "b": mark_safe("a&b")}, "&b &b"),
|
||||
'filter-slice02': ('{% autoescape off %}{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, "&b &b"),
|
||||
|
||||
'filter-unordered_list01': ('{{ a|unordered_list }}', {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"),
|
||||
'filter-unordered_list02': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"),
|
||||
'filter-unordered_list03': ('{{ a|unordered_list }}', {"a": ["x>", [[mark_safe("<y"), []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"),
|
||||
'filter-unordered_list04': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [[mark_safe("<y"), []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"),
|
||||
'filter-unordered_list05': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"),
|
||||
|
||||
# If the input to "default" filter is marked as safe, then so is the
|
||||
# output. However, if the default arg is used, auto-escaping kicks in
|
||||
# (if enabled), because we cannot mark the default as safe.
|
||||
#
|
||||
# Note: we have to use {"a": ""} here, otherwise the invalid template
|
||||
# variable string interferes with the test result.
|
||||
'filter-default01': ('{{ a|default:"x<" }}', {"a": ""}, "x<"),
|
||||
'filter-default02': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": ""}, "x<"),
|
||||
'filter-default03': ('{{ a|default:"x<" }}', {"a": mark_safe("x>")}, "x>"),
|
||||
'filter-default04': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": mark_safe("x>")}, "x>"),
|
||||
|
||||
'filter-default_if_none01': ('{{ a|default:"x<" }}', {"a": None}, "x<"),
|
||||
'filter-default_if_none02': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": None}, "x<"),
|
||||
|
||||
'filter-phone2numeric01': ('{{ a|phone2numeric }} {{ b|phone2numeric }}', {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>") }, "<1-800-2255-63> <1-800-2255-63>"),
|
||||
'filter-phone2numeric02': ('{% autoescape off %}{{ a|phone2numeric }} {{ b|phone2numeric }}{% endautoescape %}', {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>") }, "<1-800-2255-63> <1-800-2255-63>"),
|
||||
|
||||
# Chaining a bunch of safeness-preserving filters should not alter
|
||||
# the safe status either way.
|
||||
'chaining01': ('{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}', {"a": "a < b", "b": mark_safe("a < b")}, " A < b . A < b "),
|
||||
'chaining02': ('{% autoescape off %}{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}{% endautoescape %}', {"a": "a < b", "b": mark_safe("a < b")}, " A < b . A < b "),
|
||||
|
||||
# Using a filter that forces a string back to unsafe:
|
||||
'chaining03': ('{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}', {"a": "a < b", "b": mark_safe("a < b")}, "A < .A < "),
|
||||
'chaining04': ('{% autoescape off %}{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}{% endautoescape %}', {"a": "a < b", "b": mark_safe("a < b")}, "A < .A < "),
|
||||
|
||||
# Using a filter that forces safeness does not lead to double-escaping
|
||||
'chaining05': ('{{ a|escape|capfirst }}', {"a": "a < b"}, "A < b"),
|
||||
'chaining06': ('{% autoescape off %}{{ a|escape|capfirst }}{% endautoescape %}', {"a": "a < b"}, "A < b"),
|
||||
|
||||
# Force to safe, then back (also showing why using force_escape too
|
||||
# early in a chain can lead to unexpected results).
|
||||
'chaining07': ('{{ a|force_escape|cut:"b" }}', {"a": "a < b"}, "a < "),
|
||||
'chaining08': ('{% autoescape off %}{{ a|force_escape|cut:"b" }}{% endautoescape %}', {"a": "a < b"}, "a < "),
|
||||
'chaining09': ('{{ a|cut:"b"|force_escape }}', {"a": "a < b"}, "a < "),
|
||||
'chaining10': ('{% autoescape off %}{{ a|cut:"b"|force_escape }}{% endautoescape %}', {"a": "a < b"}, "a < "),
|
||||
'chaining11': ('{{ a|cut:"b"|safe }}', {"a": "a < b"}, "a < "),
|
||||
'chaining12': ('{% autoescape off %}{{ a|cut:"b"|safe }}{% endautoescape %}', {"a": "a < b"}, "a < "),
|
||||
'chaining13': ('{{ a|safe|force_escape }}', {"a": "a < b"}, "a < b"),
|
||||
'chaining14': ('{% autoescape off %}{{ a|safe|force_escape }}{% endautoescape %}', {"a": "a < b"}, "a < b"),
|
||||
}
|
|
@ -14,9 +14,11 @@ from django import template
|
|||
from django.template import loader
|
||||
from django.template.loaders import app_directories, filesystem
|
||||
from django.utils.translation import activate, deactivate, ugettext as _
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.tzinfo import LocalTimezone
|
||||
|
||||
from unicode import unicode_tests
|
||||
import filters
|
||||
|
||||
# Some other tests we would like to run
|
||||
__test__ = {
|
||||
|
@ -120,20 +122,97 @@ class Templates(unittest.TestCase):
|
|||
['/dir1/index.html'])
|
||||
|
||||
def test_templates(self):
|
||||
# NOW and NOW_tz are used by timesince tag tests.
|
||||
NOW = datetime.now()
|
||||
NOW_tz = datetime.now(LocalTimezone(datetime.now()))
|
||||
template_tests = self.get_template_tests()
|
||||
filter_tests = filters.get_filter_tests()
|
||||
|
||||
# Quickly check that we aren't accidentally using a name in both
|
||||
# template and filter tests.
|
||||
overlapping_names = [name for name in filter_tests if name in
|
||||
template_tests]
|
||||
assert not overlapping_names, 'Duplicate test name(s): %s' % ', '.join(overlapping_names)
|
||||
|
||||
template_tests.update(filter_tests)
|
||||
|
||||
# Register our custom template loader.
|
||||
def test_template_loader(template_name, template_dirs=None):
|
||||
"A custom template loader that loads the unit-test templates."
|
||||
try:
|
||||
return (template_tests[template_name][0] , "test:%s" % template_name)
|
||||
except KeyError:
|
||||
raise template.TemplateDoesNotExist, template_name
|
||||
|
||||
old_template_loaders = loader.template_source_loaders
|
||||
loader.template_source_loaders = [test_template_loader]
|
||||
|
||||
failures = []
|
||||
tests = template_tests.items()
|
||||
tests.sort()
|
||||
|
||||
# Turn TEMPLATE_DEBUG off, because tests assume that.
|
||||
old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
|
||||
|
||||
# Set TEMPLATE_STRING_IF_INVALID to a known string
|
||||
old_invalid = settings.TEMPLATE_STRING_IF_INVALID
|
||||
expected_invalid_str = 'INVALID'
|
||||
|
||||
for name, vals in tests:
|
||||
if isinstance(vals[2], tuple):
|
||||
normal_string_result = vals[2][0]
|
||||
invalid_string_result = vals[2][1]
|
||||
if '%s' in invalid_string_result:
|
||||
expected_invalid_str = 'INVALID %s'
|
||||
invalid_string_result = invalid_string_result % vals[2][2]
|
||||
template.invalid_var_format_string = True
|
||||
else:
|
||||
normal_string_result = vals[2]
|
||||
invalid_string_result = vals[2]
|
||||
|
||||
if 'LANGUAGE_CODE' in vals[1]:
|
||||
activate(vals[1]['LANGUAGE_CODE'])
|
||||
else:
|
||||
activate('en-us')
|
||||
|
||||
for invalid_str, result in [('', normal_string_result),
|
||||
(expected_invalid_str, invalid_string_result)]:
|
||||
settings.TEMPLATE_STRING_IF_INVALID = invalid_str
|
||||
try:
|
||||
test_template = loader.get_template(name)
|
||||
output = self.render(test_template, vals)
|
||||
except Exception, e:
|
||||
if e.__class__ != result:
|
||||
failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))
|
||||
continue
|
||||
if output != result:
|
||||
failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))
|
||||
|
||||
if 'LANGUAGE_CODE' in vals[1]:
|
||||
deactivate()
|
||||
|
||||
if template.invalid_var_format_string:
|
||||
expected_invalid_str = 'INVALID'
|
||||
template.invalid_var_format_string = False
|
||||
|
||||
loader.template_source_loaders = old_template_loaders
|
||||
deactivate()
|
||||
settings.TEMPLATE_DEBUG = old_td
|
||||
settings.TEMPLATE_STRING_IF_INVALID = old_invalid
|
||||
|
||||
self.assertEqual(failures, [], '\n'.join(failures))
|
||||
|
||||
def render(self, test_template, vals):
|
||||
return test_template.render(template.Context(vals[1]))
|
||||
|
||||
def get_template_tests(self):
|
||||
# SYNTAX --
|
||||
# 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
|
||||
TEMPLATE_TESTS = {
|
||||
|
||||
### BASIC SYNTAX ##########################################################
|
||||
return {
|
||||
### BASIC SYNTAX ################################################
|
||||
|
||||
# Plain text should go through the template parser untouched
|
||||
'basic-syntax01': ("something cool", {}, "something cool"),
|
||||
|
||||
# Variables should be replaced with their value in the current context
|
||||
# Variables should be replaced with their value in the current
|
||||
# context
|
||||
'basic-syntax02': ("{{ headline }}", {'headline':'Success'}, "Success"),
|
||||
|
||||
# More than one replacement variable is allowed in a template
|
||||
|
@ -240,7 +319,8 @@ class Templates(unittest.TestCase):
|
|||
'filter-syntax09': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"),
|
||||
|
||||
# Escaped string as argument
|
||||
'filter-syntax10': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote" hah'),
|
||||
'filter-syntax10': (r'{{ var|default_if_none:" endquote\" hah" }}',
|
||||
{"var": None}, ' endquote" hah'),
|
||||
|
||||
# Variable as argument
|
||||
'filter-syntax11': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'),
|
||||
|
@ -760,38 +840,6 @@ class Templates(unittest.TestCase):
|
|||
# 'now03' : ('{% now "j \"n\" Y"%}', {}, str(datetime.now().day) + '"' + str(datetime.now().month) + '"' + str(datetime.now().year)),
|
||||
# 'now04' : ('{% now "j \nn\n Y"%}', {}, str(datetime.now().day) + '\n' + str(datetime.now().month) + '\n' + str(datetime.now().year))
|
||||
|
||||
### TIMESINCE TAG ##################################################
|
||||
# Default compare with datetime.now()
|
||||
'timesince01' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'),
|
||||
'timesince02' : ('{{ a|timesince }}', {'a':(datetime.now() - timedelta(days=1, minutes = 1))}, '1 day'),
|
||||
'timesince03' : ('{{ a|timesince }}', {'a':(datetime.now() -
|
||||
timedelta(hours=1, minutes=25, seconds = 10))}, '1 hour, 25 minutes'),
|
||||
|
||||
# Compare to a given parameter
|
||||
'timesince04' : ('{{ a|timesince:b }}', {'a':NOW + timedelta(days=2), 'b':NOW + timedelta(days=1)}, '1 day'),
|
||||
'timesince05' : ('{{ a|timesince:b }}', {'a':NOW + timedelta(days=2, minutes=1), 'b':NOW + timedelta(days=2)}, '1 minute'),
|
||||
|
||||
# Check that timezone is respected
|
||||
'timesince06' : ('{{ a|timesince:b }}', {'a':NOW_tz + timedelta(hours=8), 'b':NOW_tz}, '8 hours'),
|
||||
|
||||
# Check times in the future.
|
||||
'timesince07' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(minutes=1, seconds=10)}, '0 minutes'),
|
||||
'timesince08' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(days=1, minutes=1)}, '0 minutes'),
|
||||
|
||||
### TIMEUNTIL TAG ##################################################
|
||||
# Default compare with datetime.now()
|
||||
'timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'),
|
||||
'timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'),
|
||||
'timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8 hours, 10 minutes'),
|
||||
|
||||
# Compare to a given parameter
|
||||
'timeuntil04' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=1), 'b':NOW - timedelta(days=2)}, '1 day'),
|
||||
'timeuntil05' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=2), 'b':NOW - timedelta(days=2, minutes=1)}, '1 minute'),
|
||||
|
||||
# Check times in the past.
|
||||
'timeuntil07' : ('{{ a|timeuntil }}', {'a':datetime.now() - timedelta(minutes=1, seconds=10)}, '0 minutes'),
|
||||
'timeuntil08' : ('{{ a|timeuntil }}', {'a':datetime.now() - timedelta(days=1, minutes=1)}, '0 minutes'),
|
||||
|
||||
### URL TAG ########################################################
|
||||
# Successes
|
||||
'url01' : ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),
|
||||
|
@ -819,72 +867,31 @@ class Templates(unittest.TestCase):
|
|||
'cache08' : ('{% load cache %}{% cache %}{% endcache %}', {}, template.TemplateSyntaxError),
|
||||
'cache09' : ('{% load cache %}{% cache 1 %}{% endcache %}', {}, template.TemplateSyntaxError),
|
||||
'cache10' : ('{% load cache %}{% cache foo bar %}{% endcache %}', {}, template.TemplateSyntaxError),
|
||||
|
||||
### AUTOESCAPE TAG ##############################################
|
||||
'autoescape-tag01': ("{% autoescape off %}hello{% endautoescape %}", {}, "hello"),
|
||||
'autoescape-tag02': ("{% autoescape off %}{{ first }}{% endautoescape %}", {"first": "<b>hello</b>"}, "<b>hello</b>"),
|
||||
'autoescape-tag03': ("{% autoescape on %}{{ first }}{% endautoescape %}", {"first": "<b>hello</b>"}, "<b>hello</b>"),
|
||||
|
||||
# Autoescape disabling and enabling nest in a predictable way.
|
||||
'autoescape-tag04': ("{% autoescape off %}{{ first }} {% autoescape on%}{{ first }}{% endautoescape %}{% endautoescape %}", {"first": "<a>"}, "<a> <a>"),
|
||||
|
||||
'autoescape-tag05': ("{% autoescape on %}{{ first }}{% endautoescape %}", {"first": "<b>first</b>"}, "<b>first</b>"),
|
||||
|
||||
# Strings (ASCII or unicode) already marked as "safe" are not
|
||||
# auto-escaped
|
||||
'autoescape-tag06': ("{{ first }}", {"first": mark_safe("<b>first</b>")}, "<b>first</b>"),
|
||||
'autoescape-tag07': ("{% autoescape on %}{{ first }}{% endautoescape %}", {"first": mark_safe(u"<b>Apple</b>")}, u"<b>Apple</b>"),
|
||||
|
||||
# String arguments to filters, if used in the result, are escaped,
|
||||
# too.
|
||||
'basic-syntax08': (r'{% autoescape on %}{{ var|default_if_none:" endquote\" hah" }}{% endautoescape %}', {"var": None}, ' endquote" hah'),
|
||||
|
||||
# The "safe" and "escape" filters cannot work due to internal
|
||||
# implementation details (fortunately, the (no)autoescape block
|
||||
# tags can be used in those cases)
|
||||
'autoescape-filtertag01': ("{{ first }}{% filter safe %}{{ first }} x<y{% endfilter %}", {"first": "<a>"}, template.TemplateSyntaxError),
|
||||
}
|
||||
|
||||
# Register our custom template loader.
|
||||
def test_template_loader(template_name, template_dirs=None):
|
||||
"A custom template loader that loads the unit-test templates."
|
||||
try:
|
||||
return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
|
||||
except KeyError:
|
||||
raise template.TemplateDoesNotExist, template_name
|
||||
|
||||
old_template_loaders = loader.template_source_loaders
|
||||
loader.template_source_loaders = [test_template_loader]
|
||||
|
||||
failures = []
|
||||
tests = TEMPLATE_TESTS.items()
|
||||
tests.sort()
|
||||
|
||||
# Turn TEMPLATE_DEBUG off, because tests assume that.
|
||||
old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
|
||||
|
||||
# Set TEMPLATE_STRING_IF_INVALID to a known string
|
||||
old_invalid = settings.TEMPLATE_STRING_IF_INVALID
|
||||
expected_invalid_str = 'INVALID'
|
||||
|
||||
for name, vals in tests:
|
||||
if isinstance(vals[2], tuple):
|
||||
normal_string_result = vals[2][0]
|
||||
invalid_string_result = vals[2][1]
|
||||
if '%s' in invalid_string_result:
|
||||
expected_invalid_str = 'INVALID %s'
|
||||
invalid_string_result = invalid_string_result % vals[2][2]
|
||||
template.invalid_var_format_string = True
|
||||
else:
|
||||
normal_string_result = vals[2]
|
||||
invalid_string_result = vals[2]
|
||||
|
||||
if 'LANGUAGE_CODE' in vals[1]:
|
||||
activate(vals[1]['LANGUAGE_CODE'])
|
||||
else:
|
||||
activate('en-us')
|
||||
|
||||
for invalid_str, result in [('', normal_string_result),
|
||||
(expected_invalid_str, invalid_string_result)]:
|
||||
settings.TEMPLATE_STRING_IF_INVALID = invalid_str
|
||||
try:
|
||||
output = loader.get_template(name).render(template.Context(vals[1]))
|
||||
except Exception, e:
|
||||
if e.__class__ != result:
|
||||
failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))
|
||||
continue
|
||||
if output != result:
|
||||
failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))
|
||||
|
||||
if 'LANGUAGE_CODE' in vals[1]:
|
||||
deactivate()
|
||||
|
||||
if template.invalid_var_format_string:
|
||||
expected_invalid_str = 'INVALID'
|
||||
template.invalid_var_format_string = False
|
||||
|
||||
loader.template_source_loaders = old_template_loaders
|
||||
deactivate()
|
||||
settings.TEMPLATE_DEBUG = old_td
|
||||
settings.TEMPLATE_STRING_IF_INVALID = old_invalid
|
||||
|
||||
self.assertEqual(failures, [], '\n'.join(failures))
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
Loading…
Reference in New Issue