diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py
index c9121c7b36..e673199e73 100644
--- a/django/contrib/admin/helpers.py
+++ b/django/contrib/admin/helpers.py
@@ -14,7 +14,9 @@ from django.db.models.fields.related import ManyToManyRel
from django.forms.utils import flatatt
from django.template.defaultfilters import capfirst, linebreaksbr
from django.utils import six
-from django.utils.deprecation import RemovedInDjango110Warning
+from django.utils.deprecation import (
+ RemovedInDjango20Warning, RemovedInDjango110Warning,
+)
from django.utils.encoding import force_text, smart_text
from django.utils.functional import cached_property
from django.utils.html import conditional_escape, format_html
@@ -198,11 +200,20 @@ class AdminReadonlyField(object):
if boolean:
result_repr = _boolean_icon(value)
else:
- result_repr = smart_text(value)
- if getattr(attr, "allow_tags", False):
- result_repr = mark_safe(result_repr)
+ if hasattr(value, "__html__"):
+ result_repr = value
else:
- result_repr = linebreaksbr(result_repr)
+ result_repr = smart_text(value)
+ if getattr(attr, "allow_tags", False):
+ warnings.warn(
+ "Deprecated allow_tags attribute used on %s. "
+ "Use django.utils.safestring.format_html(), "
+ "format_html_join(), or mark_safe() instead." % attr,
+ RemovedInDjango20Warning
+ )
+ result_repr = mark_safe(value)
+ else:
+ result_repr = linebreaksbr(result_repr)
else:
if isinstance(f.remote_field, ManyToManyRel) and value is not None:
result_repr = ", ".join(map(six.text_type, value.all()))
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 935f4f4b66..348a56a4f0 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -753,7 +753,6 @@ class ModelAdmin(BaseModelAdmin):
"""
return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, force_text(obj.pk))
action_checkbox.short_description = mark_safe('')
- action_checkbox.allow_tags = True
def get_actions(self, request):
"""
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index 3332fe1a35..f4033e235b 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -1,6 +1,7 @@
from __future__ import unicode_literals
import datetime
+import warnings
from django.contrib.admin.templatetags.admin_static import static
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
@@ -16,6 +17,7 @@ from django.db import models
from django.template import Library
from django.template.loader import get_template
from django.utils import formats
+from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.encoding import force_text
from django.utils.html import escapejs, format_html
from django.utils.safestring import mark_safe
@@ -207,12 +209,14 @@ def items_for_result(cl, result, form):
row_classes = ['action-checkbox']
allow_tags = getattr(attr, 'allow_tags', False)
boolean = getattr(attr, 'boolean', False)
- if boolean or not value:
- allow_tags = True
result_repr = display_for_value(value, empty_value_display, boolean)
- # Strip HTML tags in the resulting text, except if the
- # function has an "allow_tags" attribute set to True.
if allow_tags:
+ warnings.warn(
+ "Deprecated allow_tags attribute used on field {}. "
+ "Use django.utils.safestring.format_html(), "
+ "format_html_join(), or mark_safe() instead.".format(field_name),
+ RemovedInDjango20Warning
+ )
result_repr = mark_safe(result_repr)
if isinstance(value, (datetime.date, datetime.time)):
row_classes.append('nowrap')
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index 96ad90dd92..f37ec25179 100644
--- a/docs/internals/deprecation.txt
+++ b/docs/internals/deprecation.txt
@@ -265,6 +265,9 @@ details on these changes.
* The warning that :class:`~django.core.signing.Signer` issues when given an
invalid separator will become an exception.
+* Support for the ``allow_tags`` attribute on ``ModelAdmin`` methods will be
+ removed.
+
.. _deprecation-removed-in-1.9:
1.9
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
index 236841dc69..db977b74bd 100644
--- a/docs/ref/contrib/admin/index.txt
+++ b/docs/ref/contrib/admin/index.txt
@@ -583,11 +583,9 @@ subclass::
``False``.
* If the string given is a method of the model, ``ModelAdmin`` or a
- callable, Django will HTML-escape the output by default. If you'd
- rather not escape the output of the method, give the method an
- ``allow_tags`` attribute whose value is ``True``. However, to avoid an
- XSS vulnerability, you should use :func:`~django.utils.html.format_html`
- to escape user-provided inputs.
+ callable, Django will HTML-escape the output by default. To escape
+ user input and allow your own unescaped tags, use
+ :func:`~django.utils.html.format_html`.
Here's a full example model::
@@ -606,11 +604,17 @@ subclass::
self.first_name,
self.last_name)
- colored_name.allow_tags = True
-
class PersonAdmin(admin.ModelAdmin):
list_display = ('first_name', 'last_name', 'colored_name')
+ .. deprecated:: 1.9
+
+ In older versions, you could add an ``allow_tags`` attribute to the
+ method to prevent auto-escaping. This attribute is deprecated as it's
+ safer to use :func:`~django.utils.html.format_html`,
+ :func:`~django.utils.html.format_html_join`, or
+ :func:`~django.utils.safestring.mark_safe` instead.
+
* If the value of a field is ``None``, an empty string, or an iterable
without elements, Django will display ``-`` (a dash). You can override
this with :attr:`AdminSite.empty_value_display`::
@@ -688,7 +692,6 @@ subclass::
self.color_code,
self.first_name)
- colored_first_name.allow_tags = True
colored_first_name.admin_order_field = 'first_name'
class PersonAdmin(admin.ModelAdmin):
@@ -1095,12 +1098,10 @@ subclass::
mark_safe('
'),
'{}',
((line,) for line in instance.get_full_address()),
- ) or "I can't determine this address."
+ ) or mark_safe("I can't determine this address.")
# short_description functions like a model field's verbose_name
address_report.short_description = "Address"
- # in this example, we have used HTML tags in the output
- address_report.allow_tags = True
.. attribute:: ModelAdmin.save_as
diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt
index 2bf591b8b0..366e1ac934 100644
--- a/docs/releases/1.9.txt
+++ b/docs/releases/1.9.txt
@@ -1212,6 +1212,12 @@ Miscellaneous
``SimpleTestCase.assertRaisesMessage()`` is deprecated. Pass the callable as
a positional argument instead.
+* The ``allow_tags`` attribute on methods of ``ModelAdmin`` has been
+ deprecated. Use :func:`~django.utils.html.format_html`,
+ :func:`~django.utils.html.format_html_join`, or
+ :func:`~django.utils.safestring.mark_safe` when constructing the method's
+ return value instead.
+
.. removed-features-1.9:
Features removed in 1.9
diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py
index 86682581b9..5e773bcb9a 100644
--- a/tests/admin_changelist/tests.py
+++ b/tests/admin_changelist/tests.py
@@ -12,9 +12,10 @@ from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from django.template import Context, Template
-from django.test import TestCase, override_settings
+from django.test import TestCase, ignore_warnings, override_settings
from django.test.client import RequestFactory
from django.utils import formats, six
+from django.utils.deprecation import RemovedInDjango20Warning
from .admin import (
BandAdmin, ChildAdmin, ChordsBandAdmin, ConcertAdmin,
@@ -168,7 +169,7 @@ class ChangeListTests(TestCase):
link = reverse('admin:admin_changelist_child_change', args=(new_child.id,))
row_html = (
'
Multiline
html
content
Multiline
html
content
with allow tags