Opened 10 years ago

Last modified 7 months ago

#23424 assigned Bug

Verbatim tag fails to render curly braces

Reported by: stephband Owned by: Atul Bhouraskar
Component: Template system Version: dev
Severity: Normal Keywords: verbatim template tag
Cc: Chris Jerdonek, Sarah Boyce Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description (last modified by Tim Graham)

The {% verbatim %} tag fails to render curly braces under certain conditions - notably when there are two verbatim tags on the same line.

This works:

{% verbatim %}{{{% endverbatim %}   – correctly renders as –   {{
{% verbatim %}}}{% endverbatim %}   – correctly renders as –   }}
{% verbatim %}{ {% endverbatim %}text{% verbatim %} }{% endverbatim %}   – correctly renders as – { text }

However, this fails:

{% verbatim %}{{% endverbatim %}text{% verbatim %}}{% endverbatim %}   – incorrectly renders as –   {{% endverbatim %}text{% verbatim %}}
{% verbatim %}{{{% endverbatim %}text{% verbatim %}}}{% endverbatim %}   – incorrectly renders as –   {{{% endverbatim %}text{% verbatim %}}}
{% verbatim %}{{ {% endverbatim %}text{% verbatim %} }}{% endverbatim %}   – incorrectly renders as –   {{ {% endverbatim %}text{% verbatim %} }}

Change History (19)

comment:1 by stephband, 10 years ago

Description: modified (diff)

comment:2 by stephband, 10 years ago

Description: modified (diff)

comment:3 by Tim Graham, 10 years ago

Description: modified (diff)

comment:4 by Tim Graham, 10 years ago

Triage Stage: UnreviewedAccepted
Version: 1.7-rc-2master

Not sure if it will be easy/possible to fix. If not, we can document the limitation.

comment:5 by Paul Egges, 10 years ago

