"""
Example: Content type negotiation with OOPViews
===============================================
In some situations it comes in handy, to do some content type negotiation
to really provide an optimized view for the user depending on what a user's
application supports (say WML or HTML or XML over HTML). HTTP/1.1 handles
this using the "Accept"-request header to give the user the option, to say
what kind of content type she'd prefer or give a list of content types
prioritized with a value between 0 and 1.
This abstract view class should demonstrate, how you can easily handle such
situations within Django purely in the view code. The idea is pretty simple:
Simply use the ``__call__`` method as dispatcher for content-type-specific
methods.
To use this code, simply inherit the basic implementation and then specify
your content-type-specific methods and register them in the
``ctn_accept_binding``-dictionary::
from django.http import HttpResponse
from django_zsutils.utils.oopview import ctn
class TestView(ctn.AbstractCTNView):
ctn_accept_binding = {
'text/html': 'html',
'text/*': 'html',
'*/*': 'html',
}
def html(self, request, *args, **kwargs):
return HttpResponse("Hello", mimetype='text/html')
The ``ctn_accept_binding``-dictionary not only allows you to bind a method to a
content-type, but if you set a value to a tuple instead of just a string, it
will take the first element of that tuple as a priority value similar to the
one used in the "Accept"-handling. This way, you can prioritize methods for
the case, that the user requests any type of a given family like for instance
'text/*'.
"""
from django.http import HttpResponse
from . import BaseView
def provides_priority_sorting(a,b):
"""
Sorting function for ``ctn_accept_binding``
"""
if a[0].startswith('*/'): return -1
if b[0].startswith('*/'): return 1
if a[0].endswith('/*'): return -1
if b[0].endswith('/*'): return 1
a_prio = a[1][0]
b_prio = b[1][0]
if a_prio > b_prio: return 1
if a_prio < b_prio: return -1
return 0
def accept_priority_sorting(a,b):
"""
Simple priority sorter that gives first of all generic handlers the
lowest priority and gives more specific handlers by default a higher
priority. Otherwise, the q-parameter specified in the HTTP/1.1 specs
is used.
"""
a_prio = a[1]
b_prio = b[1]
a_family = a[0].split("/")[0]
b_family = b[0].split("/")[0]
if a[0] == "*/*": return -1
if b[0] == "*/*": return 1
if a_family == b_family:
# More specific has precendence, no matter what
if a[0].endswith('/*'):
return -1
if b[0].endswith('/*'):
return 1
if a_prio > b_prio:
return 1
if a_prio < b_prio:
return -1
return 0
class HttpResponseNotAcceptable(HttpResponse):
status_code = 406
class AbstractCTNView(BaseView):
ctn_accept_binding = {'*/*': 'default'}
def __init__(self, request, *args, **kwargs):
if (self.__class__ is AbstractCTNView):
raise TypeError, "AbstractContentSelectView is an abstract class"
self._ctn_request_priorities = None
self._ctn_provides_priorities = None
super(AbstractCTNView, self).__init__(request, *args, **kwargs)
def _ctn_build_provides_priorities(self):
if self._ctn_provides_priorities is not None:
return self._ctn_provides_priorities
providing = []
for x in self.ctn_accept_binding.items():
if isinstance(x[1], list) or isinstance(x[1], tuple):
providing.append(x)
else:
providing.append((x[0], (1, x[1])))
providing.sort(provides_priority_sorting)
providing.reverse()
self._ctn_provides_priorities = providing
return providing
def _ctn_build_request_priorities(self, request):
"""
Helper method for building a priority list for all the content-types
acceptable to the user.
"""
if self._ctn_request_priorities is not None:
return self._ctn_request_priorities
accept = request.META.get('HTTP_ACCEPT', "*/*")
# Accept is basically a list separated by "," with options coming
# before the actual type and being separated by a ";" from it.
# For now, all this handles is the q-parameter which handles the
# priority of the type. If not set, this is set to 1
types = []
for accepted_type in accept.split(","):
tinfo = accepted_type.split(";")
type_ = tinfo[0].lstrip().rstrip()
if len(tinfo) == 1:
q = 1
else:
parameters = [x.lstrip().rstrip() for x in tinfo[1].split(";")]
q = 1
for p in parameters:
if p.startswith("q="):
q = float(p.split("=")[1])
break
if q < 0 or q > 1:
continue
types.append((type_, q))
if len(types) > 0:
types.sort(accept_priority_sorting)
types.reverse()
else:
types.append(('*/*', 1))
self._ctn_request_priorities = types
return types
def __call__(self, request, *args, **kwargs):
"""
Main dispatcher for request.
"""
self._ctn_build_request_priorities(request)
self._ctn_build_provides_priorities()
for (type_, priority) in self._ctn_request_priorities:
(tfamily, tspec) = type_.split('/')
# If the requested type is a type-wildcard, we have to go through
# the local priority list, otherwise a normal lookup is enough.
if tspec is '*':
for binding in self._ctn_provides_priorities:
if binding[0].startswith(tfamily+'/'):
return getattr(self, binding[1][1])(request, *args, **kwargs)
else:
for t in (type_, '%s/*'%(tfamily,)):
if t in self.ctn_accept_binding.keys():
if isinstance(self.ctn_accept_binding[t], tuple):
name = self.ctn_accept_binding[t][1]
else:
name = self.ctn_accept_binding[t]
return getattr(self, name)(request, *args, **kwargs)
return HttpResponseNotAcceptable()