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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
from django.http import HttpResponse
from django.core.urlresolvers import reverse
from rest import emitters, parsers
from decimal import Decimal
class Resource(object):
class HTTPException(Exception):
def __init__(self, status, content, headers):
self.status = status
self.content = content
self.headers = headers
allowed_methods = ('GET',)
callmap = { 'GET': 'read', 'POST': 'create',
'PUT': 'update', 'DELETE': 'delete' }
emitters = [ ('application/json', emitters.JSONEmitter),
('text/html', emitters.HTMLEmitter),
('application/xhtml+xml', emitters.HTMLEmitter),
('text/plain', emitters.TextEmitter),
('application/xml', emitters.XMLEmitter), ]
parsers = { 'application/json': parsers.JSONParser,
'application/xml': parsers.XMLParser,
'application/x-www-form-urlencoded': parsers.FormParser }
def __new__(cls, request, *args, **kwargs):
self = object.__new__(cls)
self.__init__()
self._request = request
return self._handle_request(request, *args, **kwargs)
def __init__(self):
pass
def _determine_parser(self, request):
"""Return the appropriate parser for the input, given the client's 'Content-Type' header,
and the content types that this Resource knows how to parse."""
return self.parsers.values()[0]
# TODO: Raise 415 Unsupported media type
def _determine_emitter(self, request):
"""Return the appropriate emitter for the output, given the client's 'Accept' header,
and the content types that this Resource knows how to serve.
See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
"""
default = self.emitters[0]
if not request.META.has_key('HTTP_ACCEPT'):
return default
# Parse the accept header into a dict of {Priority: List of Mimetypes}
accept_list = [item.split(';') for item in request.META["HTTP_ACCEPT"].split(',')]
accept_dict = {}
for item in accept_list:
mimetype = item[0].strip()
qvalue = Decimal('1.0')
if len(item) > 1:
# Parse items that have a qvalue eg text/html;q=0.9
try:
(q, num) = item[1].split('=')
if q == 'q':
qvalue = Decimal(num)
except:
# Skip malformed entries
continue
if accept_dict.has_key(qvalue):
accept_dict[qvalue].append(mimetype)
else:
accept_dict[qvalue] = [mimetype]
# Go through all accepted mimetypes in priority order and return our first match
qvalues = accept_dict.keys()
qvalues.sort(reverse=True)
for qvalue in qvalues:
for (mimetype, emitter) in self.emitters:
for accept_mimetype in accept_dict[qvalue]:
if ((accept_mimetype == '*/*') or
(accept_mimetype.endswith('/*') and mimetype.startswith(accept_mimetype[:-1])) or
(accept_mimetype == mimetype)):
return (mimetype, emitter)
raise self.HTTPException(406, {'status': 'Not Acceptable',
'accepts': ','.join(item[0] for item in self.emitters)}, {})
def _handle_request(self, request, *args, **kwargs):
method = request.method
try:
if not method in self.allowed_methods:
raise self.HTTPException(405, {'status': 'Method Not Allowed'}, {})
# Parse the HTTP Request content
func = getattr(self, self.callmap.get(method, ''))
if method in ('PUT', 'POST'):
parser = self._determine_parser(request)
data = parser(self, request).parse(request.raw_post_data)
(status, ret, headers) = func(data, request.META, *args, **kwargs)
else:
(status, ret, headers) = func(request.META, *args, **kwargs)
except self.HTTPException, exc:
(status, ret, headers) = (exc.status, exc.content, exc.headers)
headers['Allow'] = ', '.join(self.allowed_methods)
# Serialize the HTTP Response content
try:
mimetype, emitter = self._determine_emitter(request)
except self.HTTPException, exc:
(status, ret, headers) = (exc.status, exc.content, exc.headers)
mimetype, emitter = self.emitters[0]
content = emitter(self, status, headers).emit(ret)
# Build the HTTP Response
resp = HttpResponse(content, mimetype=mimetype, status=status)
for (key, val) in headers.items():
resp[key] = val
return resp
def _not_implemented(self, operation):
resource_name = self.__class__.__name__
return (500, {'status': 'Internal Server Error',
'detail': '%s %s operation is permitted but has not been implemented' % (resource_name, operation)}, {})
def read(self, headers={}, *args, **kwargs):
return self._not_implemented('read')
def create(self, data=None, headers={}, *args, **kwargs):
return self._not_implemented('create')
def update(self, data=None, headers={}, *args, **kwargs):
return self._not_implemented('update')
def delete(self, headers={}, *args, **kwargs):
return self._not_implemented('delete')
def reverse(self, view, *args, **kwargs):
"""Return a fully qualified URI for a view, using the current request as the base URI.
"""
return self._request.build_absolute_uri(reverse(view, *args, **kwargs))
|