Skip to content

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also .

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also .
...
  • 5 commits
  • 25 files changed
  • 0 commit comments
  • 1 contributor
View
10 django/contrib/admin/helpers.py
@@ -10,7 +10,7 @@
from django.forms.util import flatatt
from django.template.defaultfilters import capfirst
from django.utils.encoding import force_unicode, smart_unicode
-from django.utils.html import escape, conditional_escape
+from django.utils.html import conditional_escape, format_html
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
@@ -163,11 +163,9 @@ def label_tag(self):
if not self.is_first:
attrs["class"] = "inline"
label = self.field['label']
- contents = capfirst(force_unicode(escape(label))) + ":"
- return mark_safe('<label%(attrs)s>%(contents)s</label>' % {
- "attrs": flatatt(attrs),
- "contents": contents,
- })
+ return format_html('<label{0}>{1}:</label>',
+ flatatt(attrs),
+ capfirst(force_unicode(label)))
def contents(self):
from django.contrib.admin.templatetags.admin_list import _boolean_icon
View
3 django/contrib/admin/models.py
@@ -6,7 +6,6 @@
from django.contrib.admin.util import quote
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode
-from django.utils.safestring import mark_safe
ADDITION = 1
CHANGE = 2
@@ -66,5 +65,5 @@ def get_admin_url(self):
This is relative to the Django admin index page.
"""
if self.content_type and self.object_id:
- return mark_safe("%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, quote(self.object_id)))
+ return "%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, quote(self.object_id))
return None
View
2 django/contrib/admin/options.py
@@ -745,7 +745,7 @@ def render_change_form(self, request, context, add=False, change=False, form_url
'has_file_field': True, # FIXME - this should check if form or formsets have a FileField,
'has_absolute_url': hasattr(self.model, 'get_absolute_url'),
'ordered_objects': ordered_objects,
- 'form_url': mark_safe(form_url),
+ 'form_url': form_url,
'opts': opts,
'content_type_id': ContentType.objects.get_for_model(self.model).id,
'save_as': self.save_as,
View
2 django/contrib/admin/templates/admin/delete_confirmation.html
@@ -7,7 +7,7 @@
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ app_label|capfirst }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst|escape }}</a>
-&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}{{ object.pk }}">{{ object|truncatewords:"18" }}</a>
+&rsaquo; <a href="{% url opts|admin_urlname:'change' object.pk|admin_urlquote %}">{{ object|truncatewords:"18" }}</a>
&rsaquo; {% trans 'Delete' %}
</div>
{% endblock %}
View
2 django/contrib/admin/templates/admin/object_history.html
@@ -7,7 +7,7 @@
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=app_label %}">{{ app_label|capfirst|escape }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ module_name }}</a>
-&rsaquo; <a href="{% url opts|admin_urlname:'change' object.pk %}">{{ object|truncatewords:"18" }}</a>
+&rsaquo; <a href="{% url opts|admin_urlname:'change' object.pk|admin_urlquote %}">{{ object|truncatewords:"18" }}</a>
&rsaquo; {% trans 'History' %}
</div>
{% endblock %}
View
42 django/contrib/admin/templatetags/admin_list.py
@@ -10,7 +10,7 @@
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.utils import formats
-from django.utils.html import escape, conditional_escape
+from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.text import capfirst
from django.utils.translation import ugettext as _
@@ -31,9 +31,12 @@ def paginator_number(cl,i):
if i == DOT:
return '... '
elif i == cl.page_num:
- return mark_safe('<span class="this-page">%d</span> ' % (i+1))
+ return format_html('<span class="this-page">{}</span> ', i+1)
else:
- return mark_safe('<a href="%s"%s>%d</a> ' % (escape(cl.get_query_string({PAGE_VAR: i})), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1))
+ return format_html('<a href="{0}"{1}>{2}</a> ',
+ cl.get_query_string({PAGE_VAR: i}),
+ mark_safe(' class="end"' if i == cl.paginator.num_pages-1 else ''),
+ i+1)
@register.inclusion_tag('admin/pagination.html')
def pagination(cl):
@@ -159,13 +162,14 @@ def result_headers(cl):
"url_primary": cl.get_query_string({ORDER_VAR: '.'.join(o_list_primary)}),
"url_remove": cl.get_query_string({ORDER_VAR: '.'.join(o_list_remove)}),
"url_toggle": cl.get_query_string({ORDER_VAR: '.'.join(o_list_toggle)}),
- "class_attrib": mark_safe(th_classes and ' class="%s"' % ' '.join(th_classes) or '')
+ "class_attrib": format_html(' class="{}"', ' '.join(th_classes))
+ if th_classes else '',
}
def _boolean_icon(field_val):
icon_url = static('admin/img/icon-%s.gif' %
{True: 'yes', False: 'no', None: 'unknown'}[field_val])
- return mark_safe('<img src="%s" alt="%s" />' % (icon_url, field_val))
+ return format_html('<img src="{0}" alt="{1}" />', icon_url, field_val)
def items_for_result(cl, result, form):
"""
@@ -182,31 +186,29 @@ def items_for_result(cl, result, form):
else:
if f is None:
if field_name == 'action_checkbox':
- row_class = ' class="action-checkbox"'
+ row_class = mark_safe(' class="action-checkbox"')
allow_tags = getattr(attr, 'allow_tags', False)
boolean = getattr(attr, 'boolean', False)
if boolean:
allow_tags = True
result_repr = display_for_value(value, boolean)
# Strip HTML tags in the resulting text, except if the
# function has an "allow_tags" attribute set to True.
- if not allow_tags:
- result_repr = escape(result_repr)
- else:
+ if allow_tags:
result_repr = mark_safe(result_repr)
if isinstance(value, (datetime.date, datetime.time)):
- row_class = ' class="nowrap"'
+ row_class = mark_safe(' class="nowrap"')
else:
if isinstance(f.rel, models.ManyToOneRel):
field_val = getattr(result, f.name)
if field_val is None:
result_repr = EMPTY_CHANGELIST_VALUE
else:
- result_repr = escape(field_val)
+ result_repr = field_val
else:
result_repr = display_for_field(value, f)
if isinstance(f, (models.DateField, models.TimeField, models.ForeignKey)):
- row_class = ' class="nowrap"'
+ row_class = mark_safe(' class="nowrap"')
if force_unicode(result_repr) == '':
result_repr = mark_safe('&nbsp;')
# If list_display_links not defined, add the link tag to the first field
@@ -222,8 +224,14 @@ def items_for_result(cl, result, form):
attr = pk
value = result.serializable_value(attr)
result_id = repr(force_unicode(value))[1:]
- yield mark_safe('<%s%s><a href="%s"%s>%s</a></%s>' % \
- (table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), conditional_escape(result_repr), table_tag))
+ yield format_html('<{0}{1}><a href="{2}"{3}>{4}</a></{5}>',
+ table_tag,
+ row_class,
+ url,
+ format_html(' onclick="opener.dismissRelatedLookupPopup(window, {0}); return false;"', result_id)
+ if cl.is_popup else '',
+ result_repr,
+ table_tag)
else:
# By default the fields come from ModelAdmin.list_editable, but if we pull
# the fields out of the form instead of list_editable custom admins
@@ -233,11 +241,9 @@ def items_for_result(cl, result, form):
form[cl.model._meta.pk.name].is_hidden)):
bf = form[field_name]
result_repr = mark_safe(force_unicode(bf.errors) + force_unicode(bf))
- else:
- result_repr = conditional_escape(result_repr)
- yield mark_safe('<td%s>%s</td>' % (row_class, result_repr))
+ yield format_html('<td{0}>{1}</td>', row_class, result_repr)
if form and not form[cl.model._meta.pk.name].is_hidden:
- yield mark_safe('<td>%s</td>' % force_unicode(form[cl.model._meta.pk.name]))
+ yield format_html('<td>{0}</td>', force_unicode(form[cl.model._meta.pk.name]))
class ResultList(list):
# Wrapper class used to return items in a list_editable
View
8 django/contrib/admin/templatetags/admin_urls.py
@@ -1,8 +1,14 @@
-from django.core.urlresolvers import reverse, NoReverseMatch
+from django.core.urlresolvers import reverse
from django import template
+from django.contrib.admin.util import quote
register = template.Library()
@register.filter
def admin_urlname(value, arg):
return 'admin:%s_%s_%s' % (value.app_label, value.module_name, arg)
+
+
+@register.filter
+def admin_urlquote(value):
+ return quote(value)
View
11 django/contrib/admin/util.py
@@ -9,8 +9,7 @@
from django.db.models.related import RelatedObject
from django.forms.forms import pretty_name
from django.utils import formats
-from django.utils.html import escape
-from django.utils.safestring import mark_safe
+from django.utils.html import format_html
from django.utils.text import capfirst
from django.utils import timezone
from django.utils.encoding import force_unicode, smart_unicode, smart_str
@@ -124,10 +123,10 @@ def format_callback(obj):
if not user.has_perm(p):
perms_needed.add(opts.verbose_name)
# Display a link to the admin page.
- return mark_safe('%s: <a href="%s">%s</a>' %
- (escape(capfirst(opts.verbose_name)),
- admin_url,
- escape(obj)))
+ return format_html('{0}: <a href="{1}">{2}</a>',
+ capfirst(opts.verbose_name),
+ admin_url,
+ obj)
else:
# Don't display link to edit, because it either has no
# admin or is edited inline.
View
15 django/contrib/admin/widgets.py
@@ -10,7 +10,7 @@
from django.core.urlresolvers import reverse
from django.forms.widgets import RadioFieldRenderer
from django.forms.util import flatatt
-from django.utils.html import escape
+from django.utils.html import escape, format_html, format_html_join
from django.utils.text import Truncator
from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
@@ -85,16 +85,17 @@ def __init__(self, attrs=None):
forms.MultiWidget.__init__(self, widgets, attrs)
def format_output(self, rendered_widgets):
- return mark_safe('<p class="datetime">%s %s<br />%s %s</p>' % \
- (_('Date:'), rendered_widgets[0], _('Time:'), rendered_widgets[1]))
+ return format_html('<p class="datetime">{0} {1}<br />{2} {3}</p>',
+ _('Date:'), rendered_widgets[0],
+ _('Time:'), rendered_widgets[1])
class AdminRadioFieldRenderer(RadioFieldRenderer):
def render(self):
"""Outputs a <ul> for this set of radio fields."""
- return mark_safe('<ul%s>\n%s\n</ul>' % (
- flatatt(self.attrs),
- '\n'.join(['<li>%s</li>' % force_unicode(w) for w in self]))
- )
+ return format_html('<ul{0}>\n{1}\n</ul>',
+ flatatt(self.attrs),
+ format_html_join('\n', '<li>{0}</li>',
+ ((force_unicode(w),) for w in self)))
class AdminRadioSelect(forms.RadioSelect):
renderer = AdminRadioFieldRenderer
View
2 django/contrib/admindocs/templates/admin_doc/bookmarklets.html
@@ -22,7 +22,7 @@
{% endblocktrans %}
<div id="content-main">
- <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){x=new XMLHttpRequest()}else{return;}x.open('HEAD',location.href,false);x.send(null);try{view=x.getResponseHeader('x-view');}catch(e){alert('No view found for this page');return;}if(view=='undefined'){alert('No view found for this page');}document.location='{{ admin_url }}doc/views/'+view+'/';})()">{% trans "Documentation for this page" %}</a></h3>
+ <h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){x=new XMLHttpRequest()}else{return;}x.open('HEAD',location.href,false);x.send(null);try{view=x.getResponseHeader('x-view');}catch(e){alert('No view found for this page');return;}if(view=='undefined'){alert('No view found for this page');}document.location='{{ admin_url|escapejs }}doc/views/'+view+'/';})()">{% trans "Documentation for this page" %}</a></h3>
<p>{% trans "Jumps you from any page to the documentation for the view that generates that page." %}</p>
<h3><a href="javascript:(function(){if(typeof ActiveXObject!='undefined'){x=new ActiveXObject('Microsoft.XMLHTTP')}else if(typeof XMLHttpRequest!='undefined'){x=new XMLHttpRequest()}else{return;}x.open('GET',location.href,false);x.send(null);try{type=x.getResponseHeader('x-object-type');id=x.getResponseHeader('x-object-id');}catch(e){type='(none)';id='(none)';}d=document;b=d.body;e=d.createElement('div');e.id='xxxhhh';s=e.style;s.position='absolute';s.left='10px';s.top='10px';s.font='10px monospace';s.border='1px black solid';s.padding='4px';s.backgroundColor='#eee';e.appendChild(d.createTextNode('Type: '+type));e.appendChild(d.createElement('br'));e.appendChild(d.createTextNode('ID: '+id));e.appendChild(d.createElement('br'));l=d.createElement('a');l.href='#';l.onclick=function(){b.removeChild(e);};l.appendChild(d.createTextNode('[close]'));l.style.textDecoration='none';e.appendChild(l);b.appendChild(e);})();">{% trans "Show object ID" %}</a></h3>
View
2 django/contrib/admindocs/views.py
@@ -37,7 +37,7 @@ def bookmarklets(request):
admin_root = urlresolvers.reverse('admin:index')
return render_to_response('admin_doc/bookmarklets.html', {
'root_path': admin_root,
- 'admin_url': mark_safe("%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), admin_root)),
+ 'admin_url': "%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), admin_root),
}, context_instance=RequestContext(request))
@staff_member_required
View
2 django/contrib/auth/admin.py
@@ -134,7 +134,7 @@ def user_change_password(self, request, id, form_url=''):
context = {
'title': _('Change password: %s') % escape(user.username),
'adminForm': adminForm,
- 'form_url': mark_safe(form_url),
+ 'form_url': form_url,
'form': form,
'is_popup': '_popup' in request.REQUEST,
'add': True,
View
13 django/contrib/auth/forms.py
@@ -1,6 +1,7 @@
from django import forms
from django.forms.util import flatatt
from django.template import loader
+from django.utils.html import format_html, format_html_join
from django.utils.http import int_to_base36
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext, ugettext_lazy as _
@@ -28,13 +29,15 @@ def render(self, name, value, attrs):
try:
hasher = identify_hasher(encoded)
except ValueError:
- summary = "<strong>Invalid password format or unknown hashing algorithm.</strong>"
+ summary = mark_safe("<strong>Invalid password format or unknown hashing algorithm.</strong>")
else:
- summary = ""
- for key, value in hasher.safe_summary(encoded).iteritems():
- summary += "<strong>%(key)s</strong>: %(value)s " % {"key": ugettext(key), "value": value}
+ summary = format_html_join('',
+ "<strong>{0}</strong>: {1} ",
+ ((ugettext(key), value)
+ for key, value in hasher.safe_summary(encoded).items())
+ )
- return mark_safe("<div%(attrs)s>%(summary)s</div>" % {"attrs": flatatt(final_attrs), "summary": summary})
+ return format_html("<div{0}>{1}</div>", flatatt(final_attrs), summary)
class ReadOnlyPasswordHashField(forms.Field):
View
15 django/contrib/databrowse/datastructures.py
@@ -8,7 +8,6 @@
from django.utils import formats
from django.utils.text import capfirst
from django.utils.encoding import smart_unicode, smart_str, iri_to_uri
-from django.utils.safestring import mark_safe
from django.db.models.query import QuerySet
EMPTY_VALUE = '(None)'
@@ -30,7 +29,7 @@ def model_databrowse(self):
return self.site.registry[self.model]
def url(self):
- return mark_safe('%s%s/%s/' % (self.site.root_url, self.model._meta.app_label, self.model._meta.module_name))
+ return '%s%s/%s/' % (self.site.root_url, self.model._meta.app_label, self.model._meta.module_name)
def objects(self, **kwargs):
return self.get_query_set().filter(**kwargs)
@@ -70,9 +69,9 @@ def choices(self):
def url(self):
if self.field.choices:
- 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))
+ 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)
elif self.field.rel:
- return mark_safe('%s%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name))
+ return '%s%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name)
class EasyChoice(object):
def __init__(self, easy_model, field, value, label):
@@ -83,7 +82,7 @@ def __repr__(self):
return smart_str('<EasyChoice for %s.%s>' % (self.model.model._meta.object_name, self.field.name))
def url(self):
- 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)))
+ 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))
class EasyInstance(object):
def __init__(self, easy_model, instance):
@@ -105,7 +104,7 @@ def pk(self):
return self.instance._get_pk_val()
def url(self):
- return mark_safe('%s%s/%s/objects/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, iri_to_uri(self.pk())))
+ return '%s%s/%s/objects/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, iri_to_uri(self.pk()))
def fields(self):
"""
@@ -187,14 +186,14 @@ def urls(self):
for value in self.values():
if value is None:
continue
- 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())))
+ 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()))
lst.append((smart_unicode(value), url))
else:
lst = [(value, None) for value in self.values()]
elif self.field.choices:
lst = []
for value in self.values():
- url = 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)))
+ 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))
lst.append((value, url))
elif isinstance(self.field, models.URLField):
val = self.values()[0]
View
11 django/contrib/databrowse/plugins/calendars.py
@@ -5,9 +5,9 @@
from django.contrib.databrowse.datastructures import EasyModel
from django.contrib.databrowse.sites import DatabrowsePlugin
from django.shortcuts import render_to_response
+from django.utils.html import format_html, format_html_join
from django.utils.text import capfirst
from django.utils.encoding import force_unicode
-from django.utils.safestring import mark_safe
from django.views.generic import dates
from django.utils import datetime_safe
@@ -64,18 +64,19 @@ def model_index_html(self, request, model, site):
fields = self.field_dict(model)
if not fields:
return ''
- return mark_safe('<p class="filter"><strong>View calendar by:</strong> %s</p>' % \
- ', '.join(['<a href="calendars/%s/">%s</a>' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()]))
+ return format_html('<p class="filter"><strong>View calendar by:</strong> {0}</p>',
+ format_html_join(', ', '<a href="calendars/{0}/">{1}</a>',
+ ((f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values())))
def urls(self, plugin_name, easy_instance_field):
if isinstance(easy_instance_field.field, models.DateField):
d = easy_instance_field.raw_value
- return [mark_safe('%s%s/%s/%s/%s/%s/' % (
+ return ['%s%s/%s/%s/%s/%s/' % (
easy_instance_field.model.url(),
plugin_name, easy_instance_field.field.name,
str(d.year),
datetime_safe.new_date(d).strftime('%b').lower(),
- d.day))]
+ d.day)]
def model_view(self, request, model_databrowse, url):
self.model, self.site = model_databrowse.model, model_databrowse.site
View
11 django/contrib/databrowse/plugins/fieldchoices.py
@@ -5,9 +5,9 @@
from django.contrib.databrowse.datastructures import EasyModel
from django.contrib.databrowse.sites import DatabrowsePlugin
from django.shortcuts import render_to_response
+from django.utils.html import format_html, format_html_join
from django.utils.text import capfirst
from django.utils.encoding import smart_str, force_unicode
-from django.utils.safestring import mark_safe
import urllib
class FieldChoicePlugin(DatabrowsePlugin):
@@ -32,16 +32,17 @@ def model_index_html(self, request, model, site):
fields = self.field_dict(model)
if not fields:
return ''
- return mark_safe('<p class="filter"><strong>View by:</strong> %s</p>' % \
- ', '.join(['<a href="fields/%s/">%s</a>' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()]))
+ return format_html('<p class="filter"><strong>View by:</strong> {0}</p>',
+ format_html_join(', ', '<a href="fields/{0}/">{1}</a>',
+ ((f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values())))
def urls(self, plugin_name, easy_instance_field):
if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values():
field_value = smart_str(easy_instance_field.raw_value)
- return [mark_safe('%s%s/%s/%s/' % (
+ return ['%s%s/%s/%s/' % (
easy_instance_field.model.url(),
plugin_name, easy_instance_field.field.name,
- urllib.quote(field_value, safe='')))]
+ urllib.quote(field_value, safe=''))]
def model_view(self, request, model_databrowse, url):
self.model, self.site = model_databrowse.model, model_databrowse.site
View
18 django/contrib/gis/maps/google/gmap.py
@@ -1,5 +1,6 @@
from django.conf import settings
from django.template.loader import render_to_string
+from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.contrib.gis.maps.google.overlays import GPolygon, GPolyline, GMarker
@@ -10,7 +11,7 @@ class GoogleMapException(Exception):
# The default Google Maps URL (for the API javascript)
# TODO: Internationalize for Japan, UK, etc.
-GOOGLE_MAPS_URL='http://maps.google.com/maps?file=api&amp;v=%s&amp;key='
+GOOGLE_MAPS_URL='http://maps.google.com/maps?file=api&v=%s&key='
class GoogleMap(object):
@@ -48,7 +49,7 @@ def __init__(self, key=None, api_url=None, version=None,
# Can specify the API URL in the `api_url` keyword.
if not api_url:
- self.api_url = mark_safe(getattr(settings, 'GOOGLE_MAPS_URL', GOOGLE_MAPS_URL) % self.version)
+ self.api_url = getattr(settings, 'GOOGLE_MAPS_URL', GOOGLE_MAPS_URL) % self.version
else:
self.api_url = api_url
@@ -111,17 +112,18 @@ def render(self):
@property
def body(self):
"Returns HTML body tag for loading and unloading Google Maps javascript."
- return mark_safe('<body %s %s>' % (self.onload, self.onunload))
+ return format_html('<body {0} {1}>', self.onload, self.onunload)
@property
def onload(self):
"Returns the `onload` HTML <body> attribute."
- return mark_safe('onload="%s.%s_load()"' % (self.js_module, self.dom_id))
+ return format_html('onload="{0}.{1}_load()"', self.js_module, self.dom_id)
@property
def api_script(self):
"Returns the <script> tag for the Google Maps API javascript."
- return mark_safe('<script src="%s%s" type="text/javascript"></script>' % (self.api_url, self.key))
+ return format_html('<script src="{0}{1}" type="text/javascript"></script>',
+ self.api_url, self.key)
@property
def js(self):
@@ -131,17 +133,17 @@ def js(self):
@property
def scripts(self):
"Returns all <script></script> tags required with Google Maps JavaScript."
- return mark_safe('%s\n <script type="text/javascript">\n//<![CDATA[\n%s//]]>\n </script>' % (self.api_script, self.js))
+ return format_html('%s\n <script type="text/javascript">\n//<![CDATA[\n%s//]]>\n </script>', self.api_script, mark_safe(self.js))
@property
def style(self):
"Returns additional CSS styling needed for Google Maps on IE."
- return mark_safe('<style type="text/css">%s</style>' % self.vml_css)
+ return format_html('<style type="text/css">{0}</style>', self.vml_css)
@property
def xhtml(self):
"Returns XHTML information needed for IE VML overlays."
- return mark_safe('<html xmlns="http://www.w3.org/1999/xhtml" %s>' % self.xmlns)
+ return format_html('<html xmlns="http://www.w3.org/1999/xhtml" {0}>', self.xmlns)
@property
def icons(self):
View
12 django/forms/forms.py
@@ -11,7 +11,7 @@
from django.forms.util import flatatt, ErrorDict, ErrorList
from django.forms.widgets import Media, media_property, TextInput, Textarea
from django.utils.datastructures import SortedDict
-from django.utils.html import conditional_escape
+from django.utils.html import conditional_escape, format_html
from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
from django.utils.safestring import mark_safe
@@ -167,7 +167,7 @@ def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_
# punctuation.
if self.label_suffix:
if label[-1] not in ':?.!':
- label += self.label_suffix
+ label = format_html('{}{}', label, self.label_suffix)
label = bf.label_tag(label) or ''
else:
label = ''
@@ -498,8 +498,8 @@ def value(self):
def label_tag(self, contents=None, attrs=None):
"""
Wraps the given contents in a <label>, if the field has an ID attribute.
- Does not HTML-escape the contents. If contents aren't given, uses the
- field's HTML-escaped label.
+ contents should be 'mark_safe'd to avoid HTML escaping. If contents
+ aren't given, uses the field's HTML-escaped label.
If attrs are given, they're used as HTML attributes on the <label> tag.
"""
@@ -508,7 +508,9 @@ def label_tag(self, contents=None, attrs=None):
id_ = widget.attrs.get('id') or self.auto_id
if id_:
attrs = attrs and flatatt(attrs) or ''
- contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, unicode(contents))
+ contents = format_html('<label for="{0}"{1}>{2}</label>',
+ widget.id_for_label(id_), attrs, contents
+ )
return mark_safe(contents)
def css_classes(self, extra_classes=None):
View
21 django/forms/util.py
@@ -1,7 +1,7 @@
from __future__ import unicode_literals
from django.conf import settings
-from django.utils.html import conditional_escape
+from django.utils.html import format_html, format_html_join
from django.utils.encoding import StrAndUnicode, force_unicode
from django.utils.safestring import mark_safe
from django.utils import timezone
@@ -17,8 +17,10 @@ def flatatt(attrs):
The returned string will contain a leading space followed by key="value",
XML-style pairs. It is assumed that the keys do not need to be XML-escaped.
If the passed dictionary is empty, then return an empty string.
+
+ The result is passed through 'mark_safe'.
"""
- return ''.join([' %s="%s"' % (k, conditional_escape(v)) for k, v in attrs.items()])
+ return format_html_join('', ' {}="{}"', attrs.items())
class ErrorDict(dict, StrAndUnicode):
"""
@@ -31,9 +33,11 @@ def __unicode__(self):
def as_ul(self):
if not self: return ''
- return mark_safe('<ul class="errorlist">%s</ul>'
- % ''.join(['<li>%s%s</li>' % (k, conditional_escape(force_unicode(v)))
- for k, v in self.items()]))
+ return format_html('<ul class="errorlist">{}</ul>',
+ format_html_join('', '<li>{0}{1}</li>',
+ ((k, force_unicode(v))
+ for k, v in self.items())
+ ))
def as_text(self):
return '\n'.join(['* %s\n%s' % (k, '\n'.join([' * %s' % force_unicode(i) for i in v])) for k, v in self.items()])
@@ -47,8 +51,11 @@ def __unicode__(self):
def as_ul(self):
if not self: return ''
- return mark_safe('<ul class="errorlist">%s</ul>'
- % ''.join(['<li>%s</li>' % conditional_escape(force_unicode(e)) for e in self]))
+ return format_html('<ul class="errorlist">{}</ul>',
+ format_html_join('', '<li>{}</li>',
+ ((force_unicode(e),) for e in self)
+ )
+ )
def as_text(self):
if not self: return ''
View
59 django/forms/widgets.py
@@ -12,7 +12,7 @@
from django.conf import settings
from django.forms.util import flatatt, to_current_timezone
from django.utils.datastructures import MultiValueDict, MergeDict
-from django.utils.html import escape, conditional_escape
+from django.utils.html import conditional_escape, format_html, format_html_join
from django.utils.translation import ugettext, ugettext_lazy
from django.utils.encoding import StrAndUnicode, force_unicode
from django.utils.safestring import mark_safe
@@ -53,15 +53,15 @@ def render(self):
return mark_safe('\n'.join(chain(*[getattr(self, 'render_' + name)() for name in MEDIA_TYPES])))
def render_js(self):
- return ['<script type="text/javascript" src="%s"></script>' % self.absolute_path(path) for path in self._js]
+ return [format_html('<script type="text/javascript" src="{0}"></script>', self.absolute_path(path)) for path in self._js]
def render_css(self):
# To keep rendering order consistent, we can't just iterate over items().
# We need to sort the keys, and iterate over the sorted list.
media = self._css.keys()
media.sort()
return chain(*[
- ['<link href="%s" type="text/css" media="%s" rel="stylesheet" />' % (self.absolute_path(path), medium)
+ [format_html('<link href="{0}" type="text/css" media="{1}" rel="stylesheet" />', self.absolute_path(path), medium)
for path in self._css[medium]]
for medium in media])
@@ -254,7 +254,7 @@ def render(self, name, value, attrs=None):
if value != '':
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_unicode(self._format_value(value))
- return mark_safe('<input%s />' % flatatt(final_attrs))
+ return format_html('<input{} />', flatatt(final_attrs))
class TextInput(Input):
input_type = 'text'
@@ -295,7 +295,7 @@ def render(self, name, value, attrs=None, choices=()):
# An ID attribute was given. Add a numeric index as a suffix
# so that the inputs don't all have the same ID attribute.
input_attrs['id'] = '%s_%s' % (id_, i)
- inputs.append('<input%s />' % flatatt(input_attrs))
+ inputs.append(format_html('<input{} />', flatatt(input_attrs)))
return mark_safe('\n'.join(inputs))
def value_from_datadict(self, data, files, name):
@@ -355,9 +355,9 @@ def render(self, name, value, attrs=None):
if value and hasattr(value, "url"):
template = self.template_with_initial
- substitutions['initial'] = ('<a href="%s">%s</a>'
- % (escape(value.url),
- escape(force_unicode(value))))
+ substitutions['initial'] = format_html('<a href="{0}">{1}</a>',
+ value.url,
+ force_unicode(value))
if not self.is_required:
checkbox_name = self.clear_checkbox_name(name)
checkbox_id = self.clear_checkbox_id(checkbox_name)
@@ -392,8 +392,9 @@ def __init__(self, attrs=None):
def render(self, name, value, attrs=None):
if value is None: value = ''
final_attrs = self.build_attrs(attrs, name=name)
- return mark_safe('<textarea%s>%s</textarea>' % (flatatt(final_attrs),
- conditional_escape(force_unicode(value))))
+ return format_html('<textarea{0}>{1}</textarea>',
+ flatatt(final_attrs),
+ force_unicode(value))
class DateInput(Input):
input_type = 'text'
@@ -511,7 +512,7 @@ def render(self, name, value, attrs=None):
if not (value is True or value is False or value is None or value == ''):
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_unicode(value)
- return mark_safe('<input%s />' % flatatt(final_attrs))
+ return format_html('<input{} />', flatatt(final_attrs))
def value_from_datadict(self, data, files, name):
if name not in data:
@@ -543,7 +544,7 @@ def __init__(self, attrs=None, choices=()):
def render(self, name, value, attrs=None, choices=()):
if value is None: value = ''
final_attrs = self.build_attrs(attrs, name=name)
- output = ['<select%s>' % flatatt(final_attrs)]
+ output = [format_html('<select{}>', flatatt(final_attrs))]
options = self.render_options(choices, [value])
if options:
output.append(options)
@@ -553,23 +554,24 @@ def render(self, name, value, attrs=None, choices=()):
def render_option(self, selected_choices, option_value, option_label):
option_value = force_unicode(option_value)
if option_value in selected_choices:
- selected_html = ' selected="selected"'
+ selected_html = mark_safe(' selected="selected"')
if not self.allow_multiple_selected:
# Only allow for a single selection.
selected_choices.remove(option_value)
else:
selected_html = ''
- return '<option value="%s"%s>%s</option>' % (
- escape(option_value), selected_html,
- conditional_escape(force_unicode(option_label)))
+ return format_html('<option value="{0}"{1}>{2}</option>',
+ option_value,
+ selected_html,
+ force_unicode(option_label))
def render_options(self, choices, selected_choices):
# Normalize to strings.
selected_choices = set(force_unicode(v) for v in selected_choices)
output = []
for option_value, option_label in chain(self.choices, choices):
if isinstance(option_label, (list, tuple)):
- output.append('<optgroup label="%s">' % escape(force_unicode(option_value)))
+ output.append(format_html('<optgroup label="{0}">', force_unicode(option_value)))
for option in option_label:
output.append(self.render_option(selected_choices, *option))
output.append('</optgroup>')
@@ -618,7 +620,7 @@ class SelectMultiple(Select):
def render(self, name, value, attrs=None, choices=()):
if value is None: value = []
final_attrs = self.build_attrs(attrs, name=name)
- output = ['<select multiple="multiple"%s>' % flatatt(final_attrs)]
+ output = [format_html('<select multiple="multiple"{}>', flatatt(final_attrs))]
options = self.render_options(choices, value)
if options:
output.append(options)
@@ -662,11 +664,11 @@ def render(self, name=None, value=None, attrs=None, choices=()):
value = value or self.value
attrs = attrs or self.attrs
if 'id' in self.attrs:
- label_for = ' for="%s_%s"' % (self.attrs['id'], self.index)
+ label_for = format_html(' for="{0}_{1}"', self.attrs['id'], self.index)
else:
label_for = ''
- choice_label = conditional_escape(force_unicode(self.choice_label))
- return mark_safe('<label%s>%s %s</label>' % (label_for, self.tag(), choice_label))
+ choice_label = force_unicode(self.choice_label)
+ return format_html('<label{0}>{1} {2}</label>', label_for, self.tag(), choice_label)
def is_checked(self):
return self.value == self.choice_value
@@ -677,7 +679,7 @@ def tag(self):
final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
if self.is_checked():
final_attrs['checked'] = 'checked'
- return mark_safe('<input%s />' % flatatt(final_attrs))
+ return format_html('<input{} />', flatatt(final_attrs))
class RadioFieldRenderer(StrAndUnicode):
"""
@@ -701,8 +703,10 @@ def __unicode__(self):
def render(self):
"""Outputs a <ul> for this set of radio fields."""
- return mark_safe('<ul>\n%s\n</ul>' % '\n'.join(['<li>%s</li>'
- % force_unicode(w) for w in self]))
+ return format_html('<ul>\n{0}\n</ul>',
+ format_html_join('\n', '<li>{0}</li>',
+ [(force_unicode(w),) for w in self]
+ ))
class RadioSelect(Select):
renderer = RadioFieldRenderer
@@ -751,15 +755,16 @@ def render(self, name, value, attrs=None, choices=()):
# so that the checkboxes don't all have the same ID attribute.
if has_id:
final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i))
- label_for = ' for="%s"' % final_attrs['id']
+ label_for = format_html(' for="{0}"', final_attrs['id'])
else:
label_for = ''
cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
option_value = force_unicode(option_value)
rendered_cb = cb.render(name, option_value)
- option_label = conditional_escape(force_unicode(option_label))
- output.append('<li><label%s>%s %s</label></li>' % (label_for, rendered_cb, option_label))
+ option_label = force_unicode(option_label)
+ output.append(format_html('<li><label{0}>{1} {2}</label></li>',
+ label_for, rendered_cb, option_label))
output.append('</ul>')
return mark_safe('\n'.join(output))
View
5 django/template/defaulttags.py
@@ -16,6 +16,7 @@
from django.template.defaultfilters import date
from django.utils.encoding import smart_unicode
from django.utils.safestring import mark_safe
+from django.utils.html import format_html
from django.utils import timezone
register = Library()
@@ -44,9 +45,9 @@ def render(self, context):
csrf_token = context.get('csrf_token', None)
if csrf_token:
if csrf_token == 'NOTPROVIDED':
- return mark_safe("")
+ return format_html("")
else:
- return mark_safe("<div style='display:none'><input type='hidden' name='csrfmiddlewaretoken' value='%s' /></div>" % csrf_token)
+ return format_html("<div style='display:none'><input type='hidden' name='csrfmiddlewaretoken' value='{}' /></div>", csrf_token)
else:
# It's very probable that the token is missing because of
# misconfiguration, so we raise a warning
View
45 django/utils/html.py
@@ -31,11 +31,11 @@
trailing_empty_content_re = re.compile(r'(?:<p>(?:&nbsp;|\s|<br \/>)*?</p>\s*)+\Z')
del x # Temporary variable
-def escape(html):
+def escape(text):
"""
- Returns the given HTML with ampersands, quotes and angle brackets encoded.
+ Returns the given text with ampersands, quotes and angle brackets encoded for use in HTML.
"""
- return mark_safe(force_unicode(html).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;'))
+ return mark_safe(force_unicode(text).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;'))
escape = allow_lazy(escape, unicode)
_base_js_escapes = (
@@ -63,14 +63,45 @@ def escapejs(value):
return value
escapejs = allow_lazy(escapejs, unicode)
-def conditional_escape(html):
+def conditional_escape(text):
"""
Similar to escape(), except that it doesn't operate on pre-escaped strings.
"""
- if isinstance(html, SafeData):
- return html
+ if isinstance(text, SafeData):
+ return text
else:
- return escape(html)
+ return escape(text)
+
+def format_html(format_string, *args, **kwargs):
+ """
+ Similar to str.format, but passes all arguments through conditional_escape,
+ and calls 'mark_safe' on the result. This function should be used instead
+ of str.format or % interpolation to build up small HTML fragments.
+ """
+ args_safe = map(conditional_escape, args)
+ kwargs_safe = dict([(k, conditional_escape(v)) for (k, v) in
+ kwargs.iteritems()])
+ return mark_safe(format_string.format(*args_safe, **kwargs_safe))
+
+def format_html_join(sep, format_string, args_generator):
+ """
+ A wrapper format_html, for the common case of a group of arguments that need
+ to be formatted using the same format string, and then joined using
+ 'sep'. 'sep' is also passed through conditional_escape.
+
+ 'args_generator' should be an iterator that returns the sequence of 'args'
+ that will be passed to format_html.
+
+ Example:
+
+ format_html_join('\n', "<li>{0} {1}</li>", ((u.first_name, u.last_name)
+ for u in users))
+
+ """
+ return mark_safe(conditional_escape(sep).join(
+ format_html(format_string, *tuple(args))
+ for args in args_generator))
+
def linebreaks(value, autoescape=False):
"""Converts newlines into <p> and <br />s."""
View
61 docs/ref/utils.txt
@@ -387,6 +387,67 @@ Atom1Feed
input is a proper string, then add support for lazy translation objects at the
end.
+``django.utils.html``
+=====================
+
+.. module:: django.utils.html
+ :synopsis: HTML helper functions
+
+Usually you should build up HTML using Django's templates to make use of its
+autoescape mechanism, using the utilities in :mod:`django.utils.safestring`
+where appropriate. This module provides some additional low level utilitiesfor
+escaping HTML.
+
+.. function:: escape(text)
+
+ Returns the given text with ampersands, quotes and angle brackets encoded
+ for use in HTML. The input is first passed through
+ :func:`~django.utils.encoding.force_unicode` and the output has
+ :func:`~django.utils.safestring.mark_safe` applied.
+
+.. function:: conditional_escape(text)
+
+ Similar to ``escape()``, except that it doesn't operate on pre-escaped strings,
+ so it will not double escape.
+
+.. function:: format_html(format_string, *args, **kwargs)
+
+ This is similar to `str.format`_, except that it is appropriate for
+ building up HTML fragments. All args and kwargs are passed through
+ :func:`conditional_escape` before being passed to ``str.format``.
+
+ For the case of building up small HTML fragments, this function is to be
+ preferred over string interpolation using ``%`` or ``str.format`` directly,
+ because it applies escaping to all arguments - just like the Template system
+ applies escaping by default.
+
+ So, instead of writing:
+
+ .. code-block:: python
+
+ mark_safe(u"%s <b>%s</b> %s" % (some_html,
+ escape(some_text),
+ escape(some_other_text),
+ ))
+
+ you should instead use:
+
+ .. code-block:: python
+
+ format_html(u"%{0} <b>{1}</b> {2}",
+ mark_safe(some_html), some_text, some_other_text)
+
+ This has the advantage that you don't need to apply :func:`escape` to each
+ argument and risk a bug and an XSS vulnerability if you forget one.
+
+ Note that although this function uses ``str.format`` to do the
+ interpolation, some of the formatting options provided by `str.format`_
+ (e.g. number formatting) will not work, since all arguments are passed
+ through :func:`conditional_escape` which (ultimately) calls
+ :func:`~django.utils.encoding.force_unicode` on the values.
+
+
+.. _str.format: http://docs.python.org/library/stdtypes.html#str.format
``django.utils.http``
=====================
View
8 tests/regressiontests/admin_views/tests.py
@@ -1369,19 +1369,19 @@ def test_get_change_view(self):
def test_changelist_to_changeform_link(self):
"The link from the changelist referring to the changeform of the object should be quoted"
response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/')
- should_contain = """<th><a href="%s/">%s</a></th></tr>""" % (quote(self.pk), escape(self.pk))
+ should_contain = """<th><a href="%s/">%s</a></th></tr>""" % (escape(quote(self.pk)), escape(self.pk))
self.assertContains(response, should_contain)
def test_recentactions_link(self):
"The link from the recent actions list referring to the changeform of the object should be quoted"
response = self.client.get('/test_admin/admin/')
- should_contain = """<a href="admin_views/modelwithstringprimarykey/%s/">%s</a>""" % (quote(self.pk), escape(self.pk))
+ should_contain = """<a href="admin_views/modelwithstringprimarykey/%s/">%s</a>""" % (escape(quote(self.pk)), escape(self.pk))
self.assertContains(response, should_contain)
def test_recentactions_without_content_type(self):
"If a LogEntry is missing content_type it will not display it in span tag under the hyperlink."
response = self.client.get('/test_admin/admin/')
- should_contain = """<a href="admin_views/modelwithstringprimarykey/%s/">%s</a>""" % (quote(self.pk), escape(self.pk))
+ should_contain = """<a href="admin_views/modelwithstringprimarykey/%s/">%s</a>""" % (escape(quote(self.pk)), escape(self.pk))
self.assertContains(response, should_contain)
should_contain = "Model with string primary key" # capitalized in Recent Actions
self.assertContains(response, should_contain)
@@ -1402,7 +1402,7 @@ def test_deleteconfirmation_link(self):
"The link from the delete confirmation page referring back to the changeform of the object should be quoted"
response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/delete/' % quote(self.pk))
# this URL now comes through reverse(), thus iri_to_uri encoding
- should_contain = """/%s/">%s</a>""" % (iri_to_uri(quote(self.pk)), escape(self.pk))
+ should_contain = """/%s/">%s</a>""" % (escape(iri_to_uri(quote(self.pk))), escape(self.pk))
self.assertContains(response, should_contain)
def test_url_conflicts_with_add(self):
View
11 tests/regressiontests/utils/html.py
@@ -34,6 +34,17 @@ def test_escape(self):
# Verify it doesn't double replace &.
self.check_output(f, '<&', '&lt;&amp;')
+ def test_format_html(self):
+ self.assertEqual(
+ html.format_html(u"{0} {1} {third} {fourth}",
+ u"< Dangerous >",
+ html.mark_safe(u"<b>safe</b>"),
+ third="< dangerous again",
+ fourth=html.mark_safe(u"<i>safe again</i>")
+ ),
+ u"&lt; Dangerous &gt; <b>safe</b> &lt; dangerous again <i>safe again</i>"
+ )
+
def test_linebreaks(self):
f = html.linebreaks
items = (

No commit comments for this range

Something went wrong with that request. Please try again.