views:

149

answers:

2

I'm very new to Django and I'm trying to build an application to present my data in tables and charts. Till now my learning process went very smooth, but now I'm a bit stuck.

My pageview retrieves large amounts of data from a database and puts it in the context. The template then generates different html-tables. So far so good.
Now I want to add different charts to the template. I manage to do this by defining <img src=".../> tags. The Matplotlib chart is generate in my chartview an returned via:

response=HttpResponse(content_type='image/png')
canvas.print_png(response)
return response

Now I have different questions:

  1. the data is retrieved twice from the database. Once in the pageview to render the tables, and again in the chartview for making the charts. What is the best way to pass the data, already in the context of the page to the chartview?
  2. I need a lot of charts, each with different datasets. I could make a chartview for each chart, but probably there is a better way. How do I pass the different dataset names to the chartview? Some charts have 20 datasets, so I don't think that passing these dataset parameters via the url (like: <imgm src="chart/dataset1/dataset2/.../dataset20/chart.png />) is the right way.
    Any advice?
+5  A: 

You can't pass the data from the page view to the chart view, since they are separate HTTP requests. You have a few options:

  1. Pass all the data in the URL of the chart. This may sound crazy, but this is just what Google Charts does: http://code.google.com/apis/chart/docs/making_charts.html

  2. Store the data in the session. The page view will stuff the data in the session, and the chart view will use it to create a chart.

  3. Cache your db queries in memcache. Since the page and the chart will both reference the same query, you are likely to hit the cache. This is a nice solution because your chart will still work even without the page getting rendered first.

  4. Just query the database again. Your DBMS likely has good caching, the performance may not be the problem you imagine.

For your second question, 20 words in a URL doesn't seem like such a big deal. Of course, you could perhaps find some pattern to the datasets chosen so that you don't need to specify them all every time, but if you need to, just make long URLs.

Ned Batchelder
Thanks for the answers, Ned. Nr 1: that will work, but my intuition says there should be a more 'estetic' solution.Nr2: Thats what I was looking for. This solved the probem I have
Mark
+1  A: 

Using template tags would probably be the route I would take here. I've had a similar situation to this, whereby I had calendar information being rendered multiple times in different formats on the same page. The way I handled it was by passing the queried data into the request context, then simply using that queryset as an argument of a template tag. The result is that you can end up with template syntax like this:

View

def my_view(request, *args, **kwargs):
    yearly_sales_qs = SaleRecord.objects.filter(param=value)
    monthly_sales_qs = SalesRecord.objects.filter(param=foo)

    return render_to_response( ..., locals(), ... )

Template

{% load data_tags %}

<div class="year">
    {% render_data_table for yearly_sales_qs %}
    {% render_bar_chart for yearly_sales_qs %}
</div>

<div class="month">
    {% render_data_table for monthly_sales_qs %}
    {% render_bar_chart for monthly_sales_qs %}
</div>

So how do you make something like that? Start by checking out the Django doc on Custom template tags and filters. It's a little more difficult to get started than the rest of Django, but once you get it, it's pretty easy.

  • Start by creating a folder "templatetags" in your app's folder.
  • Make a blank file "init.py" in that new folder
  • Add the location of that templatetags folder to the TEMPLATE_DIRS setting in settings.py (if it's not already there)

Because we'll be making several of these, we can make a base template tag that we'll inherit off of that encapsulates our basic functionality...

data_tags.py (stored inside of templatetags folder)

class DataForTag(tempalte.Node):
    @classmethod
    def handle_token(cls, parser, token, template):
        tokens = token.contents.split()
        if tokens[1] != 'for':
                raise template.TemplateSyntaxError("First argument in %r must be 'for'" % tokens[0])

        if len(tokens) == 3:
            return cls(queryset=parser.compile_filter(tokens[2]), template=template)
        else:
            raise template.TemplateSyntaxError("%r tag requires 2 arguments" % tokens[0])

    def __init__(self, queryset=None, template=None):
        self.queryset = queryset
        self.template = template

    def render(self, context):
        return render_to_string(self.template, {'queryset':self.queryset})

Then we can make individual tags that handle whatever we need them to...

@register.tag
def render_bar_chart(parser, token):
    return DataForTag.handle_token(parser, token, 'data/charts/barchart.html')

@register.tag
def render_pie_chart(parser, token):
    return DataForTag.handle_token(parser, token, 'data/charts/piechart.html')    

@register.tag
def render_data_table(parser, token):
    return DataForTag.handle_token(parser, token, 'data/table.html')   
T. Stone
Thanks T.Stone. This probably solves my problem. I need to test it first to be sure. Anyway, your answer learned me a lot about tags, which will be useful, if not now, then in the future.
Mark