cms.menu: 253 total statements, 0.0% covered

Generated: Wed 2013-03-13 10:33 CET

Source file: /media/Envs/Envs/filer-gallery/lib/python2.7/site-packages/cms/menu.py

Stats: 0 executed, 238 missed, 15 excluded, 215 ignored

  1. # -*- coding: utf-8 -*-
  2. from collections import defaultdict
  3. from cms.apphook_pool import apphook_pool
  4. from cms.models.moderatormodels import (ACCESS_DESCENDANTS,
  5. ACCESS_PAGE_AND_DESCENDANTS, ACCESS_CHILDREN, ACCESS_PAGE_AND_CHILDREN, ACCESS_PAGE)
  6. from cms.models.permissionmodels import PagePermission, GlobalPagePermission
  7. from cms.models.titlemodels import Title
  8. from cms.utils import get_language_from_request
  9. from cms.utils.i18n import get_fallback_languages
  10. from cms.utils.moderator import get_page_queryset, get_title_queryset
  11. from cms.utils.plugins import current_site
  12. from menus.base import Menu, NavigationNode, Modifier
  13. from menus.menu_pool import menu_pool
  14. from django.conf import settings
  15. from django.contrib.sites.models import Site
  16. from django.db.models.query_utils import Q
  17. from django.contrib.auth.models import Permission
  18. def get_visible_pages(request, pages, site=None):
  19. """
  20. This code is basically a many-pages-at-once version of
  21. Page.has_view_permission.
  22. pages contains all published pages
  23. check if there is ANY restriction
  24. that needs a permission page visibility calculation
  25. """
  26. is_setting_public_all = settings.CMS_PUBLIC_FOR == 'all'
  27. is_setting_public_staff = settings.CMS_PUBLIC_FOR == 'staff'
  28. is_auth_user = request.user.is_authenticated()
  29. visible_page_ids = []
  30. restricted_pages = defaultdict(list)
  31. pages_perms_q = Q()
  32. for page in pages:
  33. # taken from for_page as multiple at once version
  34. page_q = Q(page__tree_id=page.tree_id) & (
  35. Q(page=page)
  36. | (Q(page__level__lt=page.level) & (Q(grant_on=ACCESS_DESCENDANTS) | Q(grant_on=ACCESS_PAGE_AND_DESCENDANTS)))
  37. | (Q(page__level=page.level - 1) & (Q(grant_on=ACCESS_CHILDREN) | Q(grant_on=ACCESS_PAGE_AND_CHILDREN)))
  38. )
  39. pages_perms_q |= page_q
  40. pages_perms_q &= Q(can_view=True)
  41. page_permissions = PagePermission.objects.filter(pages_perms_q).select_related('page', 'group__users')
  42. for perm in page_permissions:
  43. # collect the pages that are affected by permissions
  44. if perm is not None and perm not in restricted_pages[perm.page.pk]:
  45. # affective restricted pages gathering
  46. # using mptt functions
  47. # add the page with the perm itself
  48. if perm.grant_on in [ACCESS_PAGE, ACCESS_PAGE_AND_CHILDREN ,ACCESS_PAGE_AND_DESCENDANTS]:
  49. restricted_pages[perm.page.pk].append(perm)
  50. # add children
  51. if perm.grant_on in [ACCESS_CHILDREN, ACCESS_PAGE_AND_CHILDREN]:
  52. child_ids = perm.page.get_children().values_list('id', flat=True)
  53. for id in child_ids:
  54. restricted_pages[id].append(perm)
  55. # add descendants
  56. elif perm.grant_on in [ACCESS_DESCENDANTS, ACCESS_PAGE_AND_DESCENDANTS]:
  57. child_ids = perm.page.get_descendants().values_list('id', flat=True)
  58. for id in child_ids:
  59. restricted_pages[id].append(perm)
  60. # anonymous
  61. # no restriction applied at all
  62. if (not is_auth_user and
  63. is_setting_public_all and
  64. not restricted_pages):
  65. return [page.pk for page in pages]
  66. if site is None:
  67. site = current_site(request)
  68. # authenticated user and global permission
  69. if is_auth_user:
  70. global_page_perm_q = Q(
  71. Q(user=request.user) | Q(group__user=request.user)
  72. ) & Q(can_view=True) & Q(Q(sites__in=[site.pk]) | Q(sites__isnull=True))
  73. global_view_perms = GlobalPagePermission.objects.filter(global_page_perm_q).exists()
  74. #no page perms edgcase - all visible
  75. if ((is_setting_public_all or (
  76. is_setting_public_staff and request.user.is_staff))and
  77. not restricted_pages and
  78. not global_view_perms):
  79. return [page.pk for page in pages]
  80. #no page perms edgcase - none visible
  81. elif (is_setting_public_staff and
  82. not request.user.is_staff and
  83. not restricted_pages and
  84. not global_view_perms):
  85. return []
  86. def has_global_perm():
  87. if has_global_perm.cache < 0:
  88. has_global_perm.cache = 1 if request.user.has_perm('cms.view_page') else 0
  89. return bool(has_global_perm.cache)
  90. has_global_perm.cache = -1
  91. def has_permission_membership(page):
  92. """
  93. PagePermission user group membership tests
  94. """
  95. user_pk = request.user.pk
  96. page_pk = page.pk
  97. has_perm = False
  98. for perm in restricted_pages[page_pk]:
  99. if perm.user_id == user_pk:
  100. has_perm = True
  101. if not perm.group_id:
  102. continue
  103. group_user_ids = perm.group.user_set.values_list('pk', flat=True)
  104. if user_pk in group_user_ids:
  105. has_perm = True
  106. return has_perm
  107. for page in pages:
  108. to_add = False
  109. # default to false, showing a restricted page is bad
  110. # explicitly check all the conditions
  111. # of settings and permissions
  112. is_restricted = page.pk in restricted_pages
  113. # restricted_pages contains as key any page.pk that is
  114. # affected by a permission grant_on
  115. if is_auth_user:
  116. # a global permission was given to the request's user
  117. if global_view_perms:
  118. to_add = True
  119. # setting based handling of unrestricted pages
  120. elif not is_restricted and (
  121. is_setting_public_all or (
  122. is_setting_public_staff and request.user.is_staff)
  123. ):
  124. # authenticated user, no restriction and public for all
  125. # or
  126. # authenticated staff user, no restriction and public for staff
  127. to_add = True
  128. # check group and user memberships to restricted pages
  129. elif is_restricted and has_permission_membership(page):
  130. to_add = True
  131. elif has_global_perm():
  132. to_add = True
  133. # anonymous user, no restriction
  134. elif not is_restricted and is_setting_public_all:
  135. to_add = True
  136. # store it
  137. if to_add:
  138. visible_page_ids.append(page.pk)
  139. return visible_page_ids
  140. def page_to_node(page, home, cut):
  141. '''
  142. Transform a CMS page into a navigation node.
  143. page: the page you wish to transform
  144. home: a reference to the "home" page (the page with tree_id=1)
  145. cut: Should we cut page from it's parent pages? This means the node will not
  146. have a parent anymore.
  147. '''
  148. # Theses are simple to port over, since they are not calculated.
  149. # Other attributes will be added conditionnally later.
  150. attr = {'soft_root':page.soft_root,
  151. 'auth_required':page.login_required,
  152. 'reverse_id':page.reverse_id,}
  153. parent_id = page.parent_id
  154. # Should we cut the Node from its parents?
  155. if home and page.parent_id == home.pk and cut:
  156. parent_id = None
  157. # possible fix for a possible problem
  158. #if parent_id and not page.parent.get_calculated_status():
  159. # parent_id = None # ????
  160. if page.limit_visibility_in_menu == None:
  161. attr['visible_for_authenticated'] = True
  162. attr['visible_for_anonymous'] = True
  163. else:
  164. attr['visible_for_authenticated'] = page.limit_visibility_in_menu == 1
  165. attr['visible_for_anonymous'] = page.limit_visibility_in_menu == 2
  166. if page.pk == home.pk:
  167. attr['is_home'] = True
  168. # Extenders can be either navigation extenders or from apphooks.
  169. extenders = []
  170. if page.navigation_extenders:
  171. extenders.append(page.navigation_extenders)
  172. # Is this page an apphook? If so, we need to handle the apphooks's nodes
  173. try:
  174. app_name = page.get_application_urls(fallback=False)
  175. except Title.DoesNotExist:
  176. app_name = None
  177. if app_name: # it means it is an apphook
  178. app = apphook_pool.get_apphook(app_name)
  179. for menu in app.menus:
  180. extenders.append(menu.__name__)
  181. if extenders:
  182. attr['navigation_extenders'] = extenders
  183. # Do we have a redirectURL?
  184. attr['redirect_url'] = page.get_redirect() # save redirect URL if any
  185. # Now finally, build the NavigationNode object and return it.
  186. ret_node = NavigationNode(
  187. page.get_menu_title(),
  188. page.get_absolute_url(),
  189. page.pk,
  190. parent_id,
  191. attr=attr,
  192. visible=page.in_navigation,
  193. )
  194. return ret_node
  195. class CMSMenu(Menu):
  196. def get_nodes(self, request):
  197. page_queryset = get_page_queryset(request)
  198. site = Site.objects.get_current()
  199. lang = get_language_from_request(request)
  200. filters = {
  201. 'site':site,
  202. }
  203. if settings.CMS_HIDE_UNTRANSLATED:
  204. filters['title_set__language'] = lang
  205. pages = page_queryset.published().filter(**filters).order_by("tree_id", "lft")
  206. ids = []
  207. nodes = []
  208. first = True
  209. home_cut = False
  210. home_children = []
  211. home = None
  212. actual_pages = []
  213. # cache view perms
  214. visible_pages = get_visible_pages(request, pages, site)
  215. for page in pages:
  216. # Pages are ordered by tree_id, therefore the first page is the root
  217. # of the page tree (a.k.a "home")
  218. if page.pk not in visible_pages:
  219. # Don't include pages the user doesn't have access to
  220. continue
  221. if not home:
  222. home = page
  223. page.home_pk_cache = home.pk
  224. if first and page.pk != home.pk:
  225. home_cut = True
  226. if (page.parent_id == home.pk or page.parent_id in home_children) and home_cut:
  227. home_children.append(page.pk)
  228. if (page.pk == home.pk and home.in_navigation) or page.pk != home.pk:
  229. first = False
  230. ids.append(page.id)
  231. actual_pages.append(page)
  232. titles = list(get_title_queryset(request).filter(page__in=ids, language=lang))
  233. for page in actual_pages: # add the title and slugs and some meta data
  234. for title in titles:
  235. if title.page_id == page.pk:
  236. if not hasattr(page, "title_cache"):
  237. page.title_cache = {}
  238. page.title_cache[title.language] = title
  239. nodes.append(page_to_node(page, home, home_cut))
  240. ids.remove(page.pk)
  241. if ids: # get fallback languages
  242. fallbacks = get_fallback_languages(lang)
  243. for lang in fallbacks:
  244. titles = list(get_title_queryset(request).filter(page__in=ids, language=lang))
  245. for title in titles:
  246. for page in actual_pages: # add the title and slugs and some meta data
  247. if title.page_id == page.pk:
  248. if not hasattr(page, "title_cache"):
  249. page.title_cache = {}
  250. page.title_cache[title.language] = title
  251. nodes.append(page_to_node(page, home, home_cut))
  252. ids.remove(page.pk)
  253. break
  254. if not ids:
  255. break
  256. return nodes
  257. menu_pool.register_menu(CMSMenu)
  258. class NavExtender(Modifier):
  259. def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
  260. if post_cut:
  261. return nodes
  262. exts = []
  263. # rearrange the parent relations
  264. home = None
  265. for node in nodes:
  266. if node.attr.get("is_home", False):
  267. home = node
  268. extenders = node.attr.get("navigation_extenders", None)
  269. if extenders:
  270. for ext in extenders:
  271. if not ext in exts:
  272. exts.append(ext)
  273. for extnode in nodes:
  274. if extnode.namespace == ext and not extnode.parent_id:# if home has nav extenders but home is not visible
  275. if node.attr.get("is_home", False) and not node.visible:
  276. extnode.parent_id = None
  277. extnode.parent_namespace = None
  278. extnode.parent = None
  279. else:
  280. extnode.parent_id = node.id
  281. extnode.parent_namespace = node.namespace
  282. extnode.parent = node
  283. node.children.append(extnode)
  284. removed = []
  285. # find all not assigned nodes
  286. for menu in menu_pool.menus.items():
  287. if hasattr(menu[1], 'cms_enabled') and menu[1].cms_enabled and not menu[0] in exts:
  288. for node in nodes:
  289. if node.namespace == menu[0]:
  290. removed.append(node)
  291. if breadcrumb:
  292. # if breadcrumb and home not in navigation add node
  293. if breadcrumb and home and not home.visible:
  294. home.visible = True
  295. if request.path == home.get_absolute_url():
  296. home.selected = True
  297. else:
  298. home.selected = False
  299. # remove all nodes that are nav_extenders and not assigned
  300. for node in removed:
  301. nodes.remove(node)
  302. return nodes
  303. menu_pool.register_modifier(NavExtender)
  304. class SoftRootCutter(Modifier):
  305. """
  306. Ask evildmp/superdmp if you don't understand softroots!
  307. Softroot description from the docs:
  308. A soft root is a page that acts as the root for a menu navigation tree.
  309. Typically, this will be a page that is the root of a significant new
  310. section on your site.
  311. When the soft root feature is enabled, the navigation menu for any page
  312. will start at the nearest soft root, rather than at the real root of
  313. the site’s page hierarchy.
  314. This feature is useful when your site has deep page hierarchies (and
  315. therefore multiple levels in its navigation trees). In such a case, you
  316. usually don’t want to present site visitors with deep menus of nested
  317. items.
  318. For example, you’re on the page -Introduction to Bleeding-?, so the menu
  319. might look like this:
  320. School of Medicine
  321. Medical Education
  322. Departments
  323. Department of Lorem Ipsum
  324. Department of Donec Imperdiet
  325. Department of Cras Eros
  326. Department of Mediaeval Surgery
  327. Theory
  328. Cures
  329. Bleeding
  330. Introduction to Bleeding <this is the current page>
  331. Bleeding - the scientific evidence
  332. Cleaning up the mess
  333. Cupping
  334. Leaches
  335. Maggots
  336. Techniques
  337. Instruments
  338. Department of Curabitur a Purus
  339. Department of Sed Accumsan
  340. Department of Etiam
  341. Research
  342. Administration
  343. Contact us
  344. Impressum
  345. which is frankly overwhelming.
  346. By making -Department of Mediaeval Surgery-? a soft root, the menu
  347. becomes much more manageable:
  348. Department of Mediaeval Surgery
  349. Theory
  350. Cures
  351. Bleeding
  352. Introduction to Bleeding <current page>
  353. Bleeding - the scientific evidence
  354. Cleaning up the mess
  355. Cupping
  356. Leaches
  357. Maggots
  358. Techniques
  359. Instruments
  360. """
  361. def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
  362. # only apply this modifier if we're pre-cut (since what we do is cut)
  363. if post_cut or not settings.CMS_SOFTROOT:
  364. return nodes
  365. selected = None
  366. root_nodes = []
  367. # find the selected node as well as all the root nodes
  368. for node in nodes:
  369. if node.selected:
  370. selected = node
  371. if not node.parent:
  372. root_nodes.append(node)
  373. # if we found a selected ...
  374. if selected:
  375. # and the selected is a softroot
  376. if selected.attr.get("soft_root", False):
  377. # get it's descendants
  378. nodes = selected.get_descendants()
  379. # remove the link to parent
  380. selected.parent = None
  381. # make the selected page the root in the menu
  382. nodes = [selected] + nodes
  383. else:
  384. # if it's not a soft root, walk ancestors (upwards!)
  385. nodes = self.find_ancestors_and_remove_children(selected, nodes)
  386. return nodes
  387. def find_and_remove_children(self, node, nodes):
  388. for child in node.children:
  389. if child.attr.get("soft_root", False):
  390. self.remove_children(child, nodes)
  391. return nodes
  392. def remove_children(self, node, nodes):
  393. for child in node.children:
  394. nodes.remove(child)
  395. self.remove_children(child, nodes)
  396. node.children = []
  397. def find_ancestors_and_remove_children(self, node, nodes):
  398. """
  399. Check ancestors of node for soft roots
  400. """
  401. if node.parent:
  402. if node.parent.attr.get("soft_root", False):
  403. nodes = node.parent.get_descendants()
  404. node.parent.parent = None
  405. nodes = [node.parent] + nodes
  406. else:
  407. nodes = self.find_ancestors_and_remove_children(node.parent, nodes)
  408. else:
  409. for newnode in nodes:
  410. if newnode != node and not newnode.parent:
  411. self.find_and_remove_children(newnode, nodes)
  412. for child in node.children:
  413. if child != node:
  414. self.find_and_remove_children(child, nodes)
  415. return nodes
  416. menu_pool.register_modifier(SoftRootCutter)