0001-embed-jsonresponse-into-the-package-10283.patch
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 |
- |