kopia lustrzana https://github.com/wagtail/wagtail
Limit explorer menu nav to the subtree the user has permission over
Partially addresses #2401; adapted from #2463. Updates the explorer-nav logic to take the user's permissions into account. The menu now begins at the closest common ancestor node of all pages they have add/edit/publish/lock permission for - as a result, users with permission over a specific deep section of the tree don't have to redundantly drill down to it, and we're a step closer to true 'multi-homed' installations where the user is not made aware of tree structure that exists outside of their own remit.pull/2869/merge
rodzic
043db8549d
commit
39319f2191
|
@ -0,0 +1,512 @@
|
|||
[
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "wagtailcore.page",
|
||||
"fields": {
|
||||
"title": "Root",
|
||||
"numchild": 1,
|
||||
"show_in_menus": false,
|
||||
"live": true,
|
||||
"depth": 1,
|
||||
"content_type": ["wagtailcore", "page"],
|
||||
"path": "0001",
|
||||
"url_path": "/",
|
||||
"slug": "root"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "wagtailcore.page",
|
||||
"fields": {
|
||||
"title": "Welcome to testserver!",
|
||||
"numchild": 1,
|
||||
"show_in_menus": false,
|
||||
"live": true,
|
||||
"depth": 2,
|
||||
"content_type": ["tests", "eventpage"],
|
||||
"path": "00010001",
|
||||
"url_path": "/home/",
|
||||
"slug": "home"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "tests.eventpage",
|
||||
"fields": {
|
||||
"date_from": "2014-12-25",
|
||||
"audience": "public",
|
||||
"location": "The North Pole",
|
||||
"body": "<p>Welcome!</p>",
|
||||
"cost": "Free"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 3,
|
||||
"model": "wagtailcore.page",
|
||||
"fields": {
|
||||
"title": "About us",
|
||||
"numchild": 0,
|
||||
"show_in_menus": true,
|
||||
"live": true,
|
||||
"depth": 3,
|
||||
"content_type": ["tests", "eventpage"],
|
||||
"path": "000100010001",
|
||||
"url_path": "/home/about-us/",
|
||||
"slug": "about-us"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 3,
|
||||
"model": "tests.eventpage",
|
||||
"fields": {
|
||||
"date_from": "2014-12-25",
|
||||
"audience": "public",
|
||||
"location": "The North Pole",
|
||||
"body": "<p>Welcome!</p>",
|
||||
"cost": "Free"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 4,
|
||||
"model": "wagtailcore.page",
|
||||
"fields": {
|
||||
"title": "Welcome to example.com!",
|
||||
"numchild": 1,
|
||||
"show_in_menus": false,
|
||||
"live": true,
|
||||
"depth": 2,
|
||||
"content_type": ["tests", "eventpage"],
|
||||
"path": "00010002",
|
||||
"url_path": "/example-home/",
|
||||
"slug": "example-home"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 4,
|
||||
"model": "tests.eventpage",
|
||||
"fields": {
|
||||
"date_from": "2014-12-25",
|
||||
"audience": "public",
|
||||
"location": "The North Pole",
|
||||
"body": "<p>Welcome!</p>",
|
||||
"cost": "Free"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 5,
|
||||
"model": "wagtailcore.page",
|
||||
"fields": {
|
||||
"title": "Content",
|
||||
"numchild": 2,
|
||||
"show_in_menus": true,
|
||||
"live": true,
|
||||
"depth": 3,
|
||||
"content_type": ["tests", "eventpage"],
|
||||
"path": "000100020001",
|
||||
"url_path": "/example-home/content/",
|
||||
"slug": "content",
|
||||
"owner": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 5,
|
||||
"model": "tests.eventpage",
|
||||
"fields": {
|
||||
"date_from": "2014-12-25",
|
||||
"audience": "public",
|
||||
"location": "The North Pole",
|
||||
"body": "<p>Welcome!</p>",
|
||||
"cost": "Free"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 6,
|
||||
"model": "wagtailcore.page",
|
||||
"fields": {
|
||||
"title": "Page 1",
|
||||
"numchild": 0,
|
||||
"show_in_menus": true,
|
||||
"live": true,
|
||||
"depth": 4,
|
||||
"content_type": ["tests", "eventpage"],
|
||||
"path": "0001000200010001",
|
||||
"url_path": "/example-home/content/page-1/",
|
||||
"slug": "page-1",
|
||||
"owner": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 6,
|
||||
"model": "tests.eventpage",
|
||||
"fields": {
|
||||
"date_from": "2014-12-25",
|
||||
"audience": "public",
|
||||
"location": "The North Pole",
|
||||
"body": "<p>Welcome!</p>",
|
||||
"cost": "Free"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 7,
|
||||
"model": "wagtailcore.page",
|
||||
"fields": {
|
||||
"title": "Page 2",
|
||||
"numchild": 1,
|
||||
"show_in_menus": true,
|
||||
"live": true,
|
||||
"depth": 4,
|
||||
"content_type": ["tests", "eventpage"],
|
||||
"path": "0001000200010002",
|
||||
"url_path": "/example-home/content/page-2/",
|
||||
"slug": "page-2",
|
||||
"owner": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 7,
|
||||
"model": "tests.eventpage",
|
||||
"fields": {
|
||||
"date_from": "2014-12-25",
|
||||
"audience": "public",
|
||||
"location": "The North Pole",
|
||||
"body": "<p>Welcome!</p>",
|
||||
"cost": "Free"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 8,
|
||||
"model": "wagtailcore.page",
|
||||
"fields": {
|
||||
"title": "Other Content",
|
||||
"numchild": 0,
|
||||
"show_in_menus": true,
|
||||
"live": true,
|
||||
"depth": 3,
|
||||
"content_type": ["tests", "eventpage"],
|
||||
"path": "000100020002",
|
||||
"url_path": "/example-home/other-content/",
|
||||
"slug": "other-content",
|
||||
"owner": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 8,
|
||||
"model": "tests.eventpage",
|
||||
"fields": {
|
||||
"date_from": "2014-12-25",
|
||||
"audience": "public",
|
||||
"location": "The North Pole",
|
||||
"body": "<p>Welcome!</p>",
|
||||
"cost": "Free"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 9,
|
||||
"model": "wagtailcore.page",
|
||||
"fields": {
|
||||
"title": "Child 1 of Page 2",
|
||||
"numchild": 0,
|
||||
"show_in_menus": true,
|
||||
"live": true,
|
||||
"depth": 5,
|
||||
"content_type": ["tests", "eventpage"],
|
||||
"path": "00010002000100020001",
|
||||
"url_path": "/example-home/content/page-2/child-1/",
|
||||
"slug": "child-1",
|
||||
"owner": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 9,
|
||||
"model": "tests.eventpage",
|
||||
"fields": {
|
||||
"date_from": "2014-12-25",
|
||||
"audience": "public",
|
||||
"location": "The North Pole",
|
||||
"body": "<p>Welcome!</p>",
|
||||
"cost": "Free"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 10,
|
||||
"model": "wagtailcore.page",
|
||||
"fields": {
|
||||
"title": "Welcome to example2.com!",
|
||||
"numchild": 0,
|
||||
"show_in_menus": false,
|
||||
"live": true,
|
||||
"depth": 2,
|
||||
"content_type": ["tests", "eventpage"],
|
||||
"path": "00010003",
|
||||
"url_path": "/home-2/",
|
||||
"slug": "home-2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 10,
|
||||
"model": "tests.eventpage",
|
||||
"fields": {
|
||||
"date_from": "2014-12-25",
|
||||
"audience": "private",
|
||||
"location": "The North Pole",
|
||||
"body": "<p>Welcome!</p>",
|
||||
"cost": "Free"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "wagtailcore.site",
|
||||
"fields": {
|
||||
"root_page": 2,
|
||||
"hostname": "testserver",
|
||||
"port": 80,
|
||||
"is_default_site": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "wagtailcore.site",
|
||||
"fields": {
|
||||
"root_page": 4,
|
||||
"hostname": "example.com",
|
||||
"port": 80,
|
||||
"is_default_site": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 3,
|
||||
"model": "wagtailcore.site",
|
||||
"fields": {
|
||||
"root_page": 10,
|
||||
"hostname": "example2.com",
|
||||
"port": 80,
|
||||
"is_default_site": false
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 3,
|
||||
"model": "auth.group",
|
||||
"fields": {
|
||||
"name": "Group 1",
|
||||
"permissions": [
|
||||
["access_admin", "wagtailadmin", "admin"]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 4,
|
||||
"model": "auth.group",
|
||||
"fields": {
|
||||
"name": "Group 2",
|
||||
"permissions": [
|
||||
["access_admin", "wagtailadmin", "admin"]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 5,
|
||||
"model": "auth.group",
|
||||
"fields": {
|
||||
"name": "Group 3",
|
||||
"permissions": [
|
||||
["access_admin", "wagtailadmin", "admin"]
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "wagtailcore.grouppagepermission",
|
||||
"fields": {
|
||||
"group": ["Group 1"],
|
||||
"page": 2,
|
||||
"permission_type": "add"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "wagtailcore.grouppagepermission",
|
||||
"fields": {
|
||||
"group": ["Group 1"],
|
||||
"page": 2,
|
||||
"permission_type": "edit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 3,
|
||||
"model": "wagtailcore.grouppagepermission",
|
||||
"fields": {
|
||||
"group": ["Group 1"],
|
||||
"page": 2,
|
||||
"permission_type": "publish"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 3,
|
||||
"model": "wagtailcore.grouppagepermission",
|
||||
"fields": {
|
||||
"group": ["Group 1"],
|
||||
"page": 2,
|
||||
"permission_type": "choose"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 5,
|
||||
"model": "wagtailcore.grouppagepermission",
|
||||
"fields": {
|
||||
"group": ["Group 2"],
|
||||
"page": 6,
|
||||
"permission_type": "edit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 6,
|
||||
"model": "wagtailcore.grouppagepermission",
|
||||
"fields": {
|
||||
"group": ["Group 2"],
|
||||
"page": 6,
|
||||
"permission_type": "choose"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 7,
|
||||
"model": "wagtailcore.grouppagepermission",
|
||||
"fields": {
|
||||
"group": ["Group 3"],
|
||||
"page": 8,
|
||||
"permission_type": "edit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 8,
|
||||
"model": "wagtailcore.grouppagepermission",
|
||||
"fields": {
|
||||
"group": ["Group 3"],
|
||||
"page": 8,
|
||||
"permission_type": "choose"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"pk": 1,
|
||||
"model": "customuser.customuser",
|
||||
"fields": {
|
||||
"username": "superman",
|
||||
"first_name": "Clark",
|
||||
"last_name": "Kent",
|
||||
"is_active": true,
|
||||
"is_superuser": true,
|
||||
"is_staff": true,
|
||||
"groups": [
|
||||
],
|
||||
"user_permissions": [],
|
||||
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
|
||||
"email": "ckent@dailyplanet.com"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "customuser.customuser",
|
||||
"fields": {
|
||||
"username": "jane",
|
||||
"first_name": "Jane",
|
||||
"last_name": "Smith",
|
||||
"is_active": true,
|
||||
"is_superuser": false,
|
||||
"is_staff": true,
|
||||
"groups": [
|
||||
["Group 1"]
|
||||
],
|
||||
"user_permissions": [],
|
||||
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
|
||||
"email": "jane@example.com"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 3,
|
||||
"model": "customuser.customuser",
|
||||
"fields": {
|
||||
"username": "bob",
|
||||
"first_name": "Bob",
|
||||
"last_name": "Smith",
|
||||
"is_active": true,
|
||||
"is_superuser": false,
|
||||
"is_staff": true,
|
||||
"groups": [
|
||||
["Group 2"]
|
||||
],
|
||||
"user_permissions": [],
|
||||
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
|
||||
"email": "bob@example.com"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 4,
|
||||
"model": "customuser.customuser",
|
||||
"fields": {
|
||||
"username": "sam",
|
||||
"first_name": "Sam",
|
||||
"last_name": "Smith",
|
||||
"is_active": true,
|
||||
"is_superuser": false,
|
||||
"is_staff": true,
|
||||
"groups": [
|
||||
["Group 1"],
|
||||
["Group 2"]
|
||||
],
|
||||
"user_permissions": [],
|
||||
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
|
||||
"email": "sam@example.com"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 5,
|
||||
"model": "customuser.customuser",
|
||||
"fields": {
|
||||
"username": "mary",
|
||||
"first_name": "Mary",
|
||||
"last_name": "Smith",
|
||||
"is_active": true,
|
||||
"is_superuser": false,
|
||||
"is_staff": true,
|
||||
"groups": [
|
||||
],
|
||||
"user_permissions": [
|
||||
["access_admin", "wagtailadmin", "admin"]
|
||||
],
|
||||
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
|
||||
"email": "mary@example.com"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 6,
|
||||
"model": "customuser.customuser",
|
||||
"fields": {
|
||||
"username": "josh",
|
||||
"first_name": "Josh",
|
||||
"last_name": "Smith",
|
||||
"is_active": true,
|
||||
"is_superuser": false,
|
||||
"is_staff": true,
|
||||
"groups": [
|
||||
["Group 2"],
|
||||
["Group 3"]
|
||||
],
|
||||
"user_permissions": [],
|
||||
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
|
||||
"email": "josh@example.com"
|
||||
}
|
||||
}
|
||||
|
||||
]
|
|
@ -29,10 +29,10 @@ else:
|
|||
assignment_tag = register.assignment_tag
|
||||
|
||||
|
||||
@register.inclusion_tag('wagtailadmin/shared/explorer_nav.html')
|
||||
def explorer_nav():
|
||||
@register.inclusion_tag('wagtailadmin/shared/explorer_nav.html', takes_context=True)
|
||||
def explorer_nav(context):
|
||||
return {
|
||||
'nodes': get_navigation_menu_items()
|
||||
'nodes': get_navigation_menu_items(context['request'].user)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
from wagtail.wagtailcore.models import Page
|
||||
|
||||
|
||||
class TestExplorerNavView(TestCase, WagtailTestUtils):
|
||||
"""
|
||||
Test the way that the explorer nav menu behaves for users with different permissions.
|
||||
|
||||
This is isolated in its own test case because it requires a custom page tree and custom set of
|
||||
users and groups.
|
||||
The fixture sets up this page tree:
|
||||
========================================================
|
||||
ID Site Path
|
||||
========================================================
|
||||
1 /
|
||||
2 testserver /home/
|
||||
3 testserver /home/about-us/
|
||||
4 example.com /home/
|
||||
5 example.com /home/content/
|
||||
6 example.com /home/content/page-1/
|
||||
7 example.com /home/content/page-2/
|
||||
9 example.com /home/content/page-2/child-1
|
||||
8 example.com /home/other-content/
|
||||
10 example.com /home-2/
|
||||
========================================================
|
||||
Group 1 has explore and choose permissions rooted at testserver's homepage.
|
||||
Group 2 has explore and choose permissions rooted at exammple.com's page-1.
|
||||
Group 3 has explore and choose permissions rooted at exammple.com's other-content.
|
||||
User "jane" is in Group 1.
|
||||
User "bob" is in Group 2.
|
||||
User "sam" is in Groups 1 and 2.
|
||||
User "josh" is in Groups 2 and 3.
|
||||
User "mary" is is no Groups, but she has the "access wagtail admin" permission.
|
||||
User "superman" is an admin.
|
||||
|
||||
Note that the Explorer Nav does not display leaf nodes.
|
||||
"""
|
||||
|
||||
fixtures = ['test_explorable_pages.json']
|
||||
|
||||
def test_admins_see_all_pages(self):
|
||||
self.assertTrue(self.client.login(username='superman', password='password'))
|
||||
response = self.client.get(reverse('wagtailadmin_explorer_nav'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed('wagtailadmin/shared/explorer_nav.html')
|
||||
self.assertEqual(len(response.context['nodes']), 3)
|
||||
self.assertEqual(response.context['nodes'][0][0], Page.objects.get(id=2))
|
||||
self.assertEqual(response.context['nodes'][1][0], Page.objects.get(id=4))
|
||||
self.assertEqual(response.context['nodes'][1][1][0][0], Page.objects.get(id=5))
|
||||
self.assertEqual(response.context['nodes'][1][1][0][1][0][0], Page.objects.get(id=7))
|
||||
# Even though example.com's Home 2 has no children, it's still displayed because it's at
|
||||
# the top menu level for this user
|
||||
self.assertEqual(response.context['nodes'][2][0], Page.objects.get(id=10))
|
||||
|
||||
def test_nav_root_for_nonadmin_is_closest_common_ancestor(self):
|
||||
self.assertTrue(self.client.login(username='jane', password='password'))
|
||||
response = self.client.get(reverse('wagtailadmin_explorer_nav'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed('wagtailadmin/shared/explorer_nav.html')
|
||||
self.assertEqual(len(response.context['nodes']), 1)
|
||||
self.assertEqual(response.context['nodes'][0][0], Page.objects.get(id=2))
|
||||
self.client.logout()
|
||||
|
||||
self.assertTrue(self.client.login(username='sam', password='password'))
|
||||
response = self.client.get(reverse('wagtailadmin_explorer_nav'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed('wagtailadmin/shared/explorer_nav.html')
|
||||
self.assertEqual(len(response.context['nodes']), 2)
|
||||
self.assertEqual(response.context['nodes'][0][0], Page.objects.get(id=2))
|
||||
self.assertEqual(response.context['nodes'][1][0], Page.objects.get(id=4))
|
||||
|
||||
def test_nonadmin_sees_leaf_pages_at_root_level(self):
|
||||
self.assertTrue(self.client.login(username='bob', password='password'))
|
||||
response = self.client.get(reverse('wagtailadmin_explorer_nav'))
|
||||
|
||||
# Bob's group's CCA is a leaf node, so by the naive "don't show childless pages" rule
|
||||
# he would not be shown any nodes. This would be bad, so we make an exception whereby
|
||||
# childless pages at the user's top level are shown
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed('wagtailadmin/shared/explorer_nav.html')
|
||||
self.assertEqual(len(response.context['nodes']), 1)
|
||||
self.assertEqual(response.context['nodes'][0][0], Page.objects.get(id=6))
|
||||
self.assertEqual(len(response.context['nodes'][0][1]), 0)
|
||||
|
||||
def test_nonadmin_sees_pages_below_closest_common_ancestor(self):
|
||||
self.assertTrue(self.client.login(username='josh', password='password'))
|
||||
response = self.client.get(reverse('wagtailadmin_explorer_nav'))
|
||||
|
||||
# Josh has permissions for /example-home/content/page-1 and /example-home/other-content ,
|
||||
# of which the closest common ancestor is /example-home . However, since he doesn't need
|
||||
# access to example-home itself, the menu begins at its children ('content' and
|
||||
# 'other-content') instead
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed('wagtailadmin/shared/explorer_nav.html')
|
||||
|
||||
self.assertEqual(len(response.context['nodes']), 2)
|
||||
self.assertEqual(response.context['nodes'][0][0], Page.objects.get(id=5))
|
||||
# page-1 is childless, but user has direct permission on it, so it should be shown
|
||||
self.assertEqual(len(response.context['nodes'][0][1]), 1)
|
||||
self.assertEqual(response.context['nodes'][0][1][0][0], Page.objects.get(id=6))
|
||||
self.assertEqual(len(response.context['nodes'][0][1][0][1]), 0)
|
||||
|
||||
self.assertEqual(response.context['nodes'][1][0], Page.objects.get(id=8))
|
||||
self.assertEqual(len(response.context['nodes'][1][1]), 0)
|
||||
|
||||
def test_nonadmin_sees_only_explorable_pages(self):
|
||||
self.assertTrue(self.client.login(username='sam', password='password'))
|
||||
response = self.client.get(reverse('wagtailadmin_explorer_nav'))
|
||||
|
||||
# Sam has permissions for /home and /example-home/content/page-1 , of which the closest
|
||||
# common ancestor is root; we don't show root in the menu, so the top level will consist
|
||||
# of 'home' and 'example-home' (but not the sibling 'home-2', which Sam doesn't have
|
||||
# permission on)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed('wagtailadmin/shared/explorer_nav.html')
|
||||
|
||||
self.assertEqual(len(response.context['nodes']), 2)
|
||||
# Sam should see the testserver homepage, the example.com homepage, and the Content page,
|
||||
# but should not see Page 2.
|
||||
self.assertEqual(response.context['nodes'][0][0], Page.objects.get(id=2))
|
||||
self.assertEqual(response.context['nodes'][1][0], Page.objects.get(id=4))
|
||||
self.assertEqual(response.context['nodes'][1][1][0][0], Page.objects.get(id=5))
|
||||
self.assertEqual(len(response.context['nodes'][1][1][0][1]), 1)
|
||||
# page-1 is included in the menu, despite being a leaf node, because Sam has direct
|
||||
# permission on it
|
||||
self.assertEqual(response.context['nodes'][1][1][0][1][0][0], Page.objects.get(id=6))
|
||||
self.client.logout()
|
||||
|
||||
self.assertTrue(self.client.login(username='jane', password='password'))
|
||||
response = self.client.get(reverse('wagtailadmin_explorer_nav'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed('wagtailadmin/shared/explorer_nav.html')
|
||||
self.assertEqual(len(response.context['nodes']), 1)
|
||||
self.assertEqual(response.context['nodes'][0][0], Page.objects.get(id=2))
|
||||
self.assertEqual(len(response.context['nodes'][0][1]), 0)
|
||||
|
||||
def test_nonadmin_with_no_page_perms_sees_nothing_in_nav(self):
|
||||
self.assertTrue(self.client.login(username='mary', password='password'))
|
||||
response = self.client.get(reverse('wagtailadmin_explorer_nav'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# Being in no Groups, Mary should ot be shown any nodes.
|
||||
self.assertEqual(len(response.context['nodes']), 0)
|
|
@ -171,20 +171,6 @@ class TestSendMail(TestCase):
|
|||
self.assertEqual(mail.outbox[0].from_email, "webmaster@localhost")
|
||||
|
||||
|
||||
class TestExplorerNavView(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.homepage = Page.objects.get(id=2).specific
|
||||
self.login()
|
||||
|
||||
def test_explorer_nav_view(self):
|
||||
response = self.client.get(reverse('wagtailadmin_explorer_nav'))
|
||||
|
||||
# Check response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed('wagtailadmin/shared/explorer_nav.html')
|
||||
self.assertEqual(response.context['nodes'][0][0], self.homepage)
|
||||
|
||||
|
||||
class TestTagsAutocomplete(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
|
|
@ -31,7 +31,7 @@ def get_valid_next_url_from_request(request):
|
|||
|
||||
def explorer_nav(request):
|
||||
return render(request, 'wagtailadmin/shared/explorer_nav.html', {
|
||||
'nodes': get_navigation_menu_items(),
|
||||
'nodes': get_navigation_menu_items(request.user),
|
||||
})
|
||||
|
||||
|
||||
|
|
|
@ -1344,10 +1344,72 @@ class Page(six.with_metaclass(PageBase, MP_Node, index.Indexed, ClusterableModel
|
|||
verbose_name_plural = _('pages')
|
||||
|
||||
|
||||
def get_navigation_menu_items():
|
||||
# Get all pages that appear in the navigation menu: ones which have children,
|
||||
# or are at the top-level (this rule required so that an empty site out-of-the-box has a working menu)
|
||||
pages = Page.objects.filter(Q(depth=2) | Q(numchild__gt=0)).order_by('path')
|
||||
def get_navigation_menu_items(user):
|
||||
# Get all pages that the user has direct add/edit/publish/lock permission on
|
||||
if user.is_superuser:
|
||||
# superuser has implicit permission on the root node
|
||||
pages_with_direct_permission = Page.objects.filter(depth=1)
|
||||
else:
|
||||
pages_with_direct_permission = Page.objects.filter(
|
||||
group_permissions__group__in=user.groups.all(),
|
||||
group_permissions__permission_type__in=['add', 'edit', 'publish', 'lock']
|
||||
)
|
||||
|
||||
if not(pages_with_direct_permission):
|
||||
return []
|
||||
|
||||
# Find the closest common ancestor of the pages the user has permission for;
|
||||
# this (or its children) will be the root menu level for this user.
|
||||
cca_path = pages_with_direct_permission[0].path
|
||||
for page in pages_with_direct_permission[1:]:
|
||||
# repeatedly try shorter prefixes of page.path until we find one that cca_path starts with;
|
||||
# this becomes the new cca_path
|
||||
for path_len in range(len(page.path), 0, -Page.steplen):
|
||||
path_to_test = page.path[0:path_len]
|
||||
if cca_path.startswith(path_to_test):
|
||||
cca_path = path_to_test
|
||||
break
|
||||
|
||||
# Determine the depth (within the overall page tree) at which this user's menu starts:
|
||||
# * if CCA is the root node, start at depth 2 (immediate children of root - because we
|
||||
# never want to show root in the navigation);
|
||||
# * else if CCA is a node they have direct permission for, start at that depth
|
||||
# (so that they can edit that root node)
|
||||
# * else start one level deeper (because the root node is only needed to provide navigation
|
||||
# to deeper levels, and one level deeper is the first point where there's a choice to make)
|
||||
|
||||
if len(cca_path) == Page.steplen:
|
||||
# CCA is the root node
|
||||
menu_root_depth = 2
|
||||
elif any(page.path == cca_path for page in pages_with_direct_permission):
|
||||
# user has direct permission on the CCA node
|
||||
menu_root_depth = int(len(cca_path) / Page.steplen)
|
||||
else:
|
||||
menu_root_depth = int(len(cca_path) / Page.steplen) + 1
|
||||
|
||||
# Run the query to fetch all pages to be shown in the navigation. This consists of the following
|
||||
# set of pages, for each page in pages_with_direct_permission:
|
||||
# * all ancestors (plus self) from menu_root_depth down
|
||||
# * all descendants that have children
|
||||
# * all descendants at menu_root_depth, regardless of whether they have children.
|
||||
# (this ensures that a freshly built site with no child pages won't result in an empty menu)
|
||||
|
||||
ancestor_paths = [
|
||||
page.path[0:path_len]
|
||||
for page in pages_with_direct_permission
|
||||
for path_len in range(menu_root_depth * Page.steplen, len(page.path) + Page.steplen, Page.steplen)
|
||||
]
|
||||
|
||||
criteria = Q(path__in=ancestor_paths)
|
||||
|
||||
for page in pages_with_direct_permission:
|
||||
criteria = criteria | (
|
||||
Q(path__startswith=page.path) & (
|
||||
Q(depth=menu_root_depth) | Q(numchild__gt=0)
|
||||
)
|
||||
)
|
||||
|
||||
pages = Page.objects.filter(criteria).order_by('path')
|
||||
|
||||
# Turn this into a tree structure:
|
||||
# tree_node = (page, children)
|
||||
|
@ -1358,7 +1420,9 @@ def get_navigation_menu_items():
|
|||
# at depth d, its parent must be the last page we saw at depth (d-1), and so we can
|
||||
# find it in that list.
|
||||
|
||||
depth_list = [(None, [])] # a dummy node for depth=0, since one doesn't exist in the DB
|
||||
# create dummy entries for pages at a lower depth than menu_root_depth
|
||||
# as these won't be covered by the 'pages' queryset
|
||||
depth_list = [(None, []) for i in range(0, menu_root_depth)]
|
||||
|
||||
for page in pages:
|
||||
# create a node for this page
|
||||
|
@ -1375,14 +1439,7 @@ def get_navigation_menu_items():
|
|||
# an exception here means that this node is one level deeper than any we've seen so far
|
||||
depth_list.append(node)
|
||||
|
||||
# in Wagtail, the convention is to have one root node in the db (depth=1); the menu proper
|
||||
# begins with the children of that node (depth=2).
|
||||
try:
|
||||
root, root_children = depth_list[1]
|
||||
return root_children
|
||||
except IndexError:
|
||||
# what, we don't even have a root node? Fine, just return an empty list...
|
||||
return []
|
||||
return depth_list[menu_root_depth - 1][1]
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=Page)
|
||||
|
|
Ładowanie…
Reference in New Issue