forked from jrief/django-angular
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mixins.py
113 lines (99 loc) · 5.05 KB
/
mixins.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# -*- coding: utf-8 -*-
import json
import warnings
from django.core.serializers.json import DjangoJSONEncoder
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
def allow_remote_invocation(func, method='auto'):
"""
All methods which shall be callable through a given Ajax 'action' must be
decorated with @allowed_action. This is required for safety reasons. It
inhibits the caller to invoke all available methods of a class.
"""
setattr(func, 'allow_rmi', method)
return func
def allowed_action(func):
warnings.warn("Decorator `@allowed_action` is deprecated. Use `@allow_remote_invocation` instead.", DeprecationWarning)
return allow_remote_invocation(func)
class JSONResponseException(Exception):
"""
Exception class for triggering HTTP 4XX responses with JSON content, where expected.
"""
status_code = 400
def __init__(self, message=None, status=None, *args, **kwargs):
if status is not None:
self.status_code = status
super(JSONResponseException, self).__init__(message, *args, **kwargs)
class JSONBaseMixin(object):
"""
Basic mixin for encoding HTTP responses in JSON format.
"""
json_encoder = DjangoJSONEncoder
json_content_type = 'application/json;charset=UTF-8'
def json_response(self, response_data, status=200, **kwargs):
out_data = json.dumps(response_data, cls=self.json_encoder, **kwargs)
response = HttpResponse(out_data, self.json_content_type, status=status)
response['Cache-Control'] = 'no-cache'
return response
class JSONResponseMixin(JSONBaseMixin):
"""
A mixin for View classes that dispatches requests containing the private HTTP header
``DjNg-Remote-Method`` onto a method of an instance of this class, with the given method name.
This named method must be decorated with ``@allow_remote_invocation`` and shall return a
list or dictionary which is serializable to JSON.
The returned HTTP responses are of kind ``application/json;charset=UTF-8``.
"""
def get(self, request, *args, **kwargs):
if not request.is_ajax():
return self._dispatch_super(request, *args, **kwargs)
if 'action' in kwargs:
warnings.warn("Using the keyword 'action' in URLresolvers is deprecated. Please use 'invoke_method' instead", DeprecationWarning)
remote_method = kwargs['action']
else:
remote_method = kwargs.get('invoke_method')
if remote_method:
# method for invocation is determined programmatically
handler = getattr(self, remote_method)
else:
# method for invocation is determined by HTTP header
remote_method = request.META.get('HTTP_DJNG_REMOTE_METHOD')
handler = remote_method and getattr(self, remote_method, None)
if not callable(handler):
return self._dispatch_super(request, *args, **kwargs)
if not hasattr(handler, 'allow_rmi'):
return HttpResponseForbidden("Method '{0}.{1}' has no decorator '@allow_remote_invocation'"
.format(self.__class__.__name__, remote_method))
try:
response_data = handler()
except JSONResponseException as e:
return self.json_response({'message': e.args[0]}, e.status_code)
return self.json_response(response_data)
def post(self, request, *args, **kwargs):
if not request.is_ajax():
return self._dispatch_super(request, *args, **kwargs)
try:
in_data = json.loads(request.body.decode('utf-8'))
except ValueError:
in_data = request.body.decode('utf-8')
if 'action' in in_data:
warnings.warn("Using the keyword 'action' inside the payload is deprecated. Please use 'djangoRMI' from module 'ng.django.forms'", DeprecationWarning)
remote_method = in_data.pop('action')
else:
remote_method = request.META.get('HTTP_DJNG_REMOTE_METHOD')
handler = remote_method and getattr(self, remote_method, None)
if not callable(handler):
return self._dispatch_super(request, *args, **kwargs)
if not hasattr(handler, 'allow_rmi'):
return HttpResponseForbidden("Method '{0}.{1}' has no decorator '@allow_remote_invocation'"
.format(self.__class__.__name__, remote_method), 403)
try:
response_data = handler(in_data)
except JSONResponseException as e:
return self.json_response({'message': e.args[0]}, e.status_code)
return self.json_response(response_data)
def _dispatch_super(self, request, *args, **kwargs):
base = super(JSONResponseMixin, self)
handler = getattr(base, request.method.lower(), None)
if callable(handler):
return handler(request, *args, **kwargs)
# HttpResponseNotAllowed expects permitted methods.
return HttpResponseBadRequest('This view can not handle method {0}'.format(request.method), status=405)