dynamicweb/cms/tests/test_menu.py

2216 lines
83 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import copy
from django.contrib.auth.models import AnonymousUser, Group, Permission
from django.contrib.sites.models import Site
from django.core.cache import caches
from django.db import connection
from django.template import Template, TemplateSyntaxError
from django.template.context import Context
from django.test.utils import CaptureQueriesContext, override_settings
from django.utils.translation import activate, override as force_language
from cms.api import create_page, create_page_content
from cms.apphook_pool import apphook_pool
from cms.cms_menus import CMSMenu, get_visible_page_contents
from cms.models import ACCESS_PAGE_AND_DESCENDANTS, Page, PageContent
from cms.models.permissionmodels import GlobalPagePermission, PagePermission
from cms.test_utils.fixtures.menus import (
ExtendedMenusFixture,
MenusFixture,
SoftrootFixture,
SubMenusFixture,
)
from cms.test_utils.project.sampleapp.cms_apps import (
NamespacedApp,
SampleApp,
SampleApp2,
)
from cms.test_utils.project.sampleapp.cms_menus import (
SampleAppMenu,
StaticMenu,
StaticMenu2,
)
from cms.test_utils.testcases import CMSTestCase
from cms.test_utils.util.context_managers import LanguageOverride, apphooks
from cms.test_utils.util.mock import AttributeObject
from cms.utils.conf import get_cms_setting
from cms.utils.i18n import get_default_language_for_site
from menus.base import NavigationNode
from menus.menu_pool import _build_nodes_inner_for_one_menu, menu_pool
from menus.models import CacheKey
from menus.utils import cut_levels, find_selected, mark_descendants
class BaseMenuTest(CMSTestCase):
def _get_nodes(self, path="/"):
node1 = NavigationNode("1", "/1/", 1)
node2 = NavigationNode("2", "/2/", 2, 1)
node3 = NavigationNode("3", "/3/", 3, 2)
node4 = NavigationNode("4", "/4/", 4, 2)
node5 = NavigationNode("5", "/5/", 5)
nodes = [node1, node2, node3, node4, node5]
tree = _build_nodes_inner_for_one_menu([n for n in nodes], "test")
request = self.get_request(path)
renderer = menu_pool.get_renderer(request)
renderer.apply_modifiers(tree, request)
return tree, nodes
def setUp(self):
super().setUp()
if not menu_pool.discovered:
menu_pool.discover_menus()
self.old_menu = menu_pool.menus
menu_pool.menus = {"CMSMenu": self.old_menu["CMSMenu"]}
menu_pool.clear(Site.objects.get_current().pk)
activate("en")
def tearDown(self):
menu_pool.menus = self.old_menu
super().tearDown()
def get_page(self, num):
return Page.objects.get(pagecontent_set__title="P%s" % num)
class MenuDiscoveryTest(ExtendedMenusFixture, CMSTestCase):
def setUp(self):
super().setUp()
menu_pool.discovered = False
self.old_menu = menu_pool.menus
menu_pool.menus = {}
menu_pool.discover_menus()
menu_pool.register_menu(SampleAppMenu)
menu_pool.register_menu(StaticMenu)
menu_pool.register_menu(StaticMenu2)
def tearDown(self):
menu_pool.menus = self.old_menu
super().tearDown()
def test_menu_registered(self):
menu_pool.discovered = False
menu_pool.discover_menus()
# The following tests that get_registered_menus()
# returns all menus registered based on the for_rendering flag
# A list of menu classes registered regardless of whether they
# have instances attached or not
registered = menu_pool.get_registered_menus(for_rendering=False)
# A list of menu classes registered and filter out any attached menu
# if it does not have instances.
registered_for_rendering = menu_pool.get_registered_menus(for_rendering=True)
# We've registered three menus
self.assertEqual(len(registered), 3)
# But two of those are attached menus and shouldn't be rendered.
self.assertEqual(len(registered_for_rendering), 1)
# Attached both menus to separate pages
create_page("apphooked-page", "nav_playground.html", "en", navigation_extenders="StaticMenu")
create_page("apphooked-page", "nav_playground.html", "en", navigation_extenders="StaticMenu2")
registered = menu_pool.get_registered_menus(for_rendering=False)
registered_for_rendering = menu_pool.get_registered_menus(for_rendering=True)
# Even though we've registered three menus, the total is give because two
# are attached menus and each attached menu has two instances.
self.assertEqual(len(registered), 3)
self.assertEqual(len(registered_for_rendering), 3)
def test_menu_registered_in_renderer(self):
menu_pool.discovered = False
menu_pool.discover_menus()
# The following tests that a menu renderer calculates the registered
# menus on a request basis.
request_1 = self.get_request("/en/")
request_1_renderer = menu_pool.get_renderer(request_1)
registered = menu_pool.get_registered_menus(for_rendering=False)
self.assertEqual(len(registered), 3)
self.assertEqual(len(request_1_renderer.menus), 1)
create_page("apphooked-page", "nav_playground.html", "en", navigation_extenders="StaticMenu")
create_page("apphooked-page", "nav_playground.html", "en", navigation_extenders="StaticMenu2")
request_2 = self.get_request("/en/")
request_2_renderer = menu_pool.get_renderer(request_2)
self.assertEqual(len(request_2_renderer.menus), 3)
def test_menu_expanded(self):
menu_pool.discovered = False
menu_pool.discover_menus()
with self.settings(ROOT_URLCONF="cms.test_utils.project.urls_for_apphook_tests"):
with apphooks(SampleApp):
page = create_page(
"apphooked-page",
"nav_playground.html",
"en",
apphook="SampleApp",
navigation_extenders="StaticMenu",
)
self.assertTrue(menu_pool.discovered)
menus = menu_pool.get_registered_menus()
self.assertTrue(menu_pool.discovered)
# Counts the number of StaticMenu (which is expanded) and StaticMenu2
# (which is not) and checks the key name for the StaticMenu instances
static_menus = 1
static_menus_2 = 1
for key, menu in menus.items():
if key.startswith("StaticMenu:"):
static_menus -= 1
self.assertTrue(key.endswith(str(page.pk)) or key.endswith(str(page.pk)))
if key == "StaticMenu2":
static_menus_2 -= 1
self.assertEqual(static_menus, 0)
self.assertEqual(static_menus_2, 0)
def test_multiple_menus(self):
with self.settings(ROOT_URLCONF="cms.test_utils.project.urls_for_apphook_tests"):
with apphooks(NamespacedApp, SampleApp2):
apphook_pool.discovered = False
apphook_pool.discover_apps()
create_page("apphooked-page", "nav_playground.html", "en", apphook="SampleApp2")
create_page("apphooked-page", "nav_playground.html", "en", navigation_extenders="StaticMenu")
create_page(
"apphooked-page",
"nav_playground.html",
"en",
apphook="NamespacedApp",
apphook_namespace="whatever",
navigation_extenders="StaticMenu",
)
self.assertEqual(len(menu_pool.get_menus_by_attribute("cms_enabled", True)), 2)
class ExtendedFixturesMenuTests(ExtendedMenusFixture, BaseMenuTest):
"""
Tree from fixture:
+ P1
| + P2
| + P3
| + P9
| + P10
| + P11
+ P4
| + P5
+ P6 (not in menu)
+ P7
+ P8
"""
def get_page(self, num):
return Page.objects.get(pagecontent_set__title="P%s" % num)
def get_all_pages(self):
return Page.objects.all()
def test_menu_failfast_on_invalid_usage(self):
context = self.get_context()
context["child"] = self.get_page(1)
# test standard show_menu
with self.settings(DEBUG=True, TEMPLATE_DEBUG=True):
tpl = Template("{% load menu_tags %}{% show_menu 0 0 0 0 'menu/menu.html' child %}")
self.assertRaises(TemplateSyntaxError, tpl.render, context)
def test_show_menu_cut_inactive(self):
root = self.get_page(2)
context = self.get_context(page=root)
tpl = Template("{% load menu_tags %}{% show_menu 1 100 0 1 %}")
tpl.render(context)
nodes = context["children"]
self.assertEqual(len(nodes), 2)
self.assertEqual(len(nodes[0].children), 1)
self.assertEqual(len(nodes[1].children), 0)
def test_show_submenu_nephews(self):
page_2 = self.get_page(2)
context = self.get_context(path=page_2.get_absolute_url(), page=page_2)
tpl = Template("{% load menu_tags %}{% show_sub_menu 100 1 1 %}")
tpl.render(context)
nodes = context["children"]
# P2 is the selected node
self.assertTrue(nodes[0].selected)
# Should include P10 but not P11
self.assertEqual(len(nodes[1].children), 1)
self.assertFalse(nodes[1].children[0].children)
tpl = Template("{% load menu_tags %}{% show_sub_menu 100 1 %}")
tpl.render(context)
nodes = context["children"]
# should now include both P10 and P11
self.assertEqual(len(nodes[1].children), 1)
self.assertEqual(len(nodes[1].children[0].children), 1)
def test_show_submenu_template_root_level_none_no_nephew_limit(self):
root = self.get_page(1)
context = self.get_context(path=root.get_absolute_url(), page=root)
tpl = Template("{% load menu_tags %}{% show_sub_menu 100 None 100 %}")
tpl.render(context)
nodes = context["children"]
# default nephew limit, P2 and P9 in the nodes list
self.assertEqual(len(nodes), 2)
class FixturesMenuTests(MenusFixture, BaseMenuTest):
"""
Tree from fixture:
+ P1
| + P2
| + P3
+ P4
| + P5
+ P6 (not in menu)
+ P7
+ P8
"""
def get_page(self, num):
return Page.objects.get(pagecontent_set__title="P%s" % num)
def get_all_pages(self):
return Page.objects.all()
def test_menu_failfast_on_invalid_usage(self):
context = self.get_context()
context["child"] = self.get_page(1)
# test standard show_menu
with self.settings(DEBUG=True, TEMPLATE_DEBUG=True):
tpl = Template("{% load menu_tags %}{% show_menu 0 0 0 0 'menu/menu.html' child %}")
self.assertRaises(TemplateSyntaxError, tpl.render, context)
def test_basic_cms_menu(self):
menus = menu_pool.get_registered_menus()
self.assertEqual(len(menus), 1)
with force_language("en"):
response = self.client.get(self.get_pages_root()) # path = '/'
self.assertEqual(response.status_code, 200)
request = self.get_request()
renderer = menu_pool.get_renderer(request)
# test the cms menu class
menu = renderer.get_menu("CMSMenu")
nodes = menu.get_nodes(request)
pages = self.get_all_pages().order_by("path")
self.assertEqual(len(nodes), len(pages))
self.assertSequenceEqual(
[node.get_absolute_url() for node in nodes],
[page.get_absolute_url() for page in pages],
)
def test_show_page_in_menu_after_move_page(self):
"""
Test checks if the menu cache is cleaned after move page.
"""
page = create_page("page to move", "nav_playground.html", "en")
request = self.get_request("/")
renderer = menu_pool.get_renderer(request)
renderer.draft_mode_active = True
nodes_before = renderer.get_nodes()
index_before = [i for i, s in enumerate(nodes_before) if s.title == page.get_title()]
self.assertEqual(CacheKey.objects.count(), 1)
with self.login_user_context(self.get_superuser()):
# Moves the page to the second position in the tree
data = {"id": page.pk, "position": 1}
endpoint = self.get_admin_url(Page, "move_page", page.pk)
response = self.client.post(endpoint, data)
self.assertEqual(response.status_code, 200)
self.assertEqual(CacheKey.objects.count(), 0)
request = self.get_request("/")
renderer = menu_pool.get_renderer(request)
renderer.draft_mode_active = True
nodes_after = renderer.get_nodes()
index_after = [i for i, s in enumerate(nodes_after) if s.title == page.get_title()]
self.assertEqual(CacheKey.objects.count(), 1)
self.assertNotEqual(index_before, index_after, "Index should not be the same after move page in navigation")
def test_cms_menu_public_with_multiple_languages(self):
pages = self.get_all_pages().order_by("path")
# Fallbacks on
request = self.get_request(path="/de/", language="de")
renderer = menu_pool.get_renderer(request)
menu = renderer.get_menu("CMSMenu")
nodes = menu.get_nodes(request)
self.assertEqual(len(nodes), len(pages))
with force_language("de"):
# Current language is "de" but urls should still point
# to "en" because of fallbacks.
self.assertSequenceEqual(
[node.get_absolute_url() for node in nodes],
[page.get_absolute_url("en", fallback=False) for page in pages],
)
# Fallbacks off
request = self.get_request(path="/de/", language="de")
lang_settings = copy.deepcopy(get_cms_setting("LANGUAGES"))
lang_settings[1][1]["hide_untranslated"] = True
with self.settings(CMS_LANGUAGES=lang_settings):
renderer = menu_pool.get_renderer(request)
menu = renderer.get_menu("CMSMenu")
nodes = menu.get_nodes(request)
self.assertEqual(len(nodes), 0)
for page in Page.objects.all():
create_page_content(
language="de", title=page.get_title("en"), page=page, slug="{}-de".format(page.get_slug("en"))
)
# Fallbacks on
# This time however, the "de" translations exist.
request = self.get_request(path="/de/", language="de")
renderer = menu_pool.get_renderer(request)
menu = renderer.get_menu("CMSMenu")
nodes = menu.get_nodes(request)
self.assertEqual(len(nodes), len(pages))
with force_language("de"):
self.assertSequenceEqual(
[node.get_absolute_url() for node in nodes],
[page.get_absolute_url("de", fallback=False) for page in pages],
)
# Fallbacks off
request = self.get_request(path="/de/", language="de")
with self.settings(CMS_LANGUAGES=lang_settings):
renderer = menu_pool.get_renderer(request)
menu = renderer.get_menu("CMSMenu")
nodes = menu.get_nodes(request)
with force_language("de"):
self.assertSequenceEqual(
[node.get_absolute_url() for node in nodes],
[page.get_absolute_url("de", fallback=False) for page in pages],
)
def test_show_menu(self):
root = self.get_page(1)
context = self.get_context(page=root)
# test standard show_menu
tpl = Template("{% load menu_tags %}{% show_menu %}")
tpl.render(context)
nodes = context["children"]
self.assertEqual(len(nodes), 2)
self.assertEqual(nodes[0].selected, True)
self.assertEqual(nodes[0].sibling, False)
self.assertEqual(nodes[0].descendant, False)
self.assertEqual(nodes[0].children[0].descendant, True)
self.assertEqual(nodes[0].children[0].children[0].descendant, True)
self.assertEqual(nodes[0].get_absolute_url(), self.get_pages_root())
self.assertEqual(nodes[1].get_absolute_url(), self.get_page(4).get_absolute_url())
self.assertEqual(nodes[1].sibling, True)
self.assertEqual(nodes[1].selected, False)
def test_show_menu_num_queries(self):
context = self.get_context()
# test standard show_menu
with self.assertNumQueries(5):
"""
The queries should be:
get all page contents
get all page permissions
get all page urls
get the menu cache key
set the menu cache key
"""
tpl = Template("{% load menu_tags %}{% show_menu %}")
tpl.render(context)
def test_show__key_leak(self):
context = self.get_context()
tpl = Template("{% load menu_tags %}{% show_menu %}")
self.assertEqual(CacheKey.objects.count(), 0)
tpl.render(context)
self.assertEqual(CacheKey.objects.count(), 1)
tpl.render(context)
self.assertEqual(CacheKey.objects.count(), 1)
def test_menu_cache_respects_database_keys(self):
cms_page = self.get_page(1)
context = self.get_context(path=cms_page.get_absolute_url(), page=cms_page)
context["request"].session["cms_edit"] = False
# Prime the cache
with self.assertNumQueries(5):
# The queries should be:
# get all page contents
# get all page permissions
# get all page urls
# get the menu cache key
# set the menu cache key
Template("{% load menu_tags %}{% show_menu %}").render(context)
# One new CacheKey should have been created
self.assertEqual(CacheKey.objects.count(), 1)
# Because its cached, only one query is made to the db
with self.assertNumQueries(1):
# The queries should be:
# get the menu cache key
Template("{% load menu_tags %}{% show_menu %}").render(context)
# Delete the current cache key but don't touch the cache
CacheKey.objects.all().delete()
# The menu should be recalculated
with self.assertNumQueries(5):
# The queries should be:
# check if cache key exists
# get all page contents
# get all page permissions
# get all page url objects
# set the menu cache key
Template("{% load menu_tags %}{% show_menu %}").render(context)
def test_menu_cache_default_is_default_cache(self):
from cms.utils.conf import get_menu_cache
menu_cache = get_menu_cache() # Re-read the patched settings
menu_cache.get("something") # Resolve lazy cache
self.assertEqual(menu_cache, caches["default"], "Menu cache is not \"default\"")
@override_settings(
CMS_MENU_CACHE_BACKEND="secondary",
CACHES={
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "menu-test-default",
},
"secondary": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "menu-test-secondary",
},
},
)
def test_menu_cache_uses_configured_backend(self):
from cms.utils.conf import get_menu_cache
menu_cache = get_menu_cache() # Re-read the patched settings
menu_cache.get("something") # Resolve lazy cache
self.assertEqual(menu_cache, caches["secondary"], "Menu cache ignores CMS_MENU_CACHE_BACKEND setting")
def test_menu_keys_duplicate_clear(self):
"""
Tests that the menu clears all keys, including duplicates.
"""
CacheKey.objects.create(language="fr", site=1, key="a")
CacheKey.objects.create(language="fr", site=1, key="a")
self.assertEqual(CacheKey.objects.count(), 2)
menu_pool.clear(site_id=1, language="fr")
self.assertEqual(CacheKey.objects.count(), 0)
def test_only_active_tree(self):
context = self.get_context(page=self.get_page(1))
# test standard show_menu
tpl = Template("{% load menu_tags %}{% show_menu 0 100 0 100 %}")
tpl.render(context)
nodes = context["children"]
# Expectation
# + P1 (selected)
# | + P2
# | + P3
# + P4
self.assertEqual(len(nodes), 2)
self.assertTrue(nodes[0].selected)
self.assertEqual(len(nodes[0].children), 1)
self.assertEqual(len(nodes[1].children), 0)
self.assertEqual(len(nodes[0].children[0].children), 1)
page_4 = self.get_page(4)
context = self.get_context(path=page_4.get_absolute_url(), page=page_4)
tpl = Template("{% load menu_tags %}{% show_menu 0 100 0 100 %}")
tpl.render(context)
nodes = context["children"]
# Expectation
# + P1
# + P4 (selected)
# + P5
self.assertEqual(len(nodes), 2)
self.assertEqual(len(nodes[0].children), 0)
self.assertTrue(nodes[1].selected)
self.assertEqual(len(nodes[1].children), 1)
self.assertEqual(nodes[1].children[0].children, [])
def test_only_one_active_level(self):
context = self.get_context(page=self.get_page(1))
# test standard show_menu
tpl = Template("{% load menu_tags %}{% show_menu 0 100 0 1 %}")
tpl.render(context)
nodes = context["children"]
self.assertEqual(len(nodes[1].children), 0)
self.assertEqual(len(nodes[0].children), 1)
self.assertEqual(len(nodes[0].children[0].children), 0)
def test_only_level_zero(self):
context = self.get_context()
# test standard show_menu
tpl = Template("{% load menu_tags %}{% show_menu 0 0 0 0 %}")
tpl.render(context)
nodes = context["children"]
for node in nodes:
self.assertEqual(len(node.children), 0)
def test_only_level_one(self):
site = Site.objects.get_current()
context = self.get_context()
# test standard show_menu
tpl = Template("{% load menu_tags %}{% show_menu 1 1 100 100 %}")
tpl.render(context)
nodes = context["children"]
level_2_pages = Page.objects.filter(depth=2, site=site)
self.assertEqual(len(nodes), level_2_pages.count())
for node in nodes:
self.assertEqual(len(node.children), 0)
def test_only_level_one_active(self):
context = self.get_context(page=self.get_page(1))
# test standard show_menu
tpl = Template("{% load menu_tags %}{% show_menu 1 1 0 100 %}")
tpl.render(context)
nodes = context["children"]
# Expectation
# + P2
self.assertEqual(len(nodes), 1)
self.assertEqual(nodes[0].descendant, True)
self.assertEqual(len(nodes[0].children), 0)
def test_only_level_one_active_and_inacgtive(self):
context = self.get_context(page=self.get_page(1))
# test standard show_menu
tpl = Template("{% load menu_tags %}{% show_menu 1 1 1 100 %}")
tpl.render(context)
nodes = context["children"]
# Expectation
# + P2
# + P5
# + P7 (despite the fact that the parent is not visible)
# + P8 (despite the fact that the parent is not visible)
self.assertEqual(len(nodes), 4)
self.assertEqual(nodes[0].descendant, True)
self.assertEqual(nodes[1].descendant, False)
for node in nodes:
self.assertEqual(len(node.children), 0)
def test_level_zero_and_one(self):
context = self.get_context()
# test standard show_menu
tpl = Template("{% load menu_tags %}{% show_menu 0 1 100 100 %}")
tpl.render(context)
nodes = context["children"]
# Expectation
# + P1
# | + P2
# + P4
# + P5
self.assertEqual(len(nodes), 2)
for node in nodes:
self.assertEqual(len(node.children), 1)
def test_one_inactive(self):
context = self.get_context()
# test standard show_menu
tpl = Template("{% load menu_tags %}{% show_menu 0 100 1 100 %}")
tpl.render(context)
nodes = context["children"]
# Expectation
# + P1
# | + P2
# + P4
# + P5
self.assertEqual(len(nodes), 2)
for node in nodes:
self.assertEqual(len(node.children), 1)
self.assertEqual(len(node.children[0].children), 0)
def test_show_submenu(self):
context = self.get_context(page=self.get_page(1))
# test standard show_menu
tpl = Template("{% load menu_tags %}{% show_sub_menu %}")
tpl.render(context)
nodes = context["children"]
self.assertEqual(nodes[0].descendant, True)
self.assertEqual(len(nodes), 1)
self.assertEqual(len(nodes[0].children), 1)
tpl = Template("{% load menu_tags %}{% show_sub_menu 1 %}")
tpl.render(context)
nodes = context["children"]
self.assertEqual(len(nodes), 1)
self.assertEqual(len(nodes[0].children), 0)
page_3 = self.get_page(3)
context = self.get_context(path=page_3.get_absolute_url(), page=page_3)
tpl = Template("{% load menu_tags %}{% show_sub_menu 100 1 %}")
tpl.render(context)
nodes = context["children"]
# P3 is the selected node
self.assertFalse(nodes[0].selected)
self.assertTrue(nodes[0].children[0].selected)
# top level node should be P2
self.assertEqual(nodes[0].get_absolute_url(), self.get_page(2).get_absolute_url())
# should include P3 as well
self.assertEqual(len(nodes[0].children), 1)
page_2 = self.get_page(2)
context = self.get_context(path=page_2.get_absolute_url(), page=page_2)
tpl = Template("{% load menu_tags %}{% show_sub_menu 100 0 %}")
tpl.render(context)
nodes = context["children"]
# P1 should be in the nav
self.assertEqual(nodes[0].get_absolute_url(), self.get_page(1).get_absolute_url())
# P2 is selected
self.assertTrue(nodes[0].children[0].selected)
def test_show_submenu_template_root_level_none(self):
root = self.get_page(1)
context = self.get_context(path=root.get_absolute_url(), page=root)
tpl = Template("{% load menu_tags %}{% show_sub_menu 100 None 1 %}")
tpl.render(context)
nodes = context["children"]
# First node is P2 (P1 children) thus not selected
self.assertFalse(nodes[0].selected)
# nephew limit of 1, so only P2 is the nodes list
self.assertEqual(len(nodes), 1)
# P3 is a child of P2, but not in nodes list
self.assertTrue(nodes[0].children)
def test_show_breadcrumb(self):
page_3 = self.get_page(3)
context = self.get_context(path=self.get_page(3).get_absolute_url(), page=page_3)
tpl = Template("{% load menu_tags %}{% show_breadcrumb %}")
tpl.render(context)
nodes = context["ancestors"]
self.assertEqual(len(nodes), 3)
tpl = Template("{% load menu_tags %}{% show_breadcrumb 1 %}")
tpl.render(context)
nodes = context["ancestors"]
self.assertEqual(len(nodes), 2)
context = self.get_context()
tpl = Template("{% load menu_tags %}{% show_breadcrumb %}")
tpl.render(context)
nodes = context["ancestors"]
self.assertEqual(len(nodes), 1)
tpl = Template("{% load menu_tags %}{% show_breadcrumb 1 %}")
tpl.render(context)
nodes = context["ancestors"]
self.assertEqual(len(nodes), 0)
page1 = self.get_page(1)
page1.in_navigation = False
page1.save()
page2 = self.get_page(2)
context = self.get_context(path=page2.get_absolute_url(), page=page2)
tpl = Template("{% load menu_tags %}{% show_breadcrumb %}")
tpl.render(context)
nodes = context["ancestors"]
self.assertEqual(len(nodes), 2)
self.assertEqual(nodes[0].get_absolute_url(), self.get_pages_root())
self.assertEqual(isinstance(nodes[0], NavigationNode), True)
self.assertEqual(nodes[1].get_absolute_url(), page2.get_absolute_url())
def test_language_chooser(self):
# test simple language chooser with default args
lang_settings = copy.deepcopy(get_cms_setting("LANGUAGES"))
lang_settings[1][0]["public"] = False
with self.settings(CMS_LANGUAGES=lang_settings):
context = self.get_context(path=self.get_page(3).get_absolute_url())
tpl = Template("{% load menu_tags %}{% language_chooser %}")
tpl.render(context)
self.assertEqual(len(context["languages"]), 3)
# try a different template and some different args
tpl = Template("{% load menu_tags %}{% language_chooser 'menu/test_language_chooser.html' %}")
tpl.render(context)
self.assertEqual(context["template"], "menu/test_language_chooser.html")
tpl = Template("{% load menu_tags %}{% language_chooser 'short' 'menu/test_language_chooser.html' %}")
tpl.render(context)
self.assertEqual(context["template"], "menu/test_language_chooser.html")
for lang in context["languages"]:
self.assertEqual(*lang)
def test_language_chooser_all_for_staff(self):
"""
Language chooser should show all configured languages
on the current site if the user is staff.
"""
superuser = self.get_superuser()
lang_settings = copy.deepcopy(get_cms_setting("LANGUAGES"))
# DE is marked as public False
lang_settings[1][1]["public"] = False
# FR is marked as public False
lang_settings[1][2]["public"] = False
with self.settings(CMS_LANGUAGES=lang_settings):
with self.login_user_context(superuser):
context = self.get_context(path=self.get_page(3).get_absolute_url())
Template("{% load menu_tags %}{% language_chooser %}").render(context)
self.assertEqual(len(context["languages"]), 5)
self.assertSequenceEqual(
sorted(lang[0] for lang in context["languages"]), ["de", "en", "es-mx", "fr", "pt-br"]
)
def test_language_chooser_public_for_anon(self):
"""
Language chooser should only show public configured languages
on the current site if the user is anon.
"""
# PT-BR is already set to public False
lang_settings = copy.deepcopy(get_cms_setting("LANGUAGES"))
# DE is marked as public False
lang_settings[1][1]["public"] = False
# FR is marked as public False
lang_settings[1][2]["public"] = False
with self.settings(CMS_LANGUAGES=lang_settings):
context = self.get_context(path=self.get_page(3).get_absolute_url())
Template("{% load menu_tags %}{% language_chooser %}").render(context)
self.assertEqual(len(context["languages"]), 2)
self.assertSequenceEqual(sorted(lang[0] for lang in context["languages"]), ["en", "es-mx"])
def test_page_language_url(self):
path = self.get_page(3).get_absolute_url()
context = self.get_context(path=path)
tpl = Template("{%% load menu_tags %%}{%% page_language_url '%s' %%}" % "en")
url = tpl.render(context)
self.assertEqual(url, "%s" % path)
def test_show_menu_below_id(self):
page2 = self.get_page(2)
page2.reverse_id = "hello"
page2.save()
page2 = self.reload(page2)
self.assertEqual(page2.reverse_id, "hello")
page5 = self.get_page(5)
context = self.get_context(path=page5.get_absolute_url())
tpl = Template("{% load menu_tags %}{% show_menu_below_id 'hello' %}")
tpl.render(context)
nodes = context["children"]
self.assertEqual(len(nodes), 1)
page3_url = self.get_page(3).get_absolute_url()
self.assertEqual(nodes[0].get_absolute_url(), page3_url)
page2.in_navigation = False
page2.save()
context = self.get_context(path=page5.get_absolute_url())
tpl = Template("{% load menu_tags %}{% show_menu_below_id 'hello' %}")
tpl.render(context)
nodes = context["children"]
self.assertEqual(len(nodes), 1)
self.assertEqual(nodes[0].get_absolute_url(), page3_url)
def test_home_not_in_menu(self):
page1 = self.get_page(1)
page1.update_translations(in_navigation=False)
page1.save()
page4 = self.get_page(4)
page4.update_translations(in_navigation=False)
page4.save()
context = self.get_context()
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
tpl.render(context)
nodes = context["children"]
self.assertEqual(len(nodes), 1)
self.assertEqual(nodes[0].get_absolute_url(), self.get_page(2).get_absolute_url())
self.assertEqual(nodes[0].children[0].get_absolute_url(), self.get_page(3).get_absolute_url())
page4 = self.get_page(4)
page4.update_translations(in_navigation=True)
page4.save()
menu_pool.clear(Site.objects.get_current().pk)
context = self.get_context()
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
tpl.render(context)
nodes = context["children"]
self.assertEqual(len(nodes), 2)
def test_show_submenu_from_non_menu_page(self):
"""
Here's the structure bit we're interested in:
+ P6 (not in menu)
+ P7
+ P8
When we render P6, there should be a menu entry for P7 and P8 if the
tag parameters are "1 XXX XXX XXX"
"""
page6 = self.get_page(6)
context = self.get_context(page6.get_absolute_url(), page=page6)
tpl = Template("{% load menu_tags %}{% show_menu 1 100 0 1 %}")
tpl.render(context)
nodes = context["children"]
number_of_p6_children = (
page6.get_child_pages().filter(pagecontent_set__language="en", pagecontent_set__in_navigation=True).count()
)
self.assertEqual(len(nodes), number_of_p6_children)
page7 = self.get_page(7)
context = self.get_context(page7.get_absolute_url(), page=page7)
tpl = Template("{% load menu_tags %}{% show_menu 1 100 0 1 %}")
tpl.render(context)
nodes = context["children"]
self.assertEqual(len(nodes), number_of_p6_children)
tpl = Template("{% load menu_tags %}{% show_menu 2 100 0 1 %}")
tpl.render(context)
nodes = context["children"]
number_of_p7_children = (
page7.get_child_pages().filter(pagecontent_set__language="en", pagecontent_set__in_navigation=True).count()
)
self.assertEqual(len(nodes), number_of_p7_children)
def test_show_breadcrumb_invisible(self):
parent = Page.objects.get(pagecontent_set__title="P3")
invisible_page = create_page("invisible", "nav_playground.html", "en", parent=parent, in_navigation=False)
context = self.get_context(
path=invisible_page.get_absolute_url(),
page=invisible_page,
)
tpl = Template("{% load menu_tags %}{% show_breadcrumb %}")
tpl.render(context)
nodes = context["ancestors"]
self.assertEqual(len(nodes), 3)
tpl = Template("{% load menu_tags %}{% show_breadcrumb 0 'menu/breadcrumb.html' 1 %}")
tpl.render(context)
nodes = context["ancestors"]
self.assertEqual(len(nodes), 3)
tpl = Template("{% load menu_tags %}{% show_breadcrumb 0 'menu/breadcrumb.html' 0 %}")
tpl.render(context)
nodes = context["ancestors"]
self.assertEqual(len(nodes), 4)
class MenuTests(BaseMenuTest):
def test_build_nodes_inner_for_worst_case_menu(self):
"""
Tests the worst case scenario
node5
node4
node3
node2
node1
"""
node1 = NavigationNode("Test1", "/test1/", 1, 2)
node2 = NavigationNode("Test2", "/test2/", 2, 3)
node3 = NavigationNode("Test3", "/test3/", 3, 4)
node4 = NavigationNode("Test4", "/test4/", 4, 5)
node5 = NavigationNode("Test5", "/test5/", 5, None)
menu_class_name = "Test"
nodes = [
node1,
node2,
node3,
node4,
node5,
]
len_nodes = len(nodes)
final_list = _build_nodes_inner_for_one_menu(nodes, menu_class_name)
self.assertEqual(len(final_list), len_nodes)
self.assertEqual(node1.parent, node2)
self.assertEqual(node2.parent, node3)
self.assertEqual(node3.parent, node4)
self.assertEqual(node4.parent, node5)
self.assertEqual(node5.parent, None)
self.assertEqual(node1.children, [])
self.assertEqual(node2.children, [node1])
self.assertEqual(node3.children, [node2])
self.assertEqual(node4.children, [node3])
self.assertEqual(node5.children, [node4])
def test_build_nodes_inner_for_circular_menu(self):
"""
TODO:
To properly handle this test we need to have a circular dependency
detection system.
Go nuts implementing it :)
"""
pass
def test_build_nodes_inner_for_broken_menu(self):
"""
Tests a broken menu tree (non-existing parent)
node5
node4
node3
<non-existent>
node2
node1
"""
node1 = NavigationNode("Test1", "/test1/", 1, 2)
node2 = NavigationNode("Test2", "/test2/", 2, 12)
node3 = NavigationNode("Test3", "/test3/", 3, 4)
node4 = NavigationNode("Test4", "/test4/", 4, 5)
node5 = NavigationNode("Test5", "/test5/", 5, None)
menu_class_name = "Test"
nodes = [
node1,
node2,
node3,
node4,
node5,
]
final_list = _build_nodes_inner_for_one_menu(nodes, menu_class_name)
self.assertEqual(len(final_list), 3)
self.assertFalse(node1 in final_list)
self.assertFalse(node2 in final_list)
self.assertEqual(node1.parent, None)
self.assertEqual(node2.parent, None)
self.assertEqual(node3.parent, node4)
self.assertEqual(node4.parent, node5)
self.assertEqual(node5.parent, None)
self.assertEqual(node1.children, [])
self.assertEqual(node2.children, [])
self.assertEqual(node3.children, [])
self.assertEqual(node4.children, [node3])
self.assertEqual(node5.children, [node4])
def test_utils_mark_descendants(self):
tree_nodes, flat_nodes = self._get_nodes()
mark_descendants(tree_nodes)
for node in flat_nodes:
self.assertTrue(node.descendant, node)
def test_utils_find_selected(self):
tree_nodes, flat_nodes = self._get_nodes()
node = flat_nodes[0]
selected = find_selected(tree_nodes)
self.assertEqual(selected, node)
selected = find_selected([])
self.assertEqual(selected, None)
def test_utils_cut_levels(self):
tree_nodes, flat_nodes = self._get_nodes()
self.assertEqual(cut_levels(tree_nodes, 1), [flat_nodes[1]])
def test_empty_menu(self):
context = self.get_context()
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
tpl.render(context)
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
site_2 = Site.objects.create(id=2, name="example-2.com", domain="example-2.com")
de_defaults = {
"site": site_2,
"template": "nav_playground.html",
"language": "de",
}
fr_defaults = {
"site": site_2,
"template": "nav_playground.html",
"language": "fr",
}
create_page("DE-P1", in_navigation=True, **de_defaults)
create_page("DE-P2", in_navigation=True, **de_defaults)
create_page("DE-P3", in_navigation=True, **de_defaults)
create_page("FR-P1", in_navigation=True, **fr_defaults)
create_page("FR-P2", in_navigation=True, **fr_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), 5)
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/")
self.assertEqual(nodes[3].title, "FR-P1")
self.assertEqual(nodes[3].get_absolute_url(), "/fr/fr-p1/")
self.assertEqual(nodes[4].title, "FR-P2")
self.assertEqual(nodes[4].get_absolute_url(), "/fr/fr-p2/")
menu_pool.clear(site_id=2)
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), 5)
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/")
self.assertEqual(nodes[3].title, "FR-P1")
self.assertEqual(nodes[3].get_absolute_url(), "/fr/fr-p1/")
self.assertEqual(nodes[4].title, "FR-P2")
self.assertEqual(nodes[4].get_absolute_url(), "/fr/fr-p2/")
def test_render_menu_with_invalid_language_and_page(self):
"""
This tests an edge-case where the user requests a
language not configure for the current site
while having pages on the current site with unconfigured
translations.
"""
# Refs - https://github.com/divio/django-cms/issues/6179
site_2 = Site.objects.create(id=2, name="example-2.com", domain="example-2.com")
de_defaults = {
"site": site_2,
"template": "nav_playground.html",
"language": "de",
"in_navigation": True,
}
create_page("DE-P1", **de_defaults)
create_page("DE-P2", **de_defaults)
create_page("DE-P3", **de_defaults)
# The nl language is not configured for the current site
# as a result, we have to create the pages manually.
nl_page_1 = Page(site=site_2)
nl_page_1.add_to_tree()
nl_page_1.save()
nl_page_1.pagecontent_set.create(
language="nl",
title="NL-P1",
template="nav_playground.html",
in_navigation=True,
)
nl_page_2 = Page(site=site_2)
nl_page_2.add_to_tree()
nl_page_2.save()
nl_page_2.pagecontent_set.create(
language="nl",
title="NL-P2",
template="nav_playground.html",
in_navigation=True,
)
create_page_content("fr", "FR-P2", nl_page_2, in_navigation=True)
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), 4)
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/")
self.assertEqual(nodes[3].title, "FR-P2")
self.assertEqual(nodes[3].get_absolute_url(), "/fr/fr-p2/")
menu_pool.clear(site_id=2)
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), 4)
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/")
self.assertEqual(nodes[3].title, "FR-P2")
self.assertEqual(nodes[3].get_absolute_url(), "/fr/fr-p2/")
def test_render_menu_with_invalid_language_and_no_fallbacks(self):
"""
The requested language is valid but there's no page
with it and the user has disabled all fallbacks.
The cms should render only nodes for the requested language.
"""
defaults = {
"template": "nav_playground.html",
"language": "de",
}
create_page("DE-P1", in_navigation=True, **defaults)
create_page("DE-P2", in_navigation=True, **defaults)
create_page("DE-P3", 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)
menu_pool.clear(site_id=1)
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)
def test_menu_renderer_language_code_handling(self):
"""
Tests that MenuRenderer correctly handles language code determination:
1. Uses request.LANGUAGE_CODE if available
2. Falls back to site's default language if LANGUAGE_CODE not set
"""
site = Site.objects.get_current()
# Test with LANGUAGE_CODE set in request
request = self.get_request("/")
request.LANGUAGE_CODE = "fr" # Set a specific language code
renderer = menu_pool.get_renderer(request)
self.assertEqual(renderer.request_language, "fr")
# Test fallback when LANGUAGE_CODE not set
request = self.get_request("/")
# Ensure request has no LANGUAGE_CODE
if hasattr(request, "LANGUAGE_CODE"):
delattr(request, "LANGUAGE_CODE")
renderer = menu_pool.get_renderer(request)
# Should fall back to site's default language
expected_language = get_default_language_for_site(site.pk)
self.assertEqual(renderer.request_language, expected_language)
def test_cmsnavigationnode_attr(self):
"""
Test population of the attr object of the CMSNavigationNode from pages
"""
page_defaults = {
"template": "nav_playground.html",
"language": "en",
}
home = create_page("Normal Page", in_navigation=True, reverse_id="normal-page", **page_defaults)
home.set_as_homepage()
create_page("Redirected Page", in_navigation=True, reverse_id="redirected-page", redirect='https://redirect.example.com', **page_defaults)
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"]
expected_normal_attr = {
"is_page": True,
"soft_root": False,
"redirect_url": None,
"auth_required": False,
"reverse_id": "normal-page",
"is_home": True,
"visible_for_authenticated": True,
"visible_for_anonymous": True,
"navigation_extenders": [],
}
expected_redirect_attr = {
"is_page": True,
"soft_root": False,
"redirect_url": "https://redirect.example.com",
"auth_required": False,
"reverse_id": "redirected-page",
"is_home": False,
"visible_for_authenticated": True,
"visible_for_anonymous": True,
"navigation_extenders": [],
}
self.assertEqual(2, len(nodes))
self.assertEqual(expected_normal_attr, nodes[0].attr)
self.assertEqual(expected_redirect_attr, nodes[1].attr)
@override_settings(CMS_PERMISSION=False)
class AdvancedSoftrootTests(SoftrootFixture, CMSTestCase):
"""
Tree in fixture (as taken from issue 662):
top
root
aaa
111
ccc
ddd
222
bbb
333
444
In the fixture, all pages are "in_navigation", "published" and
NOT-"soft_root".
What is a soft root?
If a page is a soft root, it becomes the root page in the menu if
we are currently on or under that page.
If we are above that page, the children of this page are not shown.
"""
def tearDown(self):
Page.objects.all().delete()
menu_pool.clear(all=True)
def get_page(self, name):
return Page.objects.get(urls__slug=name)
def assertTreeQuality(self, a, b, *attrs):
"""
Checks that the node-lists a and b are the same for attrs.
This is recursive over the tree
"""
msg = f"root nodes: {len(a)!r} != {len(b)!r} with {a!r}, {b!r}"
self.assertEqual(len(a), len(b), msg)
for n1, n2 in zip(a, b, strict=False):
for attr in attrs:
a1 = getattr(n1, attr)
a2 = getattr(n2, attr)
msg = f"{attr}: {a1!r} != {a2!r} with {n1!r}, {n2!r} ({attr})"
self.assertEqual(a1, a2, msg)
self.assertTreeQuality(n1.children, n2.children)
def test_top_not_in_nav(self):
"""
top: not in navigation
tag: show_menu 0 100 0 100
context shared: current page is aaa
context 1: root is NOT a softroot
context 2: root IS a softroot
expected result: the two node-trees should be equal
"""
top = self.get_page("top")
top.in_navigation = False
top.save()
aaa = self.get_page("aaa")
# root is NOT a soft root
context = self.get_context(aaa.get_absolute_url())
tpl = Template("{% load menu_tags %}{% show_menu 0 100 0 100 %}")
tpl.render(context)
hard_root = context["children"]
# root IS a soft root
root = self.get_page("root")
root.update_translations(soft_root=True)
aaa = self.get_page("aaa")
context = self.get_context(aaa.get_absolute_url())
tpl = Template("{% load menu_tags %}{% show_menu 0 100 0 100 %}")
tpl.render(context)
soft_root = context["children"]
# assert the two trees are equal in terms of 'level' and 'title'
self.assertTreeQuality(hard_root, soft_root, "level", "title")
def test_menu_tree_without_soft_root(self):
"""
tag: show_menu 0 100 0 100
expected result 1:
0:top
1:root
2:aaa
3:111
4:ccc
5:ddd
3:222
2:bbb
"""
aaa = self.get_page("aaa")
# root is NOT a soft root
context = self.get_context(aaa.get_absolute_url(), page=aaa)
tpl = Template("{% load menu_tags %}{% show_menu 0 100 0 100 %}")
tpl.render(context)
hard_root = context["children"]
mock_tree = [
AttributeObject(
title="top",
level=0,
children=[
AttributeObject(
title="root",
level=1,
children=[
AttributeObject(
title="aaa",
level=2,
children=[
AttributeObject(
title="111",
level=3,
children=[
AttributeObject(
title="ccc",
level=4,
children=[AttributeObject(title="ddd", level=5, children=[])],
)
],
),
AttributeObject(title="222", level=3, children=[]),
],
),
AttributeObject(title="bbb", level=2, children=[]),
],
)
],
)
]
self.assertTreeQuality(hard_root, mock_tree)
def test_menu_tree_with_soft_root(self):
"""
tag: show_menu 0 100 0 100
expected result 2:
0:root
1:aaa
2:111
3:ccc
4:ddd
2:222
1:bbb
"""
# root IS a soft root
root = self.get_page("root")
root.update_translations(soft_root=True)
aaa = self.get_page("aaa")
context = self.get_context(aaa.get_absolute_url(), page=aaa)
tpl = Template("{% load menu_tags %}{% show_menu 0 100 0 100 %}")
tpl.render(context)
soft_root = context["children"]
mock_tree = [
AttributeObject(
title="root",
level=0,
children=[
AttributeObject(
title="aaa",
level=1,
children=[
AttributeObject(
title="111",
level=2,
children=[
AttributeObject(
title="ccc",
level=3,
children=[AttributeObject(title="ddd", level=4, children=[])],
)
],
),
AttributeObject(title="222", level=2, children=[]),
],
),
AttributeObject(title="bbb", level=1, children=[]),
],
)
]
self.assertTreeQuality(soft_root, mock_tree, "title", "level")
class ShowSubMenuCheck(SubMenusFixture, BaseMenuTest):
"""
Tree from fixture:
+ P1
| + P2
| + P3
+ P4
| + P5
+ P6
+ P7 (not in menu)
+ P8
"""
def test_show_submenu(self):
page = self.get_page(6)
subpage = self.get_page(8)
context = self.get_context(page.get_absolute_url(), page=page)
# test standard show_menu
tpl = Template("{% load menu_tags %}{% show_sub_menu %}")
tpl.render(context)
nodes = context["children"]
self.assertEqual(len(nodes), 1)
self.assertEqual(nodes[0].id, subpage.pk)
def test_show_submenu_num_queries(self):
page = self.get_page(6)
subpage = self.get_page(8)
context = self.get_context(page.get_absolute_url(), page=page)
# test standard show_menu
with self.assertNumQueries(5):
"""
The queries should be:
get all page contents
get all page permissions
get all page urls
get the menu cache key
set the menu cache key
"""
tpl = Template("{% load menu_tags %}{% show_sub_menu %}")
tpl.render(context)
nodes = context["children"]
self.assertEqual(len(nodes), 1)
self.assertEqual(nodes[0].id, subpage.pk)
class ShowMenuBelowIdTests(BaseMenuTest):
"""
Test for issue 521
Build the following tree:
A
|-B
|-C
\\-D (not in nav)
"""
def test_not_in_navigation(self):
a = create_page("A", "nav_playground.html", "en", in_navigation=True, reverse_id="a")
b = create_page("B", "nav_playground.html", "en", parent=a, in_navigation=True)
c = create_page("C", "nav_playground.html", "en", parent=b, in_navigation=True)
create_page("D", "nav_playground.html", "en", parent=self.reload(b), in_navigation=False)
context = self.get_context(a.get_absolute_url())
tpl = Template("{% load menu_tags %}{% show_menu_below_id 'a' 0 100 100 100 %}")
tpl.render(context)
nodes = context["children"]
self.assertEqual(len(nodes), 1, nodes)
node = nodes[0]
self.assertEqual(node.id, b.pk)
children = node.children
self.assertEqual(len(children), 1, repr(children))
child = children[0]
self.assertEqual(child.id, c.pk)
def test_menu_beyond_soft_root(self):
"""
Test for issue 4107
Build the following tree:
A
|-B (soft_root)
|-C
"""
stdkwargs = {
"template": "nav_playground.html",
"language": "en",
"in_navigation": True,
}
a = create_page("A", reverse_id="a", **stdkwargs)
b = create_page("B", parent=a, soft_root=True, **stdkwargs)
c = create_page("C", parent=b, **stdkwargs)
context = self.get_context(a.get_absolute_url(), page=a)
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
tpl.render(context)
nodes = context["children"]
# check whole menu
self.assertEqual(len(nodes), 1)
a_node = nodes[0]
self.assertEqual(a_node.id, a.pk) # On A, show from A
self.assertEqual(len(a_node.children), 1)
b_node = a_node.children[0]
self.assertEqual(b_node.id, b.pk)
self.assertEqual(len(b_node.children), 1)
c_node = b_node.children[0]
self.assertEqual(c_node.id, c.pk)
self.assertEqual(len(c_node.children), 0)
context = self.get_context(b.get_absolute_url(), page=b)
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
tpl.render(context)
nodes = context["children"]
# check whole menu
self.assertEqual(len(nodes), 1)
b_node = nodes[0]
self.assertEqual(b_node.id, b.pk) # On B, show from B
self.assertEqual(len(b_node.children), 1)
c_node = b_node.children[0]
self.assertEqual(c_node.id, c.pk)
self.assertEqual(len(c_node.children), 0)
context = self.get_context(c.get_absolute_url(), page=c)
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
tpl.render(context)
nodes = context["children"]
# check whole menu
self.assertEqual(len(nodes), 1)
b_node = nodes[0]
self.assertEqual(b_node.id, b.pk) # On C, show from B
self.assertEqual(len(b_node.children), 1)
c_node = b_node.children[0]
self.assertEqual(c_node.id, c.pk)
self.assertEqual(len(c_node.children), 0)
context = self.get_context(a.get_absolute_url(), page=a)
tpl = Template("{% load menu_tags %}{% show_menu_below_id 'a' 0 100 100 100 %}")
tpl.render(context)
nodes = context["children"]
# check whole menu
self.assertEqual(len(nodes), 1)
b_node = nodes[0]
self.assertEqual(b_node.id, b.pk) # On A, show from B (since below A)
self.assertEqual(len(b_node.children), 1)
c_node = b_node.children[0]
self.assertEqual(c_node.id, c.pk)
self.assertEqual(len(c_node.children), 0)
context = self.get_context(b.get_absolute_url(), page=b)
tpl = Template("{% load menu_tags %}{% show_menu_below_id 'a' 0 100 100 100 %}")
tpl.render(context)
nodes = context["children"]
# check whole menu
self.assertEqual(len(nodes), 1)
b_node = nodes[0]
self.assertEqual(b_node.id, b.pk) # On B, show from B (since below A)
self.assertEqual(len(b_node.children), 1)
c_node = b_node.children[0]
self.assertEqual(c_node.id, c.pk)
self.assertEqual(len(c_node.children), 0)
context = self.get_context(c.get_absolute_url(), page=c)
tpl = Template("{% load menu_tags %}{% show_menu_below_id 'a' 0 100 100 100 %}")
tpl.render(context)
nodes = context["children"]
# check whole menu
self.assertEqual(len(nodes), 1)
b_node = nodes[0]
self.assertEqual(b_node.id, b.pk) # On C, show from B (since below A)
self.assertEqual(len(b_node.children), 1)
c_node = b_node.children[0]
self.assertEqual(c_node.id, c.pk)
self.assertEqual(len(c_node.children), 0)
def test_not_in_navigation_num_queries(self):
"""
Test for issue 521
Build the following tree:
A
|-B
|-C
\\-D (not in nav)
"""
a = create_page("A", "nav_playground.html", "en", in_navigation=True, reverse_id="a")
b = create_page("B", "nav_playground.html", "en", parent=a, in_navigation=True)
c = create_page("C", "nav_playground.html", "en", parent=b, in_navigation=True)
create_page("D", "nav_playground.html", "en", parent=self.reload(b), in_navigation=False)
with LanguageOverride("en"):
context = self.get_context(a.get_absolute_url())
with self.assertNumQueries(5):
"""
The queries should be:
get all page urls
get all page contents
get all page permissions
get the menu cache key
set the menu cache key
"""
# Actually seems to run:
tpl = Template("{% load menu_tags %}{% show_menu_below_id 'a' 0 100 100 100 %}")
tpl.render(context)
nodes = context["children"]
self.assertEqual(len(nodes), 1, nodes)
node = nodes[0]
self.assertEqual(node.id, b.pk)
children = node.children
self.assertEqual(len(children), 1, repr(children))
child = children[0]
self.assertEqual(child.id, c.pk)
def test_menu_in_soft_root(self):
"""
Test for issue 3504
Build the following tree:
A
|-B
C (soft_root)
"""
a = create_page("A", "nav_playground.html", "en", in_navigation=True, reverse_id="a")
b = create_page("B", "nav_playground.html", "en", parent=a, in_navigation=True)
c = create_page("C", "nav_playground.html", "en", in_navigation=True, soft_root=True)
context = self.get_context(a.get_absolute_url())
tpl = Template("{% load menu_tags %}{% show_menu_below_id 'a' %}")
tpl.render(context)
nodes = context["children"]
self.assertEqual(len(nodes), 1)
node = nodes[0]
self.assertEqual(node.id, b.pk)
context = self.get_context(c.get_absolute_url())
tpl = Template("{% load menu_tags %}{% show_menu_below_id 'a' %}")
tpl.render(context)
nodes = context["children"]
self.assertEqual(len(nodes), 1)
node = nodes[0]
self.assertEqual(node.id, b.pk)
@override_settings(
CMS_PERMISSION=True,
CMS_PUBLIC_FOR="staff",
)
class ViewPermissionMenuTests(CMSTestCase):
def setUp(self):
self.page = create_page("page", "nav_playground.html", "en")
self.pages = [self.page]
self.user = self.get_standard_user()
self.site = Site.objects.get_current()
def get_request(self, user=None):
attrs = {
"user": user or AnonymousUser(),
"REQUEST": {},
"POST": {},
"GET": {},
"session": {},
}
return type("Request", (object,), attrs)
def test_public_for_all_staff(self):
request = self.get_request(self.user)
request.user.is_staff = True
all = [page.get_content_obj() for page in self.pages]
with self.assertNumQueries(4):
"""
The queries are:
User permissions
Content type
GlobalPagePermission query
PagePermission count query
"""
visible = get_visible_page_contents(request, all, self.site)
self.assertEqual(visible, all)
@override_settings(CMS_PUBLIC_FOR="all")
def test_public_for_all(self):
request = self.get_request(self.user)
all = [page.get_content_obj() for page in self.pages]
with self.assertNumQueries(4):
"""
The queries are:
User permissions
Content type
GlobalPagePermission query
PagePermission query for affected pages
"""
visible = get_visible_page_contents(request, all, self.site)
self.assertEqual(visible, all)
@override_settings(CMS_PUBLIC_FOR="all")
def test_unauthed(self):
request = self.get_request()
all = [page.get_content_obj() for page in self.pages]
with self.assertNumQueries(1):
"""
The query is:
PagePermission query for affected pages
"""
visible = get_visible_page_contents(request, all, self.site)
self.assertEqual(visible, all)
def test_authed_basic_perm(self):
self.user.user_permissions.add(Permission.objects.get(codename="view_page"))
request = self.get_request(self.user)
all = [page.get_content_obj() for page in self.pages]
with self.assertNumQueries(2):
"""
The queries are:
User permissions
Content type
"""
visible = get_visible_page_contents(request, all, self.site)
self.assertEqual(visible, all)
def test_authed_no_access(self):
request = self.get_request(self.user)
all = [page.get_content_obj() for page in self.pages]
with self.assertNumQueries(4):
"""
The queries are:
User permission
Group permission
GlobalpagePermission query for user
PagePermission query for user
"""
visible = get_visible_page_contents(request, all, self.site)
self.assertEqual(visible, [])
def test_unauthed_no_access(self):
request = self.get_request()
with self.assertNumQueries(0):
nodes = get_visible_page_contents(
request, (page.get_page_content_obj_attribute() for page in self.pages), self.site
)
self.assertEqual(nodes, [])
def test_page_permissions(self):
request = self.get_request(self.user)
PagePermission.objects.create(can_view=True, user=self.user, page=self.page)
all = [page.get_content_obj() for page in self.pages]
with self.assertNumQueries(4):
"""
The queries are:
PagePermission query for affected pages
User permissions
Content type
GlobalpagePermission query for user
"""
visible = get_visible_page_contents(request, all, self.site)
self.assertEqual(visible, all)
def test_page_permissions_view_groups(self):
group = Group.objects.create(name="testgroup")
self.user.groups.add(group)
request = self.get_request(self.user)
PagePermission.objects.create(can_view=True, group=group, page=self.page)
all = [page.get_content_obj() for page in self.pages]
with self.assertNumQueries(5):
"""
The queries are:
PagePermission query for affected pages
User permissions
Content type
GlobalpagePermission query for user
Group query via PagePermission
"""
visible = get_visible_page_contents(request, all, self.site)
self.assertEqual(visible, all)
def test_global_permission(self):
GlobalPagePermission.objects.create(can_view=True, user=self.user)
request = self.get_request(self.user)
group = Group.objects.create(name="testgroup")
PagePermission.objects.create(can_view=True, group=group, page=self.page)
all = [page.get_content_obj() for page in self.pages]
with self.assertNumQueries(3):
"""
The queries are:
User permissions
Content type
GlobalpagePermission query for user
"""
visible = get_visible_page_contents(request, all, self.site)
self.assertEqual(visible, all)
@override_settings(
CMS_PERMISSION=True,
CMS_PUBLIC_FOR="all",
)
class PublicViewPermissionMenuTests(CMSTestCase):
def setUp(self):
"""
Create this published hierarchy:
A
B1 B2
C1 C2 C3 C4
"""
template = "nav_playground.html"
kw = dict(in_navigation=True)
a = create_page("a", template, "en", **kw)
b1 = create_page("b1", template, "en", parent=a, **kw)
b2 = create_page("b2", template, "en", parent=a, **kw)
c1 = create_page("c1", template, "en", parent=b1, **kw)
c2 = create_page("c2", template, "en", parent=b1, **kw)
c3 = create_page("c3", template, "en", parent=b2, **kw)
c4 = create_page("c4", template, "en", parent=b2, **kw)
self.pages = [a, b1, c1, c2, b2, c3, c4] # tree order
self.site = Site.objects.get_current()
self.user = self._create_user("standard", is_staff=False, is_superuser=False)
self.other = self._create_user("other", is_staff=False, is_superuser=False)
PagePermission.objects.create(page=b1, user=self.user, can_view=True, grant_on=ACCESS_PAGE_AND_DESCENDANTS)
PagePermission.objects.create(page=b2, user=self.other, can_view=True, grant_on=ACCESS_PAGE_AND_DESCENDANTS)
attrs = {
"user": self.user,
"REQUEST": {},
"POST": {},
"GET": {},
"session": {},
}
self.request = type("Request", (object,), attrs)
def test_list_access(self):
page_contents = [page.get_content_obj() for page in self.pages]
page_contents = get_visible_page_contents(self.request, page_contents, self.site)
titles = [page_content.title for page_content in page_contents]
self.assertSequenceEqual(sorted(titles), ["a", "b1", "c1", "c2"])
def test_qs_access(self):
page_contents = PageContent.objects.all()
page_contents = get_visible_page_contents(self.request, page_contents, self.site)
titles = [page_content.title for page_content in page_contents]
self.assertSequenceEqual(sorted(titles), ["a", "b1", "c1", "c2"])
@override_settings(
CMS_PERMISSION=True,
CMS_PUBLIC_FOR="all",
)
class ViewPermissionMenuQueryTests(CMSTestCase):
"""
Regression tests: building the menu must not run one query per page.
``CMSMenu.get_nodes`` restricts the loaded fields with ``only()``. Every
field accessed by the view-restriction check in
``get_visible_page_contents`` (such as ``page.path``) has to be part of
that ``only()`` call, otherwise each page triggers an extra query to
load the deferred field.
"""
template = "nav_playground.html"
def _get_menu_query_count(self):
request = self.get_request("/")
renderer = menu_pool.get_renderer(request)
menu = CMSMenu(renderer)
with CaptureQueriesContext(connection) as queries:
menu.get_nodes(request)
return len(queries.captured_queries)
def test_number_of_queries_does_not_scale_with_page_count(self):
create_page("home", self.template, "en", in_navigation=True)
restricted = create_page("restricted", self.template, "en", in_navigation=True)
PagePermission.objects.create(
page=restricted,
user=self.get_standard_user(),
can_view=True,
grant_on=ACCESS_PAGE_AND_DESCENDANTS,
)
for index in range(3):
create_page(f"page-{index}", self.template, "en", in_navigation=True)
self._get_menu_query_count() # warm up caches (site, content types, ...)
query_count = self._get_menu_query_count()
for index in range(5):
create_page(f"extra-{index}", self.template, "en", in_navigation=True)
self.assertEqual(self._get_menu_query_count(), query_count)
@override_settings(CMS_PERMISSION=False)
class SoftrootTests(CMSTestCase):
"""
Ask evildmp/superdmp if you don't understand softroots!
Softroot description from the docs:
A soft root is a page that acts as the root for a menu navigation tree.
Typically, this will be a page that is the root of a significant new
section on your site.
When the soft root feature is enabled, the navigation menu for any page
will start at the nearest soft root, rather than at the real root of
the site’s page hierarchy.
This feature is useful when your site has deep page hierarchies (and
therefore multiple levels in its navigation trees). In such a case, you
usually don’t want to present site visitors with deep menus of nested
items.
For example, you’re on the page “Introduction to Bleeding”, so the menu
might look like this:
School of Medicine
Medical Education
Departments
Department of Lorem Ipsum
Department of Donec Imperdiet
Department of Cras Eros
Department of Mediaeval Surgery
Theory
Cures
Bleeding
Introduction to Bleeding <this is the current page>
Bleeding - the scientific evidence
Cleaning up the mess
Cupping
Leaches
Maggots
Techniques
Instruments
Department of Curabitur a Purus
Department of Sed Accumsan
Department of Etiam
Research
Administration
Contact us
Impressum
which is frankly overwhelming.
By making “Department of Mediaeval Surgery” a soft root, the menu
becomes much more manageable:
Department of Mediaeval Surgery
Theory
Cures
Bleeding
Introduction to Bleeding <current page>
Bleeding - the scientific evidence
Cleaning up the mess
Cupping
Leaches
Maggots
Techniques
Instruments
"""
def test_basic_home(self):
"""
Given the tree:
|- Home
| |- Projects (SOFTROOT)
| | |- django CMS
| | |- django Shop
| |- People
Expected menu when on "Home" (0 100 100 100):
|- Home
| |- Projects (SOFTROOT)
| | |- django CMS
| | |- django Shop
| |- People
"""
stdkwargs = {
"template": "nav_playground.html",
"language": "en",
"in_navigation": True,
}
home = create_page("Home", **stdkwargs)
projects = create_page("Projects", parent=home, soft_root=True, **stdkwargs)
djangocms = create_page("django CMS", parent=projects, **stdkwargs)
djangoshop = create_page("django Shop", parent=projects, **stdkwargs)
people = create_page("People", parent=home, **stdkwargs)
# On Home
context = self.get_context(home.get_absolute_url())
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
tpl.render(context)
nodes = context["children"]
# check everything
self.assertEqual(len(nodes), 1)
homenode = nodes[0]
self.assertEqual(homenode.id, home.pk)
self.assertEqual(len(homenode.children), 2)
projectsnode, peoplenode = homenode.children
self.assertEqual(projectsnode.id, projects.pk)
self.assertEqual(peoplenode.id, people.pk)
self.assertEqual(len(projectsnode.children), 2)
cmsnode, shopnode = projectsnode.children
self.assertEqual(cmsnode.id, djangocms.pk)
self.assertEqual(shopnode.id, djangoshop.pk)
self.assertEqual(len(cmsnode.children), 0)
self.assertEqual(len(shopnode.children), 0)
self.assertEqual(len(peoplenode.children), 0)
def test_basic_projects(self):
"""
Given the tree:
|- Home
| |- Projects (SOFTROOT)
| | |- django CMS
| | |- django Shop
| |- People
Expected menu when on "Projects" (0 100 100 100):
|- Projects (SOFTROOT)
| |- django CMS
| |- django Shop
"""
stdkwargs = {
"template": "nav_playground.html",
"language": "en",
"in_navigation": True,
}
home = create_page("Home", **stdkwargs)
projects = create_page("Projects", parent=home, soft_root=True, **stdkwargs)
djangocms = create_page("django CMS", parent=projects, **stdkwargs)
djangoshop = create_page("django Shop", parent=projects, **stdkwargs)
create_page("People", parent=home, **stdkwargs)
# On Projects
context = self.get_context(projects.get_absolute_url(), page=projects)
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
tpl.render(context)
nodes = context["children"]
# check everything
self.assertEqual(len(nodes), 1)
projectsnode = nodes[0]
self.assertEqual(projectsnode.id, projects.pk)
self.assertEqual(len(projectsnode.children), 2)
cmsnode, shopnode = projectsnode.children
self.assertEqual(cmsnode.id, djangocms.pk)
self.assertEqual(shopnode.id, djangoshop.pk)
self.assertEqual(len(cmsnode.children), 0)
self.assertEqual(len(shopnode.children), 0)
def test_basic_djangocms(self):
"""
Given the tree:
|- Home
| |- Projects (SOFTROOT)
| | |- django CMS
| | |- django Shop
| |- People
Expected menu when on "django CMS" (0 100 100 100):
|- Projects (SOFTROOT)
| |- django CMS
| |- django Shop
"""
stdkwargs = {
"template": "nav_playground.html",
"language": "en",
"in_navigation": True,
}
home = create_page("Home", **stdkwargs)
projects = create_page("Projects", parent=home, soft_root=True, **stdkwargs)
djangocms = create_page("django CMS", parent=projects, **stdkwargs)
djangoshop = create_page("django Shop", parent=projects, **stdkwargs)
create_page("People", parent=home, **stdkwargs)
# On django CMS
context = self.get_context(djangocms.get_absolute_url(), page=djangocms)
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
tpl.render(context)
nodes = context["children"]
# check everything
self.assertEqual(len(nodes), 1)
projectsnode = nodes[0]
self.assertEqual(projectsnode.id, projects.pk)
self.assertEqual(len(projectsnode.children), 2)
cmsnode, shopnode = projectsnode.children
self.assertEqual(cmsnode.id, djangocms.pk)
self.assertEqual(shopnode.id, djangoshop.pk)
self.assertEqual(len(cmsnode.children), 0)
self.assertEqual(len(shopnode.children), 0)
def test_basic_people(self):
"""
Given the tree:
|- Home
| |- Projects (SOFTROOT)
| | |- django CMS
| | |- django Shop
| |- People
Expected menu when on "People" (0 100 100 100):
|- Home
| |- Projects (SOFTROOT)
| | |- django CMS
| | |- django Shop
| |- People
"""
stdkwargs = {
"template": "nav_playground.html",
"language": "en",
"in_navigation": True,
}
home = create_page("Home", **stdkwargs)
projects = create_page("Projects", parent=home, soft_root=True, **stdkwargs)
djangocms = create_page("django CMS", parent=projects, **stdkwargs)
djangoshop = create_page("django Shop", parent=projects, **stdkwargs)
people = create_page("People", parent=home, **stdkwargs)
# On People
context = self.get_context(home.get_absolute_url())
tpl = Template("{% load menu_tags %}{% show_menu 0 100 100 100 %}")
tpl.render(context)
nodes = context["children"]
# check everything
self.assertEqual(len(nodes), 1)
homenode = nodes[0]
self.assertEqual(homenode.id, home.pk)
self.assertEqual(len(homenode.children), 2)
projectsnode, peoplenode = homenode.children
self.assertEqual(projectsnode.id, projects.pk)
self.assertEqual(peoplenode.id, people.pk)
self.assertEqual(len(projectsnode.children), 2)
cmsnode, shopnode = projectsnode.children
self.assertEqual(cmsnode.id, djangocms.pk)
self.assertEqual(shopnode.id, djangoshop.pk)
self.assertEqual(len(cmsnode.children), 0)
self.assertEqual(len(shopnode.children), 0)
self.assertEqual(len(peoplenode.children), 0)