forked from ungleich-public/dynamicweb
2216 lines
83 KiB
Python
2216 lines
83 KiB
Python
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)
|