embed jsonresponse into the package (#10283)

This commit is contained in:
Serghei Mihai 2016-03-14 12:32:53 +01:00
parent 6791aaeb22
commit 9ba00c7024
10 changed files with 441 additions and 64 deletions

9
README
View File

@ -89,3 +89,12 @@ icon-concerto.svg license:
Creative Commons Attribution (CC BY 3.0) http://creativecommons.org/licenses/by/3.0/us/
"Family" designed by Ahmed Elzahra http://www.thenounproject.com/trochilidae/
from the Noun Project http://www.thenounproject.com/
Copyright
---------
django-jsonresponse (https://github.com/jjay/django-jsonresponse)
# Files: passerelle/utils/jsonresponse.py
# Copyright (c) 2012 Yasha Borevich <j.borevich@gmail.com>
# Licensed under the BSD license

1
debian/control vendored
View File

@ -16,7 +16,6 @@ Depends: ${python:Depends},
${misc:Depends},
python-django (>= 1.7),
python-gadjo,
python-django-jsonresponse,
python-django-model-utils,
python-requests,
python-setuptools,

View File

@ -6,8 +6,6 @@ from django.views.generic.base import View
from django.views.generic.detail import SingleObjectMixin, DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from jsonresponse import to_json
from passerelle import utils
from .models import Bdp
@ -21,7 +19,7 @@ class ResourcesView(View, SingleObjectMixin):
model = Bdp
@utils.protected_api('can_access')
@to_json('api')
@utils.to_json('api')
def get(self, request, *args, **kwargs):
text_key = request.GET.get('text_key')
id_key = request.GET.get('id_key')
@ -44,7 +42,7 @@ class PostAdherentView(View, SingleObjectMixin):
raise Http404
@utils.protected_api('can_access')
@to_json('api')
@utils.to_json('api')
def post(self, request, *args, **kwargs):
data = json.loads(request.body) # JSON w.c.s. formdata
date_de_naissance = data['fields'].get('date_de_naissance')

View File

@ -1,9 +1,6 @@
import json
from jsonresponse import to_json
from django.core.urlresolvers import reverse
from django.views.generic.edit import CreateView, UpdateView, DeleteView, View
from django.views.generic.detail import SingleObjectMixin
from django.utils.decorators import method_decorator
@ -79,14 +76,14 @@ class ChoositRegisterView(View, SingleObjectMixin):
def dispatch(self, request, *args, **kwargs):
return super(ChoositRegisterView, self).dispatch(request, *args, **kwargs)
@to_json('api')
@utils.to_json('api')
@method_decorator(csrf_exempt)
def get(self, request, *args, **kwargs):
user = request.GET.get('user')
assert user, 'missing user parameter'
return self.get_object().get_list(user)
@to_json('api')
@utils.to_json('api')
def post(self, request, *args, **kwargs):
user = request.GET.get('user')
assert user, 'missing user parameter'

View File

@ -7,8 +7,6 @@ from django.views.generic.base import View, RedirectView
from django.views.generic.detail import SingleObjectMixin, DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from jsonresponse import to_json
from passerelle import utils
from passerelle.base.views import ResourceView
@ -51,7 +49,7 @@ class InterventionSetsView(View, SingleObjectMixin):
model = ClicRdv
@to_json('api')
@utils.to_json('api')
def get(self, request, *args, **kwargs):
return self.get_object().get_interventionsets()
@ -66,7 +64,7 @@ class InterventionsView(View, SingleObjectMixin):
"""
model = ClicRdv
@to_json('api')
@utils.to_json('api')
def get(self, request, set_id, *args, **kwargs):
return self.get_object().get_interventions(set_id)
@ -78,7 +76,7 @@ class DateTimesView(View, SingleObjectMixin):
"""
model = ClicRdv
@to_json('api')
@utils.to_json('api')
def get(self, request, intervention_id, *args, **kwargs):
return self.get_object().get_datetimes(intervention_id)
@ -93,7 +91,7 @@ class DatesView(View, SingleObjectMixin):
"""
model = ClicRdv
@to_json('api')
@utils.to_json('api')
def get(self, request, intervention_id, *args, **kwargs):
return self.get_object().get_dates(intervention_id)
@ -108,7 +106,7 @@ class TimesView(View, SingleObjectMixin):
"""
model = ClicRdv
@to_json('api')
@utils.to_json('api')
def get(self, request, intervention_id, date, *args, **kwargs):
return self.get_object().get_times(intervention_id, date)
@ -140,7 +138,7 @@ class CreateAppointmentView(View, SingleObjectMixin):
model = ClicRdv
@utils.protected_api('can_manage_appointment')
@to_json('api')
@utils.to_json('api')
def post(self, request, intervention_id=None, *args, **kwargs):
if intervention_id is None:
intervention_id = self.request.GET.get('intervention')
@ -161,6 +159,6 @@ class CancelAppointmentView(View, SingleObjectMixin):
model = ClicRdv
@utils.protected_api('can_manage_appointment')
@to_json('api')
@utils.to_json('api')
def get(self, request, appointment_id, *args, **kwargs):
return self.get_object().cancel(appointment_id)

View File

@ -1,9 +1,9 @@
import json
from django.views.decorators.csrf import csrf_exempt
from jsonresponse import to_json
from models import BaseDataSource
from passerelle.utils import to_json
def get_data(request, slug, id=None):

View File

@ -13,50 +13,13 @@ from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest
from django.template import Template, Context
from django.utils.decorators import available_attrs
from django.views.generic.detail import SingleObjectMixin
from django.contrib.contenttypes.models import ContentType
from .base.context_processors import template_vars
from .base.models import ApiUser, AccessRight
from .base.signature import check_query
class to_json(jsonresponse_to_json):
def __init__(self, serializer_type, error_code=500, **kwargs):
super(to_json, self).__init__(serializer_type, error_code, **kwargs)
if 'cls' not in self.kwargs:
self.kwargs['cls'] = DjangoJSONEncoder
def api(self, func, req, *args, **kwargs):
"""
Raises the exceptions provided by "raises" argument, else wraps the
error message in json. Exceptions can define the error code returned in
JSON, and HTTP status:
class BlockedAccount(Exception):
err_code = 100
http_status = 403
"""
if req.GET.get('raise'):
return super(to_json, self).api(func, req, *args, **kwargs)
# force raise=1 to handle exceptions here
req.GET = req.GET.copy()
req.GET.update({'raise': 1})
try:
return super(to_json, self).api(func, req, *args, **kwargs)
except Exception as e:
data = self.err_to_response(e)
if getattr(e, 'err_code', None):
data['err'] = e.err_code
if getattr(e, 'http_status', None):
status = e.http_status
elif isinstance(e, ObjectDoesNotExist):
status = 404
elif isinstance(e, PermissionDenied):
status = 403
else:
status = self.error_code
return self.render_data(req, data, status)
from passerelle.base.context_processors import template_vars
from passerelle.base.models import ApiUser, AccessRight
from passerelle.base.signature import check_query
from .jsonresponse import to_json
def get_template_vars():
"""
@ -179,5 +142,3 @@ class LoggedRequest(RequestSession):
extra={'requests_response_content': content})
return response

View File

@ -0,0 +1,384 @@
# This module is a modified copy of code of Yasha's Borevich library
# django-jsonresponse (https://github.com/jjay/django-jsonresponse) distributed
# under BSD license
import json
import functools
import logging
from collections import Iterable
from django.http import HttpResponse
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.core.serializers.json import DjangoJSONEncoder
DEFAULT_DEBUG = getattr(settings, 'JSONRESPONSE_DEFAULT_DEBUG', False)
CALLBACK_NAME = getattr(settings, 'JSONRESPONSE_CALLBACK_NAME', 'callback')
class to_json(object):
"""
Wrap view functions to render python native and custom
objects to json
>>> from django.test.client import RequestFactory
>>> requests = RequestFactory()
Simple wrap returning data into json
>>> @to_json('plain')
... def hello(request):
... return dict(hello='world')
>>> resp = hello(requests.get('/hello/'))
>>> print resp.status_code
200
>>> print resp.content
{"hello": "world"}
Result can be wraped in some api manier
>>> @to_json('api')
... def goodbye(request):
... return dict(good='bye')
>>> resp = goodbye(requests.get('/goodbye', {'debug': 1}))
>>> print resp.status_code
200
>>> print resp.content
{
"data": {
"good": "bye"
},
"err": 0
}
Automaticaly error handling
>>> @to_json('api')
... def error(request):
... raise Exception('Wooot!??')
>>> resp = error(requests.get('/error', {'debug': 1}))
>>> print resp.status_code
500
>>> print resp.content # doctest: +NORMALIZE_WHITESPACE
{
"err_class": "Exception",
"err_desc": "Wooot!??",
"data": null,
"err": 1
}
>>> from django.core.exceptions import ObjectDoesNotExist
>>> @to_json('api')
... def error_404(request):
... raise ObjectDoesNotExist('Not found')
>>> resp = error_404(requests.get('/error', {'debug': 1}))
>>> print resp.status_code
404
>>> print resp.content # doctest: +NORMALIZE_WHITESPACE
{
"err_class": "django.core.exceptions.ObjectDoesNotExist",
"err_desc": "Not found",
"data": null,
"err": 1
}
You can serialize not only pure python data types.
Implement `serialize` method on toplevel object or
each element of toplevel array.
>>> class User(object):
... def __init__(self, name, age):
... self.name = name
... self.age = age
...
... def serialize(self, request):
... if request.GET.get('with_age', False):
... return dict(name=self.name, age=self.age)
... else:
... return dict(name=self.name)
>>> @to_json('objects')
... def users(request):
... return [User('Bob', 10), User('Anna', 12)]
>>> resp = users(requests.get('users', { 'debug': 1 }))
>>> print resp.status_code
200
>>> print resp.content # doctest: +NORMALIZE_WHITESPACE
{
"data": [
{
"name": "Bob"
},
{
"name": "Anna"
}
],
"err": 0
}
You can pass extra args for serialization:
>>> resp = users(requests.get('users',
... { 'debug':1, 'with_age':1 }))
>>> print resp.status_code
200
>>> print resp.content # doctest: +NORMALIZE_WHITESPACE
{
"data": [
{
"age": 10,
"name": "Bob"
},
{
"age": 12,
"name": "Anna"
}
],
"err": 0
}
It is easy to use jsonp, just pass format=jsonp
>>> resp = users(requests.get('users',
... { 'debug':1, 'format': 'jsonp' }))
>>> print resp.status_code
200
>>> print resp.content # doctest: +NORMALIZE_WHITESPACE
callback({
"data": [
{
"name": "Bob"
},
{
"name": "Anna"
}
],
"err": 0
});
You can override the name of callback method using
JSONRESPONSE_CALLBACK_NAME option or query arg callback=another_callback
>>> resp = users(requests.get('users',
... { 'debug':1, 'format': 'jsonp', 'callback': 'my_callback' }))
>>> print resp.status_code
200
>>> print resp.content # doctest: +NORMALIZE_WHITESPACE
my_callback({
"data": [
{
"name": "Bob"
},
{
"name": "Anna"
}
],
"err": 0
});
You can pass raise=1 to raise exceptions in debug purposes
instead of passing info to json response
>>> @to_json('api')
... def error(request):
... raise Exception('Wooot!??')
>>> resp = error(requests.get('/error',
... {'debug': 1, 'raise': 1}))
Traceback (most recent call last):
Exception: Wooot!??
You can wraps both methods and functions
>>> class View(object):
... @to_json('plain')
... def render(self, request):
... return dict(data='ok')
... @to_json('api')
... def render_api(self, request):
... return dict(data='ok')
>>> view = View()
>>> resp = view.render(requests.get('/render'))
>>> print resp.status_code
200
>>> print resp.content # doctest: +NORMALIZE_WHITESPACE
{"data": "ok"}
Try it one more
>>> resp = view.render(requests.get('/render'))
>>> print resp.status_code
200
>>> print resp.content # doctest: +NORMALIZE_WHITESPACE
{"data": "ok"}
Try it one more with api
>>> resp = view.render_api(requests.get('/render'))
>>> print resp.status_code
200
>>> print resp.content # doctest: +NORMALIZE_WHITESPACE
{"data": {"data": "ok"}, "err": 0}
You can pass custom kwargs to json.dumps,
just give them to constructor
>>> @to_json('plain', separators=(', ', ': '))
... def custom_kwargs(request):
... return ['a', { 'b': 1 }]
>>> resp = custom_kwargs(requests.get('/render'))
>>> print resp.status_code
200
>>> print resp.content
["a", {"b": 1}]
"""
def __init__(self, serializer_type, error_code=500, **kwargs):
"""
serializer_types:
* api - serialize buildin objects (dict, list, etc) in strict api
* objects - serialize list of region in strict api
* plain - just serialize result of function, do not wrap response and do not handle exceptions
"""
self.serializer_type = serializer_type
self.method = None
self.error_code=error_code
self.kwargs = kwargs
if 'cls' not in self.kwargs:
self.kwargs['cls'] = DjangoJSONEncoder
def __call__(self, f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
if self.method:
return self.method(f, *args, **kwargs)
if not args:
if self.serializer_type == 'plain':
self.method = self.plain_func
else:
self.method = self.api_func
if getattr(getattr(args[0], f.__name__, None), "im_self", False):
if self.serializer_type == 'plain':
self.method = self.plain_method
else:
self.method = self.api_method
else:
if self.serializer_type == 'plain':
self.method = self.plain_func
else:
self.method = self.api_func
return self.method(f, *args, **kwargs)
return wrapper
def obj_to_response(self, req, obj):
if self.serializer_type == 'objects':
if isinstance(obj, Iterable):
obj = [o.serialize(req) if obj else None for o in obj]
elif obj:
obj = obj.serialize(req)
else:
obj = None
return { "err": 0, "data": obj }
def err_to_response(self, err):
if hasattr(err, "__module__"):
err_module = err.__module__ + "."
else:
err_module = ""
if hasattr(err, "owner"):
err_module += err.owner.__name__ + "."
err_class = err_module + err.__class__.__name__
err_desc = str(err)
return {
"err": 1,
"err_class": err_class,
"err_desc": err_desc,
"data": None
}
def render_data(self, req, data, status=200):
debug = DEFAULT_DEBUG
debug = debug or req.GET.get('debug', 'false').lower() in ('true', 't', '1', 'on')
debug = debug or req.GET.get('decode', '0').lower() in ('true', 't', '1', 'on')
format = req.GET.get('format', 'json')
jsonp_cb = req.GET.get('callback', CALLBACK_NAME)
content_type = "application/json"
kwargs = dict(self.kwargs)
if debug:
kwargs["indent"] = 4
kwargs["ensure_ascii"] = False
kwargs["encoding"] = "utf8"
plain = json.dumps(data, **kwargs)
if format == 'jsonp':
plain = "%s(%s);" % (jsonp_cb, plain)
content_type = "application/javascript"
return HttpResponse(plain, content_type="%s; charset=UTF-8" % content_type, status=status)
def api_func(self, f, *args, **kwargs):
return self.api(f, args[0], *args, **kwargs)
def api_method(self, f, *args, **kwargs):
return self.api(f, args[1], *args, **kwargs)
def api(self, f, req, *args, **kwargs):
logger = logging.getLogger('passerelle.jsonresponse')
try:
resp = f(*args, **kwargs)
if isinstance(resp, HttpResponse):
return resp
data = self.obj_to_response(req, resp)
status = 200
except Exception as e:
extras = {'method': req.method}
if req.method == 'POST':
extras.update({'body': req.body})
logger.exception("Error occurred while processing request", extra=extras)
if int(req.GET.get('raise', 0)):
raise
data = self.err_to_response(e)
if getattr(e, 'err_code', None):
data['err'] = e.err_code
if getattr(e, 'http_status', None):
status = e.http_status
elif isinstance(e, ObjectDoesNotExist):
status = 404
elif isinstance(e, PermissionDenied):
status = 403
else:
status = self.error_code
return self.render_data(req, data, status)
def plain_method(self, f, *args, **kwargs):
data = f(*args, **kwargs)
if isinstance(data, HttpResponse):
return data
return self.render_data(args[1], data)
def plain_func(self, f, *args, **kwargs):
data = f(*args, **kwargs)
if isinstance(data, HttpResponse):
return data
return self.render_data(args[0], data)

View File

@ -87,7 +87,6 @@ setup(name='passerelle',
install_requires=[
'django >= 1.7, <1.8',
'django-model-utils',
'django-jsonresponse==0.10',
'django-jsonfield >= 0.9.3',
'requests',
'gadjo',

View File

@ -0,0 +1,32 @@
import logging
import pytest
import json
from django.test.client import RequestFactory
from passerelle.utils import to_json
class WrappedException(Exception):
pass
@to_json('api')
def wrapped_exception(req, *args, **kwargs):
raise WrappedException
def test_jsonresponselog_get(caplog):
request = RequestFactory()
wrapped_exception(request.get('/'))
post_payload = {'data': 'plop'}
with pytest.raises(WrappedException):
wrapped_exception(request.post('/?raise=1', post_payload))
for record in caplog.records():
assert record.name == 'passerelle.jsonresponse'
assert record.levelno == logging.ERROR
assert hasattr(record, 'method')
if record.method == 'POST':
assert hasattr(record, 'body')
assert "Error occurred while processing request" in record.message