root / snf-cyclades-app / synnefo / ui / userdata / rest.py @ 4b3b8688
History | View | Annotate | Download (8.3 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.template import RequestContext, loader |
37 |
from django.utils import simplejson as json |
38 |
from django.core import serializers |
39 |
from django.core.urlresolvers import reverse |
40 |
|
41 |
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS |
42 |
|
43 |
# base view class
|
44 |
# https://github.com/bfirsh/django-class-based-views/blob/master/class_based_views/base.py
|
45 |
|
46 |
|
47 |
class View(object): |
48 |
"""
|
49 |
Intentionally simple parent class for all views. Only implements
|
50 |
dispatch-by-method and simple sanity checking.
|
51 |
"""
|
52 |
|
53 |
method_names = ['GET', 'POST', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE'] |
54 |
|
55 |
def __init__(self, *args, **kwargs): |
56 |
"""
|
57 |
Constructor. Called in the URLconf; can contain helpful extra
|
58 |
keyword arguments, and other things.
|
59 |
"""
|
60 |
# Go through keyword arguments, and either save their values to our
|
61 |
# instance, or raise an error.
|
62 |
for key, value in kwargs.items(): |
63 |
if key in self.method_names: |
64 |
raise TypeError(u"You tried to pass in the %s method name as a" |
65 |
u" keyword argument to %s(). Don't do that."
|
66 |
% (key, self.__class__.__name__))
|
67 |
if hasattr(self, key): |
68 |
setattr(self, key, value) |
69 |
else:
|
70 |
raise TypeError(u"%s() received an invalid keyword %r" % ( |
71 |
self.__class__.__name__,
|
72 |
key, |
73 |
)) |
74 |
|
75 |
@classmethod
|
76 |
def as_view(cls, *initargs, **initkwargs): |
77 |
"""
|
78 |
Main entry point for a request-response process.
|
79 |
"""
|
80 |
def view(request, *args, **kwargs): |
81 |
self = cls(*initargs, **initkwargs)
|
82 |
return self.dispatch(request, *args, **kwargs) |
83 |
return view
|
84 |
|
85 |
def dispatch(self, request, *args, **kwargs): |
86 |
# Try to dispatch to the right method for that; if it doesn't exist,
|
87 |
# raise a big error.
|
88 |
if hasattr(self, request.method.upper()): |
89 |
self.request = request
|
90 |
self.args = args
|
91 |
self.kwargs = kwargs
|
92 |
data = request.raw_post_data |
93 |
|
94 |
if request.method.upper() in ['POST', 'PUT']: |
95 |
# Expect json data
|
96 |
if request.META.get('CONTENT_TYPE').startswith('application/json'): |
97 |
try:
|
98 |
data = json.loads(data) |
99 |
except ValueError: |
100 |
raise http.HttpResponseServerError('Invalid JSON data.') |
101 |
else:
|
102 |
raise http.HttpResponseServerError('Unsupported Content-Type.') |
103 |
try:
|
104 |
return getattr(self, request.method.upper())(request, data, *args, **kwargs) |
105 |
except ValidationError, e:
|
106 |
# specific response for validation errors
|
107 |
return http.HttpResponseServerError(json.dumps({'errors': |
108 |
e.message_dict, 'non_field_key':
|
109 |
NON_FIELD_ERRORS })) |
110 |
|
111 |
else:
|
112 |
allowed_methods = [m for m in self.method_names if hasattr(self, m)] |
113 |
return http.HttpResponseNotAllowed(allowed_methods)
|
114 |
|
115 |
|
116 |
class JSONRestView(View): |
117 |
"""
|
118 |
Class that provides helpers to produce a json response
|
119 |
"""
|
120 |
|
121 |
url_name = None
|
122 |
def __init__(self, url_name, *args, **kwargs): |
123 |
self.url_name = url_name
|
124 |
return super(JSONRestView, self).__init__(*args, **kwargs) |
125 |
|
126 |
def update_instance(self, i, data, exclude_fields=[]): |
127 |
update_keys = data.keys() |
128 |
for field in i._meta.get_all_field_names(): |
129 |
if field in update_keys and (field not in exclude_fields): |
130 |
i.__setattr__(field, data[field]) |
131 |
|
132 |
return i
|
133 |
|
134 |
def instance_to_dict(self, i, exclude_fields=[]): |
135 |
"""
|
136 |
Convert model instance to python dict
|
137 |
"""
|
138 |
d = {} |
139 |
d['uri'] = reverse(self.url_name, kwargs={'id': i.pk}) |
140 |
|
141 |
for field in i._meta.get_all_field_names(): |
142 |
if field in exclude_fields: |
143 |
continue
|
144 |
|
145 |
d[field] = i.__getattribute__(field) |
146 |
return d
|
147 |
|
148 |
def qs_to_dict_iter(self, qs, exclude_fields=[]): |
149 |
"""
|
150 |
Convert queryset to an iterator of model instances dicts
|
151 |
"""
|
152 |
for i in qs: |
153 |
yield self.instance_to_dict(i, exclude_fields) |
154 |
|
155 |
def json_response(self, data): |
156 |
return http.HttpResponse(json.dumps(data), mimetype="application/json") |
157 |
|
158 |
|
159 |
class ResourceView(JSONRestView): |
160 |
method_names = ['GET', 'POST', 'PUT', 'DELETE'] |
161 |
|
162 |
model = None
|
163 |
exclude_fields = [] |
164 |
|
165 |
def queryset(self): |
166 |
return self.model.objects.all() |
167 |
|
168 |
def instance(self): |
169 |
"""
|
170 |
Retrieve selected instance based on url parameter
|
171 |
|
172 |
id parameter should be set in urlpatterns expression
|
173 |
"""
|
174 |
try:
|
175 |
return self.queryset().get(pk=self.kwargs.get("id")) |
176 |
except self.model.DoesNotExist: |
177 |
raise http.Http404
|
178 |
|
179 |
def GET(self, request, data, *args, **kwargs): |
180 |
return self.json_response(self.instance_to_dict(self.instance(), |
181 |
self.exclude_fields))
|
182 |
|
183 |
def PUT(self, request, data, *args, **kwargs): |
184 |
instance = self.instance()
|
185 |
self.update_instance(instance, data, self.exclude_fields) |
186 |
instance.full_clean() |
187 |
instance.save() |
188 |
return self.GET(request, data, *args, **kwargs) |
189 |
|
190 |
def DELETE(self, request, data, *args, **kwargs): |
191 |
self.instance().delete()
|
192 |
return self.json_response("") |
193 |
|
194 |
|
195 |
class CollectionView(JSONRestView): |
196 |
method_names = ['GET', 'POST'] |
197 |
|
198 |
model = None
|
199 |
exclude_fields = [] |
200 |
|
201 |
def queryset(self): |
202 |
return self.model.objects.all() |
203 |
|
204 |
def GET(self, request, data, *args, **kwargs): |
205 |
return self.json_response(list(self.qs_to_dict_iter(self.queryset(), |
206 |
self.exclude_fields)))
|
207 |
|
208 |
def POST(self, request, data, *args, **kwargs): |
209 |
instance = self.model()
|
210 |
self.update_instance(instance, data, self.exclude_fields) |
211 |
instance.full_clean() |
212 |
instance.save() |
213 |
return self.json_response(self.instance_to_dict(instance, |
214 |
self.exclude_fields))
|
215 |
|
216 |
|
217 |
class UserResourceView(ResourceView): |
218 |
"""
|
219 |
Filter resource queryset for request user entries
|
220 |
"""
|
221 |
def queryset(self): |
222 |
return super(UserResourceView, |
223 |
self).queryset().filter(user=self.request.user) |
224 |
|
225 |
|
226 |
class UserCollectionView(CollectionView): |
227 |
"""
|
228 |
Filter collection queryset for request user entries
|
229 |
"""
|
230 |
def queryset(self): |
231 |
return super(UserCollectionView, self).queryset().filter(user=self.request.user_uniq) |
232 |
|
233 |
def POST(self, request, data, *args, **kwargs): |
234 |
instance = self.model()
|
235 |
self.update_instance(instance, data, self.exclude_fields) |
236 |
instance.user = request.user |
237 |
instance.full_clean() |
238 |
instance.save() |
239 |
return self.json_response(self.instance_to_dict(instance, |
240 |
self.exclude_fields))
|