Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2**Why no AJAXMixin?** 

3 

4An AJAX mixin similar to Braces' AjaxResponseMixin simply allows a view for "fork" behavior based on 

5``request.is_ajax()``. We feel a better practice is to create a view that specifically handles AJAX requests because 

6this is much more obvious, easier to test, and reduces the code complexity of the view. 

7 

8""" 

9# Imports 

10 

11from django.conf import settings 

12from django.core import serializers 

13from django.core.serializers.json import DjangoJSONEncoder 

14from django.http import HttpResponse, HttpResponseBadRequest 

15import json 

16import logging 

17 

18logger = logging.getLogger(__name__) 

19 

20# Exports 

21 

22__all__ = ( 

23 "JSONMixin", 

24) 

25 

26# Mixins 

27 

28 

29class JSONMixin(object): 

30 """Handle JSON requests/responses. 

31 

32 .. code-block:: python 

33 

34 from superdjango.views import GenericView, JSONMixin 

35 

36 class MyAjaxView(JSONMixin, GenericView): 

37 

38 def post(self, request, *args, **kwargs): 

39 # process the request that produces a dict or queryset or model instance. 

40 data = self.get_some_data() 

41 

42 return self.get_json_response(data) 

43 

44 """ 

45 ajax_required = not settings.DEBUG 

46 """Indicates requests must be made via AJAX. Defaults to ``False`` in development.""" 

47 

48 content_type = "application/json" 

49 """The expected content type of the request or response.""" 

50 

51 decoder_class = None 

52 """Optional class used to decode the request. It takes the current request instance as the only parameter and must  

53 implement a load method which returns a ``dict``. 

54 """ 

55 

56 encoder_class = DjangoJSONEncoder 

57 """The class used to encode JSON responses.""" 

58 

59 json_required = False 

60 """Indicates that requests must be made using JSON.""" 

61 

62 # noinspection PyAttributeOutsideInit 

63 def dispatch(self, request, *args, **kwargs): 

64 """Set ``request_json`` and validate the request before continuing with the dispatch.""" 

65 

66 # IF AJAX is required. 

67 if not request.is_ajax() and self.ajax_required: 

68 return self.dispatch_bad_request(error={'error': "An AJAX request is required."}) 

69 

70 # Assign for later, possible use. 

71 self.args = args 

72 self.kwargs = kwargs 

73 self.request = request 

74 

75 # Get the JSON, if any, sent to the view. 

76 self.json_request = self.get_json_request() 

77 

78 # Validate the request. 

79 conditions = [request.method != "OPTIONS", self.json_required, self.json_request is None] 

80 if all(conditions): 

81 return self.dispatch_bad_request() 

82 

83 # Continue dispatching the request. 

84 # noinspection PyUnresolvedReferences 

85 return super().dispatch(request, *args, **kwargs) 

86 

87 def dispatch_bad_request(self, error=None, **kwargs): 

88 """Handle a bad request. 

89 

90 :param error: A dictionary describing the error in the form of ``{'error': "Description."}``. Defaults to 

91 ``{'error': "Bad request."}`` 

92 :type error: dict 

93 

94 Keyword arguments are passed to ``json.dumps()``. 

95 

96 """ 

97 if error is None: 

98 error = {'error': "Bad request."} 

99 

100 kwargs.setdefault("ensure_ascii", False) 

101 json_data = json.dumps(error, cls=self.encoder_class, **kwargs).encode("utf-8") 

102 

103 return HttpResponseBadRequest(json_data, content_type=self.get_content_type()) 

104 

105 def get_content_type(self): 

106 """Get the content type for the request/response. 

107 

108 :rtype: str 

109 

110 """ 

111 return self.content_type 

112 

113 def get_json_request(self): 

114 """Get JSON from the current request. 

115 

116 :rtype: dict | None 

117 

118 """ 

119 if self.decoder_class is not None: 

120 decoder_class = self.decoder_class 

121 decoder = decoder_class(self.request) 

122 return decoder.load() 

123 

124 try: 

125 return json.loads(self.request.body.decode("utf-8")) 

126 except json.JSONDecodeError as e: 

127 if self.json_required: 

128 logger.warning("%s failed to get JSON from request: %s" % (self.__class__.__name__, e)) 

129 

130 return None 

131 

132 def get_json_response(self, data, status=200, **kwargs): 

133 """Get the JSON response. 

134  

135 :param data: The data to be serialized. If this is a dictionary, simple serialization is used using the  

136 ``encoder_class`` attribute. If a QuerySet is given, Django's built-in serializer is used.  

137  

138 :type data: dict | list | tuple | django.db.models.QuerySet 

139  

140 :param status: The HTTP status code. 

141 :type status: int 

142  

143 Additional keyword arguments are passed to ``json.dumps()`` or Django's serializer. 

144  

145 :rtype: HttpResponse 

146 

147 .. warning:: 

148 If ``data`` is not provided as a ``dict``, ``list``, or ``tuple`` it is assumed to be a QuerySet. This will 

149 produce unexpected results if you fail to provide the supported data type. 

150 

151 """ 

152 if type(data) in (dict, list, tuple): 

153 kwargs.setdefault("ensure_ascii", False) 

154 json_data = json.dumps(data, cls=self.encoder_class, **kwargs).encode("utf-8") 

155 else: 

156 json_data = serializers.serialize("json", data, **kwargs) 

157 

158 return HttpResponse(json_data, content_type=self.get_content_type(), status=status)