Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Implemented auto-escaping of variable output in templates. Fully cont…

…rollable 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
  • Loading branch information...
commit 356662cf74c99fac90afb0f5e6aac8d2d573e62a 1 parent babfe78
@malcolmt malcolmt authored
Showing with 1,202 additions and 322 deletions.
  1. +4 −2 django/contrib/admin/filterspecs.py
  2. +2 −1  django/contrib/admin/models.py
  3. +1 −1  django/contrib/admin/templates/admin/base_site.html
  4. +2 −2 django/contrib/admin/templates/admin/change_form.html
  5. +2 −2 django/contrib/admin/templates/admin/date_hierarchy.html
  6. +2 −2 django/contrib/admin/templates/admin/delete_confirmation.html
  7. +1 −1  django/contrib/admin/templates/admin/edit_inline_stacked.html
  8. +2 −2 django/contrib/admin/templates/admin/edit_inline_tabular.html
  9. +3 −3 django/contrib/admin/templates/admin/index.html
  10. +1 −1  django/contrib/admin/templates/admin/invalid_setup.html
  11. +3 −3 django/contrib/admin/templates/admin/object_history.html
  12. +1 −1  django/contrib/admin/templates/admin/pagination.html
  13. +4 −4 django/contrib/admin/templates/admin_doc/model_detail.html
  14. +1 −1  django/contrib/admin/templates/widget/foreign.html
  15. +1 −1  django/contrib/admin/templates/widget/one_to_one.html
  16. +9 −8 django/contrib/admin/templatetags/admin_list.py
  17. +11 −6 django/contrib/admin/templatetags/admin_modify.py
  18. +2 −1  django/contrib/admin/templatetags/adminapplist.py
  19. +2 −1  django/contrib/admin/utils.py
  20. +2 −1  django/contrib/admin/views/decorators.py
  21. +2 −1  django/contrib/admin/views/doc.py
  22. +17 −12 django/contrib/admin/views/main.py
  23. +4 −3 django/contrib/csrf/middleware.py
  24. +7 −6 django/contrib/databrowse/datastructures.py
  25. +7 −5 django/contrib/databrowse/plugins/calendars.py
  26. +6 −4 django/contrib/databrowse/plugins/fieldchoices.py
  27. +2 −1  django/contrib/databrowse/sites.py
  28. +8 −0 django/contrib/flatpages/views.py
  29. +4 −0 django/contrib/humanize/templatetags/humanize.py
  30. +7 −3 django/contrib/markup/templatetags/markup.py
  31. +4 −2 django/contrib/markup/tests.py
  32. +2 −1  django/contrib/sitemaps/templates/sitemap.xml
  33. +2 −1  django/contrib/sitemaps/templates/sitemap_index.xml
  34. +10 −5 django/newforms/forms.py
  35. +6 −2 django/newforms/util.py
  36. +26 −14 django/newforms/widgets.py
  37. +22 −21 django/oldforms/__init__.py
  38. +45 −25 django/template/__init__.py
  39. +4 −1 django/template/context.py
  40. +140 −33 django/template/defaultfilters.py
  41. +36 −1 django/template/defaulttags.py
  42. +5 −1 django/utils/encoding.py
  43. +26 −7 django/utils/html.py
  44. +124 −0 django/utils/safestring.py
  45. +15 −15 django/views/debug.py
  46. +148 −1 docs/templates.txt
  47. +134 −8 docs/templates_python.txt
  48. +2 −2 tests/regressiontests/defaultfilters/tests.py
  49. +1 −1  tests/regressiontests/forms/forms.py
  50. +1 −1  tests/regressiontests/forms/tests.py
  51. +2 −1  tests/regressiontests/humanize/tests.py
  52. +220 −0 tests/regressiontests/templates/filters.py
  53. +107 −100 tests/regressiontests/templates/tests.py
