Custom Tastypie Nested (Model) Resources For Dealing with Django ORM Relations

Background

So I spent 6pm Saturday night, till 9:30am Sunday morning in a hackathon where I worked on, you guessed it, tastypie; specifically, on work concerning an open source project I’m involved with, Concert.

Long story short, Concert had an issue. It’s a very javascript heavy app, which handles a lot of state information client-side. Because of this, we’re using a MVC-like javascript framework called Backbone.js, which abstracts away a lot of the interaction between our server-side django app and the client-side code.  

Traditionally, django apps end up being their own flavored instances of the Model/View/Controller paradigm, but there’s an issue with such an approach when you’re dealing with the situation described above; state-manipulating client side code and state-manipulating server side code have no enforced methods of communication when said communication is handled by django views - at least not out of the box. Luckily, this exact situation is perfectly handled if we choose to stick to a RESTful style architecture, which is where tasty-pie comes into play.

(*Note: there’s nothing saying that you can’t implement a RESTfully compliant interface using plain django views - i.e., implementing RESTful interfaces on a per case basis for all the django model classes your app has - but this has to be all done manually for each class instance, and there’s nothing forcing you to stick to the standard’s definition. It’s also, in my opinion, a waste of time when there are so many comprehensive options out there - e.g., django-tastypie, django-piston, etc.)


The Problem

The specific issue we ran into when getting tastypie to work was how to deal with many-to-many model relationships RESTfully and intelligently (blog post).

Say you’ve got a couple of related models that look like this:

and say we’ve got two tastypie resources that map to each model.

Out of the box, tastypie will give you a handful of URLs and all the basic CRUD operations you’d expect.

A url like this “/api/audiosegment/<optional_primary_key>" can be sent POST, PUT, GET, and DELETE requests, each of which does exactly what it’s supposed to: create, update, retrieve, and remove states (objects), respectively. So let’s say we send a PUT request to ”/api/tag/1" with the intention of updating what’s in the segments attribute of the already instantiated tag with primary key 1.

The PUT request itself is going to have to contain, as an argument, the new state of tag #1; it’s going to send a name, creator, collection, and a new list of segments, including all the segments previously contained by that tag and the new one.

Where this becomes an issue for us is when we try to determine what a specific request is doing, in detail. During the time the request is made, dealt with, and responded to, all any component of our app knows is that a model is being updated - whether or not a relation was was added, removed, (or any combination) from our model instance is a mystery (e.g., whether or not a segment was added or removed from Tag #1 is unknown).  Not usually a big deal, unless you have actions that are dependent on knowing such information, like if you have an notification system that informs users when audio segments get tagged.  Not knowing when relationships are being generated means not having anywhere for our notification methods to hook into.


The Solution 

The way I went around solving this was by cooking up my own recipe for nested resources.  The tastypie documentation mentions them here (Tastypie Cookbook), but the specifics are left kind of vague, which is why I’m writing this.  Perhaps I ended up reinventing the wheel, but at the time I couldn’t find anything already out there that was (significantly) helpful.

What I wanted (and got) was the ability to hit resource urls that looked something like this:

/api/<nested_name>/<nested_primary_key>/<resource_name>/<resource_pk>

or in terms of my project:

/api/audio_segment/1/tag/

POSTing to the above resource URL creates a tag, and then relates it to the specified audio_segment.  This gives me a hook for my notification creation methods, and solves all my headaches.

I also ended up implementing a slightly less RESTful shortcut which allowed you to POST to /api/audio_segment/1/tag/1/ with no data.  This would expect for both resources to already exist and then simply create the relation between the two.

DELETE works in a similar way.

GET and PUT were left unimplemented since in those cases you could just manipulate the non-nested resources and deal with them as you wish.

The code is generic, so you can include it and have your nested resources inherit from it.

It’s all GPL, as it’s being released under the original project’s name. You can look at the in use version here: GitHub.

I should mention that this isn’t perfectly RESTful. If I were to implement a purely RESTful solution it would mean creating custom resources for each model-relation itself (e.g., have a resource URI that looked like this: /api/audiosegment_tag_relation/), but for the limited number of use cases I need to meet, this is a little easier, and as far as I’m concerned, it’s probably still useful information to have in the public domain.  

Some might argue that by veering away from the RESTful model in my solution I’m actually moving back to the ad hoc approach which I argued against when discussing plain django views, but I disagree.  With standalone django views, you’d have to opt into following a standard method of communication, whereas with this implementation, you have to opt out; in the end it keeps me as a developer a little more honest.

A slightly more reusable version of the code is embedded bellow.  Another issue, which isn’t so impossible, and is more of a modularity concern than a dead-in-the-water problem, is the fact that my implementation only works for one nested resource per regular resource.  It’s feasible to consider an edition which allowed for unlimited nested resources, but that’s not necessary for Concert right now.  Maybe in the future…