Extend URL resolver support for HTTP Methods / REST support

269 views
Skip to first unread message

Simon de Haan

unread,
Sep 22, 2006, 2:51:52 AM9/22/06
to django-d...@googlegroups.com
Hello everyone,

I've posted a patch in trac which will allow the urlresolver to
select a view depending on the HTTP method (GET,POST,PUT,DELETE, etc..)
Its posted at http://code.djangoproject.com/ticket/2784

My implementation has been hacked together in a few minutes and isn't
pretty by any standards but Malcom suggested I pitch it here to start
a bit of a discussion.

The urlpatters could then look like this:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
# Example:
(r'^django_rest_urls/get/(?P<id>\d+)',
'django_rest_urls.restful.views.get'),
(r'^django_rest_urls/(?P<id>\d+)', {
'GET': 'django_rest_urls.restful.views.get',
'POST': 'django_rest_urls.restful.views.post',
'PUT': 'django_rest_urls.restful.views.put',
'DELETE': 'django_rest_urls.restful.views.delete',
}),

# Uncomment this for admin:
# (r'^admin/', include('django.contrib.admin.urls')),
)

I like the idea of allowing the methods specify the view, in certain
situations it can be cleaner than using "if request.POST" type of
stuff in the view and will make RESTful type of applications easier
to build using Django. I guess it could be beneficial for AJAX type
of backends too.

I'd be interested in your ideas.


Kind regards,

Simon de Haan

Eight Media
si...@eight.nl
+31 (0)26 38 42 440


Ahmad Alhashemi

unread,
Sep 22, 2006, 3:04:35 AM9/22/06
to django-d...@googlegroups.com
+1 on the idea.

It is starting to make less and less sense to treat a GET and a POST
the same way just because they use the same URL.

I don't think the implementation is ugly, either. I can't see how it
can be made cleaner, except for using constants instead of strings for
GET, POST, ..etc. But then again, we are probably getting those as
strings from the backends.


--Ahmad

Simon de Haan

unread,
Sep 22, 2006, 3:15:18 AM9/22/06
to django-d...@googlegroups.com
Malcom,

In the ticket you mentioned that it can be pretty easily done via a view dispatcher - could you elaborate more on this? I'd be interested as to how one would go about doing this.


Kind regards,

Simon de Haan

Eight Media
+31 (0)26 38 42 440




limodou

unread,
Sep 22, 2006, 3:18:10 AM9/22/06
to django-d...@googlegroups.com
On 9/22/06, Ahmad Alhashemi <ahmad.a...@gmail.com> wrote:
>
> +1 on the idea.
>
> It is starting to make less and less sense to treat a GET and a POST
> the same way just because they use the same URL.
>
> I don't think the implementation is ugly, either. I can't see how it
> can be made cleaner, except for using constants instead of strings for
> GET, POST, ..etc. But then again, we are probably getting those as
> strings from the backends.
>
>
If these things can be done, so if it also can support some
decorators, for example:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
# Example:
(r'^django_rest_urls/get/(?P<id>\d+)',

{'decorator':['utils.user_need_to_admin']},
'django_rest_urls.restful.views.get'),
)

I don'et think carefully about the format, just an idea. So we don't
need to write the decorator in the view code, just write them in
urls.py. And I think it will be easier maintained.

--
I like python!
My Blog: http://www.donews.net/limodou
UliPad Site: http://wiki.woodpecker.org.cn/moin/UliPad
UliPad Maillist: http://groups.google.com/group/ulipad

Simon de Haan

unread,
Sep 22, 2006, 3:35:01 AM9/22/06
to django-d...@googlegroups.com
Hi Limodou,

Not sure about your decorator idea, personally I think it goes beyond the scope of the url's in general and django's urlpatterns and is quite view specific.

Any ideas about implementing the GET/POST/PUT/DELETE etc..?


Met vriendelijke groet,

Simon de Haan

Eight Media
+31 (0)26 38 42 440


limodou

unread,
Sep 22, 2006, 3:44:00 AM9/22/06
to django-d...@googlegroups.com
On 9/22/06, Simon de Haan <si...@eight.nl> wrote:
> Hi Limodou,
>
> Not sure about your decorator idea, personally I think it goes beyond the
> scope of the url's in general and django's urlpatterns and is quite view
> specific.
>
> Any ideas about implementing the GET/POST/PUT/DELETE etc..?
>
Decorator is used for seperating some basic functionality, just like
user authentication, logging, etc. And for now, we should write them
in View code, so if the decorator is changed, so we need to modify
view code.

