Projet

Général

Profil

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

Serghei Mihai (congés, retour 15/05), 13 avril 2016 16:41

Télécharger (32,4 ko)

Voir les différences:

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

 README                           |   9 +
 debian/control                   |   1 -
 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              | 183 -------------------
 passerelle/utils/__init__.py     | 144 +++++++++++++++
 passerelle/utils/jsonresponse.py | 384 +++++++++++++++++++++++++++++++++++++++
 setup.py                         |   1 -
 tests/test_jsonresponse.py       |  32 ++++
 11 files changed, 581 insertions(+), 204 deletions(-)
 delete mode 100644 passerelle/utils.py
 create mode 100644 passerelle/utils/__init__.py
 create mode 100644 passerelle/utils/jsonresponse.py
 create mode 100644 tests/test_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,
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
import logging
5

  
6
from requests import Session as RequestSession
7
from jsonresponse import to_json as jsonresponse_to_json
8

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

  
17
from django.contrib.contenttypes.models import ContentType
18

  
19
from .base.context_processors import template_vars
20
from .base.models import ApiUser, AccessRight
21
from .base.signature import check_query
22

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

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

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

  
60

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

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

  
75

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

  
89

  
90
def get_request_users(request):
91
    users = []
92

  
93
    users.extend(ApiUser.objects.filter(keytype=''))
94

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

  
103
    elif 'apikey' in request.GET:
104
        users.extend(ApiUser.objects.filter(keytype='API',
105
            key=request.GET['apikey']))
106

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

  
114
    def ip_match(ip, match):
115
        if not ip:
116
            return True
117
        if ip == match:
118
            return True
119
        return False
120

  
121
    users = [x for x in users if ip_match(x.ipsource, request.META.get('REMOTE_ADDR'))]
122
    return users
123

  
124

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

  
132

  
133
def protected_api(perm):
134
    def decorator(view_func):
135
        @wraps(view_func, assigned=available_attrs(view_func))
136
        def _wrapped_view(instance, request, *args, **kwargs):
137
            if not isinstance(instance, SingleObjectMixin):
138
                raise Exception("protected_api must be applied on a method of a class based view")
139
            obj = instance.get_object()
140
            if not is_authorized(request, obj, perm):
141
                raise PermissionDenied()
142
            return view_func(instance, request, *args, **kwargs)
143
        return _wrapped_view
144
    return decorator
145

  
146

  
147
# Wrapper around requests.Session
148
# logging requests input and output data
149

  
150
class LoggedRequest(RequestSession):
151

  
152
    def __init__(self, *args, **kwargs):
153
        self.logger = kwargs.pop('logger')
154
        super(LoggedRequest, self).__init__(*args, **kwargs)
155

  
156
    def request(self, method, url, **kwargs):
157
        self.logger.info('%s %s' % (method, url),
158
            extra={'requests_url': url}
159
        )
160

  
161
        response = super(LoggedRequest, self).request(method, url, **kwargs)
162

  
163
        self.logger.debug('Request Headers: {}'.format(''.join([
164
            '%s: %s | ' % (k,v) for k,v in response.request.headers.items()
165
        ])))
166
        if response.request.body:
167
            self.logger.info('Request Payload: %r' %(response.request.body),
168
                extra={'requests_request_payload': '%r' %response.request.body})
169
        self.logger.info('Status code: %r' %(response.status_code),
170
            extra={'requests_response_status': response.status_code})
171
        resp_headers = ''.join([
172
            '%s: %s | ' % (k,v) for k,v in response.headers.items()
173
        ])
174
        self.logger.debug('Response Headers: %r' %resp_headers, extra={
175
            'requests_response_headers': resp_headers})
176
        content = response.content[:getattr(settings,
177
            'REQUESTS_RESPONSE_CONTENT_MAX_LENGTH',5000)]
178
        self.logger.debug('Response Content: %r' % content,
179
            extra={'requests_response_content': content})
180

  
181
        return response
182

  
183

  
passerelle/utils/__init__.py
1
from functools import wraps
2
import json
3
import re
4
import logging
5

  
6
from requests import Session as RequestSession
7
from jsonresponse import to_json as jsonresponse_to_json
8

  
9
from django.conf import settings
10
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
11
from django.core.serializers.json import DjangoJSONEncoder
12
from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest
13
from django.template import Template, Context
14
from django.utils.decorators import available_attrs
15
from django.views.generic.detail import SingleObjectMixin
16
from django.contrib.contenttypes.models import ContentType
17

  
18
from passerelle.base.context_processors import template_vars
19
from passerelle.base.models import ApiUser, AccessRight
20
from passerelle.base.signature import check_query
21

  
22
from .jsonresponse import to_json
23

  
24
def get_template_vars():
25
    """
26
    returns the template vars as dict, to be used in apps code
27
    """
28
    from django.http import HttpRequest
29
    return template_vars(HttpRequest())
30

  
31
def render_template_vars(value):
32
    """
33
    renders the template vars in a string
34
    """
