8000 Refactored the fallbacks handling logic by czpython · Pull Request #6182 · django-cms/django-cms · GitHub
[go: up one dir, main page]

Skip to content

Refactored the fallbacks handling logic #6182

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 61 additions & 30 deletions cms/cms_menus.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
from cms.utils import i18n
from cms.utils.compat import DJANGO_1_9
from cms.utils.conf import get_cms_setting
from cms.utils.i18n import get_fallback_languages, hide_untranslated
from cms.utils.i18n import (
get_default_language_for_site,
get_fallback_languages,
hide_untranslated,
is_valid_site_language,
)
from cms.utils.permissions import get_view_restrictions
from cms.utils.page import get_node_queryset
from cms.utils.page_permissions import user_can_view_all_pages
Expand Down Expand Up @@ -67,14 +72,17 @@ def user_can_see_node(node):
return [node for node in nodes if user_can_see_node(node)]


def get_menu_node_for_page(renderer, page, language):
def get_menu_node_for_page(renderer, page, language, fallbacks=None):
"""
Transform a CMS page into a navigation node.

:param renderer: MenuRenderer instance bound to the request
:param page: the page you wish to transform
:param language: The current language used to render the menu
"""
if fallbacks is None:
fallbacks = []

node = page.get_node_object(renderer.site)
# Theses are simple to port over, since they are not calculated.
# Other attributes will be added conditionally later.
Expand Down Expand Up @@ -137,23 +145,27 @@ def get_menu_node_for_page(renderer, page, language):
if exts:
attr['navigation_extenders'] = exts

translation = page.get_title_obj(language, fallback=True)

# Do we have a redirectURL?
attr['redirect_url'] = translation.redirect # save redirect URL if any

# Now finally, build the NavigationNode object and return it.
ret_node = CMSNavigationNode(
translation.menu_title or translation.title,
url='',
id=page.pk,
parent_id=parent_id,
attr=attr,
visible=page.in_navigation,
path=translation.path or translation.slug,
language=(translation.language if translation.language != language else None),
)
return ret_node
for lang in [language] + fallbacks:
translation = page.title_cache[lang]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possible KeyError?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The title_cache is populated with EmptyTitle instances for all fallbacks, these instances are considered False when evaluated below.


if translation:
# Do we have a redirectURL?
attr['redirect_url'] = translation.redirect # save redirect URL if any

# Now finally, build the NavigationNode object and return it.
ret_node = CMSNavigationNode(
title=translation.menu_title or translation.title,
url='',
id=page.pk,
parent_id=parent_id,
attr=attr,
visible=page.in_navigation,
path=translation.path or translation.slug,
language=(translation.language if translation.language != language else None),
)
return ret_node
else:
raise RuntimeError('Unable to render cms menu. There is a language misconfiguration.')


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

site = self.renderer.site
lang = self.renderer.language
lang = self.renderer.request_language
nodes = get_node_queryset(
site,
published=not self.renderer.draft_mode_active,
)

if hide_untranslated(lang, site.pk):
if is_valid_site_language(lang, site_id=site.pk):
_valid_language = True
_hide_untranslated = hide_untranslated(lang, site.pk)
else:
_valid_language = False
_hide_untranslated = False

if _valid_language:
fallbacks = get_fallback_languages(lang, site_id=site.pk) or []
languages = [lang] + [_lang for _lang in fallbacks if _lang != lang]
else:
# The request language is not configured for the current site.
# Fallback to the default language configured for the current site.
languages = [get_default_language_for_site(site.pk)]
fallbacks = languages

if _valid_language and (_hide_untranslated or not fallbacks):
# The language is correctly configured for the site.
# But the user has opted out of displaying untranslated pages
# OR has not configured any fallbacks.
if self.renderer.draft_mode_active:
nodes = nodes.filter(page__title_set__language=lang)
else:
nodes = nodes.filter(page__publisher_public__title_set__language=lang)
languages = [lang]
else:
fallbacks = get_fallback_languages(lang, site_id=site.pk)
languages = [lang] + (fallbacks or [])

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