Yeah, this thread is talking about GET/POST/..., but I think it could
be extended for other things. If these things could be implemented,
and we might implmented an interface just like zope, and use it to
constrol the security ,authentication, logging, etc.

And these extend also include GET/POST/PUT/... etc.

If this idea is sound reasonable, so we can discuss more deeply, if
not, just skip it.

Why I think the urls.py is suit for these kind of things, because all
the methods can be exposed to user are saved in it. So we can make it
more strong and controllable I think.

Malcolm Tredinnick

unread,
Sep 22, 2006, 3:53:04 AM9/22/06
to django-d...@googlegroups.com
On Fri, 2006-09-22 at 09:15 +0200, Simon de Haan wrote:
> Malcom,
>
>
> In the ticket you mentioned that it can be pretty easily done via a
> view dispatcher - could you elaborate more on this? I'd be interested
> as to how one would go about doing this.

The URL dispatcher doesn't change from what it is like now. It
dispatches to a view function that is responsible for passing off
control for any methods that need specific controllers. For example

def dispatcher(request, *args, **kwargs):
func = {
'POST': post_handler,
'PUT': put_handler,
}.get(request.method, default_handler)
return func(*args, **kwargs)

Here, you have already defined functions called post_handler,
put_handler and default_handler. Or you can do without the default
handler and just handle that inside dispatcher. The possibilities are
pretty endless here.

Regards,
Malcolm

Malcolm Tredinnick

unread,
Sep 22, 2006, 3:56:25 AM9/22/06
to django-d...@googlegroups.com
On Fri, 2006-09-22 at 15:18 +0800, limodou wrote:
> On 9/22/06, Ahmad Alhashemi <ahmad.a...@gmail.com> wrote:
> >
> > +1 on the idea.
> >
> > It is starting to make less and less sense to treat a GET and a POST
> > the same way just because they use the same URL.
> >
> > I don't think the implementation is ugly, either. I can't see how it
> > can be made cleaner, except for using constants instead of strings for
> > GET, POST, ..etc. But then again, we are probably getting those as
> > strings from the backends.
> >
> >
> If these things can be done, so if it also can support some
> decorators, for example:
>
> from django.conf.urls.defaults import *
>
> urlpatterns = patterns('',
> # Example:
> (r'^django_rest_urls/get/(?P<id>\d+)',
> {'decorator':['utils.user_need_to_admin']},
> 'django_rest_urls.restful.views.get'),
> )
>
> I don'et think carefully about the format, just an idea. So we don't
> need to write the decorator in the view code, just write them in
> urls.py. And I think it will be easier maintained.

-1.

This starts to get really messy. Imagine a whole file full of lines like
that.

The current URL setup configuration encourages code that is simple to
scan through and work out what dispatches to what, by and large. By
wanting to move everything inline, it loses the clarity that makes
Python such a useful language. You aren't even really saving a
significant number of keystrokes here.

Nothing stops you from putting your URL configuration in the same file
as your views if you don't want to have separate files. It's common to
separate them, but not at all necessary. So I don't see any value being
added here and it encourages people to write code that is harder to
read.

Having multiple ways of doing the same thing is not a Python tradition,
when the single way is clear and not onerous. That is one of the reasons
Python has lasted so long.

Regards,
Malcolm

Simon de Haan

unread,
Sep 22, 2006, 3:58:01 AM9/22/06
to django-d...@googlegroups.com
Hi,

Fair enough - you're free to discuss your ideas.

For this thread, I'd prefer sticking to ideas on whether to implement the HTTP methods in the urlresolver and if the idea is any good - how this could be done.

Right now the urlresolver works with strings or functions, not dictionaries. Adding dictionaries to the urlpatterns increases the complexity a bit and introduces a complete new concept & behaviour.

I'd prefer a simple solution that will allow you to specify the HTTP method for a view without adding noise to the whole urlresolver.

Any ideas?


Met vriendelijke groet,

Simon de Haan

Eight Media
+31 (0)26 38 42 440



Malcolm Tredinnick

