Source code for django_select2.views

import json

from django.http import HttpResponse
from django.views.generic import View
from django.core.exceptions import PermissionDenied
from django.http import Http404

from .util import get_field, is_valid_id

NO_ERR_RESP = 'nil'
"""
Equals to 'nil' constant.

Use this in :py:meth:`.Select2View.get_results` to mean no error, instead of hardcoding 'nil' value.
"""

[docs]class JSONResponseMixin(object): """ A mixin that can be used to render a JSON response. """ response_class = HttpResponse
[docs] def render_to_response(self, context, **response_kwargs): """ Returns a JSON response, transforming 'context' to make the payload. """ response_kwargs['content_type'] = 'application/json' return self.response_class( self.convert_context_to_json(context), **response_kwargs )
[docs] def convert_context_to_json(self, context): "Convert the context dictionary into a JSON object" return json.dumps(context)
[docs]class Select2View(JSONResponseMixin, View): """ Base view which is designed to respond with JSON to Ajax queries from heavy widgets/fields. Although the widgets won't enforce the type of data_view it gets, but it is recommended to sub-class this view instead of creating a Django view from scratch. .. note:: Only `GET <http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3>`_ Http requests are supported. """ def dispatch(self, request, *args, **kwargs): try: self.check_all_permissions(request, *args, **kwargs) except Exception, e: return self.respond_with_exception(e) return super(Select2View, self).dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): if request.method == 'GET': term = request.GET.get('term', None) if term is None: return self.render_to_response(self._results_to_context(('missing term', False, [], ))) if not term: return self.render_to_response(self._results_to_context((NO_ERR_RESP, False, [], ))) try: page = int(request.GET.get('page', None)) if page <= 0: page = -1 except ValueError: page = -1 if page == -1: return self.render_to_response(self._results_to_context(('bad page no.', False, [], ))) context = request.GET.get('context', None) else: return self.render_to_response(self._results_to_context(('not a get request', False, [], ))) return self.render_to_response( self._results_to_context( self.get_results(request, term, page, context) ) )
[docs] def respond_with_exception(self, e): """ :param e: Exception object. :type e: Exception :return: Response with status code of 404 if e is ``Http404`` object, else 400. :rtype: HttpResponse """ if isinstance(e, Http404): status = 404 else: status = getattr(e, 'status_code', 400) return self.render_to_response( self._results_to_context((str(e), False, [],)), status=status )
def _results_to_context(self, output): err, has_more, results = output res = [] if err == NO_ERR_RESP: for id_, text in results: res.append({'id': id_, 'text': text}) return { 'err': err, 'more': has_more, 'results': res, }
[docs] def check_all_permissions(self, request, *args, **kwargs): """ Sub-classes can use this to raise exception on permission check failures, or these checks can be placed in ``urls.py``, e.g. ``login_required(SelectClass.as_view())``. :param request: The Ajax request object. :type request: :py:class:`django.http.HttpRequest` :param args: The ``*args`` passed to :py:meth:`django.views.generic.View.dispatch`. :param kwargs: The ``**kwargs`` passed to :py:meth:`django.views.generic.View.dispatch`. .. warning:: Sub-classes should override this. You really do not want random people making Http reqeusts to your server, be able to get access to sensitive information. """ pass
[docs] def get_results(self, request, term, page, context): """ Returns the result for the given search ``term``. :param request: The Ajax request object. :type request: :py:class:`django.http.HttpRequest` :param term: The search term. :type term: :py:obj:`str` :param page: The page number. If in your last response you had signalled that there are more results, then when user scrolls more a new Ajax request would be sent for the same term but with next page number. (Page number starts at 1) :type page: :py:obj:`int` :param context: Can be anything which persists across the lifecycle of queries for the same search term. It is reset to ``None`` when the term changes. .. note:: Currently this is not used by ``heavy_data.js``. :type context: :py:obj:`str` or None Expected output is of the form:: (err, has_more, [results]) Where ``results = [(id1, text1), (id2, text2), ...]`` For example:: ('nil', False, [ (1, 'Value label1'), (20, 'Value label2'), ]) When everything is fine then the `err` must be 'nil'. `has_more` should be true if there are more rows. """ raise NotImplementedError
[docs]class AutoResponseView(Select2View): """ A central view meant to respond to Ajax queries for all Heavy widgets/fields. Although it is not mandatory to use, but is immensely helpful. .. tip:: Fields which want to use this view must sub-class :py:class:`~.widgets.AutoViewFieldMixin`. """ def check_all_permissions(self, request, *args, **kwargs): id_ = request.GET.get('field_id', None) if id_ is None or not is_valid_id(id_): raise Http404('field_id not found or is invalid') field = get_field(id_) if field is None: raise Http404('field_id not found') if not field.security_check(request, *args, **kwargs): raise PermissionDenied('permission denied') request.__django_select2_local = field def get_results(self, request, term, page, context): field = request.__django_select2_local del request.__django_select2_local return field.get_results(request, term, page, context)