Statistics
| Branch: | Tag: | Revision:

root / snf-app / synnefo / ui / userdata / rest.py @ 483c9197

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)
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))