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
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
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
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.
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
-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
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
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?
>
--
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, http://nedbatchelder.com
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
>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.
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/
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
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 <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,\.,@,;'