root / snf-cyclades-app / synnefo / ui / userdata / rest.py @ 479c3051
History | View | Annotate | Download (8.5 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_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.raw_post_data |
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.HttpResponseServerError(
|
114 |
json.dumps({'errors': e.message_dict,
|
115 |
'non_field_key': NON_FIELD_ERRORS}))
|
116 |
|
117 |
else:
|
118 |
allowed_methods = [m for m in self.method_names if hasattr(self, m)] |
119 |
return http.HttpResponseNotAllowed(allowed_methods)
|
120 |
|
121 |
|
122 |
class JSONRestView(View): |
123 |
"""
|
124 |
Class that provides helpers to produce a json response
|
125 |
"""
|
126 |
|
127 |
url_name = None
|
128 |
|
129 |
def __init__(self, url_name, *args, **kwargs): |
130 |
self.url_name = url_name
|
131 |
return super(JSONRestView, self).__init__(*args, **kwargs) |
132 |
|
133 |
def update_instance(self, i, data, exclude_fields=[]): |
134 |
update_keys = data.keys() |
135 |
for field in i._meta.get_all_field_names(): |
136 |
if field in update_keys and (field not in exclude_fields): |
137 |
i.__setattr__(field, data[field]) |
138 |
|
139 |
return i
|
140 |
|
141 |
def instance_to_dict(self, i, exclude_fields=[]): |
142 |
"""
|
143 |
Convert model instance to python dict
|
144 |
"""
|
145 |
d = {} |
146 |
d['uri'] = reverse(self.url_name, kwargs={'id': i.pk}) |
147 |
|
148 |
for field in i._meta.get_all_field_names(): |
149 |
if field in exclude_fields: |
150 |
continue
|
151 |
|
152 |
d[field] = i.__getattribute__(field) |
153 |
return d
|
154 |
|
155 |
def qs_to_dict_iter(self, qs, exclude_fields=[]): |
156 |
"""
|
157 |
Convert queryset to an iterator of model instances dicts
|
158 |
"""
|
159 |
for i in qs: |
160 |
yield self.instance_to_dict(i, exclude_fields) |
161 |
|
162 |
def json_response(self, data): |
163 |
return http.HttpResponse(json.dumps(data), mimetype="application/json") |
164 |
|
165 |
|
166 |
class ResourceView(JSONRestView): |
167 |
method_names = ['GET', 'POST', 'PUT', 'DELETE'] |
168 |
|
169 |
model = None
|
170 |
exclude_fields = [] |
171 |
|
172 |
def queryset(self): |
173 |
return self.model.objects.all() |
174 |
|
175 |
def instance(self): |
176 |
"""
|
177 |
Retrieve selected instance based on url parameter
|
178 |
|
179 |
id parameter should be set in urlpatterns expression
|
180 |
"""
|
181 |
try:
|
182 |
return self.queryset().get(pk=self.kwargs.get("id")) |
183 |
except self.model.DoesNotExist: |
184 |
raise http.Http404
|
185 |
|
186 |
def GET(self, request, data, *args, **kwargs): |
187 |
return self.json_response( |
188 |
self.instance_to_dict(self.instance(), self.exclude_fields)) |
189 |
|
190 |
def PUT(self, request, data, *args, **kwargs): |
191 |
instance = self.instance()
|
192 |
self.update_instance(instance, data, self.exclude_fields) |
193 |
instance.full_clean() |
194 |
instance.save() |
195 |
return self.GET(request, data, *args, **kwargs) |
196 |
|
197 |
def DELETE(self, request, data, *args, **kwargs): |
198 |
self.instance().delete()
|
199 |
return self.json_response("") |
200 |
|
201 |
|
202 |
class CollectionView(JSONRestView): |
203 |
method_names = ['GET', 'POST'] |
204 |
|
205 |
model = None
|
206 |
exclude_fields = [] |
207 |
|
208 |
def queryset(self): |
209 |
return self.model.objects.all() |
210 |
|
211 |
def GET(self, request, data, *args, **kwargs): |
212 |
return self.json_response( |
213 |
list(self.qs_to_dict_iter(self.queryset(), self.exclude_fields))) |
214 |
|
215 |
def POST(self, request, data, *args, **kwargs): |
216 |
instance = self.model()
|
217 |
self.update_instance(instance, data, self.exclude_fields) |
218 |
instance.full_clean() |
219 |
instance.save() |
220 |
return self.json_response( |
221 |
self.instance_to_dict(instance, self.exclude_fields)) |
222 |
|
223 |
|
224 |
class UserResourceView(ResourceView): |
225 |
"""
|
226 |
Filter resource queryset for request user entries
|
227 |
"""
|
228 |
def queryset(self): |
229 |
return super(UserResourceView, |
230 |
self).queryset().filter(user=self.request.user_uniq) |
231 |
|
232 |
|
233 |
class UserCollectionView(CollectionView): |
234 |
"""
|
235 |
Filter collection queryset for request user entries
|
236 |
"""
|
237 |
def queryset(self): |
238 |
return super(UserCollectionView, |
239 |
self).queryset().filter(user=self.request.user_uniq) |
240 |
|
241 |
def POST(self, request, data, *args, **kwargs): |
242 |
instance = self.model()
|
243 |
self.update_instance(instance, data, self.exclude_fields) |
244 |
instance.user = request.user_uniq |
245 |
instance.full_clean() |
246 |
instance.save() |
247 |
return self.json_response( |
248 |
self.instance_to_dict(instance, self.exclude_fields)) |