Question

I am trying to adapt a piece of middleware I wrote for a home project. The goal of the middleware was to append a comment block to the top of an HTML or XML (or other user defined template types) that displayed information about that template, such as the view that called it, the template location and so on. It was fantastically useful for debugging, especially when working on a project you are not familiar on as it allows you to quickly identify templates and their locations.

The issue is, in order to get the template name it relies on the use of SimpleTemplateResponse in the view, as that holds the template name, so it can be picked up in the middleware in process_template_response().

This is obviously makes the middleware wildly impractical for porting into existing projects because normally render_to_response() is used, and that returns an HttpResponse object which longer knows what template was used to render it.

The person that wrote the first instance of a template identifier (from which I adapted my version) that I came across on a project I used to work on, used threading.currentThread() or similar and found the template name that way and passed it to the middleware, but I no longer have access to that code so I cannot check how it was done.

Does anyone know of a way to access the template name that doesn't involve using SimpleTemplateResponse, or ideally patching render_to_response (though I will accept that if it seems to be the only way)?

The middleware:

# Base template for the html/url locator comment block
COMMENT_BLOCK = """
<!--
[ url      ] >> http://%(host)s%(path)s
[ referer  ] >> %(referer)s
[ module   ] >> %(module)s
[ function ] >> %(function)s, line %(line)s
[ args     ] >> args=%(args)s, kwargs=%(kwargs)s, defaults=%(defaults)s
[ template ] >> %(template)s
-->

"""

# Add any additional template types you wish to add the comment block to.
MIMETYPES = (
    "text/html",
    "text/xml",
)


class HtmlTemplateFinder:
    """
    Middleware class that adds a comment block to an html response object.
    Currently adds url, referer, the python module and its function/line,
    the function arguments, and the template passed in. Only triggers if
    a SimpleTemplateResponse object was used to render the template and
    the template is .html.
    """

    def __init__(self):
        self.host = None
        self.referer = None
        self.path = None
        self.module = None
        self.function = None
        self.line = None
        self.args = None
        self.kwargs = None
        self.defaults = None
        self.template = None
        self.valid_template = False

    def _populate_comment_block(self):
        return COMMENT_BLOCK % {
                                'host': self.host,
                                'referer': self.referer,
                                'path': self.path,
                                'module': self.module,
                                'function': self.function,
                                'line': self.line,
                                'args': self.args,
                                'kwargs': self.kwargs,
                                'defaults': self.defaults,
                                'template': self.template,
                               }

    def process_view(self, request, view_func, view_args, view_kwargs):
        self.host = request.META.get('HTTP_HOST', None)
        self.referer = request.META.get('HTTP_REFERER', None)
        self.path = request.path
        self.module = view_func.func_code.co_filename
        self.function = ('.').join((view_func.__module__, view_func.func_name))
        self.line = view_func.func_code.co_firstlineno
        self.args = view_args
        self.kwargs = view_kwargs
        self.defaults = view_func.func_defaults
        return None

    def process_template_response(self, request, response):
        from mimetypes import guess_type
        # Use this rather than response.template_name, this always returns str
        self.template = response.resolve_template(response.template_name).name
        self.valid_template = guess_type(self.template)[0] in MIMETYPES
        return response

    def process_response(self, request, response):
        import threading
        print threading.current_thread.request
        if settings.DEBUG:
            if self.valid_template:
                block = self._populate_comment_block()
                response.content = "%s%s" % (block, response.content)
        return response
Was it helpful?

Solution

You might want to use django-debug-toolbar and it's template panel instead of reinventing the wheel.

FYI, as you can see, it gets template name by monkey-patching django Template class. The code is based on this snippet.

Hope that helps.

OTHER TIPS

If your view is class based you can do:

from contextlib import suppress

class MyMiddleware(SessionMiddleware):
    def process_response(self, request, response):
        html = response.content
        dostuff()
        return super().process_response(request, response)

    def process_view(self, request, view_func, view_args, view_kwargs):
        with suppress(AttributeError):
            template_name = view_func.view_class.template_name
        with suppress(AttributeError):
            # Do something else for function based views, e.g. add it as an attribute to the function
            return view_func.template_name
        print(template_name)
        return None
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top