I want to generate human-readable HTML and CSS code (properly indented) preprocessed by the Django template system for my standalone application.
I've modified the render method from the NodeList class found in the django.template.base module. My code seems to work properly, but I'm using monkey-patching to replace the old render method.
Is there a more elegant way that does not use monkey-patching in this case? Or maybe monkey-patching is the best way here?
My code looks like this:
'''
This module monkey-patches Django template system to preserve
indentation when rendering templates.
'''
import re
from django.utils.encoding import force_text
from django.utils.safestring import mark_safe
from django.template.loader import render_to_string
from django.template import Node, NodeList, TextNode
from django.template.loader_tags import (BlockNode, ConstantIncludeNode,
IncludeNode)
NEWLINES = re.compile(r'(\r\n|\r|\n)')
INDENT = re.compile(r'(?:\r\n|\r|\n)([\ \t]+)')
def get_indent(text, i=0):
'''
Depending on value of `i`, returns first or last indent
(or any other if `i` is something other than 0 or -1)
found in `text`. Indent is any sequence of tabs or spaces
preceded by a newline.
'''
try:
return INDENT.findall(text)[i]
except IndexError:
pass
def reindent(self, context):
bits = ''
for node in self:
if isinstance(node, Node):
bit = self.render_node(node, context)
else:
bit = node
text = force_text(bit)
# Remove one indentation level
if isinstance(node, BlockNode):
if INDENT.match(text):
indent = get_indent(text)
text = re.sub(r'(\r\n|\r|\n)' + indent, r'\1', text)
# Add one indentation level
if isinstance(node, (BlockNode, ConstantIncludeNode, IncludeNode)):
text = text.strip()
if '\r' in text or '\n' in text:
indent = get_indent(bits, -1)
if indent:
text = NEWLINES.sub(r'\1' + indent, text)
bits += text
return mark_safe(bits)
# Monkey-patching Django class
NodeList.render = reindent
Modifying the template layer would be ok, but not optimal, because it simply handles how a node is rendered, not an entire document. I would recommend writing custom middleware for your project to pretty-print the rendered response for html and css pages.
Your middleware will need to implement process_template_response
which should be used to view and update the SimpleTemplateResponse
object:
- Check the
is_rendered
attribute to see if the response has been rendered - Verify the document type by either:
- Looking for the desired file type (
.html
,.css
) at the end of thetemplate_name
attribute - Looking at the
content_type
attribute (Django 1.5) or possiblymimetype
for older installs
- Looking for the desired file type (
- Re-format and update your rendered document to look gorgeous (Beautiful Soup is great for HTML, but you'll need to pick your preffered pretty-printer or roll your own).
I think Middleware is a far more elegant solution because this ultimately makes no lexical changes to your files. It is entirely separated from the logic that determines your template content (where it has no business being). Finally, you want ALL of your html and css to look great, so why tie that to your templates in the first place?
You could use class inheritance to create a different NodeList
but it will probably require some patching on a different end. Your solution seems plain and simple.
class MyNodeList(NodeList):
def render(self, context):
# call super if you require so
# your reindent functionality
来源:https://stackoverflow.com/questions/18308626/proper-indentation-in-django-templates-without-monkey-patching