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.
# 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.
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.
> 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.
> # 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.
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.
> 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
> On 9/22/06, Simon de Haan <si...@eight.nl> 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.
>> # 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.
On 9/22/06, Ahmad Alhashemi <ahmad.alhash...@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:
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.
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..?
> On 9/22/06, Ahmad Alhashemi <ahmad.alhash...@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:
> 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.
> 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.
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
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.
On Fri, 2006-09-22 at 15:18 +0800, limodou wrote: > On 9/22/06, Ahmad Alhashemi <ahmad.alhash...@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:
> 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.
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.
> 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.
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.
> 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.
> 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.
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.
Malcolm Tredinnick wrote: > 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.
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).
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_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()
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
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?
> 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_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()
> 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
----- Original Message ----- From: Simon de Haan To: django-developers@googlegroups.com Sent: Friday, September 22, 2006 3:15 PM Subject: Re: Extend URL resolver support for HTTP Methods / REST support
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.
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.
> 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/
On Fri, 2006-09-22 at 17:17 +0000, wes wrote: > 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.
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).
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' ), )
On 9/22/06, James Crasta <jcra...@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, )