PageRenderTime 60ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/cms/menu.py

https://github.com/Doap/django-cms
Python | 348 lines | 323 code | 11 blank | 14 comment | 23 complexity | 2a8e74233b565df999758e0973e65c8c MD5 | raw file
  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)
  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 django.conf import settings
  13. from django.contrib.sites.models import Site
  14. from django.db.models.query_utils import Q
  15. from menus.base import Menu, NavigationNode, Modifier
  16. from menus.menu_pool import menu_pool
  17. def get_visible_pages(request, pages, site=None):
  18. # This code is basically a many-pages-at-once version of
  19. # Page.has_view_permission, check there to see wtf is going on here.
  20. if request.user.is_staff and settings.CMS_PUBLIC_FOR in ('staff', 'all'):
  21. return [page.pk for page in pages]
  22. page_ids = []
  23. pages_perms_q = Q()
  24. for page in pages:
  25. page_q = Q(page__tree_id=page.tree_id) & (
  26. Q(page=page)
  27. | (Q(page__level__lt=page.level) & (Q(grant_on=ACCESS_DESCENDANTS) | Q(grant_on=ACCESS_PAGE_AND_DESCENDANTS)))
  28. | (Q(page__level=page.level - 1) & (Q(grant_on=ACCESS_CHILDREN) | Q(grant_on=ACCESS_PAGE_AND_CHILDREN)))
  29. )
  30. pages_perms_q |= page_q
  31. pages_perms_q &= Q(can_view=True)
  32. page_permissions = PagePermission.objects.filter(pages_perms_q).select_related('page', 'group__users')
  33. restricted_pages = defaultdict(list)
  34. for perm in page_permissions:
  35. restricted_pages[perm.page.pk].append(perm)
  36. if site is None:
  37. site = current_site(request)
  38. if request.user.is_authenticated():
  39. #return self.filter(Q(user=user) | Q(group__user=user))
  40. global_page_perm_q = Q(
  41. Q(user=request.user) | Q(group__user=request.user)
  42. ) & Q(can_view=True) & Q(Q(sites__in=[site.pk]) | Q(sites__isnull=True))
  43. global_view_perms = GlobalPagePermission.objects.filter(global_page_perm_q).exists()
  44. def has_global_perm():
  45. if has_global_perm.cache < 0:
  46. has_global_perm.cache = 1 if request.user.has_perm('cms.view_page') else 0
  47. return bool(has_global_perm.cache)
  48. has_global_perm.cache = -1
  49. def has_permission(page):
  50. """
  51. PagePermission tests
  52. """
  53. for perm in restricted_pages[page.pk]:
  54. if perm.user_id == request.user.pk:
  55. return True
  56. for perm in restricted_pages[page.pk]:
  57. if not perm.group_id:
  58. continue
  59. if request.user.pk in perm.group.user_set.values_list('id', flat=True):
  60. return True
  61. return False
  62. for page in pages:
  63. is_restricted = page.pk in restricted_pages
  64. if request.user.is_authenticated():
  65. # a global permission was given to the request's user
  66. if global_view_perms:
  67. page_ids.append(page.pk)
  68. # authenticated user, no restriction and public for all
  69. elif settings.CMS_PUBLIC_FOR == 'all':
  70. page_ids.append(page.pk)
  71. elif has_permission(page):
  72. page_ids.append(page.pk)
  73. elif has_global_perm():
  74. page_ids.append(page.pk)
  75. elif not is_restricted and settings.CMS_PUBLIC_FOR == 'all':
  76. # anonymous user, no restriction saved in database
  77. page_ids.append(page.pk)
  78. return page_ids
  79. def page_to_node(page, home, cut):
  80. '''
  81. Transform a CMS page into a navigation node.
  82. page: the page you wish to transform
  83. home: a reference to the "home" page (the page with tree_id=1)
  84. cut: Should we cut page from it's parent pages? This means the node will not
  85. have a parent anymore.
  86. '''
  87. # Theses are simple to port over, since they are not calculated.
  88. # Other attributes will be added conditionnally later.
  89. attr = {'soft_root':page.soft_root,
  90. 'auth_required':page.login_required,
  91. 'reverse_id':page.reverse_id,}
  92. parent_id = page.parent_id
  93. # Should we cut the Node from its parents?
  94. if home and page.parent_id == home.pk and cut:
  95. parent_id = None
  96. # possible fix for a possible problem
  97. #if parent_id and not page.parent.get_calculated_status():
  98. # parent_id = None # ????
  99. if page.limit_visibility_in_menu == None:
  100. attr['visible_for_authenticated'] = True
  101. attr['visible_for_anonymous'] = True
  102. else:
  103. attr['visible_for_authenticated'] = page.limit_visibility_in_menu == 1
  104. attr['visible_for_anonymous'] = page.limit_visibility_in_menu == 2
  105. if page.pk == home.pk:
  106. attr['is_home'] = True
  107. # Extenders can be either navigation extenders or from apphooks.
  108. extenders = []
  109. if page.navigation_extenders:
  110. extenders.append(page.navigation_extenders)
  111. # Is this page an apphook? If so, we need to handle the apphooks's nodes
  112. try:
  113. app_name = page.get_application_urls(fallback=False)
  114. except Title.DoesNotExist:
  115. app_name = None
  116. if app_name: # it means it is an apphook
  117. app = apphook_pool.get_apphook(app_name)
  118. for menu in app.menus:
  119. extenders.append(menu.__name__)
  120. if extenders:
  121. attr['navigation_extenders'] = extenders
  122. # Do we have a redirectURL?
  123. attr['redirect_url'] = page.get_redirect() # save redirect URL if any
  124. # Now finally, build the NavigationNode object and return it.
  125. ret_node = NavigationNode(
  126. page.get_menu_title(),
  127. page.get_absolute_url(),
  128. page.pk,
  129. parent_id,
  130. attr=attr,
  131. visible=page.in_navigation,
  132. )
  133. return ret_node
  134. class CMSMenu(Menu):
  135. def get_nodes(self, request):
  136. page_queryset = get_page_queryset(request)
  137. site = Site.objects.get_current()
  138. lang = get_language_from_request(request)
  139. filters = {
  140. 'site':site,
  141. }
  142. if settings.CMS_HIDE_UNTRANSLATED:
  143. filters['title_set__language'] = lang
  144. pages = page_queryset.published().filter(**filters).order_by("tree_id", "lft")
  145. ids = []
  146. nodes = []
  147. first = True
  148. home_cut = False
  149. home_children = []
  150. home = None
  151. actual_pages = []
  152. # cache view perms
  153. visible_pages = get_visible_pages(request, pages, site)
  154. for page in pages:
  155. # Pages are ordered by tree_id, therefore the first page is the root
  156. # of the page tree (a.k.a "home")
  157. if page.pk not in visible_pages:
  158. # Don't include pages the user doesn't have access to
  159. continue
  160. if not home:
  161. home = page
  162. page.home_pk_cache = home.pk
  163. if first and page.pk != home.pk:
  164. home_cut = True
  165. elif not settings.CMS_PUBLIC_FOR == 'all':
  166. continue
  167. if (page.parent_id == home.pk or page.parent_id in home_children) and home_cut:
  168. home_children.append(page.pk)
  169. if (page.pk == home.pk and home.in_navigation) or page.pk != home.pk:
  170. first = False
  171. ids.append(page.id)
  172. actual_pages.append(page)
  173. titles = list(get_title_queryset(request).filter(page__in=ids, language=lang))
  174. for page in actual_pages: # add the title and slugs and some meta data
  175. for title in titles:
  176. if title.page_id == page.pk:
  177. if not hasattr(page, "title_cache"):
  178. page.title_cache = {}
  179. page.title_cache[title.language] = title
  180. nodes.append(page_to_node(page, home, home_cut))
  181. ids.remove(page.pk)
  182. if ids: # get fallback languages
  183. fallbacks = get_fallback_languages(lang)
  184. for l in fallbacks:
  185. titles = list(get_title_queryset(request).filter(page__in=ids, language=l))
  186. for title in titles:
  187. for page in actual_pages: # add the title and slugs and some meta data
  188. if title.page_id == page.pk:
  189. if not hasattr(page, "title_cache"):
  190. page.title_cache = {}
  191. page.title_cache[title.language] = title
  192. nodes.append(page_to_node(page, home, home_cut))
  193. ids.remove(page.pk)
  194. break
  195. if not ids:
  196. break
  197. return nodes
  198. menu_pool.register_menu(CMSMenu)
  199. class NavExtender(Modifier):
  200. def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
  201. if post_cut:
  202. return nodes
  203. exts = []
  204. # rearrange the parent relations
  205. home = None
  206. for node in nodes:
  207. if node.attr.get("is_home", False):
  208. home = node
  209. extenders = node.attr.get("navigation_extenders", None)
  210. if extenders:
  211. for ext in extenders:
  212. if not ext in exts:
  213. exts.append(ext)
  214. for n in nodes:
  215. if n.namespace == ext and not n.parent_id:# if home has nav extenders but home is not visible
  216. if node.attr.get("is_home", False) and not node.visible:
  217. n.parent_id = None
  218. n.parent_namespace = None
  219. n.parent = None
  220. else:
  221. n.parent_id = node.id
  222. n.parent_namespace = node.namespace
  223. n.parent = node
  224. node.children.append(n)
  225. removed = []
  226. # find all not assigned nodes
  227. for menu in menu_pool.menus.items():
  228. if hasattr(menu[1], 'cms_enabled') and menu[1].cms_enabled and not menu[0] in exts:
  229. for node in nodes:
  230. if node.namespace == menu[0]:
  231. removed.append(node)
  232. if breadcrumb:
  233. # if breadcrumb and home not in navigation add node
  234. if breadcrumb and home and not home.visible:
  235. home.visible = True
  236. if request.path == home.get_absolute_url():
  237. home.selected = True
  238. else:
  239. home.selected = False
  240. # remove all nodes that are nav_extenders and not assigned
  241. for node in removed:
  242. nodes.remove(node)
  243. return nodes
  244. menu_pool.register_modifier(NavExtender)
  245. class SoftRootCutter(Modifier):
  246. """
  247. If anyone understands this, PLEASE write a meaningful description here!
  248. """
  249. def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
  250. # only apply this modifier if we're pre-cut (since what we do is cut)
  251. if post_cut or not settings.CMS_SOFTROOT:
  252. return nodes
  253. selected = None
  254. root_nodes = []
  255. # find the selected node as well as all the root nodes
  256. for node in nodes:
  257. if node.selected:
  258. selected = node
  259. if not node.parent:
  260. root_nodes.append(node)
  261. # if we found a selected ...
  262. if selected:
  263. # and the selected is a softroot
  264. if selected.attr.get("soft_root", False):
  265. # get it's descendants
  266. nodes = selected.get_descendants()
  267. # remove the link to parent
  268. selected.parent = None
  269. # make the selected page the root in the menu
  270. nodes = [selected] + nodes
  271. else:
  272. # if it's not a soft root, walk ancestors (upwards!)
  273. nodes = self.find_ancestors_and_remove_children(selected, nodes)
  274. # remove child-softroots from descendants (downwards!)
  275. nodes = self.find_and_remove_children(selected, nodes)
  276. else:
  277. # for all nodes in root, remove child-sofroots (downwards!)
  278. for node in root_nodes:
  279. self.find_and_remove_children(node, nodes)
  280. return nodes
  281. def find_and_remove_children(self, node, nodes):
  282. for n in node.children:
  283. if n.attr.get("soft_root", False):
  284. self.remove_children(n, nodes)
  285. return nodes
  286. def remove_children(self, node, nodes):
  287. for n in node.children:
  288. nodes.remove(n)
  289. self.remove_children(n, nodes)
  290. node.children = []
  291. def find_ancestors_and_remove_children(self, node, nodes):
  292. """
  293. Check ancestors of node for soft roots
  294. """
  295. if node.parent:
  296. if node.parent.attr.get("soft_root", False):
  297. nodes = node.parent.get_descendants()
  298. node.parent.parent = None
  299. nodes = [node.parent] + nodes
  300. else:
  301. nodes = self.find_ancestors_and_remove_children(node.parent, nodes)
  302. else:
  303. for n in nodes:
  304. if n != node and not n.parent:
  305. self.find_and_remove_children(n, nodes)
  306. for n in node.children:
  307. if n != node:
  308. self.find_and_remove_children(n, nodes)
  309. return nodes
  310. menu_pool.register_modifier(SoftRootCutter)