Skip to content

Commit e2375ff

Browse files
authored
Merge branch 'develop-4' into fix/delete-orphaned-plugins
2 parents 910c629 + e264d04 commit e2375ff

File tree

12 files changed

+198
-72
lines changed

12 files changed

+198
-72
lines changed

.github/workflows/new_contributor_pr.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@ jobs:
2929
needs: new
3030
steps:
3131
- name: Send Discord Webhook
32-
uses: Ilshidur/action-discord@master
33-
with:
34-
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
35-
content: |
32+
env:
33+
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL }}
34+
DISCORD_EMBEDS: |
3635
{
3736
"embeds": [
3837
{
@@ -56,3 +55,4 @@ jobs:
5655
}
5756
]
5857
}
58+
uses: Ilshidur/action-discord@master

cms/plugin_rendering.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ class BaseRenderer:
8282

8383
def __init__(self, request):
8484
self.request = request
85-
self.request_language = get_language_from_request(self.request)
8685
self._cached_templates = {}
8786
self._cached_plugin_classes = {}
8887
self._placeholders_content_cache = {}
@@ -112,6 +111,10 @@ def plugin_pool(self):
112111
import cms.plugin_pool
113112
return cms.plugin_pool.plugin_pool
114113

114+
@cached_property
115+
def request_language(self):
116+
return get_language_from_request(self.request)
117+
115118
def get_placeholder_plugin_menu(self, placeholder, page=None):
116119
registered_plugins = self.plugin_pool.registered_plugins
117120
can_add_plugin = partial(has_plugin_permission, user=self.request.user, permission_type='add')

