summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorMalcolm Tredinnick <malcolm.tredinnick@gmail.com>2007-11-14 12:58:53 +0000
committerMalcolm Tredinnick <malcolm.tredinnick@gmail.com>2007-11-14 12:58:53 +0000
commit356662cf74c99fac90afb0f5e6aac8d2d573e62a (patch)
tree6ee45dfcb9c91e1184dcc73751e0b856892451ed /django
parentbabfe78494028415b0e5f74ec2ca9b66506e8d34 (diff)
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
Diffstat (limited to 'django')
-rw-r--r--django/contrib/admin/filterspecs.py6
-rw-r--r--django/contrib/admin/models.py3
-rw-r--r--django/contrib/admin/templates/admin/base_site.html2
-rw-r--r--django/contrib/admin/templates/admin/change_form.html4
-rw-r--r--django/contrib/admin/templates/admin/date_hierarchy.html4
-rw-r--r--django/contrib/admin/templates/admin/delete_confirmation.html4
-rw-r--r--django/contrib/admin/templates/admin/edit_inline_stacked.html2
-rw-r--r--django/contrib/admin/templates/admin/edit_inline_tabular.html4
-rw-r--r--django/contrib/admin/templates/admin/index.html6
-rw-r--r--django/contrib/admin/templates/admin/invalid_setup.html2
-rw-r--r--django/contrib/admin/templates/admin/object_history.html6
-rw-r--r--django/contrib/admin/templates/admin/pagination.html2
-rw-r--r--django/contrib/admin/templates/admin_doc/model_detail.html8
-rw-r--r--django/contrib/admin/templates/widget/foreign.html2
-rw-r--r--django/contrib/admin/templates/widget/one_to_one.html2
-rw-r--r--django/contrib/admin/templatetags/admin_list.py17
-rw-r--r--django/contrib/admin/templatetags/admin_modify.py17
-rw-r--r--django/contrib/admin/templatetags/adminapplist.py3
-rw-r--r--django/contrib/admin/utils.py3
-rw-r--r--django/contrib/admin/views/decorators.py3
-rw-r--r--django/contrib/admin/views/doc.py3
-rw-r--r--django/contrib/admin/views/main.py29
-rw-r--r--django/contrib/csrf/middleware.py7
-rw-r--r--django/contrib/databrowse/datastructures.py13
-rw-r--r--django/contrib/databrowse/plugins/calendars.py12
-rw-r--r--django/contrib/databrowse/plugins/fieldchoices.py10
-rw-r--r--django/contrib/databrowse/sites.py3
-rw-r--r--django/contrib/flatpages/views.py8
-rw-r--r--django/contrib/humanize/templatetags/humanize.py4
-rw-r--r--django/contrib/markup/templatetags/markup.py10
-rw-r--r--django/contrib/markup/tests.py6
-rw-r--r--django/contrib/sitemaps/templates/sitemap.xml3
-rw-r--r--django/contrib/sitemaps/templates/sitemap_index.xml3
-rw-r--r--django/newforms/forms.py15
-rw-r--r--django/newforms/util.py8
-rw-r--r--django/newforms/widgets.py40
-rw-r--r--django/oldforms/__init__.py43
-rw-r--r--django/template/__init__.py70
-rw-r--r--django/template/context.py5
-rw-r--r--django/template/defaultfilters.py173
-rw-r--r--django/template/defaulttags.py37
-rw-r--r--django/utils/encoding.py6
-rw-r--r--django/utils/html.py33
-rw-r--r--django/utils/safestring.py124
-rw-r--r--django/views/debug.py30
45 files changed, 587 insertions, 208 deletions
diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py
index 5aa950cbf4..a4f92c986a 100644
--- a/django/contrib/admin/filterspecs.py
+++ b/django/contrib/admin/filterspecs.py
@@ -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):
diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py
index ffa2740d5e..23c8661336 100644
--- a/django/contrib/admin/models.py
+++ b/django/contrib/admin/models.py
@@ -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))
diff --git a/django/contrib/admin/templates/admin/base_site.html b/django/contrib/admin/templates/admin/base_site.html
index 2bc7310873..b867bd29bd 100644
--- a/django/contrib/admin/templates/admin/base_site.html
+++ b/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>
diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html
index 49e709caf6..2e10ec19fd 100644
--- a/django/contrib/admin/templates/admin/change_form.html
+++ b/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">
diff --git a/django/contrib/admin/templates/admin/date_hierarchy.html b/django/contrib/admin/templates/admin/date_hierarchy.html
index d2d69616c7..005851051c 100644
--- a/django/contrib/admin/templates/admin/date_hierarchy.html
+++ b/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>
diff --git a/django/contrib/admin/templates/admin/delete_confirmation.html b/django/contrib/admin/templates/admin/delete_confirmation.html
index ac9634a9e9..f2126882fa 100644
--- a/django/contrib/admin/templates/admin/delete_confirmation.html
+++ b/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 %}
diff --git a/django/contrib/admin/templates/admin/edit_inline_stacked.html b/django/contrib/admin/templates/admin/edit_inline_stacked.html
index 48ecc698d9..45aa0a4f58 100644
--- a/django/contrib/admin/templates/admin/edit_inline_stacked.html
+++ b/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 %}
diff --git a/django/contrib/admin/templates/admin/edit_inline_tabular.html b/django/contrib/admin/templates/admin/edit_inline_tabular.html
index 3d059c8b3d..e2dbcbaed2 100644
--- a/django/contrib/admin/templates/admin/edit_inline_tabular.html
+++ b/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>
diff --git a/django/contrib/admin/templates/admin/index.html b/django/contrib/admin/templates/admin/index.html
index 3626c89cb2..2f406a1754 100644
--- a/django/contrib/admin/templates/admin/index.html
+++ b/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 %}
diff --git a/django/contrib/admin/templates/admin/invalid_setup.html b/django/contrib/admin/templates/admin/invalid_setup.html
index 1d7d61f0d2..1fa0d32358 100644
--- a/django/contrib/admin/templates/admin/invalid_setup.html
+++ b/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 %}
diff --git a/django/contrib/admin/templates/admin/object_history.html b/django/contrib/admin/templates/admin/object_history.html
index 34529f69db..00e47259bf 100644
--- a/django/contrib/admin/templates/admin/object_history.html
+++ b/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>
diff --git a/django/contrib/admin/templates/admin/pagination.html b/django/contrib/admin/templates/admin/pagination.html
index e1c09b2932..0640a4690b 100644
--- a/django/contrib/admin/templates/admin/pagination.html
+++ b/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>
diff --git a/django/contrib/admin/templates/admin_doc/model_detail.html b/django/contrib/admin/templates/admin_doc/model_detail.html
index e19e31d891..81bf87db15 100644
--- a/django/contrib/admin/templates/admin_doc/model_detail.html
+++ b/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">
diff --git a/django/contrib/admin/templates/widget/foreign.html b/django/contrib/admin/templates/widget/foreign.html
index 301f5214db..6b43d044bd 100644
--- a/django/contrib/admin/templates/widget/foreign.html
+++ b/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 %}
diff --git a/django/contrib/admin/templates/widget/one_to_one.html b/django/contrib/admin/templates/widget/one_to_one.html
index efd0117bf2..a79a12314f 100644
--- a/django/contrib/admin/templates/widget/one_to_one.html
+++ b/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 %}
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index 0e96f28191..b23013becd 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -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))
diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py
index 9ce6aa77ae..e5f31ba723 100644
--- a/django/contrib/admin/templatetags/admin_modify.py
+++ b/django/contrib/admin/templatetags/admin_modify.py
@@ -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)
diff --git a/django/contrib/admin/templatetags/adminapplist.py b/django/contrib/admin/templatetags/adminapplist.py
index 673b77a1e0..bf394e2099 100644
--- a/django/contrib/admin/templatetags/adminapplist.py
+++ b/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 @@ 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,
})
diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py
index 9adf09b6a5..4a45a622b2 100644
--- a/django/contrib/admin/utils.py
+++ b/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
diff --git a/django/contrib/admin/views/decorators.py b/django/contrib/admin/views/decorators.py
index 9ae0170b54..4f6662c970 100644
--- a/django/contrib/admin/views/decorators.py
+++ b/django/contrib/admin/views/decorators.py
@@ -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))
diff --git a/django/contrib/admin/views/doc.py b/django/contrib/admin/views/doc.py
index 2ebd0cd282..44a27d6cc3 100644
--- a/django/contrib/admin/views/doc.py
+++ b/django/contrib/admin/views/doc.py
@@ -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)
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 7fa870e222..fa314803a3 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -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 '?' + '&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)
diff --git a/django/contrib/csrf/middleware.py b/django/contrib/csrf/middleware.py
index 1ef0e6cefd..1a75a5d6ab 100644
--- a/django/contrib/csrf/middleware.py
+++ b/django/contrib/csrf/middleware.py
@@ -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)
diff --git a/django/contrib/databrowse/datastructures.py b/django/contrib/databrowse/datastructures.py
index 0a26db18b1..4b0947eb34 100644
--- a/django/contrib/databrowse/datastructures.py
+++ b/django/contrib/databrowse/datastructures.py
@@ -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]
diff --git a/django/contrib/databrowse/plugins/calendars.py b/django/contrib/databrowse/plugins/calendars.py
index 1b1a197290..5d5a020e6d 100644
--- a/django/contrib/databrowse/plugins/calendars.py
+++ b/django/contrib/databrowse/plugins/calendars.py
@@ -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
diff --git a/django/contrib/databrowse/plugins/fieldchoices.py b/django/contrib/databrowse/plugins/fieldchoices.py
index a5dd07c639..dea6bac700 100644
--- a/django/contrib/databrowse/plugins/fieldchoices.py
+++ b/django/contrib/databrowse/plugins/fieldchoices.py
@@ -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
diff --git a/django/contrib/databrowse/sites.py b/django/contrib/databrowse/sites.py
index c07ec2129b..5d8c1c8b49 100644
--- a/django/contrib/databrowse/sites.py
+++ b/django/contrib/databrowse/sites.py
@@ -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,
diff --git a/django/contrib/flatpages/views.py b/django/contrib/flatpages/views.py
index f386a52101..1e451bdecc 100644
--- a/django/contrib/flatpages/views.py
+++ b/django/contrib/flatpages/views.py
@@ -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,
})
diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py
index 19591606f9..4aa5a32e3f 100644
--- a/django/contrib/humanize/templatetags/humanize.py
+++ b/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):
diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py
index 5d1f0ff1fb..13708fd26d 100644
--- a/django/contrib/markup/templatetags/markup.py
+++ b/django/contrib/markup/templatetags/markup.py
@@ -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)
diff --git a/django/contrib/markup/tests.py b/django/contrib/markup/tests.py
index bd3f52b9dd..6e7beaeb2f 100644
--- a/django/contrib/markup/tests.py
+++ b/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 @@ Paragraph 2 with "quotes" and @code@"""
<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:
diff --git a/django/contrib/sitemaps/templates/sitemap.xml b/django/contrib/sitemaps/templates/sitemap.xml
index 16d9a0bbe0..c0d529d62e 100644
--- a/django/contrib/sitemaps/templates/sitemap.xml
+++ b/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 %}
diff --git a/django/contrib/sitemaps/templates/sitemap_index.xml b/django/contrib/sitemaps/templates/sitemap_index.xml
index a2bcce85dc..497ae8bb81 100644
--- a/django/contrib/sitemaps/templates/sitemap_index.xml
+++ b/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 %}
diff --git a/django/newforms/forms.py b/django/newforms/forms.py
index 3952f6a8fb..556c00a777 100644
--- a/django/newforms/forms.py
+++ b/django/newforms/forms.py
@@ -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."
diff --git a/django/newforms/util.py b/django/newforms/util.py
index 490fbcaed0..b3edf41adf 100644
--- a/django/newforms/util.py
+++ b/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 @@ 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''
diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py
index 076845427d..350b878af9 100644
--- a/django/newforms/widgets.py
+++ b/django/newforms/widgets.py
@@ -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]
+
diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py
index 5f7b9fc2ad..fc8727185f 100644
--- a/django/oldforms/__init__.py
+++ b/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 @@ 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:
diff --git a/django/template/__init__.py b/django/template/__init__.py
index 1cfd85be06..1fd3171c12 100644
--- a/django/template/__init__.py
+++ b/django/template/__init__.py
@@ -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__
diff --git a/django/template/context.py b/django/template/context.py
index 51cd88b7e9..017d2d84b1 100644
--- a/django/template/context.py
+++ b/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 @@ class RequestContext(Context):
processors = tuple(processors)
for processor in get_standard_processors() + processors:
self.update(processor(request))
+
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index d2d4b9e508..7d4a72efb3 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -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 ``&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 @@ cut = stringfilter(cut)
###################
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.
+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)
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index d3c18897eb..d91e30bbb5 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -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 ''
@@ -393,6 +409,22 @@ class WithNode(Node):
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):
"""
Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
@@ -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)
diff --git a/django/utils/encoding.py b/django/utils/encoding.py
index 4bda9caa50..2ab0db7432 100644
--- a/django/utils/encoding.py
+++ b/django/utils/encoding.py
@@ -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
diff --git a/django/utils/html.py b/django/utils/html.py
index ebd04d1b3c..8eeaa66330 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -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('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;')
+ return mark_safe(force_unicode(html).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;'))
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('&amp;', 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'))):
diff --git a/django/utils/safestring.py b/django/utils/safestring.py
new file mode 100644
index 0000000000..c7234af0f3
--- /dev/null
+++ b/django/utils/safestring.py
@@ -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))
+
diff --git a/django/views/debug.py b/django/views/debug.py
index 717de1eb34..7c45af230a 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -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 %}
- &nbsp;&nbsp;{{ frame.lineno }}. {{ frame.context_line|escape }}<br/>
+ &nbsp;&nbsp;{{ frame.lineno }}. {{ frame.context_line }}<br/>
{% endif %}
{% endfor %}<br/>
&nbsp;&nbsp;{{ 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>