-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathmiddleware.py
More file actions
187 lines (147 loc) · 6.9 KB
/
Copy pathmiddleware.py
File metadata and controls
187 lines (147 loc) · 6.9 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
"""
Page middleware (``feincms3.root.middleware``)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The guide recommends using a middleware for the feincms3 pages app. This module
offers helpers and utilities to reduce the amount of code you have to write.
The reason why this module is called ``root`` is that the page app's mountpoint
has to be the Python app's mountpoint when using this since the middleware uses
the value of ``request.path_info`` to find a page for the current request.
If you have other requirements you may want to write your own
:ref:`urls-and-views` or investigate if modifying ``request.path_info`` works;
the latter isn't recommended though since it's not officially supported by
Django.
Example code for using this module (e.g. ``app.pages.middleware``):
.. code-block:: python
from django.shortcuts import render
from feincms3.root.middleware import add_redirect_handler, create_page_if_404_middleware
from app.pages.models import Page
from app.pages.utils import page_context
# The page handler receives the request and the page.
# ``add_redirect_handler`` wraps the handler function with support for the
# RedirectMixin.
@add_redirect_handler
def handler(request, page):
return render(request, page.type.template_name, page_context(request, page=page))
# This is the middleware which you want to add to ``MIDDLEWARE`` as
# ``app.pages.middleware.page_if_404_middleware``. The middleware should be
# added in the last position except if you have a very good reason not to
# do this.
page_if_404_middleware = create_page_if_404_middleware(
# queryset=Page.objects.active() works too (if .active() doesn't use
# get_language or anything similar)
queryset=lambda request: Page.objects.active(),
handler=handler,
)
Building a preview functionality
--------------------------------
If you wish that staff users can view a page which isn't active yet, here's how
to do that. It's basically the same as above except for the fact that the
queryset function depends on the value of ``request.user.is_staff``.
If you're using feincms3 applications, it may happen that you want to preview
an application site. This doesn't work without overriding the list of apps
passed to :func:`~feincms3.applications.apps_urlconf`. The easier way to check
if the preview should be rendered is to use the fact that most feincms3
application page types won't have ``template_name`` set. Returning a 404 error
in this case is a good alternative to crashing with an ``IsADirectoryError``
because the Django templates backend tries to load the ``templates`` directory
itself instead of a template file.
.. code-block:: python
def pages(request):
if request.user.is_staff:
return Page.objects.all()
return Page.objects.active()
@add_redirect_handler
def handler(request, page):
if not page.type.template_name:
raise Http404(
f"The page type {page.type.key!r} doesn't have a template name."
)
return render(
request,
page.type.template_name,
page_context(request, page=page),
)
page_if_404_middleware = create_page_if_404_middleware(
queryset=pages,
handler=handler,
)
"""
from functools import wraps
from django.conf import settings
from django.http import (
HttpResponseNotFound,
HttpResponsePermanentRedirect,
HttpResponseRedirect,
)
class UseRootMiddlewareResponse(HttpResponseNotFound):
"""
Used by feincms3.root.passthru to tell the middleware to do its thing
You can return this response from your own views as well if some view
cannot handle a request and you want to allow the root middleware to try
handling the current request. If you just raise ``Http404`` or return a
stock ``HttpResponseNotFound`` response the root middleware will do
nothing.
As an example, of ``get_object_or_404(Thing, pk=pk)`` you could do the
following if you want to fall back to the root middleware:
.. code-block:: python
from feincms3.root.middleware import UseRootMiddlewareResponse
def view(request, pk):
if thing := Thing.objects.filter(pk=pk).first():
return render(...)
return UseRootMiddlewareResponse()
This makes the root middleware check the pages for a match, and still
return the 404 if no page could be found.
"""
def create_page_if_404_middleware(*, queryset, handler, language_code_redirect=False):
"""
Create a middleware for handling pages
This utility is there for your convenience, you do not have to use it. The
returned middleware already handles returning non-404 responses as-is,
fetching a page instance from the database and calling a user-defined
handler on success. It optionally also supports redirecting requests to the
root of the app to a language-specific landing page.
Required arguments:
- ``queryset``: A page queryset or a callable accepting the request and
returning a page queryset.
- ``handler``: A callable accepting the request and a page and returning a
response.
Optional arguments:
- ``language_code_redirect`` (``False``): Redirect visitor to the language
code prefix (e.g. ``/en/``, ``/de-ch/``) if request path equals the
script prefix (generally ``/``), no active page for ``/`` exists and the
prefixed version exists.
"""
def outer(get_response):
def inner(request):
response = get_response(request)
if response.status_code != 404 or (
request.resolver_match
and not isinstance(response, UseRootMiddlewareResponse)
):
# Response is not a 404 OR the 404 comes from a resolved view
# which also didn't return a UseRootMiddlewareResponse.
return response
qs = queryset(request) if callable(queryset) else queryset._clone()
if page := qs.filter(path=request.path_info).first():
return handler(request, page)
if language_code_redirect and request.path_info == "/":
target = f"/{request.LANGUAGE_CODE}/"
if qs.filter(path=target).exists():
return HttpResponseRedirect(target)
if settings.APPEND_SLASH and not request.path_info.endswith("/"):
target = request.path_info + "/"
if qs.filter(path=target).exists():
return HttpResponsePermanentRedirect(target)
return response
return inner
return outer
def add_redirect_handler(handler):
"""
Wrap the page handler in a redirect mixin handler
"""
@wraps(handler)
def inner(request, page):
if redirect_to := page.get_redirect_url():
return HttpResponseRedirect(redirect_to)
return handler(request, page)
return inner