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?**
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.
8"""
9# Imports
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
18logger = logging.getLogger(__name__)
20# Exports
22__all__ = (
23 "JSONMixin",
24)
26# Mixins
29class JSONMixin(object):
30 """Handle JSON requests/responses.
32 .. code-block:: python
34 from superdjango.views import GenericView, JSONMixin
36 class MyAjaxView(JSONMixin, GenericView):
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()
42 return self.get_json_response(data)
44 """
45 ajax_required = not settings.DEBUG
46 """Indicates requests must be made via AJAX. Defaults to ``False`` in development."""
48 content_type = "application/json"
49 """The expected content type of the request or response."""
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 """
56 encoder_class = DjangoJSONEncoder
57 """The class used to encode JSON responses."""
59 json_required = False
60 """Indicates that requests must be made using JSON."""
62 # noinspection PyAttributeOutsideInit
63 def dispatch(self, request, *args, **kwargs):
64 """Set ``request_json`` and validate the request before continuing with the dispatch."""
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."})
70 # Assign for later, possible use.
71 self.args = args
72 self.kwargs = kwargs
73 self.request = request
75 # Get the JSON, if any, sent to the view.
76 self.json_request = self.get_json_request()
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()
83 # Continue dispatching the request.
84 # noinspection PyUnresolvedReferences
85 return super().dispatch(request, *args, **kwargs)
87 def dispatch_bad_request(self, error=None, **kwargs):
88 """Handle a bad request.
90 :param error: A dictionary describing the error in the form of ``{'error': "Description."}``. Defaults to
91 ``{'error': "Bad request."}``
92 :type error: dict
94 Keyword arguments are passed to ``json.dumps()``.
96 """
97 if error is None:
98 error = {'error': "Bad request."}
100 kwargs.setdefault("ensure_ascii", False)
101 json_data = json.dumps(error, cls=self.encoder_class, **kwargs).encode("utf-8")
103 return HttpResponseBadRequest(json_data, content_type=self.get_content_type())
105 def get_content_type(self):
106 """Get the content type for the request/response.
108 :rtype: str
110 """
111 return self.content_type
113 def get_json_request(self):
114 """Get JSON from the current request.
116 :rtype: dict | None
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()
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))
130 return None
132 def get_json_response(self, data, status=200, **kwargs):
133 """Get the JSON response.
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.
138 :type data: dict | list | tuple | django.db.models.QuerySet
140 :param status: The HTTP status code.
141 :type status: int
143 Additional keyword arguments are passed to ``json.dumps()`` or Django's serializer.
145 :rtype: HttpResponse
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.
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)
158 return HttpResponse(json_data, content_type=self.get_content_type(), status=status)