Skip to content

Commit

Permalink
Instrument the Django Jinja2 template backend.
Browse files Browse the repository at this point in the history
- Add jinja2 template to example app.
- Switch to the render function to include context.

It instruments the single template render, but not the inherited
templates and I'm guessing not the included templates either.
I suspect we're going to have to patch jinja templates more robustly
than relying on the django jinja backend template class.

Co-Authored-By: Tim Schilling <schillingt@better-simple.com>
  • Loading branch information
matthiask and tim-schilling committed Jul 5, 2024
1 parent e59c8ca commit c8f6763
Show file tree
Hide file tree
Showing 12 changed files with 73 additions and 5 deletions.
23 changes: 23 additions & 0 deletions debug_toolbar/panels/templates/jinja2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import functools

from django.template.backends.jinja2 import Template as JinjaTemplate
from django.template.context import make_context
from django.test.signals import template_rendered


def patch_jinja_render():
orig_render = JinjaTemplate.render

@functools.wraps(orig_render)
def wrapped_render(self, context=None, request=None):
# This patching of render only instruments the rendering
# of the immediate template. It won't include the parent template(s).
self.name = self.template.name
template_rendered.send(
sender=self, template=self, context=make_context(context, request)
)
return orig_render(self, context, request)

if JinjaTemplate.render != wrapped_render:
JinjaTemplate.original_render = JinjaTemplate.render
JinjaTemplate.render = wrapped_render
2 changes: 2 additions & 0 deletions debug_toolbar/panels/templates/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from debug_toolbar.panels import Panel
from debug_toolbar.panels.sql.tracking import SQLQueryTriggered, allow_sql
from debug_toolbar.panels.templates import views
from debug_toolbar.panels.templates.jinja2 import patch_jinja_render

# Monkey-patch to enable the template_rendered signal. The receiver returns
# immediately when the panel is disabled to keep the overhead small.
Expand All @@ -25,6 +26,7 @@
Template.original_render = Template._render
Template._render = instrumented_test_render

patch_jinja_render()

# Monkey-patch to store items added by template context processors. The
# overhead is sufficiently small to justify enabling it unconditionally.
Expand Down
3 changes: 3 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Pending
-------

* Added check for StreamingHttpResponse in alerts panel.
* Instrument the Django Jinja2 template backend. This only instruments
the immediate template that's rendered. It will not provide stats on
any parent templates.

4.4.3 (2024-07-04)
------------------
Expand Down
8 changes: 7 additions & 1 deletion example/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@
STATIC_URL = "/static/"

TEMPLATES = [
{
"NAME": "jinja2",
"BACKEND": "django.template.backends.jinja2.Jinja2",
"APP_DIRS": True,
"DIRS": [os.path.join(BASE_DIR, "example", "templates", "jinja2")],
},
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"APP_DIRS": True,
Expand All @@ -54,7 +60,7 @@
"django.contrib.messages.context_processors.messages",
],
},
}
},
]

USE_TZ = True
Expand Down
1 change: 1 addition & 0 deletions example/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<h1>Index of Tests</h1>
{% cache 10 index_cache %}
<ul>
<li><a href="{% url 'jinja' %}">Jinja2</a></li>
<li><a href="/jquery/">jQuery 3.3.1</a></li>
<li><a href="/mootools/">MooTools 1.6.0</a></li>
<li><a href="/prototype/">Prototype 1.7.3.0</a></li>
Expand Down
12 changes: 12 additions & 0 deletions example/templates/jinja2/index.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>jinja Test</title>
</head>
<body>
<h1>jinja Test</h1>
{{ foo }}
{% for i in range(10) %}{{ i }}{% endfor %} {# Jinja2 supports range(), Django templates do not #}
</body>
</html>
3 changes: 2 additions & 1 deletion example/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.views.generic import TemplateView

from debug_toolbar.toolbar import debug_toolbar_urls
from example.views import increment
from example.views import increment, jinja2_view

urlpatterns = [
path("", TemplateView.as_view(template_name="index.html"), name="home"),
Expand All @@ -12,6 +12,7 @@
TemplateView.as_view(template_name="bad_form.html"),
name="bad_form",
),
path("jinja/", jinja2_view, name="jinja"),
path("jquery/", TemplateView.as_view(template_name="jquery/index.html")),
path("mootools/", TemplateView.as_view(template_name="mootools/index.html")),
path("prototype/", TemplateView.as_view(template_name="prototype/index.html")),
Expand Down
5 changes: 5 additions & 0 deletions example/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.http import JsonResponse
from django.shortcuts import render


def increment(request):
Expand All @@ -8,3 +9,7 @@ def increment(request):
value = 1
request.session["value"] = value
return JsonResponse({"value": value})


def jinja2_view(request):
return render(request, "index.jinja", {"foo": "bar"}, using="jinja2")
5 changes: 4 additions & 1 deletion tests/panels/test_template.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from unittest import expectedFailure

import django
from django.contrib.auth.models import User
from django.template import Context, RequestContext, Template
Expand Down Expand Up @@ -135,11 +137,12 @@ def test_lazyobject_eval(self):
DEBUG=True, DEBUG_TOOLBAR_PANELS=["debug_toolbar.panels.templates.TemplatesPanel"]
)
class JinjaTemplateTestCase(IntegrationTestCase):
@expectedFailure
def test_django_jinja2(self):
r = self.client.get("/regular_jinja/foobar/")
self.assertContains(r, "Test for foobar (Jinja)")
self.assertContains(r, "<h3>Templates (2 rendered)</h3>")
self.assertContains(r, "<small>jinja2/basic.jinja</small>")
self.assertContains(r, "<small>basic.jinja</small>")


def context_processor(request):
Expand Down
9 changes: 9 additions & 0 deletions tests/templates/jinja2/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
5 changes: 4 additions & 1 deletion tests/templates/jinja2/basic.jinja
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
{% extends 'base.html' %}
{% block content %}Test for {{ title }} (Jinja){% endblock %}
{% block content %}
Test for {{ title }} (Jinja)
{% for i in range(10) %}{{ i }}{% endfor %} {# Jinja2 supports range(), Django templates do not #}
{% endblock %}
2 changes: 1 addition & 1 deletion tests/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def json_view(request):


def regular_jinjia_view(request, title):
return render(request, "jinja2/basic.jinja", {"title": title})
return render(request, "basic.jinja", {"title": title}, using="jinja2")


def listcomp_view(request):
Expand Down

0 comments on commit c8f6763

Please sign in to comment.