-
Notifications
You must be signed in to change notification settings - Fork 239
Expand file tree
/
Copy pathmixins.py
More file actions
232 lines (189 loc) · 7.7 KB
/
mixins.py
File metadata and controls
232 lines (189 loc) · 7.7 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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
from collections import OrderedDict
from django.http import Http404
from django.template import Template
from django.utils.decorators import method_decorator
from django.views import generic
from django.views.generic.base import TemplateResponseMixin
from feincms import settings
from feincms.content.application.models import standalone
class ContentModelMixin:
"""
Mixin for ``feincms.models.Base`` subclasses which need need some degree of
additional control over the request-response cycle.
"""
#: Collection of request processors
request_processors = None
#: Collection of response processors
response_processors = None
@classmethod
def register_request_processor(cls, fn, key=None):
"""
Registers the passed callable as request processor. A request processor
always receives two arguments, the current object and the request.
"""
if cls.request_processors is None:
cls.request_processors = OrderedDict()
cls.request_processors[fn if key is None else key] = fn
@classmethod
def register_response_processor(cls, fn, key=None):
"""
Registers the passed callable as response processor. A response
processor always receives three arguments, the current object, the
request and the response.
"""
if cls.response_processors is None:
cls.response_processors = OrderedDict()
cls.response_processors[fn if key is None else key] = fn
# TODO Implement admin_urlname templatetag protocol
@property
def app_label(self):
"""
Implement the admin_urlname templatetag protocol, so one can easily
generate an admin link using ::
{% url page|admin_urlname:'change' page.id %}
"""
return self._meta.app_label
@property
def model_name(self):
"See app_label"
return self.__class__.__name__.lower()
class ContentObjectMixin(TemplateResponseMixin):
"""
Mixin for Django's class based views which knows how to handle
``ContentModelMixin`` detail pages.
This is a mixture of Django's ``SingleObjectMixin`` and
``TemplateResponseMixin`` conceptually to support FeinCMS'
``ApplicationContent`` inheritance. It does not inherit
``SingleObjectMixin`` however, because that would set a
precedence for the way how detail objects are determined
(and would f.e. make the page and blog module implementation
harder).
"""
context_object_name = None
def handler(self, request, *args, **kwargs):
if not hasattr(self.request, "_feincms_extra_context"):
self.request._feincms_extra_context = {}
r = self.run_request_processors()
if r:
return r
r = self.process_content_types()
if r:
return r
response = self.render_to_response(self.get_context_data())
r = self.finalize_content_types(response)
if r:
return r
r = self.run_response_processors(response)
if r:
return r
return response
def get_template_names(self):
# According to the documentation this method is supposed to return
# a list. However, we can also return a Template instance...
if isinstance(self.template_name, (Template, list, tuple)):
return self.template_name
if self.template_name:
return [self.template_name]
self.object._needs_templates()
if self.object.template.path:
return [self.object.template.path]
# Hopefully someone else has a usable get_template_names()
# implementation...
return super().get_template_names()
def get_context_data(self, **kwargs):
context = self.request._feincms_extra_context
context[self.context_object_name or "feincms_object"] = self.object
context.update(kwargs)
return super().get_context_data(**context)
@property
def __name__(self):
"""
Dummy property to make this handler behave like a normal function.
This property is used by django-debug-toolbar
"""
return self.__class__.__name__
def run_request_processors(self):
"""
Before rendering an object, run all registered request processors. A
request processor may peruse and modify the page or the request. It can
also return a ``HttpResponse`` for shortcutting the rendering and
returning that response immediately to the client.
"""
if not getattr(self.object, "request_processors", None):
return
for fn in reversed(list(self.object.request_processors.values())):
r = fn(self.object, self.request)
if r:
return r
def run_response_processors(self, response):
"""
After rendering an object to a response, the registered response
processors are called to modify the response, eg. for setting cache or
expiration headers, keeping statistics, etc.
"""
if not getattr(self.object, "response_processors", None):
return
for fn in self.object.response_processors.values():
r = fn(self.object, self.request, response)
if r:
return r
def process_content_types(self):
"""
Run the ``process`` method of all content types sporting one
"""
# store eventual Http404 exceptions for re-raising,
# if no content type wants to handle the current self.request
http404 = None
# did any content type successfully end processing?
successful = False
for content in self.object.content.all_of_type(
tuple(self.object._feincms_content_types_with_process)
):
try:
r = content.process(self.request, view=self)
if r in (True, False):
successful = r
elif r:
return r
except Http404 as e:
http404 = e
if not successful:
if http404:
# re-raise stored Http404 exception
raise http404
extra_context = self.request._feincms_extra_context
if (
not settings.FEINCMS_ALLOW_EXTRA_PATH
and extra_context.get("extra_path", "/") != "/"
# XXX Already inside application content. I'm not sure
# whether this fix is really correct...
and not extra_context.get("app_config")
):
raise Http404(
"Not found (extra_path %r on %r)"
% (extra_context.get("extra_path", "/"), self.object)
)
def finalize_content_types(self, response):
"""
Runs finalize() on content types having such a method, adds headers and
returns the final response.
"""
for content in self.object.content.all_of_type(
tuple(self.object._feincms_content_types_with_finalize)
):
r = content.finalize(self.request, response)
if r:
return r
class ContentView(ContentObjectMixin, generic.DetailView):
def dispatch(self, request, *args, **kwargs):
if request.method.lower() not in self.http_method_names:
return self.http_method_not_allowed(request, *args, **kwargs)
self.request = request
self.args = args
self.kwargs = kwargs
self.object = self.get_object()
return self.handler(request, *args, **kwargs)
class StandaloneView(generic.View):
@method_decorator(standalone)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)