This occurs because the {{ and }} are the starting and ending delimiters for variables. This also affects block tag delimters, {% and %}, and comment tag delimiters, {# and #}. In all cases they must be matching.

comment:6 by Paul Egges, 10 years ago

Owner: changed from nobody to Paul Egges
Status: newassigned

comment:7 by shayneoneill, 10 years ago

Possibly a better example why this is a really huge headache is this

{%verbatim%} {{ {%endverbatim%} computer.{{field}} {%verbatim%} }} {%endverbatim%}

From some auditing software where we are using django views to render lists of fields , which however are being fetched via AngularJS from a third party REST source. We simply can't find a simple way around this. :(

An alternative might even be some simple tags that provide the missing character Ie {% special left_double_brace %} or whatever.

Whilst documentation would at least warn the bug is there, this is a show-stopper error for a lot of use cases.

For the purposes of anyone coming across this ticket and pulling their hair out over it, heres a template tag that might ease the pain;-

from django import template

register = template.Library()

@register.filter(name='specialbracket')
def specialbracket(value):
    if value == 'left':
        return "{{"
    elif value == 'right':
        return "}}"
    else:
        return "??"

And the above example solved with this tag;-

        <td class="field-{{ident}}">{{ 'left'|specialbracket }} computer.{{field}} {{ 'right'|specialbracket }} </td>

Last edited 10 years ago by shayneoneill (previous) (diff)

comment:8 by Aymeric Augustin, 10 years ago

Shayne, aren't you looking for the templatetag tag?

comment:9 by shayneoneill, 10 years ago

Oh wow. I didn't even realise that existed. Thanks! Still I do think that allowing multiple verbatim/endverbatim instances on a line would solve a lot of headaches.

comment:10 by Aymeric Augustin, 9 years ago

This may require switching to a proper lexer that understands literals instead of the current regex-based implementation.

comment:11 by Alexey, 5 years ago

Probably related:

{% verbatim %}{%{% endverbatim %}

With curly bracket and percentage sign (open block tag) template throws exception of unclosed verbatim tag. Django 1.11.22

comment:12 by Chris Jerdonek, 3 years ago

Cc: Chris Jerdonek added
Owner: Paul Egges removed
Status: assignednew

comment:13 by Atul Bhouraskar, 3 years ago

Has patch: set
Owner: set to Atul Bhouraskar
Status: newassigned

I've created a pull request that allows all the examples in this ticket to be correctly rendered - https://github.com/django/django/pull/14786
Test cases for the examples have been added and pass. More test cases (for examples not yet reported) may need to be added.

Note that this pull request is based off my proposed fix for #23356 where all verbatim tag logic is isolated into a Parser.parse_verbatim() method and removes all special case logic currently in the Lexer.create_token().

comment:14 by Mariusz Felisiak, 3 years ago

Patch needs improvement: set

See comment.

comment:15 by Chris Jerdonek, 3 years ago

The way I would try solving this today is, after PR #14739 is merged, make Lexer.create_token() raise a custom exception called something like VerbatimTagStarting with the end string as the argument if a verbatim tag is encountered. (The line where this happens is here.) Then, in Lexer.tokenize(), handle VerbatimTagStarting being raised by searching for the new end string, and then resuming the tag_re.split() iteration at the new location in the template string afterwards. This will be much easier to implement now that ticket #33002 is resolved. One side benefit of this is that Lexer.create_token() should become simpler (though the complexity will move to tokenize()). However, Lexer.create_token() probably shouldn't be having much logic anyways (nor state, which the changes I'm suggesting would also remove).

Last edited 3 years ago by Chris Jerdonek (previous) (diff)

comment:16 by Chris Jerdonek, 3 years ago

Here are a few comments to add to my previous comment, after thinking a little more about this:

I believe that finding the end tag for a custom verbatim tag can be done efficiently and without having to dynamically compile any new regular expressions. (The regex pattern I have in mind here would be one that ends with endverbatim.)

I also think this could be done somewhat more efficiently if Django didn't have to support pathological custom closing verbatim tags like this:

{% verbatim {% %}
Avoid template rendering via the {% verbatim %}{% endverbatim %} block.
{% endverbatim {% %}

(In the above, {% is being used as the custom myblock string below.)

{% verbatim myblock %}
Avoid template rendering via the {% verbatim %}{% endverbatim %} block.
{% endverbatim myblock %}

If closing tags like this didn't have to be supported, the approach I have in mind could I think be done with fewer operations since the regex pattern could end with %} instead of endverbatim. (The Python regex module only supports searching for non-overlapping patterns, and whether closing tags like this are allowed affects whether the regex pattern used to search for a closing verbatim tag will be overlapping or not.) But as I said, I believe it can still be done efficiently even if they are allowed.

By the way, would I be allowed to work on this? Atul, I know you assigned yourself, but I'm not sure if that was only on the assumption that this ticket would be resolved using the PR you posted for #23356.

PS - one other thing I noticed is that care needs to be taken to ensure that the regex will match {% endverbatim with any Unicode whitespace between {% and endverbatim (so not just ASCII whitespace), but not \n. That means the regex sequence \s can't be used as is since it includes \n.

comment:17 by Atul Bhouraskar, 3 years ago

Hi Chris and Mariusz,

Sorry for the delay in responding, was a bit too busy at work.

Let me add in a few general comments about this ticket:

  • The {% verbatim %} tags are only found in a very small subset of django templates (I could be very wrong here but I've never actually come across usage out in the wild yet).
  • The conditions in this ticket are only found in a small subset of all verbatim usages - ie combined with the above this is an extremely small use case.
  • Every example provided here can be implemented with either the templatetag tag as pointed out in https://code.djangoproject.com/ticket/23424#comment:8 or via a custom tag. As older comments have said we could note this in the documentation.
  • Adding complexity to create_token() and/or tokenise() - both of which are in the critical path for all template tags (ie. every single template out there is impacted), does not seem (IMHO) a good enough tradeoff to fix this.
  • My strong feeling is that any solution to this ticket could always either be considered inefficient or have special cases/ limitations which will need to be spelt out in documentation anyway.
  • It would be best to simply acknowledge the current behaviour in the documentation and provide workarounds there - happy to create a PR.

Now coming to my proposed fix (if we are to proceed with the fixing anyway):

  • The multiple re-tokenisation only happens if the conditions for this ticket are met (multiple verbatim tags on the same line containing matched var/tag braces within).
  • As I've said it is only a proof of concept fix and of course the regex etc definitely can improve.
  • The inefficiency will only be encountered if the template being parsed contains a verbatim tag (as parse_verbatim() will never get called otherwise).
  • The inefficiency is restricted to the parsing stage which should only ever happen once per template as long as the cached loader is used (which is the default). Users concerned with performance should really not opt out of this.

To summarise:

  • I would strongly support leaving the current behaviour as-is and updating the documentation as simple workarounds exist.

PS. On the other hand there is no workaround for creating "verbatim like" tags (#23356) and the proposed fix there has zero impact on all other template tags (and actually reduces complexity in create_token() with a possible small boost in performance).

in reply to:  16 comment:18 by Atul Bhouraskar, 3 years ago

Replying to Chris Jerdonek:

By the way, would I be allowed to work on this? Atul, I know you assigned yourself, but I'm not sure if that was only on the assumption that this ticket would be resolved using the PR you posted for #23356.

Sure thing :)
I can de-assign myself if you want.

comment:19 by Sarah Boyce, 7 months ago

Cc: Sarah Boyce added
Note: See TracTickets for help on using tickets.
Back to Top