35
    template = Template(value)
36
    return template.render(Context(get_template_vars()))
37

  
38

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

  
52

  
53
def get_request_users(request):
54
    users = []
55

  
56
    users.extend(ApiUser.objects.filter(keytype=''))
57

  
58
    if 'orig' in request.GET and 'signature' in request.GET:
59
        orig = request.GET['orig']
60
        query = request.META['QUERY_STRING']
61
        signature_users = ApiUser.objects.filter(keytype='SIGN', username=orig)
62
        for signature_user in signature_users:
63
            if check_query(query, signature_user.key):
64
                users.append(signature_user)
65

  
66
    elif 'apikey' in request.GET:
67
        users.extend(ApiUser.objects.filter(keytype='API',
68
            key=request.GET['apikey']))
69

  
70
    elif request.META.has_key('HTTP_AUTHORIZATION'):
71
        (scheme, param) = request.META['HTTP_AUTHORIZATION'].split(' ',1)
72
        if scheme.lower() == 'basic':
73
            username, password = param.strip().decode('base64').split(':',1)
74
            users.extend(ApiUser.objects.filter(keytype='SIGN',
75
                    username=username, key=password))
76

  
77
    def ip_match(ip, match):
78
        if not ip:
79
            return True
80
        if ip == match:
81
            return True
82
        return False
83

  
84
    users = [x for x in users if ip_match(x.ipsource, request.META.get('REMOTE_ADDR'))]
85
    return users
86

  
87

  
88
def is_authorized(request, obj, perm):
89
    resource_type = ContentType.objects.get_for_model(obj)
90
    rights = AccessRight.objects.filter(resource_type=resource_type,
91
            resource_pk=obj.id, codename=perm)
92
    users = [x.apiuser for x in rights]
93
    return set(users).intersection(get_request_users(request))
94

  
95

  
96
def protected_api(perm):
97
    def decorator(view_func):
98
        @wraps(view_func, assigned=available_attrs(view_func))
99
        def _wrapped_view(instance, request, *args, **kwargs):
100
            if not isinstance(instance, SingleObjectMixin):
101
                raise Exception("protected_api must be applied on a method of a class based view")
102
            obj = instance.get_object()
103
            if not is_authorized(request, obj, perm):
104
                raise PermissionDenied()
105
            return view_func(instance, request, *args, **kwargs)
106
        return _wrapped_view
107
    return decorator
108

  
109

  
110
# Wrapper around requests.Session
111
# logging requests input and output data
112

  
113
class LoggedRequest(RequestSession):
114

  
115
    def __init__(self, *args, **kwargs):
116
        self.logger = kwargs.pop('logger')
117
        super(LoggedRequest, self).__init__(*args, **kwargs)
118

  
119
    def request(self, method, url, **kwargs):
120
        self.logger.info('%s %s' % (method, url),
121
            extra={'requests_url': url}
122
        )
123

  
124
        response = super(LoggedRequest, self).request(method, url, **kwargs)
125

  
126
        self.logger.debug('Request Headers: {}'.format(''.join([
127
            '%s: %s | ' % (k,v) for k,v in response.request.headers.items()
128
        ])))
129
        if response.request.body:
130
            self.logger.info('Request Payload: %r' %(response.request.body),
131
                extra={'requests_request_payload': '%r' %response.request.body})
132
        self.logger.info('Status code: %r' %(response.status_code),
133
            extra={'requests_response_status': response.status_code})
134
        resp_headers = ''.join([
135
            '%s: %s | ' % (k,v) for k,v in response.headers.items()
136
        ])
137
        self.logger.debug('Response Headers: %r' %resp_headers, extra={
138
            'requests_response_headers': resp_headers})
139
        content = response.content[:getattr(settings,
140
            'REQUESTS_RESPONSE_CONTENT_MAX_LENGTH',5000)]
141
        self.logger.debug('Response Content: %r' % content,
142
            extra={'requests_response_content': content})
143

  
144
        return response
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',
tests/test_jsonresponse.py
1
import logging
2
import pytest
3
import json
4

  
5
from django.test.client import RequestFactory
6

  
7

  
8
from passerelle.utils import to_json
9

  
10
class WrappedException(Exception):
11
    pass
12

  
13

  
14
@to_json('api')
15
def wrapped_exception(req, *args, **kwargs):
16
    raise WrappedException
17

  
18

  
19
def test_jsonresponselog_get(caplog):
20
    request = RequestFactory()
21
    wrapped_exception(request.get('/'))
22
    post_payload = {'data': 'plop'}
23
    with pytest.raises(WrappedException):
24
        wrapped_exception(request.post('/?raise=1', post_payload))
25

  
26
    for record in caplog.records():
27
        assert record.name == 'passerelle.jsonresponse'
28
        assert record.levelno == logging.ERROR
29
        assert hasattr(record, 'method')
30
        if record.method == 'POST':
31
            assert hasattr(record, 'body')
32
        assert "Error occurred while processing request" in record.message
0
-