10000 Refactored the fallbacks handling logic (#6182) · django-cms/django-cms@1980431 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1980431

Browse files
authored
Refactored the fallbacks handling logic (#6182)
1 parent a970819 commit 1980431

File tree

13 files changed

+398
-294
lines changed

13 files changed

+398
-294
lines changed

cms/cms_menus.py

Lines changed: 61 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@
99
from cms.utils import i18n
1010
from cms.utils.compat import DJANGO_1_9
1111
from cms.utils.conf import get_cms_setting
12-
from cms.utils.i18n import get_fallback_languages, hide_untranslated
12+
from cms.utils.i18n import (
13+
get_default_language_for_site,
14+
get_fallback_languages,
15+
hide_untranslated,
16+
is_valid_site_language,
17+
)
1318
from cms.utils.permissions import get_view_restrictions
1419
from cms.utils.page import get_node_queryset
1520
from cms.utils.page_permissions import user_can_view_all_pages
@@ -67,14 +72,17 @@ def user_can_see_node(node):
6772
return [node for node in nodes if user_can_see_node(node)]
6873

6974

70-
def get_menu_node_for_page(renderer, page, language):
75+
def get_menu_node_for_page(renderer, page, language, fallbacks=None):
7176
"""
7277
Transform a CMS page into a navigation node.
7378
7479
:param renderer: MenuRenderer instance bound to the request
7580
:param page: the page you wish to transform
7681
:param language: The current language used to render the menu
7782
"""
83+
if fallbacks is None:
84+
fallbacks = []
85+
7886
node = page.get_node_object(renderer.site)
7987
# Theses are simple to port over, since they are not calculated.
8088
# Other attributes will be added conditionally later.
@@ -137,23 +145,27 @@ def get_menu_node_for_page(renderer, page, language):
137145
if exts:
138146
attr['navigation_extenders'] = exts
139147

140-
translation = page.get_title_obj(language, fallback=True)
141-
142-
# Do we have a redirectURL?
143-
attr['redirect_url'] = translation.redirect # save redirect URL if any
144-
145-
# Now finally, build the NavigationNode object and return it.
146-
ret_node = CMSNavigationNode(
147-
translation.menu_title or translation.title,
148-
url='',
149-
id=page.pk,
150-
parent_id=parent_id,
151-
attr=attr,
152-
visible=page.in_navigation,
153-
path=translation.path or translation.slug,
154-
language=(translation.language if translation.language != language else None),
155-
)
156-
return ret_node
148+
for lang in [language] + fallbacks:
149+
translation = page.title_cache[lang]
150+
151+
if translation:
152+
# Do we have a redirectURL?
153+
attr['redirect_url'] = translation.redirect # save redirect URL if any
154+
155+
# Now finally, build the NavigationNode object and return it.
156+
ret_node = CMSNavigationNode(
157+
title=translation.menu_title or translation.title,
158+
url='',
159+
id=page.pk,
160+
parent_id=parent_id,
161+
attr=attr,
162+
visible=page.in_navigation,
163+
path=translation.path or translation.slug,
164+
language=(translation.language if translation.language != language else None),
165+
)
166+
return ret_node
167+
else:
168+
raise RuntimeError('Unable to render cms menu. There is a language misconfiguration.')
157169

158170

159171
class CMSNavigationNode(NavigationNode):
@@ -189,21 +201,37 @@ def get_nodes(self, request):
189201
from cms.models import Title
190202

191203
site = self.renderer.site
192-
lang = self.renderer.language
204+
lang = self.renderer.request_language
193205
nodes = get_node_queryset(
194206
site,
195207
published=not self.renderer.draft_mode_active,
196208
)
197209

198-
if hide_untranslated(lang, site.pk):
210+
if is_valid_site_language(lang, site_id=site.pk):
211+
_valid_language = True
212+
_hide_untranslated = hide_untranslated(lang, site.pk)
213+
else:
214+
_valid_language = False
215+
_hide_untranslated = False
216+
217+
if _valid_language:
218+
fallbacks = get_fallback_languages(lang, site_id=site.pk) or []
219+
languages = [lang] + [_lang for _lang in fallbacks if _lang != lang]
220+
else:
221+
# The request language is not configured for the current site.
222+
# Fallback to the default language configured for the current site.
223+
languages = [get_default_language_for_site(site.pk)]
224+
fallbacks = languages
225+
226+
if _valid_language and (_hide_untranslated or not fallbacks):
227+
# The language is correctly configured for the site.
228+
# But the user has opted out of displaying untranslated pages
229+
# OR has not configured any fallbacks.
199230
if self.renderer.draft_mode_active:
200231
nodes = nodes.filter(page__title_set__language=lang)
201232
else:
202233
nodes = nodes.filter(page__publisher_public__title_set__language=lang)
203234
languages = [lang]
204-
else:
205-
fallbacks = get_fallback_languages(lang, site_id=site.pk)
206-
languages = [lang] + (fallbacks or [])
207235

208236
if self.renderer.draft_mode_active:
209237
nodes = nodes.select_related('page', 'parent__page')
@@ -244,14 +272,17 @@ def get_nodes(self, request):
244272
# Build the blank title instances only once
245273
blank_title_cache = {language: EmptyTitle(language=language) for language in languages}
246274

275+
if lang not in blank_title_cache:
276+
blank_title_cache[lang] = EmptyTitle(language=lang)
277+
247278
def _page_to_node(page):
248-
page.title_cache = {trans.language: trans for trans in page.filtered_translations}
279+
# EmptyTitle is used to prevent the cms from trying
280+
# to find a translation in the database
281+
page.title_cache = blank_title_cache.copy()
249282

250-
for language in languages:
251-
# EmptyTitle is used to prevent the cms from trying
252-
# to find a translation in the database
253-
page.title_cache.setdefault(language, blank_title_cache[language])
254-
return get_menu_node_for_page(self.renderer, page, language=lang)
283+
for trans in page.filtered_translations:
284+
page.title_cache[trans.language] = trans
285+
return get_menu_node_for_page(self.renderer, page, language=lang, fallbacks=fallbacks)
255286
return [_page_to_node(page=page) for page in pages]
256287

257288

cms/cms_toolbars.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from cms.toolbar_pool import toolbar_pool
1616
from cms.utils import get_language_from_request, page_permissions
1717
from cms.utils.conf import get_cms_setting
18-
from cms.utils.i18n import get_language_tuple, force_language, get_language_dict, get_default_language
18+
from cms.utils.i18n import get_language_tuple, force_language, get_language_dict
1919
from cms.utils.permissions import get_user_sites_queryset
2020
from cms.utils.page_permissions import (
2121
user_can_change_page,
@@ -81,12 +81,10 @@ def add_wizard_button(self):
8181
page_pk = ''
8282
disabled = True
8383

84-
lang = get_language_from_request(self.request, current_page=self.page) or get_default_language()
85-
8684
url = '{url}?page={page}&language={lang}&edit'.format(
8785
url=reverse("cms_wizard_create"),
8886
page=page_pk,
89-
lang=lang,
87+
lang=self.toolbar.site_language,
9088
)
9189
self.toolbar.add_modal_button(title, url,
9290
side=self.toolbar.RIGHT,
@@ -733,7 +731,7 @@ def add_page_menu(self):
733731
on_success=refresh,
734732
)
735733

736-
if not self.page.is_page_type:
734+
if self.current_lang and not self.page.is_page_type:
737735
# revert to live
738736
current_page_menu.add_break(PAGE_MENU_FOURTH_BREAK)
739737
revert_action = admin_reverse('cms_page_revert_to_live', args=(self.page.pk, self.current_lang))

cms/tests/test_apphooks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,6 @@ def test_page_language_url_for_apphook(self):
944944

945945
output = tag.get_context(fake_context, 'fr')
946946
url = output['content']
947-
self.assertEqual(url, '/fr/child_page/child_child_page/extra_1/')
947+
self.assertEqual(url, '/en/child_page/child_child_page/extra_1/')
948948

949949
self.apphook_clear()

cms/tests/test_i18n.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,8 +332,9 @@ def test_language_code(self):
332332
response = self.client.get('/')
333333
self.assertEqual(response.status_code, 302)
334334
response = self.client.get('/en/')
335-
self.assertEqual(response.status_code, 302)
336335
self.assertRedirects(response, '/fr/')
336+
response = self.client.get('/fr/')
337+
self.assertEqual(response.status_code, 200)
337338

338339
@override_settings(
339340
CMS_LANGUAGES={

cms/tests/test_menu.py

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
from django.conf import settings
66
from django.contrib.auth.models import AnonymousUser, Permission, Group
7+
from django.contrib.sites.models import Site
78
from django.template import Template, TemplateSyntaxError
9+
from django.template.context import Context
810
from django.test.utils import override_settings
911
from django.utils.translation import activate
1012
from cms.apphook_pool import apphook_pool
@@ -308,7 +310,7 @@ def test_cms_menu_public_with_multiple_languages(self):
308310
pages = self.get_all_pages().order_by('publisher_public__nodes__path')
309311

310312
# Fallbacks on
311-
request = self.get_request(language='de')
313+
request = self.get_request(path='/de/', language='de')
312314
renderer = menu_pool.get_renderer(request)
313315
menu = renderer.get_menu('CMSMenu')
314316
nodes = menu.get_nodes(request)
@@ -323,7 +325,7 @@ def test_cms_menu_public_with_multiple_languages(self):
323325
)
324326

325327
# Fallbacks off
326-
request = self.get_request(language='de')
328+
request = self.get_request(path='/de/', language='de')
327329
lang_settings = copy.deepcopy(get_cms_setting('LANGUAGES'))
328330
lang_settings[1][1]['hide_untranslated'] = True
329331

@@ -338,7 +340,7 @@ def test_cms_menu_public_with_multiple_languages(self):
338340

339341
# Fallbacks on
340342
# This time however, the "de" translations are published.
341-
request = self.get_request(language='de')
343+
request = self.get_request(path='/de/', language='de')
342344
renderer = menu_pool.get_renderer(request)
343345
menu = renderer.get_menu('CMSMenu')
344346
nodes = menu.get_nodes(request)
@@ -351,7 +353,7 @@ def test_cms_menu_public_with_multiple_languages(self):
351353
)
352354

353355
# Fallbacks off
354-
request = self.get_request(language='de')
356+
request = self.get_request(path='/de/', language='de')
355357

356358
with self.settings(CMS_LANGUAGES=lang_settings):
357359
renderer = menu_pool.get_renderer(request)
@@ -914,6 +916,83 @@ def test_empty_menu(self):
914916
nodes = context['children']
915917
self.assertEqual(len(nodes), 0)
916918

919+
def test_render_menu_with_invalid_language(self):
920+
"""
921+
When rendering the menu, always fallback to a configured
922+
language on the current site.
923+
"""
924+
# Refs - https://github.com/divio/django-cms/issues/6179
925+
de_site = Site.objects.create(id=2, name='example-2.com', domain='example-2.com')
926+
defaults = {
927+
'site': de_site,
928+
'template': 'nav_playground.html',
929+
'language': 'de',
930+
}
931+
create_page('DE-P1', published=True, in_navigation=True, **defaults)
932+
create_page('DE-P2', published=True, in_navigation=True, **defaults)
933+
create_page('DE-P3', published=True, in_navigation=True, **defaults)
934+
935+
with self.settings(SITE_ID=2):
936+
request = self.get_request('/en/')
937+
context = Context()
938+
context['request'] = request
939+
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
940+
tpl.render(context)
941+
nodes = context['children']
942+
self.assertEqual(len(nodes), 3)
943+
self.assertEqual(nodes[0].title, 'DE-P1')
944+
self.assertEqual(nodes[0].get_absolute_url(), '/de/de-p1/')
945+
self.assertEqual(nodes[1].title, 'DE-P2')
946+
self.assertEqual(nodes[1].get_absolute_url(), '/de/de-p2/')
947+
self.assertEqual(nodes[2].title, 'DE-P3')
948+
self.assertEqual(nodes[2].get_absolute_url(), '/de/de-p3/')
949+
950+
with self.settings(SITE_ID=2):
951+
request = self.get_request('/en/de-p2/')
952+
context = Context()
953+
context['request'] = request
954+
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
955+
tpl.render(context)
956+
nodes = context['children']
957+
self.assertEqual(len(nodes), 3)
958+
self.assertEqual(nodes[0].title, 'DE-P1')
959+
self.assertEqual(nodes[0].get_absolute_url(), '/de/de-p1/')
960+
self.assertEqual(nodes[1].title, 'DE-P2')
961+
self.assertEqual(nodes[1].get_absolute_url(), '/de/de-p2/')
962+
self.assertEqual(nodes[2].title, 'DE-P3')
963+
self.assertEqual(nodes[2].get_absolute_url(), '/de/de-p3/')
964+
965+
def test_render_menu_with_invalid_language_and_no_fallbacks(self):
966+
defaults = {
967+
'template': 'nav_playground.html',
968+
'language': 'de',
969+
}
970+
create_page('DE-P1', published=True, in_navigation=True, **defaults)
971+
create_page('DE-P2', published=True, in_navigation=True, **defaults)
972+
create_page('DE-P3', published=True, in_navigation=True, **defaults)
973+
974+
lang_settings = copy.deepcopy(get_cms_setting('LANGUAGES'))
975+
lang_settings[1][0]['fallbacks'] = []
976+
lang_settings[1][1]['fallbacks'] = []
977+
978+
with self.settings(CMS_LANGUAGES=lang_settings):
979+
request = self.get_request('/en/')
980+
context = Context()
981+
context['request'] = request
982+
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
983+
tpl.render(context)
984+
nodes = context['children']
985+
self.assertEqual(len(nodes), 0)
986+
987+
with self.settings(CMS_LANGUAGES=lang_settings):
988+
request = self.get_request('/en/de-p2/')
989+
context = Context()
990+
context['request'] = request
991+
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
992+
tpl.render(context)
993+
nodes = context['children']
994+
self.assertEqual(len(nodes), 0)
995+
917996

918997
@override_settings(CMS_PERMISSION=False)
919998
class AdvancedSoftrootTests(SoftrootFixture, CMSTestCase):

cms/tests/test_menu_page_viewperm.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,9 +264,12 @@ def get_request(self, user=None, page=None):
264264
attrs = {
265265
'user': user or AnonymousUser(),
266266
'REQUEST': {},
267+
'COOKIES': {},
268+
'META': {},
267269
'POST': {},
268270
'GET': {},
269271
'path': path,
272+
'path_info': path,
270273
'session': {},
271274
}
272275
return type('Request', (object,), attrs)

0 commit comments

Comments
 (0)
0