public
Description: Some Django utilities I share between some of my sites (like Pagination, OOPViews ...)
Homepage: http://zerokspot.com/
Clone URL: git://github.com/zerok/django-zsutils.git
Click here to lend your support to: django-zsutils and make a donation at www.pledgie.com !
100644 176 lines (153 sloc) 6.598 kb
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
"""
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()