unread,
Sep 22, 2006, 4:01:04 AM9/22/06
to django-d...@googlegroups.com
On Fri, 2006-09-22 at 08:51 +0200, Simon de Haan wrote:
> Hello everyone,
>
> I've posted a patch in trac which will allow the urlresolver to
> select a view depending on the HTTP method (GET,POST,PUT,DELETE, etc..)
> Its posted at http://code.djangoproject.com/ticket/2784
>
> My implementation has been hacked together in a few minutes and isn't
> pretty by any standards but Malcom suggested I pitch it here to start
> a bit of a discussion.
[...]

> urlpatterns = patterns('',
> # Example:
> (r'^django_rest_urls/get/(?P<id>\d+)',
> 'django_rest_urls.restful.views.get'),
> (r'^django_rest_urls/(?P<id>\d+)', {'GET': 'django_rest_urls.restful.views.get',
> 'POST': 'django_rest_urls.restful.views.post',
> 'PUT': 'django_rest_urls.restful.views.put',
> 'DELETE': 'django_rest_urls.restful.views.delete',
> }),

For the record, my main complaint here is that suddenly we now start
getting different behaviours, depending upon whether you have a function
or a dictionary as the second argument. It really does make for code
that is harder to read six to 24 months down the track.

Malcolm

limodou

unread,
Sep 22, 2006, 4:03:40 AM9/22/06
to django-d...@googlegroups.com
On 9/22/06, Simon de Haan <si...@eight.nl> wrote:
>
> Hi,
>
> Fair enough - you're free to discuss your ideas.
>
> For this thread, I'd prefer sticking to ideas on whether to implement the
> HTTP methods in the urlresolver and if the idea is any good - how this could
> be done.
>
> Right now the urlresolver works with strings or functions, not dictionaries.
> Adding dictionaries to the urlpatterns increases the complexity a bit and
> introduces a complete new concept & behaviour.

Just because I saw dict might be accepted if the original advice is
passed, so I come out that if we can use "dict" more deeply. Of cause,
even the dict is accepted in url, my proposal may never be accepted at
all. So just skip it.

>
> I'd prefer a simple solution that will allow you to specify the HTTP method
> for a view without adding noise to the whole urlresolver.
>
> Any ideas?
>

--

Simon de Haan

unread,
Sep 22, 2006, 4:20:16 AM9/22/06
to django-d...@googlegroups.com
Yeah I get it and agree with you.

So your suggestion is to not implement this in the urlpatterns and introduce it as a dispatcher in the view?

Met vriendelijke groet,

Simon de Haan

Eight Media
+31 (0)26 38 42 440


Malcolm Tredinnick

unread,
Sep 22, 2006, 4:29:26 AM9/22/06
to django-d...@googlegroups.com
On Fri, 2006-09-22 at 10:20 +0200, Simon de Haan wrote:
> Yeah I get it and agree with you.
>
>
> So your suggestion is to not implement this in the urlpatterns and
> introduce it as a dispatcher in the view?

That what works now and it's the an accepted Python idiom for simulating
a switch statement from other languages. I'm using that sort of view now
in a couple of places where I'm supporting POST, PUT, GET and DELETE all
on the same resource (an Atom publishing application).

Anyway, let's see what others think (we're in North American nap time at
the moment, so let's give them a chance to catch up). Like I said in the
bug, I'm interested to hear what possibilities people might have.

Cheers,
Malcolm


Ned Batchelder

unread,
Sep 22, 2006, 5:28:58 AM9/22/06
to django-d...@googlegroups.com
I don't know about the rest of North America, but I'm up from my nap..

I prefer keeping the dispatch in the view as well, for some of the same reasons: clarity of the url map, and singleness of purpose.  The fact is, there are all sorts of reasons why the view function may need to take different paths.  The HTTP method is just one of them.  What if I want very different code for authenticated and anonymous?  Or with cookie and without cookie?  I may have a valid reason for doing that micro-dispatch, but that doesn't mean we should invent a way of expressing it in the URL map. 

The URL map is a simple structure for associating a URL with a view.  The other details about the request that may alter the processing can all be handled in the view.  Frankly, the URL map is already a little overloaded for my tastes, but those overloads are for good map-related causes (the optional dictionary of view arguments is so that different URLs can map to the same view while expressing their differences, and helps make generic views useful; and the string-or-callable overload makes it easier and more natural to express what view to use).

--Ned.
-- 
Ned Batchelder, http://nedbatchelder.com

James Crasta

unread,
Sep 22, 2006, 7:19:30 AM9/22/06
to django-d...@googlegroups.com
I have an alternate suggestion, an expansion of malcomt's idea, rather
than changing the machinery of the current url handlers, and which
should work in your existing code. (note this is untested code, but I
believe it should work, barring any syntactical errors.)


First, we define a RestfulView base class, which leverages python's
__call__ functionality.


from django.http import HttpResponseNotAllowed

class RestfulView(object):
def __call__(self, request, *args, **kw):
# try and find a handler for the given HTTP method
method = request.META['REQUEST_METHOD'].upper()
handler = getattr(self, 'handle_%s' % method, None)

if handler is None:
# compile a list of all our methods and return an HTTP 405
methods = []
for x in dir(self):
if x.startswith('handle_'):
methods.append(x[7:])
return HttpResponseNotAllowed(methods)

return handler(request, *args, **kw)

Now we define a "view" class which extends our RestfulView (again, I
didn't check to see if this works, I just tore apart an existing view
I have) Note that the ability to use a class meant I was able to
define a helper method used by both the views.


class BlogComments(RestfulView):
def _find_post(self, request, year, month, day, slug):
""" Helper function used in all the comment views"""
try:
return Post.objects.find_post(year, month, day, slug)
except Post.DoesNotExist:
raise Http404()

def handle_GET(self, request, year, month, day, slug):
post = self._find_post(year, month, day, slug)
posts = post.comment_set.filter(previewed=True)
return render_to_response('blog/comments.html', {'posts': posts}

def handle_POST(self, request, year, month, day, slug):
post = self._find_post(year, month, day, slug):
# do AddManipulator stuff here
return render_to_response(..... )

# This line binds an instance of BlogComments to "comments".
# Since this code is at module-level it will be re-executed
# properly on a reload, which we want
comments = BlogComments()


Finally, we set up our urlconf:

urlpatterns = patterns('',
(r'^archive/(?P<year>\d+)/(?P<month>\d+)/(?P<day>\d+)/(?P<slug>[\w-]+)/comments/',
'myapp.blog.views.comments')

Note that since django trunk now supports directly referencing
callables, you could alternately do this:

from myapp.views.comments import BlogComments

urlpatterns = patterns('',
(r'^archive/(?P<year>\d+)/(?P<month>\d+)/(?P<day>\d+)/(?P<slug>[\w-]+)/comments/',
BlogComments())


The only caveat I see with this approach is that you have to
re-declare all the params in each of the request_FOO handlers, though
I don't really think that's a huge problem. For implementing a large
multi-method thing, such as say webDAV, or some sort of RPC, this is a
very clear way of implementing things, without any nasty dicts. You
can hide the RestfulView base class machinery away in another module,
and then just define your views which need it as classes, with zero
effect to your existing "traditional" views or to the base handler
machinery. It also means you don't need to use any nasty dicts of
handlers :P

Simon de Haan

unread,
Sep 22, 2006, 8:55:11 AM9/22/06
to django-d...@googlegroups.com
I like your idea! I'll try & put something together this weekend & see how it goes.

Why are you prepending handle_ on the functions instead of just having them be get(), post() etc...?
Why not put them in a list & check if they exist in that list?

allowed_methods = ['get','post','put'...]

and then

for x in dir(self):
    fi x in allow_methods:

you get the idea...

Anyone else got some good ideas? 

Kind regards,

Simon de Haan

Eight Media
+31 (0)26 38 42 440



Slowness Chen

unread,
Sep 22, 2006, 3:28:14 AM9/22/06
to django-d...@googlegroups.com
Since you can judge request method in view, dispatching the same url with different methods to proper views isn't a problem.

wes

unread,
Sep 22, 2006, 1:17:25 PM9/22/06
to Django developers
I also prefer using a dispatcher in the view, its simple and flexible,
and still allows you to use things like the @login_required decorator
on your base view function.

>I'm using that sort of view now
>in a couple of places where I'm supporting POST, PUT, GET and DELETE all
>on the same resource (an Atom publishing application).

How are you accessing the request.PUT and request.DELETE? I would think
those have to be added to the HttpRequest object before you could use
them in the view.

Wes
feednut.com/wes

James Crasta

unread,
Sep 22, 2006, 7:58:57 PM9/22/06
to django-d...@googlegroups.com
> How are you accessing the request.PUT and request.DELETE? I would think
> those have to be added to the HttpRequest object before you could use
> them in the view.

Actually, Django doesn't have any specific machinery for handling PUT
and DELETE and even more esoteric ones like PROPGET, but it'll just
pass them along like any other request. You just have to check the
request.method, (or request.META['REQUEST_METHOD'] ) which is a
string containing the method name. there won't be any handy
`request.PUT` however, you'll have to handle the inline data yourself.

on a side note, the commonly used "if request.POST:" is not
exactly the same as checking "if request.method == 'POST': " The
former only resolves to true if the QueryDict is non-empty, i.e.
there are actually post variables sent. you can theoretically POST
and not send any variables, and in that case the former expression
would actually resolve to false. This is noted in the documentation
too now; at http://www.djangoproject.com/documentation/request_response/

Malcolm Tredinnick

unread,
Sep 22, 2006, 8:39:01 PM9/22/06
to django-d...@googlegroups.com

The method type comes from request.method. The body of the request comes
from request.raw_post_data (this gives you any data that is sent in the
body, rather than just data from a POST request).

Regards,
Malcolm

henning....@gmail.com

unread,
Sep 23, 2006, 9:25:04 AM9/23/06
to Django developers
Simon de Haan wrote:
> I've posted a patch in trac which will allow the urlresolver to
> select a view depending on the HTTP method (GET,POST,PUT,DELETE, etc..)
> Its posted at http://code.djangoproject.com/ticket/2784
> [..]

> I'd be interested in your ideas.
The URL resolver could also try to find a matching view for the current
request method by appending "_on_%s" % request.METHOD.lower() after
the view name:
urlpatterns = patterns('',
(r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'news.views.article'),
)
When using POST the resolver should then try to call
"news.views.article_on_post" first.
This can be easy implemented and would keep the look of the urlconf
clean.

Or you end up with something like Cocoon's sitemaps where you can
dispatch on every header and not only PATH_INFO:
resolver = sitemap('',
(
{ "PATH_INFO":
r'^articles/(\d{4})/(\d{2})/(\d+)/$',
"REQUEST_METHOD": "POST"
}, 'news.views.article'
),
)

Russell Cloran

unread,
Sep 24, 2006, 10:06:44 AM9/24/06
to django-d...@googlegroups.com
Hi,

On 9/22/06, James Crasta <jcr...@gmail.com> wrote:
> First, we define a RestfulView base class, which leverages python's
> __call__ functionality.

And now for something completely different: A decorator! :P

OK, but seriously, I quite like the idea of using decorators. Below is
a completely hacked up implementation (I don't understand enough about
python internals to make sure it's a good implementation), along with
some examples.

Russell


class methodDispatcher:
functions = {}
def __init__(self, method=''):
self.method = method
def unResolvable(self, *args, **kwargs):
from django.http import HttpResponseNotAllowed
try:
return HttpResponseNotAllowed(self.functions[self.fname].keys())
except Exception, e:
print "This is just a proof of concept. " \
"We should've actually returned an HttpResponseNotAllowed."
def resolve(self, request, *args, **kwargs):
f = self.functions[self.fname].get(request.method,
self.functions[self.fname].get('', self.unResolvable))
f(request, *args, **kwargs)
def __call__(self, f, *args, **kwargs):
if (not self.functions.has_key(f.__name__)):
self.functions[f.__name__] = {}
self.functions[f.__name__][self.method] = f
self.fname = f.__name__
return self.resolve

@methodDispatcher('GET')
def view_1(request, *args, **kwargs):
print "I am not a poster"

@methodDispatcher('POST')
def view_1(request, *args, **kwargs):
print "I am a poster"

@methodDispatcher('GET')
def view_2(request, *args, **kwargs):
print "I am also not a poster"

@methodDispatcher('')
def view_2(request, *args, **kwargs):
print "I might be a poster ... actually, I'm a %ser" % (request.method, )

class request:
method = 'GET'
view_1(request)

request.method = 'POST'
view_1(request)
view_2(request)

request.method = 'PUT'
view_1(request)

view_2(request)


--
echo http://russell.rucus.net/spam/ | sed 's,t/.*,t,;P;s,.*//,,;s,\.,@,;'

Reply all
Reply to author
Forward
0 new messages