1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76 | from django.http import HttpResponse, HttpRequest, HttpResponseNotAllowed
class CallableClass(type):
def __new__(cls, name, bases, dct):
if 'HEAD' not in dct and 'GET' in dct:
# XXX: this function could possibly be moved out
# to the global namespace to save memory.
def HEAD(self, request, *args, **kwargs):
response = self.GET(request, *args, **kwargs)
response.content = u''
return response
dct['HEAD'] = HEAD
dct['permitted_methods'] = []
for method in ('GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'CONNECT', 'TRACE'):
if hasattr(dct.get(method, None), '__call__'):
dct['permitted_methods'].append(method)
return type.__new__(cls, name, bases, dct)
def __call__(cls, *args, **kwargs):
if args and isinstance(args[0], HttpRequest):
instance = super(CallableClass, cls).__call__()
return instance.__call__(*args, **kwargs)
else:
instance = super(CallableClass, cls).__call__(*args, **kwargs)
return instance
class View(object):
__metaclass__ = CallableViewClass
def __call__(self, request, *args, **kwargs):
if request.method in self.permitted_methods:
handler = getattr(self, request.method)
# XXX: Could possibly check if 'before' returns a response
# and return that instead.
self.before(request, args, kwargs)
return handler(request, *args, **kwargs)
return HttpResponseNotAllowed(self.permitted_methods)
def before(self, request, args, kwargs):
"""Override this method to add common functionality to all HTTP method handlers.
args and kwargs are passed as regular arguments so you can add/remove arguments:
def before(self, request, args, kwargs):
kwargs['article'] = get_object_or_404(Article, id=kwargs.pop('article_id')
def GET(self, request, article): # <== 'article' instead of 'article_id'
...
def POST(delf, request, article): # <== 'article' instead of 'article_id'
...
"""
pass
class MyView(View):
def __init__(self, arg=None):
self.arg = arg
def GET(request):
return HttpResponse(self.arg or 'No args passed')
@login_required
class MyOtherView(View):
def POST(request):
return HttpResponse()
# in urls.py
# And all the following work as expected.
urlpatterns = patterns(''
url(r'^myview1$', 'myapp.views.MyView', name='myview1'),
url(r'^myview2$', myapp.views.MyView, name='myview2'),
url(r'^myview3$', myapp.views.MyView('foobar'), name='myview3'),
url(r'^myotherview$', 'myapp.views.MyOtherView', name='otherview'),
)
|
Comments
Why not compute the permitted_methods in the metaclass and store the result instead of recomputing them every time ? Hopefully you don't add dynamically methods to your views :)
#
@gsakkis: good point, thanks!
#
I updated the code, also now I use
__new__instead of__init__in the metaclass, and use thedctargument to add members to the class instead of assigning tocls.I also added a
beforemethod which I personally use a lot, but initially left out of this snippet.Comments and critique are welcome!
#
Actually there might be an issue with using instantiated views, so might just assume the class will never be instantiated.
#