Projet

Général

Profil

0001-embed-jsonresponse-into-the-package-10283.patch

Voir les différences:

Subject: [PATCH] embed jsonresponse into the package (#10283)

 README                           |   9 +
 debian/control                   |   1 -
 debian/debian_config.py          |  11 +-
 passerelle/apps/bdp/views.py     |   6 +-
 passerelle/apps/choosit/views.py |   7 +-
 passerelle/apps/clicrdv/views.py |  16 +-
 passerelle/datasources/views.py  |   2 +-
 passerelle/utils.py              | 141 --------------
 passerelle/utils/__init__.py     | 102 +++++++++++
 passerelle/utils/jsonresponse.py | 384 +++++++++++++++++++++++++++++++++++++++
 setup.py                         |   1 -
 11 files changed, 515 insertions(+), 165 deletions(-)
 delete mode 100644 passerelle/utils.py
 create mode 100644 passerelle/utils/__init__.py
 create mode 100644 passerelle/utils/jsonresponse.py
README
89 89
Creative Commons – Attribution (CC BY 3.0) http://creativecommons.org/licenses/by/3.0/us/
90 90
"Family" designed by Ahmed Elzahra http://www.thenounproject.com/trochilidae/
91 91
from the Noun Project http://www.thenounproject.com/
92

  
93

  
94
Copyright
95
---------
96

  
97
django-jsonresponse (https://github.com/jjay/django-jsonresponse)
98
 # Files: passerelle/utils/jsonresponse.py
99
 # Copyright (c) 2012 Yasha Borevich <j.borevich@gmail.com>
100
 # Licensed under the BSD license
debian/control
16 16
    ${misc:Depends},
17 17
    python-django (>= 1.7),
18 18
    python-gadjo,
19
    python-django-jsonresponse,
20 19
    python-django-model-utils,
21 20
    python-requests,
22 21
    python-setuptools,
debian/debian_config.py
19 19
MELLON_OPENED_SESSION_COOKIE_NAME = None
20 20

  
21 21
# suds logs are buggy
22
LOGGING['loggers']['suds'] = {
22
LOGGING['loggers'].update(
23
    {'suds': {
23 24
        'level': 'ERROR',
24 25
        'handlers': ['mail_admins', 'sentry'],
25
        'propagate': True,
26
}
26
        'propagate': True
27
    },
28
    'passerelle.jsonresponse': {
29
        'level': 'ERROR'
30
    }
31
})
27 32

  
28 33
# Add passerelle hobo agent
29 34
INSTALLED_APPS = ('hobo.agent.passerelle',) + INSTALLED_APPS
passerelle/apps/bdp/views.py
6 6
from django.views.generic.detail import SingleObjectMixin, DetailView
7 7
from django.views.generic.edit import CreateView, UpdateView, DeleteView
8 8

  
9
from jsonresponse import to_json
10

  
11 9
from passerelle import utils
12 10

  
13 11
from .models import Bdp
......
21 19
    model = Bdp
22 20

  
23 21
    @utils.protected_api('can_access')
24
    @to_json('api')
22
    @utils.to_json('api')
25 23
    def get(self, request, *args, **kwargs):
26 24
        text_key = request.GET.get('text_key')
27 25
        id_key = request.GET.get('id_key')
......
44 42
        raise Http404
45 43

  
46 44
    @utils.protected_api('can_access')
47
    @to_json('api')
45
    @utils.to_json('api')
48 46
    def post(self, request, *args, **kwargs):
49 47
        data = json.loads(request.body) # JSON w.c.s. formdata
50 48
        date_de_naissance = data['fields'].get('date_de_naissance')
passerelle/apps/choosit/views.py
1 1
import json
2 2

  
3
from jsonresponse import to_json
4

  
5 3
from django.core.urlresolvers import reverse
6

  
7 4
from django.views.generic.edit import CreateView, UpdateView, DeleteView, View
8 5
from django.views.generic.detail import SingleObjectMixin
9 6
from django.utils.decorators import method_decorator
......
79 76
    def dispatch(self, request, *args, **kwargs):
80 77
        return super(ChoositRegisterView, self).dispatch(request, *args, **kwargs)
81 78

  
82
    @to_json('api')
79
    @utils.to_json('api')
83 80
    @method_decorator(csrf_exempt)
84 81
    def get(self, request, *args, **kwargs):
85 82
        user = request.GET.get('user')
86 83
        assert user, 'missing user parameter'
87 84
        return self.get_object().get_list(user)
88 85

  
89
    @to_json('api')
86
    @utils.to_json('api')
90 87
    def post(self, request, *args, **kwargs):
91 88
        user = request.GET.get('user')
92 89
        assert user, 'missing user parameter'
passerelle/apps/clicrdv/views.py
7 7
from django.views.generic.detail import SingleObjectMixin, DetailView
8 8
from django.views.generic.edit import CreateView, UpdateView, DeleteView
9 9

  
10
from jsonresponse import to_json
11

  
12 10
from passerelle import utils
13 11
from passerelle.base.views import ResourceView
14 12

  
......
51 49

  
52 50
    model = ClicRdv
53 51

  
54
    @to_json('api')
52
    @utils.to_json('api')
55 53
    def get(self, request, *args, **kwargs):
56 54
        return self.get_object().get_interventionsets()
57 55

  
......
66 64
    """
67 65
    model = ClicRdv
68 66

  
69
    @to_json('api')
67
    @utils.to_json('api')
70 68
    def get(self, request, set_id, *args, **kwargs):
71 69
        return self.get_object().get_interventions(set_id)
72 70

  
......
78 76
    """
79 77
    model = ClicRdv
80 78

  
81
    @to_json('api')
79
    @utils.to_json('api')
82 80
    def get(self, request, intervention_id, *args, **kwargs):
83 81
        return self.get_object().get_datetimes(intervention_id)
84 82

  
......
93 91
    """
94 92
    model = ClicRdv
95 93

  
96
    @to_json('api')
94
    @utils.to_json('api')
97 95
    def get(self, request, intervention_id, *args, **kwargs):
98 96
        return self.get_object().get_dates(intervention_id)
99 97

  
......
108 106
    """
109 107
    model = ClicRdv
110 108

  
111
    @to_json('api')
109
    @utils.to_json('api')
112 110
    def get(self, request, intervention_id, date, *args, **kwargs):
113 111
        return self.get_object().get_times(intervention_id, date)
114 112

  
......
140 138
    model = ClicRdv
141 139

  
142 140
    @utils.protected_api('can_manage_appointment')
143
    @to_json('api')
141
    @utils.to_json('api')
144 142
    def post(self, request, intervention_id=None, *args, **kwargs):
145 143
        if intervention_id is None:
146 144
            intervention_id = self.request.GET.get('intervention')
......
161 159
    model = ClicRdv
162 160

  
163 161
    @utils.protected_api('can_manage_appointment')
164
    @to_json('api')
162
    @utils.to_json('api')
165 163
    def get(self, request, appointment_id, *args, **kwargs):
166 164
        return self.get_object().cancel(appointment_id)
passerelle/datasources/views.py
1 1
import json
2 2

  
3 3
from django.views.decorators.csrf import csrf_exempt
4
from jsonresponse import to_json
5 4

  
6 5
from models import BaseDataSource
6
from passerelle.utils import to_json
7 7

  
8 8

  
9 9
def get_data(request, slug, id=None):
passerelle/utils.py
1
from functools import wraps
2
import json
3
import re
4

  
5
from jsonresponse import to_json as jsonresponse_to_json
6

  
7
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
8
from django.core.serializers.json import DjangoJSONEncoder
9
from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest
10
from django.template import Template, Context
11
from django.utils.decorators import available_attrs
12
from django.views.generic.detail import SingleObjectMixin
13

  
14
from django.contrib.contenttypes.models import ContentType
15

  
16
from .base.context_processors import template_vars
17
from .base.models import ApiUser, AccessRight
18
from .base.signature import check_query
19

  
20
class to_json(jsonresponse_to_json):
21
    def __init__(self, serializer_type, error_code=500, **kwargs):
22
        super(to_json, self).__init__(serializer_type, error_code, **kwargs)
23
        if 'cls' not in self.kwargs:
24
            self.kwargs['cls'] = DjangoJSONEncoder
25

  
26
    def api(self, func, req, *args, **kwargs):
27
        """
28
        Raises the exceptions provided by "raises" argument, else wraps the
29
        error message in json. Exceptions can define the error code returned in
30
        JSON, and HTTP status:
31

  
32
        class BlockedAccount(Exception):
33
            err_code = 100
34
            http_status = 403
35
        """
36
        if req.GET.get('raise'):
37
            return super(to_json, self).api(func, req, *args, **kwargs)
38
        # force raise=1 to handle exceptions here
39
        req.GET = req.GET.copy()
40
        req.GET.update({'raise': 1})
41
        try:
42
            return super(to_json, self).api(func, req, *args, **kwargs)
43
        except Exception as e:
44
            data = self.err_to_response(e)
45
            if getattr(e, 'err_code', None):
46
                data['err'] = e.err_code
47
            if getattr(e, 'http_status', None):
48
                status = e.http_status
49
            elif isinstance(e, ObjectDoesNotExist):
50
                status = 404
51
            elif isinstance(e, PermissionDenied):
52
                status = 403
53
            else:
54
                status = self.error_code
55
            return self.render_data(req, data, status)
56

  
57

  
58
def get_template_vars():
59
    """
60
    returns the template vars as dict, to be used in apps code
61
    """
62
    from django.http import HttpRequest
63
    return template_vars(HttpRequest())
64

  
65
def render_template_vars(value):
66
    """
67
    renders the template vars in a string
68
    """
69
    template = Template(value)
70
    return template.render(Context(get_template_vars()))
71

  
72

  
73
def response_for_json(request, data):
74
    response = HttpResponse(content_type='application/json')
75
    json_str = json.dumps(data)
76
    for variable in ('jsonpCallback', 'callback'):
77
        if variable in request.GET:
78
            identifier = request.GET[variable]
79
            if not re.match(r'^[$A-Za-z_][0-9A-Za-z_$]*$', identifier):
80
                return HttpResponseBadRequest('invalid JSONP callback name')
81
            json_str = '%s(%s);' % (identifier, json_str)
82
            break
83
    response.write(json_str)
84
    return response
85

  
86

  
87
def get_request_users(request):
88
    users = []
89

  
90
    users.extend(ApiUser.objects.filter(keytype=''))
91

  
92
    if 'orig' in request.GET and 'signature' in request.GET:
93
        orig = request.GET['orig']
94
        query = request.META['QUERY_STRING']
95
        signature_users = ApiUser.objects.filter(keytype='SIGN', username=orig)
96
        for signature_user in signature_users:
97
            if check_query(query, signature_user.key):
98
                users.append(signature_user)
99

  
100
    elif 'apikey' in request.GET:
101
        users.extend(ApiUser.objects.filter(keytype='API',
102
            key=request.GET['apikey']))
103

  
104
    elif request.META.has_key('HTTP_AUTHORIZATION'):
105
        (scheme, param) = request.META['HTTP_AUTHORIZATION'].split(' ',1)
106
        if scheme.lower() == 'basic':
107
            username, password = param.strip().decode('base64').split(':',1)
108
            users.extend(ApiUser.objects.filter(keytype='SIGN',
109
                    username=username, key=password))
110

  
111
    def ip_match(ip, match):
112
        if not ip:
113
            return True
114
        if ip == match:
115
            return True
116
        return False
117

  
118
    users = [x for x in users if ip_match(x.ipsource, request.META.get('REMOTE_ADDR'))]
119
    return users
120

  
121

  
122
def is_authorized(request, obj, perm):
123
    resource_type = ContentType.objects.get_for_model(obj)
124
    rights = AccessRight.objects.filter(resource_type=resource_type,
125
            resource_pk=obj.id, codename=perm)
126
    users = [x.apiuser for x in rights]
127
    return set(users).intersection(get_request_users(request))
128

  
129

  
130
def protected_api(perm):
131
    def decorator(view_func):
132
        @wraps(view_func, assigned=available_attrs(view_func))
133
        def _wrapped_view(instance, request, *args, **kwargs):
134
            if not isinstance(instance, SingleObjectMixin):
135
                raise Exception("protected_api must be applied on a method of a class based view")
136
            obj = instance.get_object()
137
            if not is_authorized(request, obj, perm):
138
                raise PermissionDenied()
139
            return view_func(instance, request, *args, **kwargs)
140
        return _wrapped_view
141
    return decorator
passerelle/utils/__init__.py
1
from functools import wraps
2
import json
3
import re
4

  
5
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
6
from django.core.serializers.json import DjangoJSONEncoder
7
from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest
8
from django.template import Template, Context
9
from django.utils.decorators import available_attrs
10
from django.views.generic.detail import SingleObjectMixin
11
from django.contrib.contenttypes.models import ContentType
12

  
13
from passerelle.base.context_processors import template_vars
14
from passerelle.base.models import ApiUser, AccessRight
15
from passerelle.base.signature import check_query
16

  
17
from .jsonresponse import to_json
18

  
19
def get_template_vars():
20
    """
21
    returns the template vars as dict, to be used in apps code
22
    """
23
    from django.http import HttpRequest
24
    return template_vars(HttpRequest())
25

  
26
def render_template_vars(value):
27
    """
28
    renders the template vars in a string
29
    """
30
    template = Template(value)
31
    return template.render(Context(get_template_vars()))
32

  
33

  
34
def response_for_json(request, data):
35
    response = HttpResponse(content_type='application/json')
36
    json_str = json.dumps(data)
37
    for variable in ('jsonpCallback', 'callback'):
38
        if variable in request.GET:
39
            identifier = request.GET[variable]
40
            if not re.match(r'^[$A-Za-z_][0-9A-Za-z_$]*$', identifier):
41
                return HttpResponseBadRequest('invalid JSONP callback name')
42
            json_str = '%s(%s);' % (identifier, json_str)
43
            break
44
    response.write(json_str)
45
    return response
46

  
47

  
48
def get_request_users(request):
49
    users = []
50

  
51
    users.extend(ApiUser.objects.filter(keytype=''))
52

  
53
    if 'orig' in request.GET and 'signature' in request.GET:
54
        orig = request.GET['orig']
55
        query = request.META['QUERY_STRING']
56
        signature_users = ApiUser.objects.filter(keytype='SIGN', username=orig)
57
        for signature_user in signature_users:
58
            if check_query(query, signature_user.key):
59
                users.append(signature_user)
60

  
61
    elif 'apikey' in request.GET:
62
        users.extend(ApiUser.objects.filter(keytype='API',
63
            key=request.GET['apikey']))
64

  
65
    elif request.META.has_key('HTTP_AUTHORIZATION'):
66
        (scheme, param) = request.META['HTTP_AUTHORIZATION'].split(' ',1)
67
        if scheme.lower() == 'basic':
68
            username, password = param.strip().decode('base64').split(':',1)
69
            users.extend(ApiUser.objects.filter(keytype='SIGN',
70
                    username=username, key=password))
71

  
72
    def ip_match(ip, match):
73
        if not ip:
74
            return True
75
        if ip == match:
76
            return True
77
        return False
78

  
79
    users = [x for x in users if ip_match(x.ipsource, request.META.get('REMOTE_ADDR'))]
80
    return users
81

  
82

  
83
def is_authorized(request, obj, perm):
84
    resource_type = ContentType.objects.get_for_model(obj)
85
    rights = AccessRight.objects.filter(resource_type=resource_type,
86
            resource_pk=obj.id, codename=perm)
87
    users = [x.apiuser for x in rights]
88
    return set(users).intersection(get_request_users(request))
89

  
90

  
91
def protected_api(perm):
92
    def decorator(view_func):
93
        @wraps(view_func, assigned=available_attrs(view_func))
94
        def _wrapped_view(instance, request, *args, **kwargs):
95
            if not isinstance(instance, SingleObjectMixin):
96
                raise Exception("protected_api must be applied on a method of a class based view")
97
            obj = instance.get_object()
98
            if not is_authorized(request, obj, perm):
99
                raise PermissionDenied()
100
            return view_func(instance, request, *args, **kwargs)
101
        return _wrapped_view
102
    return decorator
passerelle/utils/jsonresponse.py
1
# This module is a modified copy of code of Yasha's Borevich library
2
# django-jsonresponse (https://github.com/jjay/django-jsonresponse) distributed
3
# under BSD license
4

  
5
import json
6
import functools
7
import logging
8
from collections import Iterable
9

  
10
from django.http import HttpResponse
11
from django.conf import settings
12
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
13
from django.core.serializers.json import DjangoJSONEncoder
14

  
15
DEFAULT_DEBUG = getattr(settings, 'JSONRESPONSE_DEFAULT_DEBUG', False)
16
CALLBACK_NAME = getattr(settings, 'JSONRESPONSE_CALLBACK_NAME', 'callback')
17

  
18

  
19
class to_json(object):
20
    """
21
    Wrap view functions to render python native and custom
22
    objects to json
23

  
24
    >>> from django.test.client import RequestFactory
25
    >>> requests = RequestFactory()
26

  
27
    Simple wrap returning data into json
28

  
29
    >>> @to_json('plain')
30
    ... def hello(request):
31
    ...    return dict(hello='world')
32

  
33
    >>> resp = hello(requests.get('/hello/'))
34
    >>> print resp.status_code
35
    200
36
    >>> print resp.content
37
    {"hello": "world"}
38

  
39
    Result can be wraped in some api manier
40

  
41
    >>> @to_json('api')
42
    ... def goodbye(request):
43
    ...    return dict(good='bye')
44
    >>> resp = goodbye(requests.get('/goodbye', {'debug': 1}))
45
    >>> print resp.status_code
46
    200
47
    >>> print resp.content
48
    {
49
        "data": {
50
            "good": "bye"
51
        },
52
        "err": 0
53
    }
54

  
55
    Automaticaly error handling
56

  
57
    >>> @to_json('api')
58
    ... def error(request):
59
    ...    raise Exception('Wooot!??')
60

  
61
    >>> resp = error(requests.get('/error', {'debug': 1}))
62
    >>> print resp.status_code
63
    500
64
    >>> print resp.content # doctest: +NORMALIZE_WHITESPACE
65
    {
66
        "err_class": "Exception",
67
        "err_desc": "Wooot!??",
68
        "data": null,
69
        "err": 1
70
    }
71

  
72
    >>> from django.core.exceptions import ObjectDoesNotExist
73
    >>> @to_json('api')
74
    ... def error_404(request):
75
    ...     raise ObjectDoesNotExist('Not found')
76

  
77
    >>> resp = error_404(requests.get('/error', {'debug': 1}))
78
    >>> print resp.status_code
79
    404
80
    >>> print resp.content # doctest: +NORMALIZE_WHITESPACE
81
    {
82
        "err_class": "django.core.exceptions.ObjectDoesNotExist",
83
        "err_desc": "Not found",
84
        "data": null,
85
        "err": 1
86
    }
87

  
88

  
89
    You can serialize not only pure python data types.
90
    Implement `serialize` method on toplevel object or
91
    each element of toplevel array.
92

  
93
    >>> class User(object):
94
    ...     def __init__(self, name, age):
95
    ...         self.name = name
96
    ...         self.age = age
97
    ...
98
    ...     def serialize(self, request):
99
    ...         if request.GET.get('with_age', False):
100
    ...             return dict(name=self.name, age=self.age)
101
    ...         else:
102
    ...             return dict(name=self.name)
103

  
104
    >>> @to_json('objects')
105
    ... def users(request):
106
    ...    return [User('Bob', 10), User('Anna', 12)]
107

  
108
    >>> resp = users(requests.get('users', { 'debug': 1 }))
109
    >>> print resp.status_code
110
    200
111
    >>> print resp.content # doctest: +NORMALIZE_WHITESPACE
112
    {
113
        "data": [
114
            {
115
                "name": "Bob"
116
            },
117
            {
118
                "name": "Anna"
119
            }
120
        ],
121
        "err": 0
122
    }
123

  
124
    You can pass extra args for serialization:
125

  
126
    >>> resp = users(requests.get('users',
127
    ...     { 'debug':1, 'with_age':1 }))
128
    >>> print resp.status_code
129
    200
130
    >>> print resp.content # doctest: +NORMALIZE_WHITESPACE
131
    {
132
        "data": [
133
            {
134
                "age": 10,
135
                "name": "Bob"
136
            },
137
            {
138
                "age": 12,
139
                "name": "Anna"
140
            }
141
        ],
142
        "err": 0
143
    }
144

  
145
    It is easy to use jsonp, just pass format=jsonp
146

  
147
    >>> resp = users(requests.get('users',
148
    ...     { 'debug':1, 'format': 'jsonp' }))
149
    >>> print resp.status_code
150
    200
151
    >>> print resp.content # doctest: +NORMALIZE_WHITESPACE
152
    callback({
153
        "data": [
154
            {
155
                "name": "Bob"
156
            },
157
            {
158
                "name": "Anna"
159
            }
160
        ],
161
        "err": 0
162
    });
163

  
164
    You can override the name of callback method using
165
    JSONRESPONSE_CALLBACK_NAME option or query arg callback=another_callback
166

  
167
    >>> resp = users(requests.get('users',
168
    ...     { 'debug':1, 'format': 'jsonp', 'callback': 'my_callback' }))
169
    >>> print resp.status_code
170
    200
171
    >>> print resp.content # doctest: +NORMALIZE_WHITESPACE
172
    my_callback({
173
        "data": [
174
            {
175
                "name": "Bob"
176
            },
177
            {
178
                "name": "Anna"
179
            }
180
        ],
181
        "err": 0
182
    });
183

  
184
    You can pass raise=1 to raise exceptions in debug purposes
185
    instead of passing info to json response
186

  
187
    >>> @to_json('api')
188
    ... def error(request):
189
    ...    raise Exception('Wooot!??')
190

  
191
    >>> resp = error(requests.get('/error',
192
    ...     {'debug': 1, 'raise': 1}))
193
    Traceback (most recent call last):
194
    Exception: Wooot!??
195

  
196
    You can wraps both methods and functions
197

  
198
    >>> class View(object):
199
    ...     @to_json('plain')
200
    ...     def render(self, request):
201
    ...         return dict(data='ok')
202
    ...     @to_json('api')
203
    ...     def render_api(self, request):
204
    ...         return dict(data='ok')
205

  
206

  
207
    >>> view = View()
208
    >>> resp = view.render(requests.get('/render'))
209
    >>> print resp.status_code
210
    200
211
    >>> print resp.content # doctest: +NORMALIZE_WHITESPACE
212
    {"data": "ok"}
213

  
214
    Try it one more
215

  
216
    >>> resp = view.render(requests.get('/render'))
217
    >>> print resp.status_code
218
    200
219
    >>> print resp.content # doctest: +NORMALIZE_WHITESPACE
220
    {"data": "ok"}
221

  
222
    Try it one more with api
223

  
224
    >>> resp = view.render_api(requests.get('/render'))
225
    >>> print resp.status_code
226
    200
227
    >>> print resp.content # doctest: +NORMALIZE_WHITESPACE
228
    {"data": {"data": "ok"}, "err": 0}
229

  
230

  
231
    You can pass custom kwargs to json.dumps,
232
    just give them to constructor
233

  
234
    >>> @to_json('plain', separators=(',  ', ':  '))
235
    ... def custom_kwargs(request):
236
    ...    return ['a', { 'b': 1 }]
237
    >>> resp = custom_kwargs(requests.get('/render'))
238
    >>> print resp.status_code
239
    200
240
    >>> print resp.content
241
    ["a",  {"b":  1}]
242
    """
243
    def __init__(self, serializer_type, error_code=500, **kwargs):
244
        """
245
        serializer_types:
246
            * api - serialize buildin objects (dict, list, etc) in strict api
247
            * objects - serialize list of region in strict api
248
            * plain - just serialize result of function, do not wrap response and do not handle exceptions
249
        """
250
        self.serializer_type = serializer_type
251
        self.method = None
252
        self.error_code=error_code
253
        self.kwargs = kwargs
254
        if 'cls' not in self.kwargs:
255
            self.kwargs['cls'] = DjangoJSONEncoder
256

  
257
    def __call__(self, f):
258
        @functools.wraps(f)
259
        def wrapper(*args, **kwargs):
260
            if self.method:
261
                return self.method(f, *args, **kwargs)
262

  
263
            if not args:
264
                if self.serializer_type == 'plain':
265
                    self.method = self.plain_func
266
                else:
267
                    self.method = self.api_func
268

  
269
            if getattr(getattr(args[0], f.__name__, None), "im_self", False):
270
                if self.serializer_type == 'plain':
271
                    self.method = self.plain_method
272
                else:
273
                    self.method = self.api_method
274
            else:
275
                if self.serializer_type == 'plain':
276
                    self.method = self.plain_func
277
                else:
278
                    self.method = self.api_func
279

  
280
            return self.method(f, *args, **kwargs)
281

  
282
        return wrapper
283

  
284
    def obj_to_response(self, req, obj):
285
        if self.serializer_type == 'objects':
286
            if isinstance(obj, Iterable):
287
                obj = [o.serialize(req) if obj else None for o in obj]
288
            elif obj:
289
                obj = obj.serialize(req)
290
            else:
291
                obj = None
292

  
293
        return { "err": 0, "data": obj }
294

  
295
    def err_to_response(self, err):
296
        if hasattr(err, "__module__"):
297
            err_module = err.__module__ + "."
298
        else:
299
            err_module = ""
300

  
301
        if hasattr(err, "owner"):
302
            err_module += err.owner.__name__ + "."
303

  
304
        err_class = err_module + err.__class__.__name__
305

  
306
        err_desc = str(err)
307

  
308
        return {
309
            "err": 1,
310
            "err_class": err_class,
311
            "err_desc": err_desc,
312
            "data": None
313
        }
314

  
315
    def render_data(self, req, data, status=200):
316
        debug = DEFAULT_DEBUG
317
        debug = debug or req.GET.get('debug', 'false').lower() in ('true', 't', '1', 'on')
318
        debug = debug or req.GET.get('decode', '0').lower() in ('true', 't', '1', 'on')
319
        format = req.GET.get('format', 'json')
320
        jsonp_cb = req.GET.get('callback', CALLBACK_NAME)
321
        content_type = "application/json"
322

  
323
        kwargs = dict(self.kwargs)
324
        if debug:
325
            kwargs["indent"] = 4
326
            kwargs["ensure_ascii"] = False
327
            kwargs["encoding"] = "utf8"
328

  
329
        plain = json.dumps(data, **kwargs)
330
        if format == 'jsonp':
331
            plain = "%s(%s);" % (jsonp_cb, plain)
332
            content_type = "application/javascript"
333

  
334
        return HttpResponse(plain, content_type="%s; charset=UTF-8" % content_type, status=status)
335

  
336
    def api_func(self, f, *args, **kwargs):
337
        return self.api(f, args[0], *args, **kwargs)
338

  
339
    def api_method(self, f, *args, **kwargs):
340
        return self.api(f, args[1], *args, **kwargs)
341

  
342
    def api(self, f, req, *args, **kwargs):
343
        logger = logging.getLogger('passerelle.jsonresponse')
344
        try:
345
            resp = f(*args, **kwargs)
346
            if isinstance(resp, HttpResponse):
347
                return resp
348

  
349
            data = self.obj_to_response(req, resp)
350
            status = 200
351
        except Exception as e:
352
            extras = {'method': req.method}
353
            if req.method == 'POST':
354
                extras.update({'body': req.body})
355
            logger.exception("Error occurred while processing request", extra=extras)
356
            if int(req.GET.get('raise', 0)):
357
                raise
358

  
359
            data = self.err_to_response(e)
360
            if getattr(e, 'err_code', None):
361
                data['err'] = e.err_code
362
            if getattr(e, 'http_status', None):
363
                status = e.http_status
364
            elif isinstance(e, ObjectDoesNotExist):
365
                status = 404
366
            elif isinstance(e, PermissionDenied):
367
                status = 403
368
            else:
369
                status = self.error_code
370
        return self.render_data(req, data, status)
371

  
372
    def plain_method(self, f, *args, **kwargs):
373
        data = f(*args, **kwargs)
374
        if isinstance(data, HttpResponse):
375
            return data
376

  
377
        return self.render_data(args[1], data)
378

  
379
    def plain_func(self, f, *args, **kwargs):
380
        data = f(*args, **kwargs)
381
        if isinstance(data, HttpResponse):
382
            return data
383

  
384
        return self.render_data(args[0], data)
setup.py
87 87
        install_requires=[
88 88
            'django >= 1.7, <1.8',
89 89
            'django-model-utils',
90
            'django-jsonresponse==0.10',
91 90
            'django-jsonfield >= 0.9.3',
92 91
            'requests',
93 92
            'gadjo',
94
-