if lang not in blank_title_cache:
blank_title_cache[lang] = EmptyTitle(language=lang)

def _page_to_node(page):
page.title_cache = {trans.language: trans for trans in page.filtered_translations}
# EmptyTitle is used to prevent the cms from trying
# to find a translation in the database
page.title_cache = blank_title_cache.copy()

for language in languages:
# EmptyTitle is used to prevent the cms from trying
# to find a translation in the database
page.title_cache.setdefault(language, blank_title_cache[language])
return get_menu_node_for_page(self.renderer, page, language=lang)
for trans in page.filtered_translations:
page.title_cache[trans.language] = trans
return get_menu_node_for_page(self.renderer, page, language=lang, fallbacks=fallbacks)
return [_page_to_node(page=page) for page in pages]


Expand Down
8 changes: 3 additions & 5 deletions cms/cms_toolbars.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from cms.toolbar_pool import toolbar_pool
from cms.utils import get_language_from_request, page_permissions
from cms.utils.conf import get_cms_setting
from cms.utils.i18n import get_language_tuple, force_language, get_language_dict, get_default_language
from cms.utils.i18n import get_language_tuple, force_language, get_language_dict
from cms.utils.permissions import get_user_sites_queryset
from cms.utils.page_permissions import (
user_can_change_page,
Expand Down Expand Up @@ -81,12 +81,10 @@ def add_wizard_button(self):
page_pk = ''
disabled = True

lang = get_language_from_request(self.request, current_page=self.page) or get_default_language()

url = '{url}?page={page}&language={lang}&edit'.format(
url=reverse("cms_wizard_create"),
page=page_pk,
lang=lang,
lang=self.toolbar.site_language,
)
self.toolbar.add_modal_button(title, url,
side=self.toolbar.RIGHT,
Expand Down Expand Up @@ -733,7 +731,7 @@ def add_page_menu(self):
on_success=refresh,
)

if not self.page.is_page_type:
if self.current_lang and not self.page.is_page_type:
# revert to live
current_page_menu.add_break(PAGE_MENU_FOURTH_BREAK)
revert_action = admin_reverse('cms_page_revert_to_live', args=(self.page.pk, self.current_lang))
Expand Down
2 changes: 1 addition & 1 deletion cms/tests/test_apphooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,6 @@ def test_page_language_url_for_apphook(self):

output = tag.get_context(fake_context, 'fr')
url = output['content']
self.assertEqual(url, '/fr/child_page/child_child_page/extra_1/')
self.assertEqual(url, '/en/child_page/child_child_page/extra_1/')

self.apphook_clear()
3 changes: 2 additions & 1 deletion cms/tests/test_i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,9 @@ def test_language_code(self):
response = self.client.get('/')
self.assertEqual(response.status_code, 302)
response = self.client.get('/en/')
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, '/fr/')
response = self.client.get('/fr/')
self.assertEqual(response.status_code, 200)

@override_settings(
CMS_LANGUAGES={
Expand Down
87 changes: 83 additions & 4 deletions cms/tests/test_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

from django.conf import settings
from django.contrib.auth.models import AnonymousUser, Permission, Group
from django.contrib.sites.models import Site
from django.template import Template, TemplateSyntaxError
from django.template.context import Context
from django.test.utils import override_settings
from django.utils.translation import activate
from cms.apphook_pool import apphook_pool
Expand Down Expand Up @@ -308,7 +310,7 @@ def test_cms_menu_public_with_multiple_languages(self):
pages = self.get_all_pages().order_by('publisher_public__nodes__path')

# Fallbacks on
request = self.get_request(language='de')
request = self.get_request(path='/de/', language='de')
renderer = menu_pool.get_renderer(request)
menu = renderer.get_menu('CMSMenu')
nodes = menu.get_nodes(request)
Expand All @@ -323,7 +325,7 @@ def test_cms_menu_public_with_multiple_languages(self):
)

# Fallbacks off
request = self.get_request(language='de')
request = self.get_request(path='/de/', language='de')
lang_settings = copy.deepcopy(get_cms_setting('LANGUAGES'))
lang_settings[1][1]['hide_untranslated'] = True

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

# Fallbacks on
# This time however, the "de" translations are published.
request = self.get_request(language='de')
request = self.get_request(path='/de/', language='de')
renderer = menu_pool.get_renderer(request)
menu = renderer.get_menu('CMSMenu')
nodes = menu.get_nodes(request)
Expand All @@ -351,7 +353,7 @@ def test_cms_menu_public_with_multiple_languages(self):
)