View
6 django/contrib/admin/filterspecs.py
@@ -9,6 +9,8 @@
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 @@ def title(self):
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 @@ def output(self, cl):
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):
View
3  django/contrib/admin/models.py
@@ -3,6 +3,7 @@
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 @@ def get_admin_url(self):
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))
View
2  django/contrib/admin/templates/admin/base_site.html
@@ -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>
View
4 django/contrib/admin/templates/admin/change_form.html
@@ -10,8 +10,8 @@
{% block breadcrumbs %}{% if not is_popup %}
<div class="breadcrumbs">
<a href="../../../">{% trans "Home" %}</a> &rsaquo;
- <a href="../">{{ opts.verbose_name_plural|capfirst|escape }}</a> &rsaquo;
- {% if add %}{% trans "Add" %} {{ opts.verbose_name|escape }}{% else %}{{ original|truncatewords:"18"|escape }}{% endif %}
+ <a href="../">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
+ {% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
</div>
{% endif %}{% endblock %}
{% block content %}<div id="content-main">
View
4 django/contrib/admin/templates/admin/date_hierarchy.html
@@ -1,9 +1,9 @@
{% if show %}
<div class="xfull">
<ul class="toplinks">
-{% if back %}<li class="date-back"><a href="{{ back.link }}">&lsaquo; {{ back.title|escape }}</a></li>{% endif %}
+{% if back %}<li class="date-back"><a href="{{ back.link }}">&lsaquo; {{ 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>
View
4 django/contrib/admin/templates/admin/delete_confirmation.html
@@ -3,7 +3,7 @@
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="../../../../">{% trans "Home" %}</a> &rsaquo;
- <a href="../../">{{ opts.verbose_name_plural|capfirst|escape }}</a> &rsaquo;
+ <a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
<a href="../">{{ object|escape|truncatewords:"18" }}</a> &rsaquo;
{% 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 %}
View
2  django/contrib/admin/templates/admin/edit_inline_stacked.html
@@ -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 }}&nbsp;#{{ forloop.counter }}</h2>
+ <h2>{{ bound_related_object.relation.opts.verbose_name|capfirst }}&nbsp;#{{ 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 %}
View
4 django/contrib/admin/templates/admin/edit_inline_tabular.html
@@ -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>
View
6 django/contrib/admin/templates/admin/index.html
@@ -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 %}
View
2  django/contrib/admin/templates/admin/invalid_setup.html
@@ -1,7 +1,7 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {{ title|escape }}</div>{% endblock %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans 'Home' %}</a> &rsaquo; {{ title }}</div>{% endblock %}
{% block content %}
View
6 django/contrib/admin/templates/admin/object_history.html
@@ -1,7 +1,7 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
{% block breadcrumbs %}
-<div class="breadcrumbs"><a href="../../../../">{% trans 'Home' %}</a> &rsaquo; <a href="../../">{{ module_name|escape }}</a> &rsaquo; <a href="../">{{ object|escape|truncatewords:"18" }}</a> &rsaquo; {% trans 'History' %}</div>
+<div class="breadcrumbs"><a href="../../../../">{% trans 'Home' %}</a> &rsaquo; <a href="../../">{{ module_name }}</a> &rsaquo; <a href="../">{{ object|truncatewords:"18" }}</a> &rsaquo; {% 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>
View
2  django/contrib/admin/templates/admin/pagination.html
@@ -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 %}&nbsp;&nbsp;<a href="{{ show_all_url }}" class="showall">{% trans 'Show all' %}</a>{% endif %}
</p>
View
8 django/contrib/admin/templates/admin_doc/model_detail.html
@@ -8,16 +8,16 @@
</style>
{% endblock %}
-{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> &rsaquo; <a href="../../">Documentation</a> &rsaquo; <a href="../">Models</a> &rsaquo; {{ name|escape }}</div>{% endblock %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Home</a> &rsaquo; <a href="../../">Documentation</a> &rsaquo; <a href="../">Models</a> &rsaquo; {{ 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">
View
2  django/contrib/admin/templates/widget/foreign.html
@@ -15,6 +15,6 @@
{{ bound_field.original_value }}
{% endif %}
{% if bound_field.raw_id_admin %}
- {% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14"|escape }}</strong>{% endif %}
+ {% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}
{% endif %}
{% endif %}
View
2  django/contrib/admin/templates/widget/one_to_one.html
@@ -1,2 +1,2 @@
{% if add %}{% include "widget/foreign.html" %}{% endif %}
-{% if change %}{% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14"|escape }}</strong>{% endif %}{% endif %}
+{% if change %}{% if bound_field.existing_display %}&nbsp;<strong>{{ bound_field.existing_display|truncatewords:"14" }}</strong>{% endif %}{% endif %}
View
17 django/contrib/admin/templatetags/admin_list.py
@@ -4,8 +4,9 @@
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))
View
17 django/contrib/admin/templatetags/admin_modify.py
@@ -3,6 +3,8 @@
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)
View
3  django/contrib/admin/templatetags/adminapplist.py
@@ -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 @@ def render(self, context):
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,
})
View
3  django/contrib/admin/utils.py
@@ -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
View
3  django/contrib/admin/views/decorators.py
@@ -4,6 +4,7 @@
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))
View
3  django/contrib/admin/views/doc.py
@@ -10,6 +10,7 @@
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)
View
29 django/contrib/admin/views/main.py
@@ -14,6 +14,7 @@
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 @@ def __init__(self, field, field_mapping, original):
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 @@ def get_query_string(self, new_params=None, remove=None):
del p[k]
elif v is not None:
p[k] = v
- return '?' + '&amp;'.join([u'%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20')
+ return mark_safe('?' + '&amp;'.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)
View
7 django/contrib/csrf/middleware.py
@@ -7,11 +7,12 @@
"""
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 @@ def process_response(self, request, response):
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)
View
13 django/contrib/databrowse/datastructures.py
@@ -8,6 +8,7 @@
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 @@ def model_databrowse(self):
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 @@ def choices(self):
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 @@ def __repr__(self):
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 @@ def urls(self):
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]
View
12 django/contrib/databrowse/plugins/calendars.py
@@ -5,8 +5,9 @@
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 @@ def model_index_html(self, request, model, site):
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
View
10 django/contrib/databrowse/plugins/fieldchoices.py
@@ -5,6 +5,7 @@
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 @@ def model_index_html(self, request, model, site):
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
View
3  django/contrib/databrowse/sites.py
@@ -2,6 +2,7 @@
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 @@ def root(self, request, url):
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,
View
8 django/contrib/flatpages/views.py
@@ -4,6 +4,7 @@
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,
})
View
4 django/contrib/humanize/templatetags/humanize.py
@@ -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):
View
10 django/contrib/markup/templatetags/markup.py
@@ -17,6 +17,7 @@
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)
View
6 django/contrib/markup/tests.py
@@ -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 @@ def test_textile(self):
<p>Paragraph 2 with &#8220;quotes&#8221; and <code>code</code></p>""")
else:
- self.assertEqual(rendered, textile_content)
+ self.assertEqual(rendered, escape(textile_content))
def test_markdown(self):
try:
View
3  django/contrib/sitemaps/templates/sitemap.xml
@@ -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 %}
View
3  django/contrib/sitemaps/templates/sitemap_index.xml
@@ -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 %}
View
15 django/newforms/forms.py
@@ -7,6 +7,7 @@
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 @@ def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_
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 @@ def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_
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 @@ def label_tag(self, contents=None, attrs=None):
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."
View
8 django/newforms/util.py
@@ -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 @@ def __unicode__(self):
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 @@ def __unicode__(self):
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''
View
40 django/newforms/widgets.py
@@ -14,6 +14,7 @@
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 @@ def __init__(self, attrs=None, choices=()):
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 @@ def render(self, name, value, attrs=None):
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 @@ def render(self, name, value, attrs=None):
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 @@ def render(self, name, value, attrs=None, choices=()):
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 @@ def render(self, name, value, attrs=None, choices=()):
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 @@ def __init__(self, name, value, attrs, choice, index):
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 @@ def tag(self):
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 @@ def __unicode__(self):
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 @@ def render(self, name, value, attrs=None, choices=()):
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 @@ def render(self, name, value, attrs=None, choices=()):
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 @@ def decompress(self, value):
if value:
return [value.date(), value.time().replace(microsecond=0)]
return [None, None]
+
View
43 django/oldforms/__init__.py
@@ -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 @@ def errors(self):
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 @@ def has_errors(self):
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 @@ def render(self, data):
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 @@ def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_li
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 @@ def __init__(self, field_name, is_required=False, validator_list=None, max_lengt
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 @@ def render(self, data):
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 @@ def render(self, data):
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 @@ def __unicode__(self):
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 @@ def __len__(self):
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 @@ def render(self, data):
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 @@ def render(self, data):
(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 @@ def isNonEmptyFile(self, field_data, all_data):
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:
View
70 django/template/__init__.py
@@ -57,6 +57,8 @@
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 @@ def resolve(self, context, ignore_failures=False):
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 @@ def __init__(self, var):
# 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 @@ def resolve(self, context):
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 @@ def _resolve_lookup(self, context):
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 @@ def __repr__(self):
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 @@ def render(self, context):
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__
View
5 django/template/context.py
@@ -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 @@ def __init__(self, request, dict=None, processors=None):
processors = tuple(processors)
for processor in get_standard_processors() + processors:
self.update(processor(request))
+
View
173 django/template/defaultfilters.py
@@ -7,6 +7,7 @@
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 _dec(*args, **kwargs):
# 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 _dec(*args, **kwargs):
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 ``&amp;`` 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 @@ def cut(value, arg):
###################
def escape(value):
- "Escapes a string's HTML"
+ """
+ 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 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 escape(value)
+ return mark_safe(escape(value))
escape = stringfilter(escape)
+force_escape.is_safe = True
-def linebreaks(value):
+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.