1. Jesper Nøhr
  2. django-piston
Issue #89 open

Integration of REST API into site urls, and using piston resources to serve standard views

James Fisher
created an issue

The way I would like to use piston differs from the recommendations in one big way: I don't place my API under /api/, and instead, I scatter the API across my whole site to correspond with the standard HTML views that my site produces. In such a way:

/people # Standard HTML list of people, with fancy CSS, JS, etc. /people.xml # Structured XML list of people /people.json # Structured JSON list of people

My logic behind this is that a standard views, templates and everything, is not really anything more than an Emitter that is specialised to be lovely and human-readable.

My ideal way of accomplishing this would be to format my urls.py like this:

{{{

!python

url(u'^people(.(?P<emitter_format>.+))?$', PeopleResource), }}}

Thus if we visit /people.xml, we get an XML list; if we visit /people.json we get a JSON list, etc.

We can also chop off the extension and just visit /people. Unfortunately, this again produces a JSON list. This is because in resource.py, in determine_emitter, we have:

{{{

!python

    if not em:
        em = request.GET.get('format', 'json')

}}}

Let's comment that out, so the function signals that no known emitter was requested by returning None.

Unfortunately, Resource.call expects to be given a valid emitter, and if it isn't, it attempts to call None(). Instead, what I would like it to do if it finds no emitter is to use a function defined by me, and what this function would essentially be is a 'view': a standard python function that returns an HttpResponse. So let's let the Resource know what view we would like to use:

{{{

!python

class Resource(object): ... def init(self, handler, authentication=None, view=None): ... self.view = view }}}

And finally teach it to use the view as an alternative to the emitter, so in call:

{{{

!python

    try:
        emitter, ct = Emitter.get(em_format)
    except ValueError: # Either the format is unrecognised, or not given
        if self.view:
            return self.view(request, result, typemapper, handler, fields, anonymous)
        else:
            raise NotImplementedError("No explicit emitter format was given, and no default view exists.")
    else: # Use the requested emitter
        srl = emitter(result, typemapper, handler, fields, anonymous)
        try:
            """
            Decide whether or not we want a generator here,
            or we just want to buffer up the entire result
            before sending it to the client. Won't matter for
            smaller datasets, but larger will have an impact.
            """
            if self.stream: stream = srl.stream_render(request)
            else: stream = srl.render(request)

            if not isinstance(stream, HttpResponse):
                resp = HttpResponse(stream, mimetype=ct)
            else:
                resp = stream

            resp.streaming = self.stream

            return resp
        except HttpStatusCode, e:
            return e.response

}}}

So now, to implement my desired way of using Piston, I would first define a view (the only difference from normal views is that it takes the result (queryset) directly): {{{

!python

views.py

def people_view(request, result, args, *kwargs): return render_to_response( 'people.html', { 'people': result } ) }}}

And let the Resource know about my view: {{{

!python

PeopleResource = Resource(PeopleHandler, view=people_view) }}}

Now, personally I'm pretty happy with the url schema I can quickly define with that. What I'm wondering is - does this contravene any established principles? Is it unusual or confusing to provide my JSON list of people just by appending ".json" to the standard url?

Comments (5)

  1. Jesper Nøhr repo owner
    • changed status to open

    It's an unusual way to use piston, but I see where you're going. There's certainly a use for it, for people like you.

    You know the drill; fork, test, pull request. Also, if I can get you to write a test for this use case, I'll be very happy.

    Using .json for the emitter is not unusual; emitter_format is there for a reason, and you can also override it like so:

    url(r'regex', resource, { 'emitter_format': 'json' })
    

    Perhaps a custom emitter that calls the view would be possible? I dunno. Your way is cleaner. Just a thought that wouldn't involve any code change.

  2. James Fisher reporter

    I'll fork in a bit. But first I'm writing up another issue that kinda relates to this and I think comes before it logically: handlers for collections and for members. I think this is my final biggish quibble :).

  3. Log in to comment