# Fallbacks off
request = self.get_request(language='de')
request = self.get_request(path='/de/', language='de')

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

def test_render_menu_with_invalid_language(self):
"""
When rendering the menu, always fallback to a configured
language on the current site.
"""
# Refs - https://github.com/divio/django-cms/issues/6179
de_site = Site.objects.create(id=2, name='example-2.com', domain='example-2.com')
defaults = {
'site': de_site,
'template': 'nav_playground.html',
'language': 'de',
}
create_page('DE-P1', published=True, in_navigation=True, **defaults)
create_page('DE-P2', published=True, in_navigation=True, **defaults)
create_page('DE-P3', published=True, in_navigation=True, **defaults)

with self.settings(SITE_ID=2):
request = self.get_request('/en/')
context = Context()
context['request'] = request
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
tpl.render(context)
nodes = context['children']
self.assertEqual(len(nodes), 3)
self.assertEqual(nodes[0].title, 'DE-P1')
self.assertEqual(nodes[0].get_absolute_url(), '/de/de-p1/')
self.assertEqual(nodes[1].title, 'DE-P2')
self.assertEqual(nodes[1].get_absolute_url(), '/de/de-p2/')
self.assertEqual(nodes[2].title, 'DE-P3')
self.assertEqual(nodes[2].get_absolute_url(), '/de/de-p3/')

with self.settings(SITE_ID=2):
request = self.get_request('/en/de-p2/')
context = Context()
context['request'] = request
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
tpl.render(context)
nodes = context['children']
self.assertEqual(len(nodes), 3)
self.assertEqual(nodes[0].title, 'DE-P1')
self.assertEqual(nodes[0].get_absolute_url(), '/de/de-p1/')
self.assertEqual(nodes[1].title, 'DE-P2')
self.assertEqual(nodes[1].get_absolute_url(), '/de/de-p2/')
self.assertEqual(nodes[2].title, 'DE-P3')
self.assertEqual(nodes[2].get_absolute_url(), '/de/de-p3/')

def test_render_menu_with_invalid_language_and_no_fallbacks(self):
defaults = {
'template': 'nav_playground.html',
'language': 'de',
}
create_page('DE-P1', published=True, in_navigation=True, **defaults)
create_page('DE-P2', published=True, in_navigation=True, **defaults)
create_page('DE-P3', published=True, in_navigation=True, **defaults)

lang_settings = copy.deepcopy(get_cms_setting('LANGUAGES'))
lang_settings[1][0]['fallbacks'] = []
lang_settings[1][1]['fallbacks'] = []

with self.settings(CMS_LANGUAGES=lang_settings):
request = self.get_request('/en/')
context = Context()
context['request'] = request
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
tpl.render(context)
nodes = context['children']
self.assertEqual(len(nodes), 0)

with self.settings(CMS_LANGUAGES=lang_settings):
request = self.get_request('/en/de-p2/')
context = Context()
context['request'] = request
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
tpl.render(context)
nodes = context['children']
self.assertEqual(len(nodes), 0)


@override_settings(CMS_PERMISSION=False)
class AdvancedSoftrootTests(SoftrootFixture, CMSTestCase):
Expand Down
3 changes: 3 additions & 0 deletions cms/tests/test_menu_page_viewperm.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,12 @@ def get_request(self, user=None, page=None):
attrs = {
'user': user or AnonymousUser(),
'REQUEST': {},
'COOKIES': {},
'META': {},
'POST': {},
'GET': {},
'path': path,
'path_info': path,
'session': {},
}
return type('Request', (object,), attrs)
Expand Down
Loading
0