Using the Django Template Engine Without Templates

Django’s template language make it easy to traverse objects and their relations. It handles any inspections that we might need to emit values the particular path, so we needn’t worry if it a dict, list or a callable and it “does the right thing”. The engine is usually used for rendering static .html/.js/.txt that have been predefined by a developer or designer.

This is all well and good, until you want the end user to have some kind of control over the template, especially if they are trying to create some kind of .csv report. Suddenly the end user or designer has to become conscious of escaping quotes and commas and the format of data that might be emitted.

Utilizing the variable resolution of the django.template package along with the python csv package, we can create custom .CSV reports without burdening the user or designer to worry about the format of content of the data that is being emitted.

Let’s start with the first snippet.

from django import template
from django.conf import settings

def resolve_values(values, context):
    """
    Yields resolved variables or '' if not found.
 
    @param values: A list of strings 'row.resolvable.value'
    @param context: The context which to try and resolve from
    """
    for cell in values:
        try:
            yield template.Variable(cell).resolve(context)
        except template.VariableDoesNotExist:
            if settings.DEBUG:
                raise
            else:
                yield ''

This first snippet takes a list of template variable paths and resolves them when iterated over. We hide any resolution errors in production, but raise in development so we can debug the paths easier.

The second snippet takes a dictionary of Header names: ‘resolution path of the variable’ pairs.

def render_report(template_dict, queryset):
    """
    Renders reports containing all of values passed in the template dict

    @param template_dict: A dict of Header: row.resolvable.value
    @param queryset: The queryset to use when rendering the report
    @return: A unicode string with the contents of the report in CSV
    """
    csvfile = StringIO()
    header = template_dict.keys()
    csvwriter = csv.DictWriter(csvfile, header)

    def read_and_flush():
        csvfile.seek(0)
        data = csvfile.read()
        csvfile.seek(0)
        csvfile.truncate()
        return data

    def data():
        csvwriter.writeheader()
        for row in queryset:
            csv_row = dict(zip(template_dict.keys(), resolve_values(template_dict, {'row': row})))
            csvwriter.writerow(csv_row)
        data = read_and_flush()
        yield data
    return u''.join(data())

Utilizing the two functions above, it would not be difficult to create a form that uses a MultiChoiceField with (‘post.title’, ‘Post Title’) tuples to allow a user to select exactly which fields they want in their report while relieving them of any potential spacing, escaping, or html entity translation filters.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>