Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

349 lines (310 sloc) 13.802 kb
# -*- coding: utf-8 -*-
from collections import defaultdict
from cms.apphook_pool import apphook_pool
from cms.models.moderatormodels import (ACCESS_DESCENDANTS,
from cms.models.permissionmodels import PagePermission, GlobalPagePermission
from cms.models.titlemodels import Title
from cms.utils import get_language_from_request
from cms.utils.i18n import get_fallback_languages
from cms.utils.moderator import get_page_queryset, get_title_queryset
from cms.utils.plugins import current_site
from django.conf import settings
from django.contrib.sites.models import Site
from django.db.models.query_utils import Q
from menus.base import Menu, NavigationNode, Modifier
from menus.menu_pool import menu_pool
def get_visible_pages(request, pages, site=None):
# This code is basically a many-pages-at-once version of
# Page.has_view_permission, check there to see wtf is going on here.
if request.user.is_staff and settings.CMS_PUBLIC_FOR in ('staff', 'all'):
return [ for page in pages]
page_ids = []
pages_perms_q = Q()
for page in pages:
page_q = Q(page__tree_id=page.tree_id) & (
| (Q(page__level__lt=page.level) & (Q(grant_on=ACCESS_DESCENDANTS) | Q(grant_on=ACCESS_PAGE_AND_DESCENDANTS)))
| (Q(page__level=page.level - 1) & (Q(grant_on=ACCESS_CHILDREN) | Q(grant_on=ACCESS_PAGE_AND_CHILDREN)))
pages_perms_q |= page_q
pages_perms_q &= Q(can_view=True)
page_permissions = PagePermission.objects.filter(pages_perms_q).select_related('page', 'group__users')
restricted_pages = defaultdict(list)
for perm in page_permissions:
if site is None:
site = current_site(request)
if request.user.is_authenticated():
#return self.filter(Q(user=user) | Q(group__user=user))
global_page_perm_q = Q(
Q(user=request.user) | Q(group__user=request.user)
) & Q(can_view=True) & Q(Q(sites__in=[]) | Q(sites__isnull=True))
global_view_perms = GlobalPagePermission.objects.filter(global_page_perm_q).exists()
def has_global_perm():
if has_global_perm.cache < 0:
has_global_perm.cache = 1 if request.user.has_perm('cms.view_page') else 0
return bool(has_global_perm.cache)
has_global_perm.cache = -1
def has_permission(page):
PagePermission tests
for perm in restricted_pages[]:
if perm.user_id ==
return True
for perm in restricted_pages[]:
if not perm.group_id:
if in'id', flat=True):
return True
return False
for page in pages:
is_restricted = in restricted_pages
if request.user.is_authenticated():
# a global permission was given to the request's user
if global_view_perms:
# authenticated user, no restriction and public for all
elif settings.CMS_PUBLIC_FOR == 'all':
elif has_permission(page):
elif has_global_perm():
elif not is_restricted and settings.CMS_PUBLIC_FOR == 'all':
# anonymous user, no restriction saved in database
return page_ids
def page_to_node(page, home, cut):
Transform a CMS page into a navigation node.
page: the page you wish to transform
home: a reference to the "home" page (the page with tree_id=1)
cut: Should we cut page from it's parent pages? This means the node will not
have a parent anymore.
# Theses are simple to port over, since they are not calculated.
# Other attributes will be added conditionnally later.
attr = {'soft_root':page.soft_root,
parent_id = page.parent_id
# Should we cut the Node from its parents?
if home and page.parent_id == and cut:
parent_id = None
# possible fix for a possible problem
#if parent_id and not page.parent.get_calculated_status():
# parent_id = None # ????
if page.limit_visibility_in_menu == None:
attr['visible_for_authenticated'] = True
attr['visible_for_anonymous'] = True
attr['visible_for_authenticated'] = page.limit_visibility_in_menu == 1
attr['visible_for_anonymous'] = page.limit_visibility_in_menu == 2
if ==
attr['is_home'] = True
# Extenders can be either navigation extenders or from apphooks.
extenders = []
if page.navigation_extenders:
# Is this page an apphook? If so, we need to handle the apphooks's nodes
app_name = page.get_application_urls(fallback=False)
except Title.DoesNotExist:
app_name = None
if app_name: # it means it is an apphook
app = apphook_pool.get_apphook(app_name)
for menu in app.menus:
if extenders:
attr['navigation_extenders'] = extenders
# Do we have a redirectURL?
attr['redirect_url'] = page.get_redirect() # save redirect URL if any
# Now finally, build the NavigationNode object and return it.
ret_node = NavigationNode(
return ret_node
class CMSMenu(Menu):
def get_nodes(self, request):
page_queryset = get_page_queryset(request)
site = Site.objects.get_current()
lang = get_language_from_request(request)
filters = {
filters['title_set__language'] = lang
pages = page_queryset.published().filter(**filters).order_by("tree_id", "lft")
ids = []
nodes = []
first = True
home_cut = False
home_children = []
home = None
actual_pages = []
# cache view perms
visible_pages = get_visible_pages(request, pages, site)
for page in pages:
# Pages are ordered by tree_id, therefore the first page is the root
# of the page tree (a.k.a "home")
if not in visible_pages:
# Don't include pages the user doesn't have access to
if not home:
home = page
page.home_pk_cache =
if first and !=
home_cut = True
elif not settings.CMS_PUBLIC_FOR == 'all':
if (page.parent_id == or page.parent_id in home_children) and home_cut:
if ( == and home.in_navigation) or !=
first = False
titles = list(get_title_queryset(request).filter(page__in=ids, language=lang))
for page in actual_pages: # add the title and slugs and some meta data
for title in titles:
if title.page_id ==
if not hasattr(page, "title_cache"):
page.title_cache = {}
page.title_cache[title.language] = title
nodes.append(page_to_node(page, home, home_cut))
if ids: # get fallback languages
fallbacks = get_fallback_languages(lang)
for l in fallbacks:
titles = list(get_title_queryset(request).filter(page__in=ids, language=l))
for title in titles:
for page in actual_pages: # add the title and slugs and some meta data
if title.page_id ==
if not hasattr(page, "title_cache"):
page.title_cache = {}
page.title_cache[title.language] = title
nodes.append(page_to_node(page, home, home_cut))
if not ids:
return nodes
class NavExtender(Modifier):
def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
if post_cut:
return nodes
exts = []
# rearrange the parent relations
home = None
for node in nodes:
if node.attr.get("is_home", False):
home = node
extenders = node.attr.get("navigation_extenders", None)
if extenders:
for ext in extenders:
if not ext in exts:
for n in nodes:
if n.namespace == ext and not n.parent_id:# if home has nav extenders but home is not visible
if node.attr.get("is_home", False) and not node.visible:
n.parent_id = None
n.parent_namespace = None
n.parent = None
n.parent_id =
n.parent_namespace = node.namespace
n.parent = node
removed = []
# find all not assigned nodes
for menu in menu_pool.menus.items():
if hasattr(menu[1], 'cms_enabled') and menu[1].cms_enabled and not menu[0] in exts:
for node in nodes:
if node.namespace == menu[0]:
if breadcrumb:
# if breadcrumb and home not in navigation add node
if breadcrumb and home and not home.visible:
home.visible = True
if request.path == home.get_absolute_url():
home.selected = True
home.selected = False
# remove all nodes that are nav_extenders and not assigned
for node in removed:
return nodes
class SoftRootCutter(Modifier):
If anyone understands this, PLEASE write a meaningful description here!
def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
# only apply this modifier if we're pre-cut (since what we do is cut)
if post_cut or not settings.CMS_SOFTROOT:
return nodes
selected = None
root_nodes = []
# find the selected node as well as all the root nodes
for node in nodes:
if node.selected:
selected = node
if not node.parent:
# if we found a selected ...
if selected:
# and the selected is a softroot
if selected.attr.get("soft_root", False):
# get it's descendants
nodes = selected.get_descendants()
# remove the link to parent
selected.parent = None
# make the selected page the root in the menu
nodes = [selected] + nodes
# if it's not a soft root, walk ancestors (upwards!)
nodes = self.find_ancestors_and_remove_children(selected, nodes)
# remove child-softroots from descendants (downwards!)
nodes = self.find_and_remove_children(selected, nodes)
# for all nodes in root, remove child-sofroots (downwards!)
for node in root_nodes:
self.find_and_remove_children(node, nodes)
return nodes
def find_and_remove_children(self, node, nodes):
for n in node.children:
if n.attr.get("soft_root", False):
self.remove_children(n, nodes)
return nodes
def remove_children(self, node, nodes):
for n in node.children:
self.remove_children(n, nodes)
node.children = []
def find_ancestors_and_remove_children(self, node, nodes):
Check ancestors of node for soft roots
if node.parent:
if node.parent.attr.get("soft_root", False):
nodes = node.parent.get_descendants()
node.parent.parent = None
nodes = [node.parent] + nodes
nodes = self.find_ancestors_and_remove_children(node.parent, nodes)
for n in nodes:
if n != node and not n.parent:
self.find_and_remove_children(n, nodes)
for n in node.children:
if n != node:
self.find_and_remove_children(n, nodes)
return nodes
Jump to Line
Something went wrong with that request. Please try again.