[Django] #23441: Maximum recursion depth exceeded when using custom `inclusion_tag` with recursion in templates and `django.template.loaders.cached.Loader`

91 views
Skip to first unread message

Django

unread,
Sep 6, 2014, 1:36:04 PM9/6/14
to django-...@googlegroups.com
#23441: Maximum recursion depth exceeded when using custom `inclusion_tag` with
recursion in templates and `django.template.loaders.cached.Loader`
-------------------------------+--------------------
Reporter: vstoykov | Owner: nobody
Type: Bug | Status: new
Component: Uncategorized | Version: 1.7
Severity: Normal | Keywords:
Triage Stage: Unreviewed | Has patch: 1
Easy pickings: 0 | UI/UX: 0
-------------------------------+--------------------
When custom `inclusion_tag` is created which use recursion in the template
(call itself) and also template loader used is
`django.template.loaders.cached.Loader`. In this situation when template
containing this inclusion tag is rendered for the second time (then the
template is fetched from the cache) then `maximum recursion depth
exceeded` error is raised.

The reason for this is when `{% extends %}` template tag start to look for
`{% block %}` tags it calls `Node.get_nodes_by_type` which start looking
for `{% block %}` tags in the template. When reach our custom inclusion
tag node and call it's `get_nodes_by_type` method to look for `{% block
%}` then start iteration over cached nodes from included template. This
will not happen when standard template loaders are used because template
for inclusion tag is not parsed yet.

To reproduce the error you can use github repo created for that purpose
(https://github.com/vstoykov/wpadmin-test)

I think that there is two possible approaches to fix this, both of which
prevent iteration over cached nodes.

1. Set `child_nodelists` attribute of `InclusionNode` to be empty iterable
(tuple for example).
2. Change the name of the attribute in which the cached nodes are stored
from `nodelist` to something else.

For me the first variant is preferred because we explicitly tell that this
node doesn't have any child nodes.

I created a patch with first variant. If you are ok with it I can make
pull request on github if necessary.

Attached Traceback:
{{{
Environment:

Request Method: GET
Request URL: http://localhost:8000/admin/

Django Version: 1.7
Python Version: 2.7.6
Installed Applications:
('wpadmin',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.redirects',
'django.contrib.sites')
Installed Middleware:
('django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware')


Template error:
In template /home/venko/.pyvirtualenvs/django-max-
recursion/local/lib/python2.7/site-
packages/wpadmin/templates/admin/base_site.html, error at line 1
maximum recursion depth exceeded
1 : {% extends "admin/base.html" %}
2 : {% load i18n wpadmin_tags %}
3 :
4 : {% block title %}{{ title }} | {% wpadmin_render_custom_title %}{%
endblock %}
5 :
6 : {% block branding %}
7 : <h1 id="site-name">{% trans 'Django administration' %}</h1>
8 : {% endblock %}
9 :
10 : {% block nav-global %}{% endblock %}
11 :

Traceback:
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/core/handlers/base.py" in get_response
137. response = response.render()
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/response.py" in render
103. self.content = self.rendered_content
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/response.py" in rendered_content
80. content = template.render(context)
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/base.py" in render
148. return self._render(context)
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/base.py" in _render
142. return self.nodelist.render(context)
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/base.py" in render
844. bit = self.render_node(node, context)
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/debug.py" in render_node
80. return node.render(context)
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/loader_tags.py" in render
126. return compiled_parent._render(context)
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/base.py" in _render
142. return self.nodelist.render(context)
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/base.py" in render
844. bit = self.render_node(node, context)
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/debug.py" in render_node
80. return node.render(context)
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/loader_tags.py" in render
120.
compiled_parent.nodelist.get_nodes_by_type(BlockNode))
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/base.py" in get_nodes_by_type
854. nodes.extend(node.get_nodes_by_type(nodetype))
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/base.py" in get_nodes_by_type
831. nodes.extend(nodelist.get_nodes_by_type(nodetype))
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/base.py" in get_nodes_by_type
854. nodes.extend(node.get_nodes_by_type(nodetype))
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/base.py" in get_nodes_by_type
831. nodes.extend(nodelist.get_nodes_by_type(nodetype))
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/base.py" in get_nodes_by_type
854. nodes.extend(node.get_nodes_by_type(nodetype))
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/base.py" in get_nodes_by_type
831. nodes.extend(nodelist.get_nodes_by_type(nodetype))
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/base.py" in get_nodes_by_type
854. nodes.extend(node.get_nodes_by_type(nodetype))

............................

File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/base.py" in get_nodes_by_type
831. nodes.extend(nodelist.get_nodes_by_type(nodetype))
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/base.py" in get_nodes_by_type
854. nodes.extend(node.get_nodes_by_type(nodetype))
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/base.py" in get_nodes_by_type
829. nodelist = getattr(self, attr, None)
File "/home/venko/.pyvirtualenvs/django-max-recursion/local/lib/python2.7
/site-packages/django/template/defaulttags.py" in nodelist
298. return NodeList(node for _, nodelist in
self.conditions_nodelists for node in nodelist)

Exception Type: RuntimeError at /admin/
Exception Value: maximum recursion depth exceeded

}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/23441>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Sep 6, 2014, 2:13:51 PM9/6/14
to django-...@googlegroups.com
#23441: Maximum recursion depth exceeded when using custom `inclusion_tag` with
recursion in templates and `django.template.loaders.cached.Loader`
-------------------------------+--------------------------------------

Reporter: vstoykov | Owner: nobody
Type: Bug | Status: new
Component: Uncategorized | Version: 1.7
Severity: Normal | Resolution:
Keywords: | Triage Stage: Unreviewed
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------+--------------------------------------
Changes (by vstoykov):

* needs_better_patch: => 0
* needs_tests: => 0
* needs_docs: => 0


Old description:

New description:

When custom `inclusion_tag` is created which use recursion in the template
(call itself) and also template loader used is
`django.template.loaders.cached.Loader`. In this situation when template
containing this inclusion tag is rendered for the second time (then the
template is fetched from the cache) then `maximum recursion depth
exceeded` error is raised.

The reason for this is when `{% extends %}` template tag start to look for
`{% block %}` tags it calls `Node.get_nodes_by_type` which start looking
for `{% block %}` tags in the template. When reach our custom inclusion
tag node and call it's `get_nodes_by_type` method to look for `{% block
%}` then start iteration over cached nodes from included template. This
will not happen when standard template loaders are used because template
for inclusion tag is not parsed yet.

To reproduce the error you can use github repo created for that purpose
(https://github.com/vstoykov/wpadmin-test)

I think that there is two possible approaches to fix this, both of which
prevent iteration over cached nodes.

1. Set `child_nodelists` attribute of `InclusionNode` to be empty iterable
(tuple for example).
2. Change the name of the attribute in which the cached nodes are stored
from `nodelist` to something else.

For me the first variant is preferred because we explicitly tell that this
node doesn't have any child nodes.

I created a patch with first variant and also a pull request.

Attached Traceback:
{{{
Environment:

............................

}}}

--

--
Ticket URL: <https://code.djangoproject.com/ticket/23441#comment:1>

Django

unread,
Sep 8, 2014, 9:26:30 AM9/8/14
to django-...@googlegroups.com
#23441: Maximum recursion depth exceeded when using custom `inclusion_tag` with
recursion in templates and `django.template.loaders.cached.Loader`
-------------------------------+------------------------------------

Reporter: vstoykov | Owner: nobody
Type: Bug | Status: new
Component: Uncategorized | Version: 1.7
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted

Has patch: 1 | Needs documentation: 0
Needs tests: 1 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------+------------------------------------
Changes (by timgraham):

* needs_tests: 0 => 1
* stage: Unreviewed => Accepted


Comment:

I haven't verified that the issue isn't a bug in the custom include tag,
but tests are at least needed before we can commit this.

--
Ticket URL: <https://code.djangoproject.com/ticket/23441#comment:2>

Django

unread,
Sep 8, 2014, 9:26:43 AM9/8/14
to django-...@googlegroups.com
#23441: Maximum recursion depth exceeded when using custom `inclusion_tag` with
recursion in templates and `django.template.loaders.cached.Loader`
---------------------------------+------------------------------------

Reporter: vstoykov | Owner: nobody
Type: Bug | Status: new
Component: Template system | Version: 1.7

Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 1 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
---------------------------------+------------------------------------
Changes (by timgraham):

* component: Uncategorized => Template system


--
Ticket URL: <https://code.djangoproject.com/ticket/23441#comment:3>

Django

unread,
Sep 8, 2014, 3:29:38 PM9/8/14
to django-...@googlegroups.com
#23441: Maximum recursion depth exceeded when using custom `inclusion_tag` with
recursion in templates and `django.template.loaders.cached.Loader`
---------------------------------+------------------------------------

Reporter: vstoykov | Owner: nobody
Type: Bug | Status: new
Component: Template system | Version: 1.7
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 1 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
---------------------------------+------------------------------------

Comment (by vstoykov):

I updated my pull request with very specific test which describes the
problem which can lead in some circumstances to the "maximum recursion
depth exceeded" error.

--
Ticket URL: <https://code.djangoproject.com/ticket/23441#comment:4>

Django

unread,
Apr 1, 2015, 1:04:37 PM4/1/15
to django-...@googlegroups.com
#23441: Maximum recursion depth exceeded when using custom `inclusion_tag` with
recursion in templates and `django.template.loaders.cached.Loader`
---------------------------------+------------------------------------
Reporter: vstoykov | Owner: nobody
Type: Bug | Status: closed

Component: Template system | Version: 1.7
Severity: Normal | Resolution: fixed

Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 1 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
---------------------------------+------------------------------------
Changes (by Tim Graham <timograham@…>):

* status: new => closed
* resolution: => fixed


Comment:

In [changeset:"0808ccce3808235c5b5a56e3f689cec0d4bc0ebf" 0808ccc]:
{{{
#!CommitTicketReference repository=""
revision="0808ccce3808235c5b5a56e3f689cec0d4bc0ebf"
Fixed #23441, #24555 -- Improved the behavior of InclusionNode.

This change:

* Makes the InclusionNode cache-safe by removing render-time side effects
to its nodelist.
* Ensures the render_context stack is properly scoped and reset by
updating
the render call to use Template.render rather than Nodelist.render.
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/23441#comment:5>

Django

unread,
Apr 1, 2015, 1:18:23 PM4/1/15
to django-...@googlegroups.com
#23441: Maximum recursion depth exceeded when using custom `inclusion_tag` with
recursion in templates and `django.template.loaders.cached.Loader`
---------------------------------+------------------------------------
Reporter: vstoykov | Owner: nobody

Type: Bug | Status: closed
Component: Template system | Version: 1.7
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 1 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
---------------------------------+------------------------------------

Comment (by Tim Graham <timograham@…>):

In [changeset:"5c63c45512eeca1f9c2dbdb0c46cbc7d9c550bdc" 5c63c45]:
{{{
#!CommitTicketReference repository=""
revision="5c63c45512eeca1f9c2dbdb0c46cbc7d9c550bdc"
[1.8.x] Fixed #23441, #24555 -- Improved the behavior of InclusionNode.

This change:

* Makes the InclusionNode cache-safe by removing render-time side effects
to its nodelist.
* Ensures the render_context stack is properly scoped and reset by
updating
the render call to use Template.render rather than Nodelist.render.

Backport of 0808ccce3808235c5b5a56e3f689cec0d4bc0ebf from master
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/23441#comment:6>

Reply all
Reply to author
Forward
0 new messages