views:

126

answers:

2

I want to be able to display set of data differently according to url parameters.

My URL looks like /page/{limit}/{offset}/{format}/.

For example:

/page/20/0/xml/ - subset [0:20) in xml
/page/100/20/json/ - subset [20:100) in json

Also I want to be able to do the same for csv, text, excel, pdf, html, etc...

I have to be able to set different mimetypes and content-types for different formats. For XML should be application/xhtml+xml, for csv - text/plain, etc...

In HTML mode I want to be able to pass this data into some template (I'm using Django).

I'm planing to make set look like:

dataset = {
    "meta" : {"offset" : 15, "limit" : 10, "total" : 1000},
    "columns" : {"name" : "Name", "status" : "Status", "creation_date" : "Creation Date"}
    "items" : 
        [
            {"name" : "John Smith", "status" : 1, "creation_date" : "2009-06-30 10:10:09"},
            {"name" : "Joe The Plummer", "status" : 2, "creation_date" : "2009-06-30 10:10:09"}
        ]
};

and have output like this:

CSV output:

Name, Status, Creation Date
John Smith, 1, 2009-06-30 10:10:09
Joe The Plummer, 2, 2009-06-30 10:10:09

XML output:

<items>
    <item id="1">
        <name>John Smith</name>
        <status>1</status>
        <creation_date>2009-06-30 10:10:09</creation_date>
    </item>
    <item id="2">
        <name>Joe The Plummer</name>
        <status>2</status>
        <creation_date>2009-06-30 10:10:09</creation_date>
    </item>
</items>

So I think to have implemented my own renderers for each type - like XMLRenderer, RSSRenderer, JSONRenderer, etc...

if format == "xml":
    context = XMLRenderer().render(data = dataset)

    return HttpResponse(content, mimetype="application/xhtml+xml")
elif format == "json":
    context = JSONRenderer().render(data = dataset)

    return HttpResponse(content, mimetype="text/plain")
elif format == "rss":
    context = RSSRenderer(title="Some long title here", link="/page/10/10/rss/").render(data = dataset)

    return HttpResponse(content, mimetype="application/xhtml+xml")

# few more formats...

else:
    return render_to_response(SOME_TEMPLATE, dataset)

Is it correct approach?

A: 

Yes, that is a correct approach.

Lennart Regebro
+1  A: 

I suggest having the renderer also know about the mimetype rather than hardcoding the latter in the code that calls the renderer -- better to concentrate format-specific knowledge in one place, so the calling code would be

content, mimetype = renderer().render(data=dataset)
return HttpResponse(content, mimetype=mimetype)

also, this is a great opportunity for the Registry design pattern (most long trees of if/elif are, but one where you're essentially deciding just which object or class to use is perfect!-). So you either hardcode a dict:

format2renderer = dict(
  xml=XMLRenderer,
  rss=RSSRenderer,
  # ...etc...
)

or maybe even better make renderers registers themselves in the dict at startup, but that may be too advanced/hard to arrange. In either case, what you have before the calling snippet I just quoted would be just:

renderer = format2renderer.get(format)
if renderer is not None: ...

and when None you may apply your default code. I find dict lookups and polymorphism SO much easier to maintain and enhance than if/elif trees!-)

Alex Martelli
Good idea to have default mimetype in Renderer class, thanks. But I'm not sure about returning it on render() call. I'm going to have method get_mimetype() in my parent Renderer class. For example json_renderer = JSONRenderer(); content = json_renderer.render(data = dataset); return HttpResponse(content, mimetype=json_renderer.get_mimetype()). I like this way better because might be in the future I'm going to render data to file or log or whatever, so I don't have to know about mimetype as second return parameter from render(). Thanks.
zdmytriv
I was thinking about dict and Registry design pattern and come up that it's not going to work for me. Because for some Renderer-s I have to pass extra parameters in constructor like "title" and "id" for RSSRenderer(title = "Some title", id = "http://example.com/bla/"), or pass to xslt for XMLRenderer(xslt = "http://example.com/bla.xslt"). Thanks.
zdmytriv
dict/registry still works great even when some renderers need parameters: just register the triple (renderer, a, kw) where a and kw are a tuple of the positional args and a dict of the named args (each empty if none), get the None case out of the way ASAP, and call renderer(format, *a, **kw). Equivalently you can use functools.partial of course.
Alex Martelli