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.db import models
|
||||||
from django.utils.encoding import smart_unicode, iri_to_uri
|
from django.utils.encoding import smart_unicode, iri_to_uri
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
from django.utils.html import escape
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
class FilterSpec(object):
|
class FilterSpec(object):
|
||||||
@ -39,7 +41,7 @@ class FilterSpec(object):
|
|||||||
def output(self, cl):
|
def output(self, cl):
|
||||||
t = []
|
t = []
|
||||||
if self.has_output():
|
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):
|
for choice in self.choices(cl):
|
||||||
t.append(u'<li%s><a href="%s">%s</a></li>\n' % \
|
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']),
|
iri_to_uri(choice['query_string']),
|
||||||
choice['display']))
|
choice['display']))
|
||||||
t.append('</ul>\n\n')
|
t.append('</ul>\n\n')
|
||||||
return "".join(t)
|
return mark_safe("".join(t))
|
||||||
|
|
||||||
class RelatedFilterSpec(FilterSpec):
|
class RelatedFilterSpec(FilterSpec):
|
||||||
def __init__(self, f, request, params, model, model_admin):
|
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.contrib.auth.models import User
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.encoding import smart_unicode
|
from django.utils.encoding import smart_unicode
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
ADDITION = 1
|
ADDITION = 1
|
||||||
CHANGE = 2
|
CHANGE = 2
|
||||||
@ -49,4 +50,4 @@ class LogEntry(models.Model):
|
|||||||
Returns the admin URL to edit the object represented by this log entry.
|
Returns the admin URL to edit the object represented by this log entry.
|
||||||
This is relative to the Django admin index page.
|
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.http import Http404, HttpResponse, HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404, render_to_response
|
from django.shortcuts import get_object_or_404, render_to_response
|
||||||
from django.utils.html import escape
|
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.text import capfirst, get_text_list
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.encoding import force_unicode
|
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_file_field': True, # FIXME - this should check if form or formsets have a FileField,
|
||||||
'has_absolute_url': hasattr(model, 'get_absolute_url'),
|
'has_absolute_url': hasattr(model, 'get_absolute_url'),
|
||||||
'ordered_objects': ordered_objects,
|
'ordered_objects': ordered_objects,
|
||||||
'form_url': form_url,
|
'form_url': mark_safe(form_url),
|
||||||
'opts': opts,
|
'opts': opts,
|
||||||
'content_type_id': ContentType.objects.get_for_model(model).id,
|
'content_type_id': ContentType.objects.get_for_model(model).id,
|
||||||
'save_on_top': self.save_on_top,
|
'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
|
# Populate deleted_objects, a data structure of all related objects that
|
||||||
# will also be deleted.
|
# 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()
|
perms_needed = sets.Set()
|
||||||
get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1, self.admin_site)
|
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.contrib.auth import authenticate, login
|
||||||
from django.db.models.base import ModelBase
|
from django.db.models.base import ModelBase
|
||||||
from django.shortcuts import render_to_response
|
from django.shortcuts import render_to_response
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.translation import ugettext_lazy, ugettext as _
|
from django.utils.translation import ugettext_lazy, ugettext as _
|
||||||
import base64
|
import base64
|
||||||
@ -272,7 +273,7 @@ class AdminSite(object):
|
|||||||
if True in perms.values():
|
if True in perms.values():
|
||||||
model_dict = {
|
model_dict = {
|
||||||
'name': capfirst(model._meta.verbose_name_plural),
|
'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,
|
'perms': perms,
|
||||||
}
|
}
|
||||||
if app_label in app_dict:
|
if app_label in app_dict:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% extends "admin/base.html" %}
|
{% extends "admin/base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block title %}{{ title|escape }} | {% trans 'Django site admin' %}{% endblock %}
|
{% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %}
|
||||||
|
|
||||||
{% block branding %}
|
{% block branding %}
|
||||||
<h1 id="site-name">{% trans 'Django administration' %}</h1>
|
<h1 id="site-name">{% trans 'Django administration' %}</h1>
|
||||||
|
@ -17,8 +17,8 @@
|
|||||||
{% block breadcrumbs %}{% if not is_popup %}
|
{% block breadcrumbs %}{% if not is_popup %}
|
||||||
<div class="breadcrumbs">
|
<div class="breadcrumbs">
|
||||||
<a href="../../../">{% trans "Home" %}</a> ›
|
<a href="../../../">{% trans "Home" %}</a> ›
|
||||||
<a href="../">{{ opts.verbose_name_plural|capfirst|escape }}</a> ›
|
<a href="../">{{ opts.verbose_name_plural|capfirst }}</a> ›
|
||||||
{% if add %}{% trans "Add" %} {{ opts.verbose_name|escape }}{% else %}{{ original|truncatewords:"18"|escape }}{% endif %}
|
{% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}{% endblock %}
|
{% endif %}{% endblock %}
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{% if show %}
|
{% if show %}
|
||||||
<div class="xfull">
|
<div class="xfull">
|
||||||
<ul class="toplinks">
|
<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 %}
|
{% 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 %}
|
{% endfor %}
|
||||||
</ul><br class="clear" />
|
</ul><br class="clear" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
{% block breadcrumbs %}
|
{% block breadcrumbs %}
|
||||||
<div class="breadcrumbs">
|
<div class="breadcrumbs">
|
||||||
<a href="../../../../">{% trans "Home" %}</a> ›
|
<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> ›
|
<a href="../">{{ object|escape|truncatewords:"18" }}</a> ›
|
||||||
{% trans 'Delete' %}
|
{% trans 'Delete' %}
|
||||||
</div>
|
</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>
|
<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>
|
<ul>
|
||||||
{% for obj in perms_lacking %}
|
{% for obj in perms_lacking %}
|
||||||
<li>{{ obj|escape }}</li>
|
<li>{{ obj }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -20,9 +20,9 @@
|
|||||||
{% for model in app.models %}
|
{% for model in app.models %}
|
||||||
<tr>
|
<tr>
|
||||||
{% if model.perms.change %}
|
{% 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 %}
|
{% else %}
|
||||||
<th scope="row">{{ model.name|escape }}</th>
|
<th scope="row">{{ model.name }}</th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if model.perms.add %}
|
{% if model.perms.add %}
|
||||||
@ -59,7 +59,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<ul class="actionlist">
|
<ul class="actionlist">
|
||||||
{% for entry in admin_log %}
|
{% 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 %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% extends "admin/base_site.html" %}
|
{% extends "admin/base_site.html" %}
|
||||||
{% load i18n %}
|
{% 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 %}
|
{% 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>
|
<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 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 %}
|
{% 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 %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@ -24,8 +24,8 @@
|
|||||||
{% for action in action_list %}
|
{% for action in action_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ action.action_time|date:_("DATE_WITH_TIME_FULL") }}</th>
|
<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.user.username }}{% if action.user.first_name %} ({{ action.user.first_name }} {{ action.user.last_name }}){% endif %}</td>
|
||||||
<td>{{ action.change_message|escape }}</td>
|
<td>{{ action.change_message }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -6,6 +6,6 @@
|
|||||||
{% paginator_number cl i %}
|
{% paginator_number cl i %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% 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 %}
|
{% if show_all_url %} <a href="{{ show_all_url }}" class="showall">{% trans 'Show all' %}</a>{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
@ -9,16 +9,16 @@
|
|||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% 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 %}
|
{% block content %}
|
||||||
<div id="content-main">
|
<div id="content-main">
|
||||||
<h1>{{ summary|escape }}</h1>
|
<h1>{{ summary }}</h1>
|
||||||
|
|
||||||
{% if description %}
|
{% if description %}
|
||||||
<p>{% filter escape|linebreaksbr %}{% trans description %}{% endfilter %}</p>
|
<p>{% filter linebreaksbr %}{% trans description %}{% endfilter %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="module">
|
<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.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import dateformat
|
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.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.translation import get_date_formats, get_partial_date_formats, ugettext as _
|
||||||
from django.utils.encoding import smart_unicode, smart_str, force_unicode
|
from django.utils.encoding import smart_unicode, smart_str, force_unicode
|
||||||
from django.template import Library
|
from django.template import Library
|
||||||
@ -19,9 +20,9 @@ def paginator_number(cl,i):
|
|||||||
if i == DOT:
|
if i == DOT:
|
||||||
return u'... '
|
return u'... '
|
||||||
elif i == cl.page_num:
|
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:
|
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)
|
paginator_number = register.simple_tag(paginator_number)
|
||||||
|
|
||||||
def pagination(cl):
|
def pagination(cl):
|
||||||
@ -117,7 +118,7 @@ def result_headers(cl):
|
|||||||
|
|
||||||
def _boolean_icon(field_val):
|
def _boolean_icon(field_val):
|
||||||
BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'}
|
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):
|
def items_for_result(cl, result):
|
||||||
first = True
|
first = True
|
||||||
@ -193,10 +194,10 @@ def items_for_result(cl, result):
|
|||||||
# Convert the pk to something that can be used in Javascript.
|
# Convert the pk to something that can be used in Javascript.
|
||||||
# Problem cases are long ints (23L) and non-ASCII strings.
|
# Problem cases are long ints (23L) and non-ASCII strings.
|
||||||
result_id = repr(force_unicode(getattr(result, pk)))[1:]
|
result_id = repr(force_unicode(getattr(result, pk)))[1:]
|
||||||
yield (u'<%s%s><a href="%s"%s>%s</a></%s>' % \
|
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 ''), result_repr, table_tag))
|
(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:
|
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):
|
def results(cl):
|
||||||
for res in cl.result_list:
|
for res in cl.result_list:
|
||||||
@ -220,7 +221,7 @@ def date_hierarchy(cl):
|
|||||||
day_lookup = cl.params.get(day_field)
|
day_lookup = cl.params.get(day_field)
|
||||||
year_month_format, month_day_format = get_partial_date_formats()
|
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:
|
if year_lookup and month_lookup and day_lookup:
|
||||||
day = datetime.date(int(year_lookup), int(month_lookup), int(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.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.encoding import force_unicode
|
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:
|
if related.field.rel.edit_inline or not has_admin:
|
||||||
# Don't display link to edit, because it either has no
|
# Don't display link to edit, because it either has no
|
||||||
# admin or is edited inline.
|
# 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:
|
else:
|
||||||
# Display a link to the admin page.
|
# Display a link to the admin page.
|
||||||
nh(deleted_objects, current_depth, [u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
|
nh(deleted_objects, current_depth, [mark_safe(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(),
|
(escape(force_unicode(capfirst(related.opts.verbose_name))),
|
||||||
sub_obj._get_pk_val(), sub_obj), []])
|
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)
|
get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2, admin_site)
|
||||||
else:
|
else:
|
||||||
has_related_objs = False
|
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)), []])
|
nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), escape(sub_obj)), []])
|
||||||
else:
|
else:
|
||||||
# Display a link to the admin page.
|
# Display a link to the admin page.
|
||||||
nh(deleted_objects, current_depth, [u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % \
|
nh(deleted_objects, current_depth, [mark_safe(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)), []])
|
(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)
|
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
|
# If there were related objects, and the user doesn't have
|
||||||
# permission to delete them, add the missing perm to perms_needed.
|
# 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:
|
else:
|
||||||
# Display a link to the admin page.
|
# Display a link to the admin page.
|
||||||
nh(deleted_objects, current_depth, [
|
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>' % \
|
(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
|
# If there were related objects, and the user doesn't have
|
||||||
# permission to change them, add the missing perm to perms_needed.
|
# permission to change them, add the missing perm to perms_needed.
|
||||||
if has_admin and has_related_objs:
|
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.contrib.auth import authenticate, login
|
||||||
from django.shortcuts import render_to_response
|
from django.shortcuts import render_to_response
|
||||||
from django.utils.translation import ugettext_lazy, ugettext as _
|
from django.utils.translation import ugettext_lazy, ugettext as _
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
import base64, datetime, md5
|
import base64, datetime, md5
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ def _display_login_form(request, error_message=''):
|
|||||||
post_data = _encode_post_data({})
|
post_data = _encode_post_data({})
|
||||||
return render_to_response('admin/login.html', {
|
return render_to_response('admin/login.html', {
|
||||||
'title': _('Log in'),
|
'title': _('Log in'),
|
||||||
'app_path': request.path,
|
'app_path': mark_safe(request.path),
|
||||||
'post_data': post_data,
|
'post_data': post_data,
|
||||||
'error_message': error_message
|
'error_message': error_message
|
||||||
}, context_instance=template.RequestContext(request))
|
}, context_instance=template.RequestContext(request))
|
||||||
|
@ -173,7 +173,7 @@ class ChangeList(object):
|
|||||||
del p[k]
|
del p[k]
|
||||||
elif v is not None:
|
elif v is not None:
|
||||||
p[k] = v
|
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):
|
def get_results(self, request):
|
||||||
paginator = ObjectPaginator(self.query_set, self.list_per_page)
|
paginator = ObjectPaginator(self.query_set, self.list_per_page)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import re
|
import re
|
||||||
from email.Parser import HeaderParser
|
from email.Parser import HeaderParser
|
||||||
from email.Errors import HeaderParseError
|
from email.Errors import HeaderParseError
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
try:
|
try:
|
||||||
import docutils.core
|
import docutils.core
|
||||||
import docutils.nodes
|
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,
|
parts = docutils.core.publish_parts(text, source_path=thing_being_parsed,
|
||||||
destination_path=None, writer_name='html',
|
destination_path=None, writer_name='html',
|
||||||
settings_overrides=overrides)
|
settings_overrides=overrides)
|
||||||
return parts['fragment']
|
return mark_safe(parts['fragment'])
|
||||||
|
|
||||||
#
|
#
|
||||||
# reST roles
|
# reST roles
|
||||||
|
@ -10,6 +10,7 @@ from django.core import urlresolvers
|
|||||||
from django.contrib.admindocs import utils
|
from django.contrib.admindocs import utils
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
import inspect, os, re
|
import inspect, os, re
|
||||||
|
|
||||||
# Exclude methods starting with these strings from documentation
|
# 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.
|
# Hack! This couples this view to the URL it lives at.
|
||||||
admin_root = request.path[:-len('doc/bookmarklets/')]
|
admin_root = request.path[:-len('doc/bookmarklets/')]
|
||||||
return render_to_response('admin_doc/bookmarklets.html', {
|
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))
|
}, context_instance=RequestContext(request))
|
||||||
bookmarklets = staff_member_required(bookmarklets)
|
bookmarklets = staff_member_required(bookmarklets)
|
||||||
|
|
||||||
|
@ -7,11 +7,12 @@ against request forgeries from other sites.
|
|||||||
"""
|
"""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import HttpResponseForbidden
|
from django.http import HttpResponseForbidden
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
import md5
|
import md5
|
||||||
import re
|
import re
|
||||||
import itertools
|
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 = \
|
_POST_FORM_RE = \
|
||||||
re.compile(r'(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
|
re.compile(r'(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
|
||||||
@ -82,10 +83,10 @@ class CsrfMiddleware(object):
|
|||||||
itertools.repeat(''))
|
itertools.repeat(''))
|
||||||
def add_csrf_field(match):
|
def add_csrf_field(match):
|
||||||
"""Returns the matched <form> tag plus the added <input> element"""
|
"""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() + \
|
"<input type='hidden' " + idattributes.next() + \
|
||||||
" name='csrfmiddlewaretoken' value='" + csrf_token + \
|
" name='csrfmiddlewaretoken' value='" + csrf_token + \
|
||||||
"' /></div>"
|
"' /></div>")
|
||||||
|
|
||||||
# Modify any POST forms
|
# Modify any POST forms
|
||||||
response.content = _POST_FORM_RE.sub(add_csrf_field, response.content)
|
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.text import capfirst
|
||||||
from django.utils.translation import get_date_formats
|
from django.utils.translation import get_date_formats
|
||||||
from django.utils.encoding import smart_unicode, smart_str, iri_to_uri
|
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
|
from django.db.models.query import QuerySet
|
||||||
|
|
||||||
EMPTY_VALUE = '(None)'
|
EMPTY_VALUE = '(None)'
|
||||||
@ -28,7 +29,7 @@ class EasyModel(object):
|
|||||||
return self.site.registry[self.model]
|
return self.site.registry[self.model]
|
||||||
|
|
||||||
def url(self):
|
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):
|
def objects(self, **kwargs):
|
||||||
return self.get_query_set().filter(**kwargs)
|
return self.get_query_set().filter(**kwargs)
|
||||||
@ -68,9 +69,9 @@ class EasyField(object):
|
|||||||
|
|
||||||
def url(self):
|
def url(self):
|
||||||
if self.field.choices:
|
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:
|
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):
|
class EasyChoice(object):
|
||||||
def __init__(self, easy_model, field, value, label):
|
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))
|
return smart_str(u'<EasyChoice for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
|
||||||
|
|
||||||
def url(self):
|
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):
|
class EasyInstance(object):
|
||||||
def __init__(self, easy_model, instance):
|
def __init__(self, easy_model, instance):
|
||||||
@ -184,14 +185,14 @@ class EasyInstanceField(object):
|
|||||||
if self.field.rel.to in self.model.model_list:
|
if self.field.rel.to in self.model.model_list:
|
||||||
lst = []
|
lst = []
|
||||||
for value in self.values():
|
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))
|
lst.append((smart_unicode(value), url))
|
||||||
else:
|
else:
|
||||||
lst = [(value, None) for value in self.values()]
|
lst = [(value, None) for value in self.values()]
|
||||||
elif self.field.choices:
|
elif self.field.choices:
|
||||||
lst = []
|
lst = []
|
||||||
for value in self.values():
|
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))
|
lst.append((value, url))
|
||||||
elif isinstance(self.field, models.URLField):
|
elif isinstance(self.field, models.URLField):
|
||||||
val = self.values()[0]
|
val = self.values()[0]
|
||||||
|
@ -5,8 +5,9 @@ from django.contrib.databrowse.sites import DatabrowsePlugin
|
|||||||
from django.shortcuts import render_to_response
|
from django.shortcuts import render_to_response
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.translation import get_date_formats
|
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.encoding import force_unicode
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.views.generic import date_based
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@ -29,16 +30,17 @@ class CalendarPlugin(DatabrowsePlugin):
|
|||||||
fields = self.field_dict(model)
|
fields = self.field_dict(model)
|
||||||
if not fields:
|
if not fields:
|
||||||
return u''
|
return u''
|
||||||
return u'<p class="filter"><strong>View calendar by:</strong> %s</p>' % \
|
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()])
|
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):
|
def urls(self, plugin_name, easy_instance_field):
|
||||||
if isinstance(easy_instance_field.field, models.DateField):
|
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,
|
plugin_name, easy_instance_field.field.name,
|
||||||
easy_instance_field.raw_value.year,
|
easy_instance_field.raw_value.year,
|
||||||
easy_instance_field.raw_value.strftime('%b').lower(),
|
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):
|
def model_view(self, request, model_databrowse, url):
|
||||||
self.model, self.site = model_databrowse.model, model_databrowse.site
|
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.shortcuts import render_to_response
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.encoding import smart_str, force_unicode
|
from django.utils.encoding import smart_str, force_unicode
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
from django.views.generic import date_based
|
from django.views.generic import date_based
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
@ -32,15 +33,16 @@ class FieldChoicePlugin(DatabrowsePlugin):
|
|||||||
fields = self.field_dict(model)
|
fields = self.field_dict(model)
|
||||||
if not fields:
|
if not fields:
|
||||||
return u''
|
return u''
|
||||||
return u'<p class="filter"><strong>View by:</strong> %s</p>' % \
|
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()])
|
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):
|
def urls(self, plugin_name, easy_instance_field):
|
||||||
if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values():
|
if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values():
|
||||||
field_value = smart_str(easy_instance_field.raw_value)
|
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,
|
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):
|
def model_view(self, request, model_databrowse, url):
|
||||||
self.model, self.site = model_databrowse.model, model_databrowse.site
|
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.db import models
|
||||||
from django.contrib.databrowse.datastructures import EasyModel, EasyChoice
|
from django.contrib.databrowse.datastructures import EasyModel, EasyChoice
|
||||||
from django.shortcuts import render_to_response
|
from django.shortcuts import render_to_response
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
class AlreadyRegistered(Exception):
|
class AlreadyRegistered(Exception):
|
||||||
pass
|
pass
|
||||||
@ -60,7 +61,7 @@ class ModelDatabrowse(object):
|
|||||||
|
|
||||||
def main_view(self, request):
|
def main_view(self, request):
|
||||||
easy_model = EasyModel(self.site, self.model)
|
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', {
|
return render_to_response('databrowse/model_detail.html', {
|
||||||
'model': easy_model,
|
'model': easy_model,
|
||||||
'root_url': self.site.root_url,
|
'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.http import HttpResponse
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.xheaders import populate_xheaders
|
from django.core.xheaders import populate_xheaders
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
DEFAULT_TEMPLATE = 'flatpages/default.html'
|
DEFAULT_TEMPLATE = 'flatpages/default.html'
|
||||||
|
|
||||||
@ -30,6 +31,13 @@ def flatpage(request, url):
|
|||||||
t = loader.select_template((f.template_name, DEFAULT_TEMPLATE))
|
t = loader.select_template((f.template_name, DEFAULT_TEMPLATE))
|
||||||
else:
|
else:
|
||||||
t = loader.get_template(DEFAULT_TEMPLATE)
|
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, {
|
c = RequestContext(request, {
|
||||||
'flatpage': f,
|
'flatpage': f,
|
||||||
})
|
})
|
||||||
|
@ -21,6 +21,7 @@ def ordinal(value):
|
|||||||
if value % 100 in (11, 12, 13): # special case
|
if value % 100 in (11, 12, 13): # special case
|
||||||
return u"%d%s" % (value, t[0])
|
return u"%d%s" % (value, t[0])
|
||||||
return u'%d%s' % (value, t[value % 10])
|
return u'%d%s' % (value, t[value % 10])
|
||||||
|
ordinal.is_safe = True
|
||||||
register.filter(ordinal)
|
register.filter(ordinal)
|
||||||
|
|
||||||
def intcomma(value):
|
def intcomma(value):
|
||||||
@ -34,6 +35,7 @@ def intcomma(value):
|
|||||||
return new
|
return new
|
||||||
else:
|
else:
|
||||||
return intcomma(new)
|
return intcomma(new)
|
||||||
|
intcomma.is_safe = True
|
||||||
register.filter(intcomma)
|
register.filter(intcomma)
|
||||||
|
|
||||||
def intword(value):
|
def intword(value):
|
||||||
@ -55,6 +57,7 @@ def intword(value):
|
|||||||
new_value = value / 1000000000000.0
|
new_value = value / 1000000000000.0
|
||||||
return ungettext('%(value).1f trillion', '%(value).1f trillion', new_value) % {'value': new_value}
|
return ungettext('%(value).1f trillion', '%(value).1f trillion', new_value) % {'value': new_value}
|
||||||
return value
|
return value
|
||||||
|
intword.is_safe = False
|
||||||
register.filter(intword)
|
register.filter(intword)
|
||||||
|
|
||||||
def apnumber(value):
|
def apnumber(value):
|
||||||
@ -69,6 +72,7 @@ def apnumber(value):
|
|||||||
if not 0 < value < 10:
|
if not 0 < value < 10:
|
||||||
return value
|
return value
|
||||||
return (_('one'), _('two'), _('three'), _('four'), _('five'), _('six'), _('seven'), _('eight'), _('nine'))[value-1]
|
return (_('one'), _('two'), _('three'), _('four'), _('five'), _('six'), _('seven'), _('eight'), _('nine'))[value-1]
|
||||||
|
apnumber.is_safe = True
|
||||||
register.filter(apnumber)
|
register.filter(apnumber)
|
||||||
|
|
||||||
def naturalday(value, arg=None):
|
def naturalday(value, arg=None):
|
||||||
|
@ -17,6 +17,7 @@ silently fail and return the un-marked-up text.
|
|||||||
from django import template
|
from django import template
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.encoding import smart_str, force_unicode
|
from django.utils.encoding import smart_str, force_unicode
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
@ -28,7 +29,8 @@ def textile(value):
|
|||||||
raise template.TemplateSyntaxError, "Error in {% textile %} filter: The Python textile library isn't installed."
|
raise template.TemplateSyntaxError, "Error in {% textile %} filter: The Python textile library isn't installed."
|
||||||
return force_unicode(value)
|
return force_unicode(value)
|
||||||
else:
|
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):
|
def markdown(value):
|
||||||
try:
|
try:
|
||||||
@ -38,7 +40,8 @@ def markdown(value):
|
|||||||
raise template.TemplateSyntaxError, "Error in {% markdown %} filter: The Python markdown library isn't installed."
|
raise template.TemplateSyntaxError, "Error in {% markdown %} filter: The Python markdown library isn't installed."
|
||||||
return force_unicode(value)
|
return force_unicode(value)
|
||||||
else:
|
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):
|
def restructuredtext(value):
|
||||||
try:
|
try:
|
||||||
@ -50,7 +53,8 @@ def restructuredtext(value):
|
|||||||
else:
|
else:
|
||||||
docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {})
|
docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {})
|
||||||
parts = publish_parts(source=smart_str(value), writer_name="html4css1", settings_overrides=docutils_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(textile)
|
||||||
register.filter(markdown)
|
register.filter(markdown)
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
# Quick tests for the markup templatetags (django.contrib.markup)
|
# Quick tests for the markup templatetags (django.contrib.markup)
|
||||||
|
|
||||||
from django.template import Template, Context, add_to_builtins
|
|
||||||
import re
|
import re
|
||||||
import unittest
|
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')
|
add_to_builtins('django.contrib.markup.templatetags.markup')
|
||||||
|
|
||||||
class Templates(unittest.TestCase):
|
class Templates(unittest.TestCase):
|
||||||
@ -24,7 +26,7 @@ Paragraph 2 with "quotes" and @code@"""
|
|||||||
|
|
||||||
<p>Paragraph 2 with “quotes” and <code>code</code></p>""")
|
<p>Paragraph 2 with “quotes” and <code>code</code></p>""")
|
||||||
else:
|
else:
|
||||||
self.assertEqual(rendered, textile_content)
|
self.assertEqual(rendered, escape(textile_content))
|
||||||
|
|
||||||
def test_markdown(self):
|
def test_markdown(self):
|
||||||
try:
|
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">
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
{% spaceless %}
|
{% spaceless %}
|
||||||
{% for url in urlset %}
|
{% for url in urlset %}
|
||||||
@ -11,3 +11,4 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endspaceless %}
|
{% endspaceless %}
|
||||||
</urlset>
|
</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">
|
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
{% for location in sitemaps %}<sitemap><loc>{{ location|escape }}</loc></sitemap>{% endfor %}
|
{% for location in sitemaps %}<sitemap><loc>{{ location|escape }}</loc></sitemap>{% endfor %}
|
||||||
</sitemapindex>
|
</sitemapindex>
|
||||||
|
{% endautoescape %}
|
||||||
|
@ -7,6 +7,7 @@ from copy import deepcopy
|
|||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
|
from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from fields import Field
|
from fields import Field
|
||||||
from widgets import Media, media_property, TextInput, Textarea
|
from widgets import Media, media_property, TextInput, Textarea
|
||||||
@ -123,7 +124,8 @@ class BaseForm(StrAndUnicode):
|
|||||||
output.append(error_row % force_unicode(bf_errors))
|
output.append(error_row % force_unicode(bf_errors))
|
||||||
if bf.label:
|
if bf.label:
|
||||||
label = escape(force_unicode(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 self.label_suffix:
|
||||||
if label[-1] not in ':?.!':
|
if label[-1] not in ':?.!':
|
||||||
label += self.label_suffix
|
label += self.label_suffix
|
||||||
@ -141,11 +143,14 @@ class BaseForm(StrAndUnicode):
|
|||||||
str_hidden = u''.join(hidden_fields)
|
str_hidden = u''.join(hidden_fields)
|
||||||
if output:
|
if output:
|
||||||
last_row = output[-1]
|
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
|
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)
|
output.append(str_hidden)
|
||||||
return u'\n'.join(output)
|
return mark_safe(u'\n'.join(output))
|
||||||
|
|
||||||
def as_table(self):
|
def as_table(self):
|
||||||
"Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
|
"Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
|
||||||
@ -343,7 +348,7 @@ class BoundField(StrAndUnicode):
|
|||||||
if id_:
|
if id_:
|
||||||
attrs = attrs and flatatt(attrs) or ''
|
attrs = attrs and flatatt(attrs) or ''
|
||||||
contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, contents)
|
contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, contents)
|
||||||
return contents
|
return mark_safe(contents)
|
||||||
|
|
||||||
def _is_hidden(self):
|
def _is_hidden(self):
|
||||||
"Returns True if this BoundField's widget is hidden."
|
"Returns True if this BoundField's widget is hidden."
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode
|
from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode
|
||||||
from django.utils.functional import Promise
|
from django.utils.functional import Promise
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
def flatatt(attrs):
|
def flatatt(attrs):
|
||||||
"""
|
"""
|
||||||
@ -22,7 +23,9 @@ class ErrorDict(dict, StrAndUnicode):
|
|||||||
|
|
||||||
def as_ul(self):
|
def as_ul(self):
|
||||||
if not self: return u''
|
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):
|
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()])
|
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):
|
def as_ul(self):
|
||||||
if not self: return u''
|
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):
|
def as_text(self):
|
||||||
if not self: return u''
|
if not self: return u''
|
||||||
|
@ -14,6 +14,7 @@ from django.utils.datastructures import MultiValueDict
|
|||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.translation import ugettext
|
from django.utils.translation import ugettext
|
||||||
from django.utils.encoding import StrAndUnicode, force_unicode
|
from django.utils.encoding import StrAndUnicode, force_unicode
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
from util import flatatt
|
from util import flatatt
|
||||||
from urlparse import urljoin
|
from urlparse import urljoin
|
||||||
|
|
||||||
@ -188,8 +189,10 @@ class Input(Widget):
|
|||||||
def render(self, name, value, attrs=None):
|
def render(self, name, value, attrs=None):
|
||||||
if value is None: value = ''
|
if value is None: value = ''
|
||||||
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
|
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.
|
if value != '':
|
||||||
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))
|
||||||
|
|
||||||
class TextInput(Input):
|
class TextInput(Input):
|
||||||
input_type = 'text'
|
input_type = 'text'
|
||||||
@ -222,7 +225,9 @@ class MultipleHiddenInput(HiddenInput):
|
|||||||
def render(self, name, value, attrs=None, choices=()):
|
def render(self, name, value, attrs=None, choices=()):
|
||||||
if value is None: value = []
|
if value is None: value = []
|
||||||
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
|
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):
|
def value_from_datadict(self, data, files, name):
|
||||||
if isinstance(data, MultiValueDict):
|
if isinstance(data, MultiValueDict):
|
||||||
@ -251,7 +256,8 @@ class Textarea(Widget):
|
|||||||
if value is None: value = ''
|
if value is None: value = ''
|
||||||
value = force_unicode(value)
|
value = force_unicode(value)
|
||||||
final_attrs = self.build_attrs(attrs, name=name)
|
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):
|
class DateTimeInput(Input):
|
||||||
input_type = 'text'
|
input_type = 'text'
|
||||||
@ -285,8 +291,9 @@ class CheckboxInput(Widget):
|
|||||||
if result:
|
if result:
|
||||||
final_attrs['checked'] = 'checked'
|
final_attrs['checked'] = 'checked'
|
||||||
if value not in ('', True, False, None):
|
if value not in ('', True, False, None):
|
||||||
final_attrs['value'] = force_unicode(value) # Only add the 'value' attribute if a value is non-empty.
|
# Only add the 'value' attribute if a value is non-empty.
|
||||||
return u'<input%s />' % flatatt(final_attrs)
|
final_attrs['value'] = force_unicode(value)
|
||||||
|
return mark_safe(u'<input%s />' % flatatt(final_attrs))
|
||||||
|
|
||||||
def value_from_datadict(self, data, files, name):
|
def value_from_datadict(self, data, files, name):
|
||||||
if name not in data:
|
if name not in data:
|
||||||
@ -307,13 +314,14 @@ class Select(Widget):
|
|||||||
if value is None: value = ''
|
if value is None: value = ''
|
||||||
final_attrs = self.build_attrs(attrs, name=name)
|
final_attrs = self.build_attrs(attrs, name=name)
|
||||||
output = [u'<select%s>' % flatatt(final_attrs)]
|
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):
|
for option_value, option_label in chain(self.choices, choices):
|
||||||
option_value = force_unicode(option_value)
|
option_value = force_unicode(option_value)
|
||||||
selected_html = (option_value == str_value) and u' selected="selected"' or ''
|
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'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(force_unicode(option_label))))
|
||||||
output.append(u'</select>')
|
output.append(u'</select>')
|
||||||
return u'\n'.join(output)
|
return mark_safe(u'\n'.join(output))
|
||||||
|
|
||||||
class NullBooleanSelect(Select):
|
class NullBooleanSelect(Select):
|
||||||
"""
|
"""
|
||||||
@ -350,7 +358,7 @@ class SelectMultiple(Widget):
|
|||||||
selected_html = (option_value in str_values) and ' selected="selected"' or ''
|
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'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(force_unicode(option_label))))
|
||||||
output.append(u'</select>')
|
output.append(u'</select>')
|
||||||
return u'\n'.join(output)
|
return mark_safe(u'\n'.join(output))
|
||||||
|
|
||||||
def value_from_datadict(self, data, files, name):
|
def value_from_datadict(self, data, files, name):
|
||||||
if isinstance(data, MultiValueDict):
|
if isinstance(data, MultiValueDict):
|
||||||
@ -371,7 +379,8 @@ class RadioInput(StrAndUnicode):
|
|||||||
self.index = index
|
self.index = index
|
||||||
|
|
||||||
def __unicode__(self):
|
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):
|
def is_checked(self):
|
||||||
return self.value == self.choice_value
|
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)
|
final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
|
||||||
if self.is_checked():
|
if self.is_checked():
|
||||||
final_attrs['checked'] = 'checked'
|
final_attrs['checked'] = 'checked'
|
||||||
return u'<input%s />' % flatatt(final_attrs)
|
return mark_safe(u'<input%s />' % flatatt(final_attrs))
|
||||||
|
|
||||||
class RadioFieldRenderer(StrAndUnicode):
|
class RadioFieldRenderer(StrAndUnicode):
|
||||||
"""
|
"""
|
||||||
@ -406,7 +415,8 @@ class RadioFieldRenderer(StrAndUnicode):
|
|||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
"""Outputs a <ul> for this set of radio fields."""
|
"""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):
|
class RadioSelect(Select):
|
||||||
|
|
||||||
@ -443,7 +453,8 @@ class CheckboxSelectMultiple(SelectMultiple):
|
|||||||
has_id = attrs and 'id' in attrs
|
has_id = attrs and 'id' in attrs
|
||||||
final_attrs = self.build_attrs(attrs, name=name)
|
final_attrs = self.build_attrs(attrs, name=name)
|
||||||
output = [u'<ul>']
|
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)):
|
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,
|
# 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.
|
# 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)
|
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'<li><label>%s %s</label></li>' % (rendered_cb, escape(force_unicode(option_label))))
|
||||||
output.append(u'</ul>')
|
output.append(u'</ul>')
|
||||||
return u'\n'.join(output)
|
return mark_safe(u'\n'.join(output))
|
||||||
|
|
||||||
def id_for_label(self, id_):
|
def id_for_label(self, id_):
|
||||||
# See the comment for RadioSelect.id_for_label()
|
# See the comment for RadioSelect.id_for_label()
|
||||||
@ -560,3 +571,4 @@ class SplitDateTimeWidget(MultiWidget):
|
|||||||
if value:
|
if value:
|
||||||
return [value.date(), value.time().replace(microsecond=0)]
|
return [value.date(), value.time().replace(microsecond=0)]
|
||||||
return [None, None]
|
return [None, None]
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext, ungettext
|
from django.utils.translation import ugettext, ungettext
|
||||||
from django.utils.encoding import smart_unicode, force_unicode
|
from django.utils.encoding import smart_unicode, force_unicode
|
||||||
@ -189,9 +190,9 @@ class FormFieldWrapper(object):
|
|||||||
|
|
||||||
def html_error_list(self):
|
def html_error_list(self):
|
||||||
if self.errors():
|
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:
|
else:
|
||||||
return ''
|
return mark_safe('')
|
||||||
|
|
||||||
def get_id(self):
|
def get_id(self):
|
||||||
return self.formfield.get_id()
|
return self.formfield.get_id()
|
||||||
@ -226,7 +227,7 @@ class FormFieldCollection(FormFieldWrapper):
|
|||||||
return bool(len(self.errors()))
|
return bool(len(self.errors()))
|
||||||
|
|
||||||
def html_combined_error_list(self):
|
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):
|
class InlineObjectCollection(object):
|
||||||
"An object that acts like a sparse list of form field collections."
|
"An object that acts like a sparse list of form field collections."
|
||||||
@ -418,9 +419,9 @@ class TextField(FormField):
|
|||||||
max_length = u''
|
max_length = u''
|
||||||
if self.max_length:
|
if self.max_length:
|
||||||
max_length = u'maxlength="%s" ' % 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.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):
|
def html2python(data):
|
||||||
return data
|
return data
|
||||||
@ -442,9 +443,9 @@ class LargeTextField(TextField):
|
|||||||
def render(self, data):
|
def render(self, data):
|
||||||
if data is None:
|
if data is None:
|
||||||
data = ''
|
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.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):
|
class HiddenField(FormField):
|
||||||
def __init__(self, field_name, is_required=False, validator_list=None, max_length=None):
|
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[:]
|
self.validator_list = validator_list[:]
|
||||||
|
|
||||||
def render(self, data):
|
def render(self, data):
|
||||||
return u'<input type="hidden" id="%s" name="%s" value="%s" />' % \
|
return mark_safe(u'<input type="hidden" id="%s" name="%s" value="%s" />' % \
|
||||||
(self.get_id(), self.field_name, escape(data))
|
(self.get_id(), self.field_name, escape(data)))
|
||||||
|
|
||||||
class CheckboxField(FormField):
|
class CheckboxField(FormField):
|
||||||
def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False):
|
def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False):
|
||||||
@ -468,9 +469,9 @@ class CheckboxField(FormField):
|
|||||||
checked_html = ''
|
checked_html = ''
|
||||||
if data or (data is '' and self.checked_by_default):
|
if data or (data is '' and self.checked_by_default):
|
||||||
checked_html = ' checked="checked"'
|
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.get_id(), self.__class__.__name__,
|
||||||
self.field_name, checked_html)
|
self.field_name, checked_html))
|
||||||
|
|
||||||
def html2python(data):
|
def html2python(data):
|
||||||
"Convert value from browser ('on' or '') to a Python boolean"
|
"Convert value from browser ('on' or '') to a Python boolean"
|
||||||
@ -502,7 +503,7 @@ class SelectField(FormField):
|
|||||||
selected_html = u' selected="selected"'
|
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' <option value="%s"%s>%s</option>' % (escape(value), selected_html, force_unicode(escape(display_name))))
|
||||||
output.append(u' </select>')
|
output.append(u' </select>')
|
||||||
return u'\n'.join(output)
|
return mark_safe(u'\n'.join(output))
|
||||||
|
|
||||||
def isValidChoice(self, data, form):
|
def isValidChoice(self, data, form):
|
||||||
str_data = smart_unicode(data)
|
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 = [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.extend([u'<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist])
|
||||||
output.append(u'</ul>')
|
output.append(u'</ul>')
|
||||||
return u''.join(output)
|
return mark_safe(u''.join(output))
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for d in self.datalist:
|
for d in self.datalist:
|
||||||
yield d
|
yield d
|
||||||
@ -571,11 +572,11 @@ class RadioSelectField(FormField):
|
|||||||
datalist.append({
|
datalist.append({
|
||||||
'value': value,
|
'value': value,
|
||||||
'name': display_name,
|
'name': display_name,
|
||||||
'field': u'<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
|
'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),
|
(self.get_id() + u'_' + unicode(i), self.field_name, value, selected_html)),
|
||||||
'label': u'<label for="%s">%s</label>' % \
|
'label': mark_safe(u'<label for="%s">%s</label>' % \
|
||||||
(self.get_id() + u'_' + unicode(i), display_name),
|
(self.get_id() + u'_' + unicode(i), display_name),
|
||||||
})
|
)})
|
||||||
return RadioFieldRenderer(datalist, self.ul_class)
|
return RadioFieldRenderer(datalist, self.ul_class)
|
||||||
|
|
||||||
def isValidChoice(self, data, form):
|
def isValidChoice(self, data, form):
|
||||||
@ -614,7 +615,7 @@ class SelectMultipleField(SelectField):
|
|||||||
selected_html = u' selected="selected"'
|
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' <option value="%s"%s>%s</option>' % (escape(value), selected_html, force_unicode(escape(choice))))
|
||||||
output.append(u' </select>')
|
output.append(u' </select>')
|
||||||
return u'\n'.join(output)
|
return mark_safe(u'\n'.join(output))
|
||||||
|
|
||||||
def isValidChoice(self, field_data, all_data):
|
def isValidChoice(self, field_data, all_data):
|
||||||
# data is something like ['1', '2', '3']
|
# 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), self.__class__.__name__, field_name, checked_html,
|
||||||
self.get_id() + escape(value), choice))
|
self.get_id() + escape(value), choice))
|
||||||
output.append(u'</ul>')
|
output.append(u'</ul>')
|
||||||
return u'\n'.join(output)
|
return mark_safe(u'\n'.join(output))
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# FILE UPLOADS #
|
# FILE UPLOADS #
|
||||||
@ -688,8 +689,8 @@ class FileUploadField(FormField):
|
|||||||
raise validators.CriticalValidationError, ugettext("The submitted file is empty.")
|
raise validators.CriticalValidationError, ugettext("The submitted file is empty.")
|
||||||
|
|
||||||
def render(self, data):
|
def render(self, data):
|
||||||
return u'<input type="file" id="%s" class="v%s" name="%s" />' % \
|
return mark_safe(u'<input type="file" id="%s" class="v%s" name="%s" />' % \
|
||||||
(self.get_id(), self.__class__.__name__, self.field_name)
|
(self.get_id(), self.__class__.__name__, self.field_name))
|
||||||
|
|
||||||
def html2python(data):
|
def html2python(data):
|
||||||
if data is None:
|
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.text import smart_split
|
||||||
from django.utils.encoding import smart_unicode, force_unicode
|
from django.utils.encoding import smart_unicode, force_unicode
|
||||||
from django.utils.translation import ugettext as _
|
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')
|
__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
|
||||||
|
|
||||||
@ -595,7 +597,16 @@ class FilterExpression(object):
|
|||||||
arg_vals.append(arg)
|
arg_vals.append(arg)
|
||||||
else:
|
else:
|
||||||
arg_vals.append(arg.resolve(context))
|
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
|
return obj
|
||||||
|
|
||||||
def args_check(name, func, provided):
|
def args_check(name, func, provided):
|
||||||
@ -637,7 +648,7 @@ def resolve_variable(path, context):
|
|||||||
"""
|
"""
|
||||||
Returns the resolved variable, which may contain attribute syntax, within
|
Returns the resolved variable, which may contain attribute syntax, within
|
||||||
the given context.
|
the given context.
|
||||||
|
|
||||||
Deprecated; use the Variable class instead.
|
Deprecated; use the Variable class instead.
|
||||||
"""
|
"""
|
||||||
return Variable(path).resolve(context)
|
return Variable(path).resolve(context)
|
||||||
@ -647,7 +658,7 @@ class Variable(object):
|
|||||||
A template variable, resolvable against a given context. The variable may be
|
A template variable, resolvable against a given context. The variable may be
|
||||||
a hard-coded string (if it begins and ends with single or double quote
|
a hard-coded string (if it begins and ends with single or double quote
|
||||||
marks)::
|
marks)::
|
||||||
|
|
||||||
>>> c = {'article': {'section':'News'}}
|
>>> c = {'article': {'section':'News'}}
|
||||||
>>> Variable('article.section').resolve(c)
|
>>> Variable('article.section').resolve(c)
|
||||||
u'News'
|
u'News'
|
||||||
@ -662,25 +673,25 @@ class Variable(object):
|
|||||||
|
|
||||||
(The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
|
(The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, var):
|
def __init__(self, var):
|
||||||
self.var = var
|
self.var = var
|
||||||
self.literal = None
|
self.literal = None
|
||||||
self.lookups = None
|
self.lookups = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# First try to treat this variable as a number.
|
# First try to treat this variable as a number.
|
||||||
#
|
#
|
||||||
# Note that this could cause an OverflowError here that we're not
|
# Note that this could cause an OverflowError here that we're not
|
||||||
# catching. Since this should only happen at compile time, that's
|
# catching. Since this should only happen at compile time, that's
|
||||||
# probably OK.
|
# probably OK.
|
||||||
self.literal = float(var)
|
self.literal = float(var)
|
||||||
|
|
||||||
# So it's a float... is it an int? If the original value contained a
|
# So it's a float... is it an int? If the original value contained a
|
||||||
# dot or an "e" then it was a float, not an int.
|
# dot or an "e" then it was a float, not an int.
|
||||||
if '.' not in var and 'e' not in var.lower():
|
if '.' not in var and 'e' not in var.lower():
|
||||||
self.literal = int(self.literal)
|
self.literal = int(self.literal)
|
||||||
|
|
||||||
# "2." is invalid
|
# "2." is invalid
|
||||||
if var.endswith('.'):
|
if var.endswith('.'):
|
||||||
raise ValueError
|
raise ValueError
|
||||||
@ -691,12 +702,12 @@ class Variable(object):
|
|||||||
# we're also dealing with a literal.
|
# we're also dealing with a literal.
|
||||||
if var[0] in "\"'" and var[0] == var[-1]:
|
if var[0] in "\"'" and var[0] == var[-1]:
|
||||||
self.literal = var[1:-1]
|
self.literal = var[1:-1]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Otherwise we'll set self.lookups so that resolve() knows we're
|
# Otherwise we'll set self.lookups so that resolve() knows we're
|
||||||
# dealing with a bonafide variable
|
# dealing with a bonafide variable
|
||||||
self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
|
self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
|
||||||
|
|
||||||
def resolve(self, context):
|
def resolve(self, context):
|
||||||
"""Resolve this variable against a given context."""
|
"""Resolve this variable against a given context."""
|
||||||
if self.lookups is not None:
|
if self.lookups is not None:
|
||||||
@ -705,18 +716,18 @@ class Variable(object):
|
|||||||
else:
|
else:
|
||||||
# We're dealing with a literal, so it's already been "resolved"
|
# We're dealing with a literal, so it's already been "resolved"
|
||||||
return self.literal
|
return self.literal
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s: %r>" % (self.__class__.__name__, self.var)
|
return "<%s: %r>" % (self.__class__.__name__, self.var)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.var
|
return self.var
|
||||||
|
|
||||||
def _resolve_lookup(self, context):
|
def _resolve_lookup(self, context):
|
||||||
"""
|
"""
|
||||||
Performs resolution of a real variable (i.e. not a literal) against the
|
Performs resolution of a real variable (i.e. not a literal) against the
|
||||||
given context.
|
given context.
|
||||||
|
|
||||||
As indicated by the method's name, this method is an implementation
|
As indicated by the method's name, this method is an implementation
|
||||||
detail and shouldn't be called by external code. Use Variable.resolve()
|
detail and shouldn't be called by external code. Use Variable.resolve()
|
||||||
instead.
|
instead.
|
||||||
@ -757,14 +768,7 @@ class Variable(object):
|
|||||||
current = settings.TEMPLATE_STRING_IF_INVALID
|
current = settings.TEMPLATE_STRING_IF_INVALID
|
||||||
else:
|
else:
|
||||||
raise
|
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
|
return current
|
||||||
|
|
||||||
class Node(object):
|
class Node(object):
|
||||||
@ -838,16 +842,31 @@ class VariableNode(Node):
|
|||||||
return "<Variable Node: %s>" % self.filter_expression
|
return "<Variable Node: %s>" % self.filter_expression
|
||||||
|
|
||||||
def render(self, context):
|
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):
|
class DebugVariableNode(VariableNode):
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
try:
|
try:
|
||||||
return self.filter_expression.resolve(context)
|
output = force_unicode(self.filter_expression.resolve(context))
|
||||||
except TemplateSyntaxError, e:
|
except TemplateSyntaxError, e:
|
||||||
if not hasattr(e, 'source'):
|
if not hasattr(e, 'source'):
|
||||||
e.source = self.source
|
e.source = self.source
|
||||||
raise
|
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):
|
def generic_tag_compiler(params, defaults, name, node_class, parser, token):
|
||||||
"Returns a template.Node subclass."
|
"Returns a template.Node subclass."
|
||||||
@ -961,7 +980,8 @@ class Library(object):
|
|||||||
else:
|
else:
|
||||||
t = get_template(file_name)
|
t = get_template(file_name)
|
||||||
self.nodelist = t.nodelist
|
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 = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
|
||||||
compile_func.__doc__ = func.__doc__
|
compile_func.__doc__ = func.__doc__
|
||||||
|
@ -9,9 +9,11 @@ class ContextPopException(Exception):
|
|||||||
|
|
||||||
class Context(object):
|
class Context(object):
|
||||||
"A stack container for variable context"
|
"A stack container for variable context"
|
||||||
def __init__(self, dict_=None):
|
|
||||||
|
def __init__(self, dict_=None, autoescape=True):
|
||||||
dict_ = dict_ or {}
|
dict_ = dict_ or {}
|
||||||
self.dicts = [dict_]
|
self.dicts = [dict_]
|
||||||
|
self.autoescape = autoescape
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return repr(self.dicts)
|
return repr(self.dicts)
|
||||||
@ -97,3 +99,4 @@ class RequestContext(Context):
|
|||||||
processors = tuple(processors)
|
processors = tuple(processors)
|
||||||
for processor in get_standard_processors() + processors:
|
for processor in get_standard_processors() + processors:
|
||||||
self.update(processor(request))
|
self.update(processor(request))
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ from django.template import Variable, Library
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext, ungettext
|
from django.utils.translation import ugettext, ungettext
|
||||||
from django.utils.encoding import force_unicode, iri_to_uri
|
from django.utils.encoding import force_unicode, iri_to_uri
|
||||||
|
from django.utils.safestring import mark_safe, SafeData
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
@ -29,6 +30,9 @@ def stringfilter(func):
|
|||||||
# Include a reference to the real function (used to check original
|
# Include a reference to the real function (used to check original
|
||||||
# arguments by the template parser).
|
# arguments by the template parser).
|
||||||
_dec._decorated_function = getattr(func, '_decorated_function', func)
|
_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
|
return _dec
|
||||||
|
|
||||||
###################
|
###################
|
||||||
@ -39,17 +43,20 @@ def stringfilter(func):
|
|||||||
def addslashes(value):
|
def addslashes(value):
|
||||||
"""Adds slashes - useful for passing strings to JavaScript, for example."""
|
"""Adds slashes - useful for passing strings to JavaScript, for example."""
|
||||||
return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
|
return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
|
||||||
|
addslashes.is_safe = True
|
||||||
addslashes = stringfilter(addslashes)
|
addslashes = stringfilter(addslashes)
|
||||||
|
|
||||||
def capfirst(value):
|
def capfirst(value):
|
||||||
"""Capitalizes the first character of the value."""
|
"""Capitalizes the first character of the value."""
|
||||||
return value and value[0].upper() + value[1:]
|
return value and value[0].upper() + value[1:]
|
||||||
|
capfirst.is_safe=True
|
||||||
capfirst = stringfilter(capfirst)
|
capfirst = stringfilter(capfirst)
|
||||||
|
|
||||||
def fix_ampersands(value):
|
def fix_ampersands(value):
|
||||||
"""Replaces ampersands with ``&`` entities."""
|
"""Replaces ampersands with ``&`` entities."""
|
||||||
from django.utils.html import fix_ampersands
|
from django.utils.html import fix_ampersands
|
||||||
return fix_ampersands(value)
|
return fix_ampersands(value)
|
||||||
|
fix_ampersands.is_safe=True
|
||||||
fix_ampersands = stringfilter(fix_ampersands)
|
fix_ampersands = stringfilter(fix_ampersands)
|
||||||
|
|
||||||
def floatformat(text, arg=-1):
|
def floatformat(text, arg=-1):
|
||||||
@ -90,31 +97,39 @@ def floatformat(text, arg=-1):
|
|||||||
return force_unicode(f)
|
return force_unicode(f)
|
||||||
m = f - int(f)
|
m = f - int(f)
|
||||||
if not m and d < 0:
|
if not m and d < 0:
|
||||||
return u'%d' % int(f)
|
return mark_safe(u'%d' % int(f))
|
||||||
else:
|
else:
|
||||||
formatstr = u'%%.%df' % abs(d)
|
formatstr = u'%%.%df' % abs(d)
|
||||||
return formatstr % f
|
return mark_safe(formatstr % f)
|
||||||
|
floatformat.is_safe = True
|
||||||
|
|
||||||
def iriencode(value):
|
def iriencode(value):
|
||||||
"""Escapes an IRI value for use in a URL."""
|
"""Escapes an IRI value for use in a URL."""
|
||||||
return force_unicode(iri_to_uri(value))
|
return force_unicode(iri_to_uri(value))
|
||||||
iriencode = stringfilter(iriencode)
|
iriencode = stringfilter(iriencode)
|
||||||
|
|
||||||
def linenumbers(value):
|
def linenumbers(value, autoescape=None):
|
||||||
"""Displays text with line numbers."""
|
"""Displays text with line numbers."""
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
lines = value.split(u'\n')
|
lines = value.split(u'\n')
|
||||||
# Find the maximum width of the line count, for use with zero padding
|
# 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))))
|
width = unicode(len(unicode(len(lines))))
|
||||||
for i, line in enumerate(lines):
|
if not autoescape or isinstance(value, SafeData):
|
||||||
lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line))
|
for i, line in enumerate(lines):
|
||||||
return u'\n'.join(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)
|
linenumbers = stringfilter(linenumbers)
|
||||||
|
|
||||||
def lower(value):
|
def lower(value):
|
||||||
"""Converts a string into all lowercase."""
|
"""Converts a string into all lowercase."""
|
||||||
return value.lower()
|
return value.lower()
|
||||||
|
lower.is_safe = True
|
||||||
lower = stringfilter(lower)
|
lower = stringfilter(lower)
|
||||||
|
|
||||||
def make_list(value):
|
def make_list(value):
|
||||||
@ -125,6 +140,7 @@ def make_list(value):
|
|||||||
For a string, it's a list of characters.
|
For a string, it's a list of characters.
|
||||||
"""
|
"""
|
||||||
return list(value)
|
return list(value)
|
||||||
|
make_list.is_safe = False
|
||||||
make_list = stringfilter(make_list)
|
make_list = stringfilter(make_list)
|
||||||
|
|
||||||
def slugify(value):
|
def slugify(value):
|
||||||
@ -135,7 +151,8 @@ def slugify(value):
|
|||||||
import unicodedata
|
import unicodedata
|
||||||
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
|
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
|
||||||
value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
|
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)
|
slugify = stringfilter(slugify)
|
||||||
|
|
||||||
def stringformat(value, arg):
|
def stringformat(value, arg):
|
||||||
@ -152,10 +169,12 @@ def stringformat(value, arg):
|
|||||||
return (u"%" + unicode(arg)) % value
|
return (u"%" + unicode(arg)) % value
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return u""
|
return u""
|
||||||
|
stringformat.is_safe = True
|
||||||
|
|
||||||
def title(value):
|
def title(value):
|
||||||
"""Converts a string into titlecase."""
|
"""Converts a string into titlecase."""
|
||||||
return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
|
return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
|
||||||
|
title.is_safe = True
|
||||||
title = stringfilter(title)
|
title = stringfilter(title)
|
||||||
|
|
||||||
def truncatewords(value, arg):
|
def truncatewords(value, arg):
|
||||||
@ -170,6 +189,7 @@ def truncatewords(value, arg):
|
|||||||
except ValueError: # Invalid literal for int().
|
except ValueError: # Invalid literal for int().
|
||||||
return value # Fail silently.
|
return value # Fail silently.
|
||||||
return truncate_words(value, length)
|
return truncate_words(value, length)
|
||||||
|
truncatewords.is_safe = True
|
||||||
truncatewords = stringfilter(truncatewords)
|
truncatewords = stringfilter(truncatewords)
|
||||||
|
|
||||||
def truncatewords_html(value, arg):
|
def truncatewords_html(value, arg):
|
||||||
@ -184,23 +204,28 @@ def truncatewords_html(value, arg):
|
|||||||
except ValueError: # invalid literal for int()
|
except ValueError: # invalid literal for int()
|
||||||
return value # Fail silently.
|
return value # Fail silently.
|
||||||
return truncate_html_words(value, length)
|
return truncate_html_words(value, length)
|
||||||
|
truncatewords_html.is_safe = True
|
||||||
truncatewords_html = stringfilter(truncatewords_html)
|
truncatewords_html = stringfilter(truncatewords_html)
|
||||||
|
|
||||||
def upper(value):
|
def upper(value):
|
||||||
"""Converts a string into all uppercase."""
|
"""Converts a string into all uppercase."""
|
||||||
return value.upper()
|
return value.upper()
|
||||||
|
upper.is_safe = False
|
||||||
upper = stringfilter(upper)
|
upper = stringfilter(upper)
|
||||||
|
|
||||||
def urlencode(value):
|
def urlencode(value):
|
||||||
"""Escapes a value for use in a URL."""
|
"""Escapes a value for use in a URL."""
|
||||||
from django.utils.http import urlquote
|
from django.utils.http import urlquote
|
||||||
return urlquote(value)
|
return urlquote(value)
|
||||||
|
urlencode.is_safe = False
|
||||||
urlencode = stringfilter(urlencode)
|
urlencode = stringfilter(urlencode)
|
||||||
|
|
||||||
def urlize(value):
|
def urlize(value, autoescape=None):
|
||||||
"""Converts URLs in plain text into clickable links."""
|
"""Converts URLs in plain text into clickable links."""
|
||||||
from django.utils.html import urlize
|
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)
|
urlize = stringfilter(urlize)
|
||||||
|
|
||||||
def urlizetrunc(value, limit):
|
def urlizetrunc(value, limit):
|
||||||
@ -211,12 +236,14 @@ def urlizetrunc(value, limit):
|
|||||||
Argument: Length to truncate URLs to.
|
Argument: Length to truncate URLs to.
|
||||||
"""
|
"""
|
||||||
from django.utils.html import urlize
|
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)
|
urlizetrunc = stringfilter(urlizetrunc)
|
||||||
|
|
||||||
def wordcount(value):
|
def wordcount(value):
|
||||||
"""Returns the number of words."""
|
"""Returns the number of words."""
|
||||||
return len(value.split())
|
return len(value.split())
|
||||||
|
wordcount.is_safe = False
|
||||||
wordcount = stringfilter(wordcount)
|
wordcount = stringfilter(wordcount)
|
||||||
|
|
||||||
def wordwrap(value, arg):
|
def wordwrap(value, arg):
|
||||||
@ -227,6 +254,7 @@ def wordwrap(value, arg):
|
|||||||
"""
|
"""
|
||||||
from django.utils.text import wrap
|
from django.utils.text import wrap
|
||||||
return wrap(value, int(arg))
|
return wrap(value, int(arg))
|
||||||
|
wordwrap.is_safe = True
|
||||||
wordwrap = stringfilter(wordwrap)
|
wordwrap = stringfilter(wordwrap)
|
||||||
|
|
||||||
def ljust(value, arg):
|
def ljust(value, arg):
|
||||||
@ -236,6 +264,7 @@ def ljust(value, arg):
|
|||||||
Argument: field size.
|
Argument: field size.
|
||||||
"""
|
"""
|
||||||
return value.ljust(int(arg))
|
return value.ljust(int(arg))
|
||||||
|
ljust.is_safe = True
|
||||||
ljust = stringfilter(ljust)
|
ljust = stringfilter(ljust)
|
||||||
|
|
||||||
def rjust(value, arg):
|
def rjust(value, arg):
|
||||||
@ -245,16 +274,24 @@ def rjust(value, arg):
|
|||||||
Argument: field size.
|
Argument: field size.
|
||||||
"""
|
"""
|
||||||
return value.rjust(int(arg))
|
return value.rjust(int(arg))
|
||||||
|
rjust.is_safe = True
|
||||||
rjust = stringfilter(rjust)
|
rjust = stringfilter(rjust)
|
||||||
|
|
||||||
def center(value, arg):
|
def center(value, arg):
|
||||||
"""Centers the value in a field of a given width."""
|
"""Centers the value in a field of a given width."""
|
||||||
return value.center(int(arg))
|
return value.center(int(arg))
|
||||||
|
center.is_safe = True
|
||||||
center = stringfilter(center)
|
center = stringfilter(center)
|
||||||
|
|
||||||
def cut(value, arg):
|
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)
|
cut = stringfilter(cut)
|
||||||
|
|
||||||
###################
|
###################
|
||||||
@ -262,29 +299,60 @@ cut = stringfilter(cut)
|
|||||||
###################
|
###################
|
||||||
|
|
||||||
def escape(value):
|
def escape(value):
|
||||||
"Escapes a string's HTML"
|
"""
|
||||||
from django.utils.html import escape
|
Marks the value as a string that should not be auto-escaped.
|
||||||
return escape(value)
|
"""
|
||||||
|
from django.utils.safestring import mark_for_escaping
|
||||||
|
return mark_for_escaping(value)
|
||||||
|
escape.is_safe = True
|
||||||
escape = stringfilter(escape)
|
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
|
Replaces line breaks in plain text with appropriate HTML; a single
|
||||||
newline becomes an HTML line break (``<br />``) and a new line
|
newline becomes an HTML line break (``<br />``) and a new line
|
||||||
followed by a blank line becomes a paragraph break (``</p>``).
|
followed by a blank line becomes a paragraph break (``</p>``).
|
||||||
"""
|
"""
|
||||||
from django.utils.html import linebreaks
|
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)
|
linebreaks = stringfilter(linebreaks)
|
||||||
|
|
||||||
def linebreaksbr(value):
|
def linebreaksbr(value, autoescape=None):
|
||||||
"""
|
"""
|
||||||
Converts all newlines in a piece of plain text to HTML line breaks
|
Converts all newlines in a piece of plain text to HTML line breaks
|
||||||
(``<br />``).
|
(``<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)
|
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):
|
def removetags(value, tags):
|
||||||
"""Removes a space separated list of [X]HTML tags from the output."""
|
"""Removes a space separated list of [X]HTML tags from the output."""
|
||||||
tags = [re.escape(tag) for tag in tags.split()]
|
tags = [re.escape(tag) for tag in tags.split()]
|
||||||
@ -294,12 +362,14 @@ def removetags(value, tags):
|
|||||||
value = starttag_re.sub(u'', value)
|
value = starttag_re.sub(u'', value)
|
||||||
value = endtag_re.sub(u'', value)
|
value = endtag_re.sub(u'', value)
|
||||||
return value
|
return value
|
||||||
|
removetags.is_safe = True
|
||||||
removetags = stringfilter(removetags)
|
removetags = stringfilter(removetags)
|
||||||
|
|
||||||
def striptags(value):
|
def striptags(value):
|
||||||
"""Strips all [X]HTML tags."""
|
"""Strips all [X]HTML tags."""
|
||||||
from django.utils.html import strip_tags
|
from django.utils.html import strip_tags
|
||||||
return strip_tags(value)
|
return strip_tags(value)
|
||||||
|
striptags.is_safe = True
|
||||||
striptags = stringfilter(striptags)
|
striptags = stringfilter(striptags)
|
||||||
|
|
||||||
###################
|
###################
|
||||||
@ -315,6 +385,7 @@ def dictsort(value, arg):
|
|||||||
decorated = [(var_resolve(item), item) for item in value]
|
decorated = [(var_resolve(item), item) for item in value]
|
||||||
decorated.sort()
|
decorated.sort()
|
||||||
return [item[1] for item in decorated]
|
return [item[1] for item in decorated]
|
||||||
|
dictsort.is_safe = False
|
||||||
|
|
||||||
def dictsortreversed(value, arg):
|
def dictsortreversed(value, arg):
|
||||||
"""
|
"""
|
||||||
@ -326,6 +397,7 @@ def dictsortreversed(value, arg):
|
|||||||
decorated.sort()
|
decorated.sort()
|
||||||
decorated.reverse()
|
decorated.reverse()
|
||||||
return [item[1] for item in decorated]
|
return [item[1] for item in decorated]
|
||||||
|
dictsortreversed.is_safe = False
|
||||||
|
|
||||||
def first(value):
|
def first(value):
|
||||||
"""Returns the first item in a list."""
|
"""Returns the first item in a list."""
|
||||||
@ -333,25 +405,36 @@ def first(value):
|
|||||||
return value[0]
|
return value[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return u''
|
return u''
|
||||||
|
first.is_safe = True
|
||||||
|
|
||||||
def join(value, arg):
|
def join(value, arg):
|
||||||
"""Joins a list with a string, like Python's ``str.join(list)``."""
|
"""Joins a list with a string, like Python's ``str.join(list)``."""
|
||||||
try:
|
try:
|
||||||
return arg.join(map(force_unicode, value))
|
data = arg.join(map(force_unicode, value))
|
||||||
except AttributeError: # fail silently but nicely
|
except AttributeError: # fail silently but nicely
|
||||||
return value
|
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):
|
def length(value):
|
||||||
"""Returns the length of the value - useful for lists."""
|
"""Returns the length of the value - useful for lists."""
|
||||||
return len(value)
|
return len(value)
|
||||||
|
length.is_safe = True
|
||||||
|
|
||||||
def length_is(value, arg):
|
def length_is(value, arg):
|
||||||
"""Returns a boolean of whether the value's length is the argument."""
|
"""Returns a boolean of whether the value's length is the argument."""
|
||||||
return len(value) == int(arg)
|
return len(value) == int(arg)
|
||||||
|
length_is.is_safe = True
|
||||||
|
|
||||||
def random(value):
|
def random(value):
|
||||||
"""Returns a random item from the list."""
|
"""Returns a random item from the list."""
|
||||||
return random_module.choice(value)
|
return random_module.choice(value)
|
||||||
|
random.is_safe = True
|
||||||
|
|
||||||
def slice_(value, arg):
|
def slice_(value, arg):
|
||||||
"""
|
"""
|
||||||
@ -372,8 +455,9 @@ def slice_(value, arg):
|
|||||||
|
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return value # Fail silently.
|
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 --
|
Recursively takes a self-nested list and returns an HTML unordered list --
|
||||||
WITHOUT opening and closing <ul> tags.
|
WITHOUT opening and closing <ul> tags.
|
||||||
@ -394,6 +478,11 @@ def unordered_list(value):
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
"""
|
"""
|
||||||
|
if autoescape:
|
||||||
|
from django.utils.html import conditional_escape
|
||||||
|
escaper = conditional_escape
|
||||||
|
else:
|
||||||
|
escaper = lambda x: x
|
||||||
def convert_old_style_list(list_):
|
def convert_old_style_list(list_):
|
||||||
"""
|
"""
|
||||||
Converts old style lists to the new easier to understand format.
|
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 = _helper(sublist_item, tabs+1)
|
||||||
sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
|
sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
|
||||||
indent, indent)
|
indent, indent)
|
||||||
output.append('%s<li>%s%s</li>' % (indent, force_unicode(title),
|
output.append('%s<li>%s%s</li>' % (indent,
|
||||||
sublist))
|
escaper(force_unicode(title)), sublist))
|
||||||
i += 1
|
i += 1
|
||||||
return '\n'.join(output)
|
return '\n'.join(output)
|
||||||
value, converted = convert_old_style_list(value)
|
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 #
|
# INTEGERS #
|
||||||
@ -457,6 +548,7 @@ def unordered_list(value):
|
|||||||
def add(value, arg):
|
def add(value, arg):
|
||||||
"""Adds the arg to the value."""
|
"""Adds the arg to the value."""
|
||||||
return int(value) + int(arg)
|
return int(value) + int(arg)
|
||||||
|
add.is_safe = False
|
||||||
|
|
||||||
def get_digit(value, arg):
|
def get_digit(value, arg):
|
||||||
"""
|
"""
|
||||||
@ -476,6 +568,7 @@ def get_digit(value, arg):
|
|||||||
return int(str(value)[-arg])
|
return int(str(value)[-arg])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return 0
|
return 0
|
||||||
|
get_digit.is_safe = False
|
||||||
|
|
||||||
###################
|
###################
|
||||||
# DATES #
|
# DATES #
|
||||||
@ -489,6 +582,7 @@ def date(value, arg=None):
|
|||||||
if arg is None:
|
if arg is None:
|
||||||
arg = settings.DATE_FORMAT
|
arg = settings.DATE_FORMAT
|
||||||
return format(value, arg)
|
return format(value, arg)
|
||||||
|
date.is_safe = False
|
||||||
|
|
||||||
def time(value, arg=None):
|
def time(value, arg=None):
|
||||||
"""Formats a time according to the given format."""
|
"""Formats a time according to the given format."""
|
||||||
@ -498,6 +592,7 @@ def time(value, arg=None):
|
|||||||
if arg is None:
|
if arg is None:
|
||||||
arg = settings.TIME_FORMAT
|
arg = settings.TIME_FORMAT
|
||||||
return time_format(value, arg)
|
return time_format(value, arg)
|
||||||
|
time.is_safe = False
|
||||||
|
|
||||||
def timesince(value, arg=None):
|
def timesince(value, arg=None):
|
||||||
"""Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
|
"""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:
|
if arg:
|
||||||
return timesince(arg, value)
|
return timesince(arg, value)
|
||||||
return timesince(value)
|
return timesince(value)
|
||||||
|
timesince.is_safe = False
|
||||||
|
|
||||||
def timeuntil(value, arg=None):
|
def timeuntil(value, arg=None):
|
||||||
"""Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
|
"""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:
|
if arg:
|
||||||
return timesince(arg, value)
|
return timesince(arg, value)
|
||||||
return timesince(datetime.now(), value)
|
return timesince(datetime.now(), value)
|
||||||
|
timeuntil.is_safe = False
|
||||||
|
|
||||||
###################
|
###################
|
||||||
# LOGIC #
|
# LOGIC #
|
||||||
@ -525,16 +622,19 @@ def timeuntil(value, arg=None):
|
|||||||
def default(value, arg):
|
def default(value, arg):
|
||||||
"""If value is unavailable, use given default."""
|
"""If value is unavailable, use given default."""
|
||||||
return value or arg
|
return value or arg
|
||||||
|
default.is_safe = False
|
||||||
|
|
||||||
def default_if_none(value, arg):
|
def default_if_none(value, arg):
|
||||||
"""If value is None, use given default."""
|
"""If value is None, use given default."""
|
||||||
if value is None:
|
if value is None:
|
||||||
return arg
|
return arg
|
||||||
return value
|
return value
|
||||||
|
default_if_none.is_safe = False
|
||||||
|
|
||||||
def divisibleby(value, arg):
|
def divisibleby(value, arg):
|
||||||
"""Returns True if the value is devisible by the argument."""
|
"""Returns True if the value is devisible by the argument."""
|
||||||
return int(value) % int(arg) == 0
|
return int(value) % int(arg) == 0
|
||||||
|
divisibleby.is_safe = False
|
||||||
|
|
||||||
def yesno(value, arg=None):
|
def yesno(value, arg=None):
|
||||||
"""
|
"""
|
||||||
@ -566,6 +666,7 @@ def yesno(value, arg=None):
|
|||||||
if value:
|
if value:
|
||||||
return yes
|
return yes
|
||||||
return no
|
return no
|
||||||
|
yesno.is_safe = False
|
||||||
|
|
||||||
###################
|
###################
|
||||||
# MISC #
|
# MISC #
|
||||||
@ -588,29 +689,30 @@ def filesizeformat(bytes):
|
|||||||
if bytes < 1024 * 1024 * 1024:
|
if bytes < 1024 * 1024 * 1024:
|
||||||
return ugettext("%.1f MB") % (bytes / (1024 * 1024))
|
return ugettext("%.1f MB") % (bytes / (1024 * 1024))
|
||||||
return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
|
return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
|
||||||
|
filesizeformat.is_safe = True
|
||||||
|
|
||||||
def pluralize(value, arg=u's'):
|
def pluralize(value, arg=u's'):
|
||||||
"""
|
"""
|
||||||
Returns a plural suffix if the value is not 1. By default, 's' is used as
|
Returns a plural suffix if the value is not 1. By default, 's' is used as
|
||||||
the suffix:
|
the suffix:
|
||||||
|
|
||||||
* If value is 0, vote{{ value|plurlize }} displays "0 votes".
|
* If value is 0, vote{{ value|pluralize }} displays "0 votes".
|
||||||
* If value is 1, vote{{ value|plurlize }} displays "1 vote".
|
* If value is 1, vote{{ value|pluralize }} displays "1 vote".
|
||||||
* If value is 2, vote{{ value|plurlize }} displays "2 votes".
|
* If value is 2, vote{{ value|pluralize }} displays "2 votes".
|
||||||
|
|
||||||
If an argument is provided, that string is used instead:
|
If an argument is provided, that string is used instead:
|
||||||
|
|
||||||
* If value is 0, class{{ value|plurlize:"es" }} displays "0 classes".
|
* If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".
|
||||||
* If value is 1, class{{ value|plurlize:"es" }} displays "1 class".
|
* If value is 1, class{{ value|pluralize:"es" }} displays "1 class".
|
||||||
* If value is 2, class{{ value|plurlize:"es" }} displays "2 classes".
|
* If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".
|
||||||
|
|
||||||
If the provided argument contains a comma, the text before the comma is
|
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
|
used for the singular case and the text after the comma is used for the
|
||||||
plural case:
|
plural case:
|
||||||
|
|
||||||
* If value is 0, cand{{ value|plurlize:"y,ies" }} displays "0 candies".
|
* If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".
|
||||||
* If value is 1, cand{{ value|plurlize:"y,ies" }} displays "1 candy".
|
* If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".
|
||||||
* If value is 2, cand{{ value|plurlize:"y,ies" }} displays "2 candies".
|
* If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".
|
||||||
"""
|
"""
|
||||||
if not u',' in arg:
|
if not u',' in arg:
|
||||||
arg = u',' + arg
|
arg = u',' + arg
|
||||||
@ -631,11 +733,13 @@ def pluralize(value, arg=u's'):
|
|||||||
except TypeError: # len() of unsized object.
|
except TypeError: # len() of unsized object.
|
||||||
pass
|
pass
|
||||||
return singular_suffix
|
return singular_suffix
|
||||||
|
pluralize.is_safe = False
|
||||||
|
|
||||||
def phone2numeric(value):
|
def phone2numeric(value):
|
||||||
"""Takes a phone number and converts it in to its numerical equivalent."""
|
"""Takes a phone number and converts it in to its numerical equivalent."""
|
||||||
from django.utils.text import phone2numeric
|
from django.utils.text import phone2numeric
|
||||||
return phone2numeric(value)
|
return phone2numeric(value)
|
||||||
|
phone2numeric.is_safe = True
|
||||||
|
|
||||||
def pprint(value):
|
def pprint(value):
|
||||||
"""A wrapper around pprint.pprint -- for debugging, really."""
|
"""A wrapper around pprint.pprint -- for debugging, really."""
|
||||||
@ -644,6 +748,7 @@ def pprint(value):
|
|||||||
return pformat(value)
|
return pformat(value)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
return u"Error in formatting: %s" % force_unicode(e, errors="replace")
|
return u"Error in formatting: %s" % force_unicode(e, errors="replace")
|
||||||
|
pprint.is_safe = True
|
||||||
|
|
||||||
# Syntax: register.filter(name of filter, callback)
|
# Syntax: register.filter(name of filter, callback)
|
||||||
register.filter(add)
|
register.filter(add)
|
||||||
@ -662,6 +767,7 @@ register.filter(filesizeformat)
|
|||||||
register.filter(first)
|
register.filter(first)
|
||||||
register.filter(fix_ampersands)
|
register.filter(fix_ampersands)
|
||||||
register.filter(floatformat)
|
register.filter(floatformat)
|
||||||
|
register.filter(force_escape)
|
||||||
register.filter(get_digit)
|
register.filter(get_digit)
|
||||||
register.filter(iriencode)
|
register.filter(iriencode)
|
||||||
register.filter(join)
|
register.filter(join)
|
||||||
@ -679,6 +785,7 @@ register.filter(pprint)
|
|||||||
register.filter(removetags)
|
register.filter(removetags)
|
||||||
register.filter(random)
|
register.filter(random)
|
||||||
register.filter(rjust)
|
register.filter(rjust)
|
||||||
|
register.filter(safe)
|
||||||
register.filter('slice', slice_)
|
register.filter('slice', slice_)
|
||||||
register.filter(slugify)
|
register.filter(slugify)
|
||||||
register.filter(stringformat)
|
register.filter(stringformat)
|
||||||
|
@ -14,9 +14,25 @@ from django.template import get_library, Library, InvalidTemplateLibrary
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.encoding import smart_str, smart_unicode
|
from django.utils.encoding import smart_str, smart_unicode
|
||||||
from django.utils.itercompat import groupby
|
from django.utils.itercompat import groupby
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
register = Library()
|
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):
|
class CommentNode(Node):
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
return ''
|
return ''
|
||||||
@ -392,6 +408,22 @@ class WithNode(Node):
|
|||||||
context.pop()
|
context.pop()
|
||||||
return output
|
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
|
#@register.tag
|
||||||
def comment(parser, token):
|
def comment(parser, token):
|
||||||
"""
|
"""
|
||||||
@ -492,12 +524,15 @@ def do_filter(parser, token):
|
|||||||
|
|
||||||
Sample usage::
|
Sample usage::
|
||||||
|
|
||||||
{% filter escape|lower %}
|
{% filter force_escape|lower %}
|
||||||
This text will be HTML-escaped, and will appear in lowercase.
|
This text will be HTML-escaped, and will appear in lowercase.
|
||||||
{% endfilter %}
|
{% endfilter %}
|
||||||
"""
|
"""
|
||||||
_, rest = token.contents.split(None, 1)
|
_, rest = token.contents.split(None, 1)
|
||||||
filter_expr = parser.compile_filter("var|%s" % (rest))
|
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',))
|
nodelist = parser.parse(('endfilter',))
|
||||||
parser.delete_first_token()
|
parser.delete_first_token()
|
||||||
return FilterNode(filter_expr, nodelist)
|
return FilterNode(filter_expr, nodelist)
|
||||||
|
@ -3,6 +3,7 @@ import urllib
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.utils.functional import Promise
|
from django.utils.functional import Promise
|
||||||
|
from django.utils.safestring import SafeData, mark_safe
|
||||||
|
|
||||||
class DjangoUnicodeDecodeError(UnicodeDecodeError):
|
class DjangoUnicodeDecodeError(UnicodeDecodeError):
|
||||||
def __init__(self, obj, *args):
|
def __init__(self, obj, *args):
|
||||||
@ -51,7 +52,10 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
|
|||||||
else:
|
else:
|
||||||
s = unicode(str(s), encoding, errors)
|
s = unicode(str(s), encoding, errors)
|
||||||
elif not isinstance(s, unicode):
|
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:
|
except UnicodeDecodeError, e:
|
||||||
raise DjangoUnicodeDecodeError(s, *e.args)
|
raise DjangoUnicodeDecodeError(s, *e.args)
|
||||||
return s
|
return s
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import re
|
import re
|
||||||
import string
|
import string
|
||||||
|
|
||||||
|
from django.utils.safestring import SafeData, mark_safe
|
||||||
from django.utils.encoding import force_unicode
|
from django.utils.encoding import force_unicode
|
||||||
from django.utils.functional import allow_lazy
|
from django.utils.functional import allow_lazy
|
||||||
|
|
||||||
@ -27,16 +28,28 @@ del x # Temporary variable
|
|||||||
|
|
||||||
def escape(html):
|
def escape(html):
|
||||||
"Return the given HTML with ampersands, quotes and carets encoded."
|
"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)
|
escape = allow_lazy(escape, unicode)
|
||||||
|
|
||||||
def linebreaks(value):
|
def conditional_escape(html):
|
||||||
"Convert newlines into <p> and <br />s."
|
"""
|
||||||
|
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
|
value = re.sub(r'\r\n|\r|\n', '\n', force_unicode(value)) # normalize newlines
|
||||||
paras = re.split('\n{2,}', value)
|
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)
|
return u'\n\n'.join(paras)
|
||||||
linebreaks = allow_lazy(linebreaks, unicode)
|
linebreaks = allow_lazy(linebreaks, unicode)
|
||||||
|
|
||||||
def strip_tags(value):
|
def strip_tags(value):
|
||||||
"Return the given HTML with all tags stripped."
|
"Return the given HTML with all tags stripped."
|
||||||
@ -58,7 +71,7 @@ def fix_ampersands(value):
|
|||||||
return unencoded_ampersands_re.sub('&', force_unicode(value))
|
return unencoded_ampersands_re.sub('&', force_unicode(value))
|
||||||
fix_ampersands = allow_lazy(fix_ampersands, unicode)
|
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.
|
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"
|
If nofollow is True, the URLs in link text will get a rel="nofollow"
|
||||||
attribute.
|
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))
|
words = word_split_re.split(force_unicode(text))
|
||||||
nofollow_attr = nofollow and ' rel="nofollow"' or ''
|
nofollow_attr = nofollow and ' rel="nofollow"' or ''
|
||||||
for i, word in enumerate(words):
|
for i, word in enumerate(words):
|
||||||
match = punctuation_re.match(word)
|
match = punctuation_re.match(word)
|
||||||
if match:
|
if match:
|
||||||
lead, middle, trail = match.groups()
|
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 \
|
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 \
|
len(middle) > 0 and middle[0] in string.letters + string.digits and \
|
||||||
(middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))):
|
(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>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="summary">
|
<div id="summary">
|
||||||
<h1>{{ exception_type }} at {{ request.path|escape }}</h1>
|
<h1>{{ exception_type }} at {{ request.path|escape }}</h1>
|
||||||
<h2>{{ exception_value|escape }}</h2>
|
<h2>{{ exception_value|escape }}</h2>
|
||||||
@ -395,7 +394,7 @@ TECHNICAL_500_TEMPLATE = """
|
|||||||
<div id="template">
|
<div id="template">
|
||||||
<h2>Template error</h2>
|
<h2>Template error</h2>
|
||||||
<p>In template <code>{{ template_info.name }}</code>, error at line <strong>{{ template_info.line }}</strong></p>
|
<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 %}">
|
<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 %}
|
{% for source_line in template_info.source_lines %}
|
||||||
{% ifequal source_line.0 template_info.line %}
|
{% ifequal source_line.0 template_info.line %}
|
||||||
@ -413,6 +412,7 @@ TECHNICAL_500_TEMPLATE = """
|
|||||||
<h2>Traceback <span>(innermost last)</span></h2>
|
<h2>Traceback <span>(innermost last)</span></h2>
|
||||||
<div class="commands"><a href="#" onclick="return switchPastebinFriendly(this);">Switch to copy-and-paste view</a></div>
|
<div class="commands"><a href="#" onclick="return switchPastebinFriendly(this);">Switch to copy-and-paste view</a></div>
|
||||||
<br/>
|
<br/>
|
||||||
|
{% autoescape off %}
|
||||||
<div id="browserTraceback">
|
<div id="browserTraceback">
|
||||||
<ul class="traceback">
|
<ul class="traceback">
|
||||||
{% for frame in frames %}
|
{% for frame in frames %}
|
||||||
@ -422,11 +422,11 @@ TECHNICAL_500_TEMPLATE = """
|
|||||||
{% if frame.context_line %}
|
{% if frame.context_line %}
|
||||||
<div class="context" id="c{{ frame.id }}">
|
<div class="context" id="c{{ frame.id }}">
|
||||||
{% if frame.pre_context %}
|
{% 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 %}
|
{% 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 %}
|
{% 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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -446,7 +446,7 @@ TECHNICAL_500_TEMPLATE = """
|
|||||||
{% for var in frame.vars|dictsort:"0" %}
|
{% for var in frame.vars|dictsort:"0" %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ var.0 }}</td>
|
<td>{{ var.0 }}</td>
|
||||||
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
|
<td class="code"><div>{{ var.1|pprint }}</div></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -466,7 +466,7 @@ Traceback (most recent call last):<br/>
|
|||||||
{% for frame in frames %}
|
{% for frame in frames %}
|
||||||
File "{{ frame.filename }}" in {{ frame.function }}<br/>
|
File "{{ frame.filename }}" in {{ frame.function }}<br/>
|
||||||
{% if frame.context_line %}
|
{% if frame.context_line %}
|
||||||
{{ frame.lineno }}. {{ frame.context_line|escape }}<br/>
|
{{ frame.lineno }}. {{ frame.context_line }}<br/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}<br/>
|
{% endfor %}<br/>
|
||||||
{{ exception_type }} at {{ request.path|escape }}<br/>
|
{{ exception_type }} at {{ request.path|escape }}<br/>
|
||||||
@ -476,6 +476,7 @@ Traceback (most recent call last):<br/>
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
{% endautoescape %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="requestinfo">
|
<div id="requestinfo">
|
||||||
@ -494,7 +495,7 @@ Traceback (most recent call last):<br/>
|
|||||||
{% for var in request.GET.items %}
|
{% for var in request.GET.items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ var.0 }}</td>
|
<td>{{ var.0 }}</td>
|
||||||
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
|
<td class="code"><div>{{ var.1|pprint }}</div></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -516,7 +517,7 @@ Traceback (most recent call last):<br/>
|
|||||||
{% for var in request.POST.items %}
|
{% for var in request.POST.items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ var.0 }}</td>
|
<td>{{ var.0 }}</td>
|
||||||
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
|
<td class="code"><div>{{ var.1|pprint }}</div></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -538,7 +539,7 @@ Traceback (most recent call last):<br/>
|
|||||||
{% for var in request.COOKIES.items %}
|
{% for var in request.COOKIES.items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ var.0 }}</td>
|
<td>{{ var.0 }}</td>
|
||||||
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
|
<td class="code"><div>{{ var.1|pprint }}</div></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -559,7 +560,7 @@ Traceback (most recent call last):<br/>
|
|||||||
{% for var in request.META.items|dictsort:"0" %}
|
{% for var in request.META.items|dictsort:"0" %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ var.0 }}</td>
|
<td>{{ var.0 }}</td>
|
||||||
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
|
<td class="code"><div>{{ var.1|pprint }}</div></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -578,7 +579,7 @@ Traceback (most recent call last):<br/>
|
|||||||
{% for var in settings.items|dictsort:"0" %}
|
{% for var in settings.items|dictsort:"0" %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ var.0 }}</td>
|
<td>{{ var.0 }}</td>
|
||||||
<td class="code"><div>{{ var.1|pprint|escape }}</div></td>
|
<td class="code"><div>{{ var.1|pprint }}</div></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -593,7 +594,6 @@ Traceback (most recent call last):<br/>
|
|||||||
display a standard 500 page.
|
display a standard 500 page.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
@ -645,12 +645,12 @@ TECHNICAL_404_TEMPLATE = """
|
|||||||
</p>
|
</p>
|
||||||
<ol>
|
<ol>
|
||||||
{% for pattern in urlpatterns %}
|
{% for pattern in urlpatterns %}
|
||||||
<li>{{ pattern|escape }}</li>
|
<li>{{ pattern }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ol>
|
||||||
<p>The current URL, <code>{{ request_path|escape }}</code>, didn't match any of these.</p>
|
<p>The current URL, <code>{{ request_path|escape }}</code>, didn't match any of these.</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{{ reason|escape }}</p>
|
<p>{{ reason }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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
|
two similarly-named ``{% block %}`` tags in a template, that template's parent
|
||||||
wouldn't know which one of the blocks' content to use.
|
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
|
Using the built-in reference
|
||||||
============================
|
============================
|
||||||
|
|
||||||
@ -374,6 +472,24 @@ available, and what they do.
|
|||||||
Built-in tag reference
|
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
|
block
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
@ -452,7 +568,7 @@ just like in variable syntax.
|
|||||||
|
|
||||||
Sample usage::
|
Sample usage::
|
||||||
|
|
||||||
{% filter escape|lower %}
|
{% filter force_escape|lower %}
|
||||||
This text will be HTML-escaped, and will appear in all lowercase.
|
This text will be HTML-escaped, and will appear in all lowercase.
|
||||||
{% endfilter %}
|
{% endfilter %}
|
||||||
|
|
||||||
@ -1076,6 +1192,10 @@ Returns true if the value is divisible by the argument.
|
|||||||
escape
|
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:
|
Escapes a string's HTML. Specifically, it makes these replacements:
|
||||||
|
|
||||||
* ``"&"`` to ``"&"``
|
* ``"&"`` to ``"&"``
|
||||||
@ -1084,6 +1204,16 @@ Escapes a string's HTML. Specifically, it makes these replacements:
|
|||||||
* ``'"'`` (double quote) to ``'"'``
|
* ``'"'`` (double quote) to ``'"'``
|
||||||
* ``"'"`` (single 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
|
filesizeformat
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -1140,6 +1270,17 @@ value Template Output
|
|||||||
Using ``floatformat`` with no argument is equivalent to using ``floatformat``
|
Using ``floatformat`` with no argument is equivalent to using ``floatformat``
|
||||||
with an argument of ``-1``.
|
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
|
get_digit
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
||||||
@ -1264,6 +1405,12 @@ Right-aligns the value in a field of a given width.
|
|||||||
|
|
||||||
**Argument:** field size
|
**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
|
slice
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
|
@ -219,13 +219,13 @@ be replaced with the name of the invalid variable.
|
|||||||
|
|
||||||
While ``TEMPLATE_STRING_IF_INVALID`` can be a useful debugging tool,
|
While ``TEMPLATE_STRING_IF_INVALID`` can be a useful debugging tool,
|
||||||
it is a bad idea to turn it on as a 'development default'.
|
it is a bad idea to turn it on as a 'development default'.
|
||||||
|
|
||||||
Many templates, including those in the Admin site, rely upon the
|
Many templates, including those in the Admin site, rely upon the
|
||||||
silence of the template system when a non-existent variable is
|
silence of the template system when a non-existent variable is
|
||||||
encountered. If you assign a value other than ``''`` to
|
encountered. If you assign a value other than ``''`` to
|
||||||
``TEMPLATE_STRING_IF_INVALID``, you will experience rendering
|
``TEMPLATE_STRING_IF_INVALID``, you will experience rendering
|
||||||
problems with these templates and sites.
|
problems with these templates and sites.
|
||||||
|
|
||||||
Generally, ``TEMPLATE_STRING_IF_INVALID`` should only be enabled
|
Generally, ``TEMPLATE_STRING_IF_INVALID`` should only be enabled
|
||||||
in order to debug a specific template problem, then cleared
|
in order to debug a specific template problem, then cleared
|
||||||
once debugging is complete.
|
once debugging is complete.
|
||||||
@ -722,6 +722,95 @@ decorator instead::
|
|||||||
If you leave off the ``name`` argument, as in the second example above, Django
|
If you leave off the ``name`` argument, as in the second example above, Django
|
||||||
will use the function's name as the filter name.
|
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
|
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
|
efficient template system, because a template can render multiple context
|
||||||
without having to be parsed multiple times.
|
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
|
Registering the tag
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -917,7 +1043,7 @@ current context, available in the ``render`` method::
|
|||||||
def __init__(self, date_to_be_formatted, format_string):
|
def __init__(self, date_to_be_formatted, format_string):
|
||||||
self.date_to_be_formatted = date_to_be_formatted
|
self.date_to_be_formatted = date_to_be_formatted
|
||||||
self.format_string = format_string
|
self.format_string = format_string
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
try:
|
try:
|
||||||
actual_date = resolve_variable(self.date_to_be_formatted, context)
|
actual_date = resolve_variable(self.date_to_be_formatted, context)
|
||||||
@ -934,26 +1060,26 @@ format it accordingly.
|
|||||||
``template.resolve_variable()`` is still available, but has been deprecated
|
``template.resolve_variable()`` is still available, but has been deprecated
|
||||||
in favor of a new ``template.Variable`` class. Using this class will usually
|
in favor of a new ``template.Variable`` class. Using this class will usually
|
||||||
be more efficient than calling ``template.resolve_variable``
|
be more efficient than calling ``template.resolve_variable``
|
||||||
|
|
||||||
To use the ``Variable`` class, simply instantiate it with the name of the
|
To use the ``Variable`` class, simply instantiate it with the name of the
|
||||||
variable to be resolved, and then call ``variable.resolve(context)``. So,
|
variable to be resolved, and then call ``variable.resolve(context)``. So,
|
||||||
in the development version, the above example would be more correctly
|
in the development version, the above example would be more correctly
|
||||||
written as:
|
written as:
|
||||||
|
|
||||||
.. parsed-literal::
|
.. parsed-literal::
|
||||||
|
|
||||||
class FormatTimeNode(template.Node):
|
class FormatTimeNode(template.Node):
|
||||||
def __init__(self, date_to_be_formatted, format_string):
|
def __init__(self, date_to_be_formatted, format_string):
|
||||||
self.date_to_be_formatted = **Variable(date_to_be_formatted)**
|
self.date_to_be_formatted = **Variable(date_to_be_formatted)**
|
||||||
self.format_string = format_string
|
self.format_string = format_string
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
try:
|
try:
|
||||||
actual_date = **self.date_to_be_formatted.resolve(context)**
|
actual_date = **self.date_to_be_formatted.resolve(context)**
|
||||||
return actual_date.strftime(self.format_string)
|
return actual_date.strftime(self.format_string)
|
||||||
except template.VariableDoesNotExist:
|
except template.VariableDoesNotExist:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
Changes are highlighted in bold.
|
Changes are highlighted in bold.
|
||||||
|
|
||||||
Variable resolution will throw a ``VariableDoesNotExist`` exception if it cannot
|
Variable resolution will throw a ``VariableDoesNotExist`` exception if it cannot
|
||||||
|
@ -194,10 +194,10 @@ u'a stri to be maled'
|
|||||||
>>> cut(u'a string to be mangled', 'strings')
|
>>> cut(u'a string to be mangled', 'strings')
|
||||||
u'a string to be mangled'
|
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'
|
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'
|
u'<some html & special characters > here \xc4\x90\xc3\x85\xe2\x82\xac\xc2\xa3'
|
||||||
|
|
||||||
>>> linebreaks(u'line 1')
|
>>> linebreaks(u'line 1')
|
||||||
|
@ -1554,7 +1554,7 @@ does not have help text, nothing will be output.
|
|||||||
... </form>''')
|
... </form>''')
|
||||||
>>> print t.render(Context({'form': UserRegistration(auto_id=False)}))
|
>>> print t.render(Context({'form': UserRegistration(auto_id=False)}))
|
||||||
<form action="">
|
<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>Password1: <input type="password" name="password1" /></p>
|
||||||
<p>Password2: <input type="password" name="password2" /></p>
|
<p>Password2: <input type="password" name="password2" /></p>
|
||||||
<input type="submit" />
|
<input type="submit" />
|
||||||
|
@ -52,7 +52,7 @@ __test__ = {
|
|||||||
'localflavor_sk_tests': localflavor_sk_tests,
|
'localflavor_sk_tests': localflavor_sk_tests,
|
||||||
'localflavor_uk_tests': localflavor_uk_tests,
|
'localflavor_uk_tests': localflavor_uk_tests,
|
||||||
'localflavor_us_tests': localflavor_us_tests,
|
'localflavor_us_tests': localflavor_us_tests,
|
||||||
'regressions_tests': regression_tests,
|
'regression_tests': regression_tests,
|
||||||
'formset_tests': formset_tests,
|
'formset_tests': formset_tests,
|
||||||
'media_tests': media_tests,
|
'media_tests': media_tests,
|
||||||
'util_tests': util_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.template import Template, Context, add_to_builtins
|
||||||
from django.utils.dateformat import DateFormat
|
from django.utils.dateformat import DateFormat
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
from django.utils.html import escape
|
||||||
|
|
||||||
add_to_builtins('django.contrib.humanize.templatetags.humanize')
|
add_to_builtins('django.contrib.humanize.templatetags.humanize')
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ class HumanizeTests(unittest.TestCase):
|
|||||||
test_content = test_list[index]
|
test_content = test_list[index]
|
||||||
t = Template('{{ test_content|%s }}' % method)
|
t = Template('{{ test_content|%s }}' % method)
|
||||||
rendered = t.render(Context(locals())).strip()
|
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]))
|
msg="%s test failed, produced %s, should've produced %s" % (method, rendered, result_list[index]))
|
||||||
|
|
||||||
def test_ordinal(self):
|
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 import loader
|
||||||
from django.template.loaders import app_directories, filesystem
|
from django.template.loaders import app_directories, filesystem
|
||||||
from django.utils.translation import activate, deactivate, ugettext as _
|
from django.utils.translation import activate, deactivate, ugettext as _
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.tzinfo import LocalTimezone
|
from django.utils.tzinfo import LocalTimezone
|
||||||
|
|
||||||
from unicode import unicode_tests
|
from unicode import unicode_tests
|
||||||
|
import filters
|
||||||
|
|
||||||
# Some other tests we would like to run
|
# Some other tests we would like to run
|
||||||
__test__ = {
|
__test__ = {
|
||||||
@ -120,20 +122,97 @@ class Templates(unittest.TestCase):
|
|||||||
['/dir1/index.html'])
|
['/dir1/index.html'])
|
||||||
|
|
||||||
def test_templates(self):
|
def test_templates(self):
|
||||||
# NOW and NOW_tz are used by timesince tag tests.
|
template_tests = self.get_template_tests()
|
||||||
NOW = datetime.now()
|
filter_tests = filters.get_filter_tests()
|
||||||
NOW_tz = datetime.now(LocalTimezone(datetime.now()))
|
|
||||||
|
|
||||||
|
# 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 --
|
# SYNTAX --
|
||||||
# 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
|
# 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
|
||||||
TEMPLATE_TESTS = {
|
return {
|
||||||
|
### BASIC SYNTAX ################################################
|
||||||
### BASIC SYNTAX ##########################################################
|
|
||||||
|
|
||||||
# Plain text should go through the template parser untouched
|
# Plain text should go through the template parser untouched
|
||||||
'basic-syntax01': ("something cool", {}, "something cool"),
|
'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"),
|
'basic-syntax02': ("{{ headline }}", {'headline':'Success'}, "Success"),
|
||||||
|
|
||||||
# More than one replacement variable is allowed in a template
|
# 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"),
|
'filter-syntax09': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"),
|
||||||
|
|
||||||
# Escaped string as argument
|
# 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
|
# Variable as argument
|
||||||
'filter-syntax11': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'),
|
'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)),
|
# '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))
|
# '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 ########################################################
|
### URL TAG ########################################################
|
||||||
# Successes
|
# Successes
|
||||||
'url01' : ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),
|
'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),
|
'cache08' : ('{% load cache %}{% cache %}{% endcache %}', {}, template.TemplateSyntaxError),
|
||||||
'cache09' : ('{% load cache %}{% cache 1 %}{% endcache %}', {}, template.TemplateSyntaxError),
|
'cache09' : ('{% load cache %}{% cache 1 %}{% endcache %}', {}, template.TemplateSyntaxError),
|
||||||
'cache10' : ('{% load cache %}{% cache foo bar %}{% 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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user