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