-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
/
views.py
342 lines (292 loc) · 13.5 KB
/
views.py
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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
from urllib.parse import quote
from django.apps import apps
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME, login as auth_login
from django.contrib.auth.views import redirect_to_login
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.http import (
Http404,
HttpResponse,
HttpResponseBadRequest,
HttpResponseRedirect,
)
from django.shortcuts import render
from django.urls import Resolver404, resolve, reverse
from django.utils.cache import patch_cache_control
from django.utils.timezone import now
from django.utils.translation import activate, get_language_from_request
from django.views.decorators.http import require_POST
from cms.apphook_pool import apphook_pool
from cms.cache.page import get_page_cache
from cms.exceptions import LanguageError
from cms.forms.login import CMSToolbarLoginForm
from cms.models import PageContent
from cms.models.pagemodel import TreeNode
from cms.page_rendering import (
_handle_no_apphook,
_handle_no_page,
_render_welcome_page,
render_pagecontent,
)
from cms.toolbar.utils import get_object_preview_url, get_toolbar_from_request
from cms.utils import get_current_site
from cms.utils.compat import DJANGO_2_2, DJANGO_3_0, DJANGO_3_1
from cms.utils.conf import get_cms_setting
from cms.utils.helpers import is_editable_model
from cms.utils.i18n import (
get_default_language_for_site,
get_fallback_languages,
get_language_list,
get_public_languages,
get_redirect_on_fallback,
is_language_prefix_patterns_used,
)
from cms.utils.page import get_page_from_request
if DJANGO_2_2:
from django.utils.http import (
is_safe_url as url_has_allowed_host_and_scheme,
)
else:
from django.utils.http import url_has_allowed_host_and_scheme
def _clean_redirect_url(redirect_url, language):
if (redirect_url and is_language_prefix_patterns_used() and redirect_url[0] == "/" and not redirect_url.startswith(
f"/{language}/"
)):
# add language prefix to url
redirect_url = f"/{language}/{redirect_url.lstrip('/')}"
return redirect_url
def details(request, slug):
"""
The main view of the Django-CMS! Takes a request and a slug, renders the
page.
"""
is_authenticated = request.user.is_authenticated
response_timestamp = now()
if get_cms_setting("PAGE_CACHE") and (
not hasattr(request, 'toolbar') or (
not request.toolbar.edit_mode_active and not request.toolbar.show_toolbar and not is_authenticated
)
):
cache_content = get_page_cache(request)
if cache_content is not None:
content, headers, expires_datetime = cache_content
response = HttpResponse(content)
response.xframe_options_exempt = True
if DJANGO_2_2 or DJANGO_3_0 or DJANGO_3_1:
response._headers = headers
else:
# for django3.2 and above. response.headers replaces response._headers in earlier versions of django
response.headers = headers
# Recalculate the max-age header for this cached response
max_age = int(
(expires_datetime - response_timestamp).total_seconds() + 0.5)
patch_cache_control(response, max_age=max_age)
return response
# Get a Page model object from the request
site = get_current_site()
page = get_page_from_request(request, use_path=slug)
toolbar = get_toolbar_from_request(request)
tree_nodes = TreeNode.objects.get_for_site(site)
if not page and not slug and not tree_nodes.exists():
# render the welcome page if the requested path is root "/"
# and there's no pages
return _render_welcome_page(request)
if not page and get_cms_setting("REDIRECT_TO_LOWERCASE_SLUG"):
# Redirect to the lowercase version of the slug
if slug.lower() != slug:
# Only redirect if the slug changes
redirect_url = reverse("pages-details-by-slug", kwargs={"slug": slug.lower()})
if get_cms_setting('REDIRECT_PRESERVE_QUERY_PARAMS'):
query_string = request.META.get('QUERY_STRING')
if query_string:
redirect_url += "?" + query_string
return HttpResponseRedirect(redirect_url)
if not page:
# raise 404 or redirect to PageContent's
# changelist in the admin if this is a
# request to the root URL
return _handle_no_page(request)
request.current_page = page
if hasattr(request, 'user') and request.user.is_staff:
user_languages = get_language_list(site_id=site.pk)
else:
user_languages = get_public_languages(site_id=site.pk)
request_language = None
if is_language_prefix_patterns_used():
request_language = get_language_from_request(request, check_path=True)
if not request_language:
request_language = get_default_language_for_site(get_current_site().pk)
if not page.is_home and request_language not in user_languages:
# The homepage is treated differently because
# when a request goes to the root of the site (/)
# without a language, Django will redirect to the user's
# browser language which might not be a valid cms language,
# this means we need to correctly redirect that request.
return _handle_no_page(request)
# get_published_languages will return all languages in draft mode
# and published only in live mode.
# These languages are then filtered out by the user allowed languages
pagecontent_languages = [
pagecontent.language for pagecontent in page._prefetched_objects_cache.get('pagecontent_set', [])
]
available_languages = [
language for language in user_languages
if language in pagecontent_languages
]
own_urls = [
request.build_absolute_uri(request.path),
'/%s' % request.path,
request.path,
]
try:
redirect_on_fallback = get_redirect_on_fallback(request_language, site_id=site.pk)
except LanguageError:
redirect_on_fallback = False
if request_language not in user_languages:
# Language is not allowed
# Use the default site language
default_language = get_default_language_for_site(site.pk)
fallbacks = get_fallback_languages(default_language, site_id=site.pk)
fallbacks = [default_language] + fallbacks
else:
fallbacks = get_fallback_languages(request_language, site_id=site.pk)
# Only fallback to languages the user is allowed to see
fallback_languages = [
language for language in fallbacks
if language != request_language and language in available_languages
]
language_is_unavailable = request_language not in available_languages
first_fallback_language = next(iter(fallback_languages or []), None)
if language_is_unavailable and not fallback_languages:
# There is no page with the requested language
# and there's no configured fallbacks
return _handle_no_page(request)
elif language_is_unavailable and redirect_on_fallback:
# There is no page with the requested language and
# redirect_on_fallback is True,
# so redirect to the first configured / available fallback language
redirect_url = page.get_absolute_url(
first_fallback_language, fallback=False)
else:
page_path = page.get_absolute_url(request_language)
page_slug = page.get_path(request_language) or page.get_slug(request_language)
if slug and slug != page_slug and request.path[:len(page_path)] != page_path:
# The current language does not match its slug.
# Redirect to the current language.
return HttpResponseRedirect(page_path)
# Check if the page has a redirect url defined for this language.
redirect_url = page.get_redirect(request_language, fallback=False) or ''
redirect_url = _clean_redirect_url(redirect_url, request_language)
if redirect_url:
if request.user.is_staff and toolbar.edit_mode_active:
toolbar.redirect_url = redirect_url
elif redirect_url not in own_urls:
if get_cms_setting('REDIRECT_PRESERVE_QUERY_PARAMS'):
query_string = request.META.get('QUERY_STRING')
if query_string:
redirect_url += "?" + query_string
# prevent redirect to self
return HttpResponseRedirect(redirect_url)
# permission checks
if page.login_required and not request.user.is_authenticated:
return redirect_to_login(quote(request.get_full_path()), settings.LOGIN_URL)
content_language = request_language
if language_is_unavailable:
# When redirect_on_fallback is False and
# language is unavailable, render the content
# in the first fallback language available
# by switching to it
content_language = first_fallback_language
# translation.activate() is used without context
# as the context won't be preserved when the
# plugins get rendered
activate(content_language)
content = page.get_content_obj(language=content_language)
# use the page object with populated cache
content.page = page
if hasattr(request, 'toolbar'):
request.toolbar.set_object(content)
return render_pagecontent(request, content)
@require_POST
def login(request):
redirect_to = request.GET.get(REDIRECT_FIELD_NAME)
if not url_has_allowed_host_and_scheme(url=redirect_to, allowed_hosts=request.get_host()):
redirect_to = reverse("pages-root")
else:
redirect_to = quote(redirect_to)
if request.user.is_authenticated:
return HttpResponseRedirect(redirect_to)
form = CMSToolbarLoginForm(request=request, data=request.POST)
if form.is_valid():
auth_login(request, form.user_cache)
else:
redirect_to += '?cms_toolbar_login_error=1'
return HttpResponseRedirect(redirect_to)
def render_object_structure(request, content_type_id, object_id):
try:
content_type = ContentType.objects.get_for_id(content_type_id)
except ContentType.DoesNotExist as err:
raise Http404 from err
try:
content_type_obj = content_type.get_object_for_this_type(pk=object_id)
except ObjectDoesNotExist as err:
raise Http404 from err
context = {
'object': content_type_obj,
'cms_toolbar': request.toolbar,
}
if isinstance(content_type_obj, PageContent):
request.current_page = content_type_obj.page
toolbar = get_toolbar_from_request(request)
toolbar.set_object(content_type_obj)
return render(request, 'cms/toolbar/structure.html', context)
def render_object_endpoint(request, content_type_id, object_id, require_editable):
try:
content_type = ContentType.objects.get_for_id(content_type_id)
except ContentType.DoesNotExist as err:
raise Http404 from err
else:
model = content_type.model_class()
if require_editable and not is_editable_model(model):
return HttpResponseBadRequest('Requested object does not support frontend rendering')
try:
if issubclass(model, PageContent):
# An apphook might be attached to a PageContent object
content_type_obj = model.admin_manager.select_related("page").get(pk=object_id)
request.current_page = content_type_obj.page
if (
content_type_obj.page.application_urls and # noqa: W504
content_type_obj.page.application_urls in dict(apphook_pool.get_apphooks())
):
try:
# If so, try get the absolute URL and pass it to the toolbar as request_path
# The apphook's view function will be called.
absolute_url = content_type_obj.get_absolute_url()
from cms.toolbar.toolbar import CMSToolbar
request.toolbar = CMSToolbar(request, request_path=absolute_url)
# Resolve the apphook's url to get its view function
view_func, args, kwargs = resolve(absolute_url)
if view_func is not details:
return view_func(request, *args, **kwargs)
except Resolver404:
# Apphook does not provide a view for its "root", show warning message
return _handle_no_apphook(request)
else:
content_type_obj = content_type.get_object_for_this_type(pk=object_id)
except ObjectDoesNotExist as err:
raise Http404 from err
extension = apps.get_app_config('cms').cms_extension
if model not in extension.toolbar_enabled_models:
return HttpResponseBadRequest('Requested object does not support frontend rendering')
toolbar = get_toolbar_from_request(request)
toolbar.set_object(content_type_obj)
if require_editable and not toolbar.object_is_editable():
# If not editable, switch from edit to preview endpoint
return HttpResponseRedirect(get_object_preview_url(content_type_obj))
render_func = extension.toolbar_enabled_models[model]
return render_func(request, content_type_obj)
def render_object_edit(request, content_type_id, object_id):
return render_object_endpoint(request, content_type_id, object_id, require_editable=True)
def render_object_preview(request, content_type_id, object_id):
return render_object_endpoint(request, content_type_id, object_id, require_editable=False)