Compizfox Compizfox - 13 days ago 6
reST (reStructuredText) Question

Resource views in Django

I'm using Django for the first time (I'm also new to Python in general) and I've been struggling with a certain problem related to class based views for a while now. I'm coming from PHP with Laravel, where you can have so-called "RESTful Resource Controllers".

For everyone not familiar with Laravel: the idea is that you have one controller (or view, in Django-speak) per 'resource'. This controller/view defines methods such as

index()
,
show()
,
create()
,
store()
,
edit()
, etc. The associated urls and HTTP methods for these controller methods are
GET /photos
,
GET /photos/1
,
GET /photos/create
,
POST /photos
,
GET /photos/1/edit
respectively (for example, if you're dealing with photos). In Laravel's routing, you have to declare just one line
Route::resource('photos', 'PhotoController')
and those URLs are automatically generated. For more info, I refer to Laravel's docs.

I really like this pattern and I'd like to use it in Django.

As far as I understand, Django doesn't really have such a thing out of the box. Django does have class based views, but those don't represent resources. Rather, you have a view for your 'index()' (
PhotoListView
), a view for your 'show()' (
PhotoDetailView
) and so on.

I'm not sure how I should implement this pattern in Django, and I'm not even sure if what I want is a good idea.

After researching I found some info that might be of use:

http://watchitlater.com/blog/2010/02/django-restful-resources/

https://baxeico.wordpress.com/2014/06/25/put-and-delete-http-requests-with-django-and-jquery/

I'm not sure what the custom View class from the first link accomplishes that the stock Django View can't (a normal Django View does more or less the same thing, right?), and I don't think the second link does exactly what I want (if I understand correctly, the article merely describes a middleware as a solution for the problem that Django doesn't put data sent with a DELETE/PUT request in a request).

Based on this, I devised two potential solutions:


  • Use a single line in the URLconf that matches all URLs for a certain resource, like this:

    url(r'^photos/(.*)$', PhotoView.as_view())


    The
    as_view()
    function of the custom View base class would then handle the routing of the URLs like
    /photos
    ,
    /photos/1
    and
    /photos/1/edit
    and so on to the correct class methods. The disadvantage is that you put routing in a view (not the correct place to do that) and that you can't use named patterns this way to refer to the URLs elsewhere in your code. It is closest to the way its works in Laravel though.

  • Use separate lines in the URLconf for each URL, like:

    url(r'^photos$', PhotoView.index)
    url(r'^photos/(\d+)$', PhotoView.show)
    url(r'^photos/(\d+)/edit$', PhotoView.edit)


    The benefit of this is that named patterns work as usual, and all the routing stays in the URLconf. However, I don't know how to accomplish this in terms of implementation of the View class (I'd have to decorate every method @classonlymethod, right).



Sorry for the wall of text, I'd gladly hear your thoughts on how to best solve this. Or maybe I'm just batshit crazy and should I just use function based views like every normal Django coder?

Answer

I ended up writing a custom class based view that is more or less a mix of the two proposed solutions in the question.

The general idea is that I have a ResourceView that follows the same structure as Django's View class (it has as_view() and dispatch() methods). Just like Django's View, as_view() defines a closure inside that is returned to the URLconf. This closure in turn relies on dispatch() to dispatch the request to the correct (child) method. For this, it uses a '2d map' (dict of dicts) to determine which method should be called for which combination of route (URL) and HTTP method. The difference with Django's View is that as_view() is called in the URLconf with a parameter that specifies the route.

I also have a ResourceRouter that automatically generates the URLconf corresponding to the view. This approach is somewhat inspired by how DRF handles this.

Last but not least I wrote a decorator that 'binds' the model instance to the method, so instead of the pk the model instance is passed to the view method.

The code can be found here: https://gist.github.com/Compizfox/c4e3044755417f59dc33ce97ac8ca07c