deprecating the "escape" half of django.utils.safestring (#24046)

175 views
Skip to first unread message

Tim Graham

unread,
Mar 5, 2016, 11:16:49 AM3/5/16
to Django developers (Contributions to Django itself)

Aymeric raised this ticket [0] while working on multiple template engines:


"Since any data that isn't explicitly marked as safe must be treated as unsafe, I don't understand why we have EscapeData and its subclasses nor the mark_for_escaping function. It seems to me that we could keep only the "safe" half of django.utils.safestring and deprecate the "escape" half. As a matter of fact the "escape" isn't used meaningfully anywhere in Django."


I implemented a proof of concept to show what things would look like after the deprecation completes:

https://github.com/django/django/pull/6015


It removes usage of mark_for_escaping() and EscapeData in the template engine to give an idea of what the code might look like with them removed. It wasn't difficult to get the tests passing besides a couple modifications to tests in test_force_escape.py which don't seem like practical usage (chained usage of escape|force_escape).


Here's a test case that demonstrates a small behavior change that would happen:


@setup({'chaining111': '{% autoescape off %}{{ a|escape|add:"<script>" }}{% endautoescape %}'})
def test_chaining111(self):
    output = self.engine.render_to_string('chaining111', {'a': 'a < b'})
    self.assertEqual(output, 'a &lt; b&lt;script&gt;')


The documentation for the escape filter says,

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.

My implementation changes this so that the escape filter no longer executes after all other filters (it runs conditional_escape() right away). This means that the above test fails and the output is: 'a &lt; b<script>'.


The behavior of escape running last no matter its position doesn't seem so intuitive, but it could be problematic to simply change it. A way forward could be to deprecate the escape filter in favor of a new filter called conditional_escape which would simply call the function of the same name. With the new filter, template authors will get equivalent behavior to escape as long as they put this filter last.


Alternatively, we could raise a deprecation warning if the escape filter isn't last in the list of filters and then change the behavior to use conditional_escape() at the end of the deprecation period. This has the potential to be less safe for users, however, as a project might skip over the Django versions with the warnings and not realize the behavior has changed. On the other hand, I hope few users are running with autoscape off and writing code like the test.


What do you think?


[0] https://code.djangoproject.com/ticket/24046

Collin Anderson

unread,
Mar 9, 2016, 10:12:10 PM3/9/16
to django-d...@googlegroups.com
This makes sense to me. mark_for_escaping() seems like a no-op to me.

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/9ac23f7b-40fa-4295-bad1-568e4ffbd72e%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Collin Anderson

unread,
Mar 9, 2016, 10:17:32 PM3/9/16
to django-d...@googlegroups.com
Ohh. Nevermind. Sorry for not reading the whole thing. I forgot about the {% autoescape off %} case.

Aymeric Augustin

unread,
May 8, 2016, 3:09:53 AM5/8/16
to django-d...@googlegroups.com
On 05 Mar 2016, at 17:16, Tim Graham <timog...@gmail.com> wrote:

The behavior of escape running last no matter its position doesn't seem so intuitive

That’s putting it mildly, considering that Django explicitly makes the parallel between template filters and Unix pipes.

but it could be problematic to simply change it. A way forward could be to deprecate the escape filter in favor of a new filter called conditional_escape which would simply call the function of the same name. With the new filter, template authors will get equivalent behavior to escape as long as they put this filter last. 

This is a bit annoying because we won’t end up with the best name: |conditional_escape instead of just |escape.

Alternatively, we could raise a deprecation warning if the escape filter isn't last in the list of filters and then change the behavior to use conditional_escape() at the end of the deprecation period.

This would be my inclination.

This has the potential to be less safe for users, however, as a project might skip over the Django versions with the warnings and not realize the behavior has changed. On the other hand, I hope few users are running with autoscape off and writing code like the test.

Considering that:

- we recommend upgrading version by version,
- hitting this edge case requires some seriously contrived code,
- with the new LTS scheme we designed recently, the warning will remain longer, (until the next LTS inclusive, I think?), reducing the risk to miss it,

I don’t think we should let this concern stop us. 

-- 
Aymeric.

Reply all
Reply to author
Forward
0 new messages