root / snf-cyclades-app / synnefo / userdata / rest.py @ 19b2c29d
History | View | Annotate | Download (8.6 kB)
1 |
#
|
---|---|
2 |
# Copyright 2011 GRNET S.A. All rights reserved.
|
3 |
#
|
4 |
# Redistribution and use in source and binary forms, with or
|
5 |
# without modification, are permitted provided that the following
|
6 |
# conditions are met:
|
7 |
#
|
8 |
# 1. Redistributions of source code must retain the above
|
9 |
# copyright notice, this list of conditions and the following
|
10 |
# disclaimer.
|
11 |
#
|
12 |
# 2. Redistributions in binary form must reproduce the above
|
13 |
# copyright notice, this list of conditions and the following
|
14 |
# disclaimer in the documentation and/or other materials
|
15 |
# provided with the distribution.
|
16 |
#
|
17 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
|
18 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
19 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
20 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
|
21 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
22 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
23 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
24 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
25 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
26 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
27 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
28 |
# POSSIBILITY OF SUCH DAMAGE.
|
29 |
#
|
30 |
# The views and conclusions contained in the software and
|
31 |
# documentation are those of the authors and should not be
|
32 |
# interpreted as representing official policies, either expressed
|
33 |
# or implied, of GRNET S.A.
|
34 |
|
35 |
from django import http |
36 |
from django.utils import simplejson as json |
37 |
from django.core.urlresolvers import reverse |
38 |
from django.http import HttpResponse |
39 |
|
40 |
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS |
41 |
|
42 |
from snf_django.lib.astakos import get_user |
43 |
from django.conf import settings |
44 |
|
45 |
|
46 |
class View(object): |
47 |
"""
|
48 |
Intentionally simple parent class for all views. Only implements
|
49 |
dispatch-by-method and simple sanity checking.
|
50 |
"""
|
51 |
|
52 |
method_names = ['GET', 'POST', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE'] |
53 |
|
54 |
def __init__(self, *args, **kwargs): |
55 |
"""
|
56 |
Constructor. Called in the URLconf; can contain helpful extra
|
57 |
keyword arguments, and other things.
|
58 |
"""
|
59 |
# Go through keyword arguments, and either save their values to our
|
60 |
# instance, or raise an error.
|
61 |
for key, value in kwargs.items(): |
62 |
if key in self.method_names: |
63 |
raise TypeError(u"You tried to pass in the %s method name as a" |
64 |
u" keyword argument to %s(). Don't do that."
|
65 |
% (key, self.__class__.__name__))
|
66 |
if hasattr(self, key): |
67 |
setattr(self, key, value) |
68 |
else:
|
69 |
raise TypeError(u"%s() received an invalid keyword %r" % ( |
70 |
self.__class__.__name__,
|
71 |
key, |
72 |
)) |
73 |
|
74 |
@classmethod
|
75 |
def as_view(cls, *initargs, **initkwargs): |
76 |
"""
|
77 |
Main entry point for a request-response process.
|
78 |
"""
|
79 |
def view(request, *args, **kwargs): |
80 |
get_user(request, settings.ASTAKOS_AUTH_URL) |
81 |
if not request.user_uniq: |
82 |
return HttpResponse(status=401) |
83 |
self = cls(*initargs, **initkwargs)
|
84 |
return self.dispatch(request, *args, **kwargs) |
85 |
return view
|
86 |
|
87 |
def dispatch(self, request, *args, **kwargs): |
88 |
# Try to dispatch to the right method for that; if it doesn't exist,
|
89 |
# raise a big error.
|
90 |
if hasattr(self, request.method.upper()): |
91 |
self.request = request
|
92 |
self.args = args
|
93 |
self.kwargs = kwargs
|
94 |
data = request.body |
95 |
|
96 |
if request.method.upper() in ['POST', 'PUT']: |
97 |
# Expect json data
|
98 |
if request.META.get('CONTENT_TYPE').startswith( |
99 |
'application/json'):
|
100 |
try:
|
101 |
data = json.loads(data) |
102 |
except ValueError: |
103 |
return \
|
104 |
http.HttpResponseServerError('Invalid JSON data.')
|
105 |
else:
|
106 |
return http.HttpResponseServerError(
|
107 |
'Unsupported Content-Type.')
|
108 |
try:
|
109 |
return getattr(self, request.method.upper())( |
110 |
request, data, *args, **kwargs) |
111 |
except ValidationError, e:
|
112 |
# specific response for validation errors
|
113 |
return http.HttpResponse(
|
114 |
json.dumps({'errors': e.message_dict,
|
115 |
'non_field_key': NON_FIELD_ERRORS}),
|
116 |
status=422, content_type="application/json") |
117 |
|
118 |
else:
|
119 |
allowed_methods = \ |
120 |
[m for m in self.method_names if hasattr(self, m)] |
121 |
return http.HttpResponseNotAllowed(allowed_methods)
|
122 |
|
123 |
|
124 |
class JSONRestView(View): |
125 |
"""
|
126 |
Class that provides helpers to produce a json response
|
127 |
"""
|
128 |
|
129 |
url_name = None
|
130 |
|
131 |
def __init__(self, url_name, *args, **kwargs): |
132 |
self.url_name = url_name
|
133 |
return super(JSONRestView, self).__init__(*args, **kwargs) |
134 |
|
135 |
def update_instance(self, i, data, exclude_fields=[]): |
136 |
update_keys = data.keys() |
137 |
for field in i._meta.get_all_field_names(): |
138 |
if field in update_keys and (field not in exclude_fields): |
139 |
i.__setattr__(field, data[field]) |
140 |
|
141 |
return i
|
142 |
|
143 |
def instance_to_dict(self, i, exclude_fields=[]): |
144 |
"""
|
145 |
Convert model instance to python dict
|
146 |
"""
|
147 |
d = {} |
148 |
d['uri'] = reverse(self.url_name, kwargs={'id': i.pk}) |
149 |
|
150 |
for field in i._meta.get_all_field_names(): |
151 |
if field in exclude_fields: |
152 |
continue
|
153 |
|
154 |
d[field] = i.__getattribute__(field) |
155 |
return d
|
156 |
|
157 |
def qs_to_dict_iter(self, qs, exclude_fields=[]): |
158 |
"""
|
159 |
Convert queryset to an iterator of model instances dicts
|
160 |
"""
|
161 |
for i in qs: |
162 |
yield self.instance_to_dict(i, exclude_fields) |
163 |
|
164 |
def json_response(self, data): |
165 |
return http.HttpResponse(json.dumps(data), mimetype="application/json") |
166 |
|
167 |
|
168 |
class ResourceView(JSONRestView): |
169 |
method_names = ['GET', 'POST', 'PUT', 'DELETE'] |
170 |
|
171 |
model = None
|
172 |
exclude_fields = [] |
173 |
|
174 |
def queryset(self): |
175 |
return self.model.objects.all() |
176 |
|
177 |
def instance(self): |
178 |
"""
|
179 |
Retrieve selected instance based on url parameter
|
180 |
|
181 |
id parameter should be set in urlpatterns expression
|
182 |
"""
|
183 |
try:
|
184 |
return self.queryset().get(pk=self.kwargs.get("id")) |
185 |
except self.model.DoesNotExist: |
186 |
raise http.Http404
|
187 |
|
188 |
def GET(self, request, data, *args, **kwargs): |
189 |
return self.json_response( |
190 |
self.instance_to_dict(self.instance(), self.exclude_fields)) |
191 |
|
192 |
def PUT(self, request, data, *args, **kwargs): |
193 |
instance = self.instance()
|
194 |
self.update_instance(instance, data, self.exclude_fields) |
195 |
instance.full_clean() |
196 |
instance.save() |
197 |
return self.GET(request, data, *args, **kwargs) |
198 |
|
199 |
def DELETE(self, request, data, *args, **kwargs): |
200 |
self.instance().delete()
|
201 |
return self.json_response("") |
202 |
|
203 |
|
204 |
class CollectionView(JSONRestView): |
205 |
method_names = ['GET', 'POST'] |
206 |
|
207 |
model = None
|
208 |
exclude_fields = [] |
209 |
|
210 |
def queryset(self): |
211 |
return self.model.objects.all() |
212 |
|
213 |
def GET(self, request, data, *args, **kwargs): |
214 |
return self.json_response( |
215 |
list(self.qs_to_dict_iter(self.queryset(), self.exclude_fields))) |
216 |
|
217 |
def POST(self, request, data, *args, **kwargs): |
218 |
instance = self.model()
|
219 |
self.update_instance(instance, data, self.exclude_fields) |
220 |
instance.full_clean() |
221 |
instance.save() |
222 |
return self.json_response( |
223 |
self.instance_to_dict(instance, self.exclude_fields)) |
224 |
|
225 |
|
226 |
class UserResourceView(ResourceView): |
227 |
"""
|
228 |
Filter resource queryset for request user entries
|
229 |
"""
|
230 |
def queryset(self): |
231 |
return super(UserResourceView, |
232 |
self).queryset().filter(user=self.request.user_uniq) |
233 |
|
234 |
|
235 |
class UserCollectionView(CollectionView): |
236 |
"""
|
237 |
Filter collection queryset for request user entries
|
238 |
"""
|
239 |
def queryset(self): |
240 |
return super(UserCollectionView, |
241 |
self).queryset().filter(user=self.request.user_uniq) |
242 |
|
243 |
def POST(self, request, data, *args, **kwargs): |
244 |
instance = self.model()
|
245 |
self.update_instance(instance, data, self.exclude_fields) |
246 |
instance.user = request.user_uniq |
247 |
instance.full_clean() |
248 |
instance.save() |
249 |
return self.json_response( |
250 |
self.instance_to_dict(instance, self.exclude_fields)) |