cms/test_utils/testcases.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
)
3232
from cms.plugin_rendering import ContentRenderer, StructureRenderer
3333
from cms.test_utils.util.context_managers import UserLoginContext
34+
from cms.toolbar.utils import get_toolbar_from_request
3435
from cms.utils.compat import DJANGO_4_1
3536
from cms.utils.conf import get_cms_setting
3637
from cms.utils.permissions import set_current_user
@@ -662,6 +663,12 @@ def _add_plugin_to_placeholder(self, placeholder, plugin_type='LinkPlugin', lang
662663
plugin = add_plugin(placeholder, plugin_type, language, **plugin_data[plugin_type])
663664
return plugin
664665

666+
def _render_placeholder(self, placeholder, context, **kwargs):
667+
request = context['request']
668+
toolbar = get_toolbar_from_request(request)
669+
content_renderer = toolbar.content_renderer
670+
return content_renderer.render_placeholder(placeholder, context, **kwargs)
671+
665672

666673
class CMSTestCase(BaseCMSTestCase, testcases.TestCase):
667674
pass

cms/tests/test_i18n.py

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
1+
from collections import deque
12
from importlib import import_module
3+
from unittest.mock import patch
24

35
from django.conf import settings
6+
from django.template.context import Context
47
from django.test.utils import override_settings
58

69
from cms import api
10+
from cms.plugin_rendering import (
11+
ContentRenderer,
12+
LegacyRenderer,
13+
StructureRenderer,
14+
)
715
from cms.test_utils.testcases import CMSTestCase
816
from cms.utils import get_language_from_request, i18n
917
from cms.utils.compat import DJANGO_2_2
18+
from cms.views import details
1019

1120
if DJANGO_2_2:
1221
from django.utils.translation import LANGUAGE_SESSION_KEY
@@ -336,11 +345,15 @@ def test_get_fallback_languages(self):
336345
class TestLanguageFallbacks(CMSTestCase):
337346

338347
def test_language_code(self):
348+
'''
349+
No redirect_on_fallback will return 200 with the fallback content
350+
'''
339351
self.create_homepage("home", "nav_playground.html", "fr")
340352
response = self.client.get('/')
353+
# no language code will cause a redirect.
341354
self.assertEqual(response.status_code, 302)
342355
response = self.client.get('/en/')
343-
self.assertRedirects(response, '/fr/')
356+
self.assertEqual(response.status_code, 200)
344357
response = self.client.get('/fr/')
345358
self.assertEqual(response.status_code, 200)
346359

@@ -386,6 +399,125 @@ def test_session_language(self):
386399
self.assertEqual(response.status_code, 302)
387400
self.assertRedirects(response, '/en/')
388401

402+
@override_settings(
403+
CMS_LANGUAGES={
404+
'default': {
405+
'fallbacks': ['en', 'fr'],
406+
'public': True, # no 404s
407+
'redirect_on_fallback': False
408+
}
409+
},
410+
)
411+
def test_no_redirect_on_fallback(self):
412+
homepage = self.create_homepage(
413+
"home",
414+
"nav_playground.html",
415+
"fr"
416+
)
417+
page_data = self.get_new_page_data_dbfields(
418+
language="fr"
419+
)
420+
page = api.create_page(**page_data)
421+
response = self.client.get(page.get_absolute_url(language="en"))
422+
self.assertEqual(response.status_code, 200)
423+
424+
# homepage should be the same
425+
response = self.client.get(homepage.get_absolute_url(language="en"))
426+
self.assertEqual(response.status_code, 200)
427+
428+
@override_settings(
429+
CMS_LANGUAGES={
430+
'default': {
431+
'fallbacks': ['en', 'fr'],
432+
'public': True, # no 404s
433+
'redirect_on_fallback': False
434+
}
435+
},
436+
)
437+
def test_no_redirect_on_fallback_content(self):
438+
"""
439+
Test that the fallback content will be displayed
440+
"""
441+
homepage = self.create_homepage(
442+
"home",
443+
"nav_playground.html",
444+
"fr"
445+
)
446+
homepage_ph_fr = homepage.get_placeholders(
447+
"fr").get(slot="body")
448+
api.add_plugin(
449+
homepage_ph_fr,
450+
plugin_type="TextPlugin",
451+
language="fr",
452+
body="Hello, world!",
453+
)
454+
page_data = self.get_new_page_data_dbfields(
455+
language="fr"
456+
)
457+
page = api.create_page(**page_data)
458+
page_ph_fr = page.get_placeholders(
459+
"fr").get(slot="body")
460+
api.add_plugin(
461+
page_ph_fr,
462+
plugin_type="TextPlugin",
463+
language="fr",
464+
body="Hello, world!",
465+
)
466+
with patch("cms.views.render_pagecontent") as mock_render:
467+
# normal page
468+
path = page.get_absolute_url(language="en")
469+
request = self.get_request(path)
470+
details(request, slug=page.get_path("en"))
471+
mock_render.assert_called_once_with(
472+
request,
473+
page.get_content_obj()
474+
)
475+
# check that the french plugins will render
476+
context = Context({'request': request})
477+
rendered_placeholder = self._render_placeholder(page_ph_fr, context)
478+
self.assertEqual(rendered_placeholder, "Hello, world!")
479+
480+
with patch("cms.views.render_pagecontent") as mock_render:
481+
# homepage should be the same
482+
path = homepage.get_absolute_url(language="en")
483+
request = self.get_request(path)
484+
details(request, slug=homepage.get_path("en"))
485+
mock_render.assert_called_once_with(
486+
request,
487+
homepage.get_content_obj()
488+
)
489+
# check that the french plugins will render
490+
context = Context({'request': request})
491+
rendered_placeholder = self._render_placeholder(homepage_ph_fr, context)
492+
self.assertEqual(rendered_placeholder, "Hello, world!")
493+
494+
@override_settings(
495+
CMS_LANGUAGES={
496+
'default': {
497+
'fallbacks': ['en', 'fr'],
498+
'redirect_on_fallback': True,
499+
}
500+
}
501+
)
502+
def test_redirect_on_fallback(self):
503+
page_data = self.get_new_page_data_dbfields(
504+
language="fr"
505+
)
506+
page = api.create_page(**page_data)
507+
response_fr = self.client.get(page.get_absolute_url(language="fr"))
508+
self.assertEqual(response_fr.status_code, 200)
509+
response_en = self.client.get(page.get_absolute_url(language="en"))
510+
self.assertRedirects(response_en, page.get_absolute_url(language="fr"))
511+
512+
# homepage should be no different.
513+
home_page_data = self.get_new_page_data_dbfields(
514+
language="fr",
515+
)
516+
self.create_homepage(**home_page_data)
517+
response_fr = self.client.get(page.get_absolute_url(language="fr"))
518+
self.assertEqual(response_fr.status_code, 200)
519+
response_en = self.client.get(page.get_absolute_url(language="en"))
520+
self.assertRedirects(response_en, page.get_absolute_url(language="fr"))
389521

390522
@override_settings(
391523
LANGUAGE_CODE='en',

cms/tests/test_multilingual.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ def test_page_with_invalid_language_for_anon_user(self):
211211
# url uses "en" as the request language
212212
# but the site is configured to use "de" and "fr"
213213
response = self.client.get('/en/')
214-
self.assertRedirects(response, '/de/')
214+
self.assertEqual(response.status_code, 200)
215215
response = self.client.get('/en/%s/' % page_2.get_path('de'))
216216
self.assertEqual(response.status_code, 404)
217217

@@ -235,8 +235,10 @@ def test_page_with_invalid_language_for_auth_user(self):
235235
with self.login_user_context(superuser):
236236
# url uses "en" as the request language
237237
# but the site is configured to use "de" and "fr"
238+
# and no redirect on fallback so cms will render
239+
# in place
238240
response = self.client.get('/en/')
239-
self.assertRedirects(response, '/de/')
241+
self.assertEqual(response.status_code, 200)
240242
response = self.client.get('/en/%s/' % page_2.get_path('de'))
241243
self.assertEqual(response.status_code, 404)
242244

@@ -284,7 +286,9 @@ def test_language_fallback(self):
284286

285287
with self.settings(CMS_LANGUAGES=lang_settings):
286288
response = self.client.get("/de/")
287-
self.assertRedirects(response, '/en/')
289+
# as per the comments above, the content should
290+
# be rendered in place, no redirect should happen
291+
self.assertEqual(response.status_code, 200)
288292

289293
def test_no_english_defined(self):
290294
with self.settings(

cms/tests/test_plugins.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,6 @@ def register_plugins(*plugins):
7070
plugin_pool.unregister_plugin(plugin)
7171

7272

73-
def _render_placeholder(placeholder, context, **kwargs):
74-
request = context['request']
75-
toolbar = get_toolbar_from_request(request)
76-
content_renderer = toolbar.content_renderer
77-
return content_renderer.render_placeholder(placeholder, context, **kwargs)
78-
79-
8073
class DumbFixturePlugin(CMSPluginBase):
8174
model = CMSPlugin
8275
name = "Dumb Test Plugin. It does nothing."
@@ -277,7 +270,7 @@ def test_plugin_order(self):
277270
self.assertEqual(db_plugin_2.position, 2)
278271
# Finally we render the placeholder to test the actual content
279272
context = self.get_context(page_en.get_absolute_url(), page=page_en)
280-
rendered_placeholder = _render_placeholder(ph_en, context)
273+
rendered_placeholder = self._render_placeholder(ph_en, context)
281274
self.assertEqual(rendered_placeholder, "I'm the firstI'm the second")
282275

283276
def test_plugin_order_alt(self):
@@ -316,7 +309,7 @@ def test_plugin_order_alt(self):
316309

317310
# Finally we render the placeholder to test the actual content
318311
draft_page_context = self.get_context(cms_page.get_absolute_url(), page=cms_page)
319-
rendered_placeholder = _render_placeholder(placeholder, draft_page_context)
312+
rendered_placeholder = self._render_placeholder(placeholder, draft_page_context)
320313
self.assertEqual(rendered_placeholder, "I'm the firstI'm the secondI'm the third")
321314

322315
def test_plugin_position(self):
@@ -633,7 +626,7 @@ def test_empty_plugin_is_ignored(self):
633626
)
634627

635628
# this should not raise any errors, but just ignore the empty plugin
636-
out = _render_placeholder(placeholder, self.get_context(), width=300)
629+
out = self._render_placeholder(placeholder, self.get_context(), width=300)
637630
self.assertFalse(len(out))
638631
self.assertFalse(len(placeholder._plugins_cache))
639632

cms/utils/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# TODO: this is just stuff from utils.py - should be split / moved
22

33
from cms.utils.i18n import (
4+
get_current_language,
45
get_default_language,
56
get_language_code,
67
get_language_list,
@@ -27,8 +28,9 @@ def get_language_from_request(request, current_page=None):
2728
language = get_language_code(language)
2829
if language not in get_language_list(site_id):
2930
language = None
30-
if not language:
31-
language = get_language_code(getattr(request, 'LANGUAGE_CODE', None))
31+
if not language and request:
32+
# get the active language
33+
language = get_current_language()
3234
if language:
3335
if language not in get_language_list(site_id):
3436
language = None

cms/utils/check.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -232,13 +232,11 @@ def check_middlewares(output):
232232
'cms.middleware.toolbar.ToolbarMiddleware',
233233
'cms.middleware.language.LanguageCookieMiddleware',
234234
)
235-
if getattr(settings, 'MIDDLEWARE', None):
236-
middlewares = settings.MIDDLEWARE
237-
else:
238-
middlewares = settings.MIDDLEWARE_CLASSES
235+
middlewares = getattr(settings, 'MIDDLEWARE', [])
236+
239237
for middleware in required_middlewares:
240238
if middleware not in middlewares:
241-
section.error("%s middleware must be in MIDDLEWARE_CLASSES" % middleware)
239+
section.error("%s middleware must be in MIDDLEWARE" % middleware)
242240

243241

244242
@define_check

cms/views.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from django.urls import Resolver404, resolve, reverse
1717
from django.utils.cache import patch_cache_control
1818
from django.utils.timezone import now
19-
from django.utils.translation import get_language_from_request
19+
from django.utils.translation import activate, get_language_from_request
2020
from django.views.decorators.http import require_POST
2121

2222
from cms.apphook_pool import apphook_pool
@@ -174,17 +174,18 @@ def details(request, slug):
174174
if language != request_language and language in available_languages
175175
]
176176
language_is_unavailable = request_language not in available_languages
177+
first_fallback_language = next(iter(fallback_languages or []), None)
177178

178179
if language_is_unavailable and not fallback_languages:
179180
# There is no page with the requested language
180181
# and there's no configured fallbacks
181182
return _handle_no_page(request)
182-
elif language_is_unavailable and (redirect_on_fallback or page.is_home):
183+
elif language_is_unavailable and redirect_on_fallback:
183184
# There is no page with the requested language and
184-
# the user has explicitly requested to redirect on fallbacks,
185+
# redirect_on_fallback is True,
185186
# so redirect to the first configured / available fallback language
186-
fallback = fallback_languages[0]
187-
redirect_url = page.get_absolute_url(fallback, fallback=False)
187+
redirect_url = page.get_absolute_url(
188+
first_fallback_language, fallback=False)
188189
else:
189190
page_path = page.get_absolute_url(request_language)
190191
page_slug = page.get_path(request_language) or page.get_slug(request_language)
@@ -212,7 +213,19 @@ def details(request, slug):
212213
if page.login_required and not request.user.is_authenticated:
213214
return redirect_to_login(quote(request.get_full_path()), settings.LOGIN_URL)
214215

215-
content = page.get_content_obj(language=request_language)
216+
content_language = request_language
217+
if language_is_unavailable:
218+
# When redirect_on_fallback is False and
219+
# language is unavailable, render the content
220+
# in the first fallback language available
221+
# by switching to it
222+
content_language = first_fallback_language
223+
# translation.activate() is used without context
224+
# as the context won't be preserved when the
225+
# plugins get rendered
226+
activate(content_language)
227+
228+
content = page.get_content_obj(language=content_language)
216229
# use the page object with populated cache
217230
content.page = page
218231
if hasattr(request, 'toolbar'):

0 commit comments

Comments
 (0)