Skip to content
This repository

ISSUE 1073: Menu Rendering Permission Fix and Testcases #1106

Merged
merged 22 commits into from over 2 years ago

5 participants

FrankBie2 Frank Bieniek Jonas Obrist Keryn Knight Chris Glass
FrankBie2

The testcases demonstrate the fix of issue 1073
Rendering of serveral menu grant on options and visibility testcases.

FrankBie2

Should I improve anything or is it ready to get into the develop branch?

cms/menu.py
((35 lines not shown))
  29 + #edgecases
  30 + #anonymous user public for all, no pageperm objects
  31 + if (not request.user.is_authenticated() and
  32 + settings.CMS_PUBLIC_FOR == 'all' and
  33 + PagePermission.objects.filter(can_view = True).count() == 0):
  34 + return [page.pk for page in pages]
  35 +
  36 + #auth user
  37 + if request.user.is_authenticated():
  38 + #but no page perms - all
  39 + if (settings.CMS_PUBLIC_FOR == 'all' and
  40 + PagePermission.objects.filter(can_view = True).count() == 0 and
  41 + GlobalPagePermission.objects.filter(can_view = True).count() == 0):
  42 + return [page.pk for page in pages]
  43 +
  44 +# this edg casese are to rare to justify 3 additional queries
1

lines 44-62 are all commented out code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/menu.py
((31 lines not shown))
25 25
  26 + # check if there is ANY restriction
  27 + # that needs a permission pages visibility calculation
  28 +
  29 + #edgecases
  30 + #anonymous user public for all, no pageperm objects
  31 + if (not request.user.is_authenticated() and
  32 + settings.CMS_PUBLIC_FOR == 'all' and
  33 + PagePermission.objects.filter(can_view = True).count() == 0):
1

can_view = True ought to be can_view=True. Same on line 40. A minor pep8 pet peeve ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/menu.py
((7 lines not shown))
38 82 for perm in page_permissions:
39   - restricted_pages[perm.page.pk].append(perm)
40   -
  83 + # collect the pages that are effected by permissions
1

affected, not effected, I believe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/menu.py
((34 lines not shown))
71 138 for page in pages:
  139 + #restricted_pages contains as key any page.pk that is
  140 + #effected by a permission grant_on
  141 + to_add = False
1

Out of 8 instances this bool used in the code that follows, and 10 if branches, in only two instances is it not true. Is it not possible to default to true instead of false and just test for those two cases where it flips to false explicitly, and document what assumptions skipping the other checks makes about the permission? It seems like it'd make future maintenance easier, if feasible.

This is the only change of note that isn't necessarily cosmetic, I think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/menu.py
@@ -178,7 +266,7 @@ def get_nodes(self, request):
178 266
179 267 # cache view perms
180 268 visible_pages = get_visible_pages(request, pages, site)
181   -
  269 + #print "vis pages %s" % visible_pages
2

This should go :) also should've used import pdb; pdb.set_trace(), it's a much better tool for verifying (says someone who used to live and die by printing) data.

Chris Glass Collaborator

I could not agree more :p

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/menu.py
@@ -295,7 +381,7 @@ class SoftRootCutter(Modifier):
295 381 usually don’t want to present site visitors with deep menus of nested
296 382 items.
297 383
298   - For example, you’re on the page “Introduction to Bleeding, so the menu
  384 + For example, you’re on the page “Introduction to Bleeding�?, so the menu
1

I don't know if this is just the git/github diff, but there's a crazy character being displayed to me here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/menu.py
@@ -326,7 +412,7 @@ class SoftRootCutter(Modifier):
326 412
327 413 which is frankly overwhelming.
328 414
329   - By making “Department of Mediaeval Surgery a soft root, the menu
  415 + By making “Department of Mediaeval Surgery�? a soft root, the menu
1

Crazy character again. Disregard if it's a false positive, as above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/models/pagemodel.py
@@ -680,17 +680,20 @@ def get_template_name(self):
680 680 def has_view_permission(self, request):
681 681 from cms.models.permissionmodels import PagePermission, GlobalPagePermission
682 682 from cms.utils.plugins import current_site
683   - # staff is allowed to see everything
684   - if request.user.is_staff and settings.CMS_PUBLIC_FOR in ('staff', 'all'):
685   - return True
  683 +
  684 + # staff is not allowed to see everything
  685 + # only the not restricted pages
  686 + # so this block is useless
  687 + #@FIXME: cleanup
1

Do what the FIXME says and dump the commented out code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/utils/permissions.py
@@ -98,11 +98,18 @@ def has_page_change_permission(request):
98 98 return False
99 99
100 100 def get_any_page_view_permissions(request, page):
101   - from cms.utils.plugins import current_site
102   - return PagePermission.objects.filter(
103   - page__pk=page.pk,
104   - page__site=current_site(request),
105   - can_view=True)
  101 + """
  102 + Used by the admin template tag is_restricted
  103 + """
  104 + #this block covers only the direct associated pages
  105 + #and do not show the inherited grant view permissions
  106 + #from cms.utils.plugins import current_site
1

Get rid of commented out code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
FrankBie2

corrected your remarks

Frank Bieniek

Are we now ready to go? Feedback welcome. Thx

cms/menu.py
((44 lines not shown))
27 36 for page in pages:
  37 + # taken from for_page as multipe at once version
  38 + if page.level == None:
  39 + # page.level might be None, set it to an invalid value
5
Jonas Obrist Collaborator
ojii added a note

in what (valid) case will page.level be None? Isn't this a bug?

If it is a bug, here is not the place to fix mptt functions, just get the menu blocker solved

Some Tests use unsaved Page() instances, there it breaks.

Jonas Obrist Collaborator
ojii added a note

I see, so shouldn't we fix the tests?

Frank Bieniek
FrankBie added a note
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Jonas Obrist ojii commented on the diff
cms/tests/menu.py
... ... @@ -857,13 +864,11 @@ def test_authed_basic_perm_num_queries(self):
857 864 page.level = 0
858 865 page.tree_id = 1
859 866 pages = [page]
860   - with self.assertNumQueries(4):
  867 + with self.assertNumQueries(2):
2
Jonas Obrist Collaborator
ojii added a note

nice

Chris Glass Collaborator

shiny :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/tests/menu.py
... ... @@ -836,6 +841,8 @@ def test_authed_basic_perm(self):
836 841 with SettingsOverride(CMS_PUBLIC_FOR='staff'):
837 842 user = User.objects.create_user('user', 'user@domain.com', 'user')
838 843 user.user_permissions.add(Permission.objects.get(codename='view_page'))
  844 + user.is_staff = True
  845 + user.save()
3
Jonas Obrist Collaborator
ojii added a note

don't use create_user above, saves us 1 query and makes tests a bit faster

Do not get what you mean?

Jonas Obrist Collaborator
ojii added a note

create_user is a query, so is .save(), just making a User instance (user = User()....) and then .save() = half the queries

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/tests/menu_page_viewperm.py
((256 lines not shown))
  256 + self.assertContains( response, "href=\"/en/page_b/page_b_a/\"" )
  257 + self.assertContains( response, "href=\"/en/page_b/page_b_b/\"" )
  258 + self.assertContains( response, "href=\"/en/page_b/page_b_b/page_b_b_a/\"" )
  259 + self.assertContains( response, "href=\"/en/page_b/page_b_b/page_b_b_b/\"" )
  260 + self.assertContains( response, "href=\"/en/page_b/page_b_b/page_b_b_c/\"" )
  261 + self.assertContains( response, "href=\"/en/page_b/page_b_c/\"" )
  262 + self.assertContains( response, "href=\"/en/page_b/page_b_d/\"" )
  263 + self.assertContains( response, "href=\"/en/page_b/page_b_d/page_b_d_a/\"" )
  264 + self.assertContains( response, "href=\"/en/page_b/page_b_d/page_b_d_b/\"" )
  265 + self.assertContains( response, "href=\"/en/page_b/page_b_d/page_b_d_c/\"" )
  266 + self.assertContains( response, "href=\"/en/page_c/\"" )
  267 + self.assertContains( response, "href=\"/en/page_d/\"" )
  268 + self.assertContains( response, "href=\"/en/page_d/page_d_a/\"" )
  269 + self.assertContains( response, "href=\"/en/page_d/page_d_b/\"" )
  270 + self.assertContains( response, "href=\"/en/page_d/page_d_c/\"" )
  271 + self.assertContains( response, "href=\"/en/page_d/page_d_d/\"" )
5
Jonas Obrist Collaborator
ojii added a note

testing menus should really not be done like this, but rather using the python APIs and checking the navigation nodes returned.

We need to test the rendered results based on the login states, that is why it makes more sense to check the final results.
This way the final result is checked and not an inbetween step.

Jonas Obrist Collaborator
ojii added a note

checking html is very unreliable. checking the actual node structure returned by the menu apis is better.

Sure, but in the tests here I would have to load all the pages from the db for every test, to compare them against the menu api result.
Fake the login, and setup the context manually? - Or do I oversee something?

Replace the html check with api visible pages checks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
FrankBie2

I have changed all the tests to check the visibile_pages and do access checks according to the group settings.

Keryn Knight kezabelle commented on the diff
cms/menu.py
((44 lines not shown))
27 36 for page in pages:
  37 + # taken from for_page as multipe at once version
  38 + if page.level == None:
  39 + # page.level might be None, set it to an invalid value
  40 + page.level = -1
  41 +
28 42 page_q = Q(page__tree_id=page.tree_id) & (
29 43 Q(page=page)
30 44 | (Q(page__level__lt=page.level) & (Q(grant_on=ACCESS_DESCENDANTS) | Q(grant_on=ACCESS_PAGE_AND_DESCENDANTS)))
31 45 | (Q(page__level=page.level - 1) & (Q(grant_on=ACCESS_CHILDREN) | Q(grant_on=ACCESS_PAGE_AND_CHILDREN)))
32 46 )
4

Did you end up adding an equivalent test for this equivalent of the alleged problem in #1113? I don't know if this query would be affected by that issue, but given the code is the same I'm just asking for the sake of completeness, and there's so many tests I can't see the wood for the trees.

I have not encountered the problem, during all test variations, but have not added a specific testcase covering it.

The django cms menu is a complex beast, if permissions is turned on.

cms_public_for: staff, all = 2 possibilities (a)
authenticated, anonymour user = 2 possibilities (b)
user_view_perm to a page, yes/no = 2 possibilities (c)
and the grant_on options page, child, page+child, descendant, descendant+page = 5 possibilities (d)
a*b*c*d tests to cover some basic settings.

Just added a test for #1113

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
FrankBie2

tests are now fixed in tests.menu

Collaborator

awesome!

Jonas Obrist
Collaborator

Okay reviewed all the code changes and looks good so far, now only tests left to review and it's peanut butter merging time!

cms/menu.py
((67 lines not shown))
  56 + restricted_pages[perm.page.pk].append(perm)
  57 + # add children
  58 + if perm.grant_on in [ACCESS_CHILDREN, ACCESS_PAGE_AND_CHILDREN]:
  59 + child_ids = perm.page.get_children().values_list('id', flat=True)
  60 + for id in child_ids:
  61 + restricted_pages[id].append(perm)
  62 + # add descendants
  63 + elif perm.grant_on in [ACCESS_DESCENDANTS, ACCESS_PAGE_AND_DESCENDANTS]:
  64 + child_ids = perm.page.get_descendants().values_list('id', flat=True)
  65 + for id in child_ids:
  66 + restricted_pages[id].append(perm)
  67 + # anonymous
  68 + # no restriction applied at all
  69 + if (not is_auth_user and
  70 + is_setting_public_all and
  71 + len(restricted_pages) == 0):
2
Jonas Obrist Collaborator
ojii added a note

here, on line 88 and 94 you check len(restricted_pages) ==0 although just checking for restriced_pages would suffice (empty lists are False in boolean checks, non-empty ones True). I would assume that if some_list is slightly better optimized in Python than if len(somelist) == 0.

Frank Bieniek
FrankBie added a note

fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/menu.py
((93 lines not shown))
46 80 global_page_perm_q = Q(
47 81 Q(user=request.user) | Q(group__user=request.user)
48 82 ) & Q(can_view=True) & Q(Q(sites__in=[site.pk]) | Q(sites__isnull=True))
49 83 global_view_perms = GlobalPagePermission.objects.filter(global_page_perm_q).exists()
  84 +
  85 + #no page perms edgcase - all visible
  86 + if ((is_setting_public_all or (
  87 + is_setting_public_staff and request.user.is_staff))and
  88 + len(restricted_pages) == 0 and
  89 + global_view_perms == False):
  90 + return [page.pk for page in pages]
  91 + #no page perms edgcase - none visible
  92 + elif (is_setting_public_staff and
  93 + not request.user.is_staff and
  94 + len(restricted_pages) == 0 and
  95 + global_view_perms == False):
1
Jonas Obrist Collaborator
ojii added a note

Please just put and global_view_perms, since according to PEP8 "Don't compare boolean values to True or False using =="

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/menu.py
((9 lines not shown))
60 108 """
61   - for perm in restricted_pages[page.pk]:
62   - if perm.user_id == request.user.pk:
63   - return True
64   - for perm in restricted_pages[page.pk]:
  109 + user_pk = request.user.pk
  110 + page_pk = page.pk
  111 + has_perm = False
  112 + for perm in restricted_pages[page_pk]:
  113 + if perm.user_id == user_pk:
  114 + has_perm = True
  115 +
  116 + for perm in restricted_pages[page_pk]:
1
Jonas Obrist Collaborator
ojii added a note

why is there a second loop here? why not do the if perm.user_id == user_pk and if not perm.group_id checks in the same loop?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/menu.py
((22 lines not shown))
65 117 if not perm.group_id:
66 118 continue
67   - if request.user.pk in perm.group.user_set.values_list('id', flat=True):
68   - return True
69   - return False
  119 + group_user_ids = perm.group.user_set.values_list('pk', flat=True)
  120 + if user_pk in group_user_ids and len(group_user_ids) > 0 :
1
Jonas Obrist Collaborator
ojii added a note

why is there a check for len(group_user_ids) > 0? user_pk can only ever be in group_user_ids if group_user_ids has items (eg, it's len is bigger than 0).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/tests/menu_page_viewperm.py
((197 lines not shown))
  197 + group = Group.objects.get(name__iexact=self.GROUPNAME_3)
  198 + PagePermission.objects.create(can_view=True, group=group, page=page, grant_on=ACCESS_PAGE_AND_DESCENDANTS)
  199 +
  200 + page = Page.objects.get(title_set__title="page_b_b")
  201 + group = Group.objects.get(name__iexact=self.GROUPNAME_4)
  202 + PagePermission.objects.create(can_view=True, group=group, page=page, grant_on=ACCESS_DESCENDANTS)
  203 +
  204 + page = Page.objects.get(title_set__title="page_d")
  205 + group = Group.objects.get(name__iexact=self.GROUPNAME_5)
  206 + PagePermission.objects.create(can_view=True, group=group, page=page, grant_on=ACCESS_PAGE)
  207 +
  208 + self.assertEquals(5, PagePermission.objects.all().count())
  209 + self.assertEquals(0, GlobalPagePermission.objects.all().count())
  210 +
  211 +
  212 + def _check_url_page_found(self, url):
1
Jonas Obrist Collaborator
ojii added a note

nothing major, but since these (the _check... methods) are essentially assertions, maybe rename them to something like assertPageFound, assertPageNotFound etc. Just to keep it consistent with the rest of unittest and make it very clear below that these are assertions that could fail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/tests/menu_page_viewperm.py
((253 lines not shown))
  253 + helper function to check the expected_granted_pages are
  254 + not in the restricted_pages list and
  255 + all visible pages are in the expected_granted_pages
  256 + """
  257 + # log the user in if present
  258 + user = None
  259 + if username is not None:
  260 + user = User.objects.get(username__iexact=username)
  261 + request = self.get_request(user)
  262 + visible_page_ids = get_visible_pages(request, all_pages, self.site)
  263 + self.assertTrue(len(visible_page_ids) == len(expected_granted_pages))
  264 + public_page_ids = Page.objects.filter(title_set__title__in=expected_granted_pages).values_list('id', flat=True)
  265 + restricted_pages = Page.objects.exclude(title_set__title__in=expected_granted_pages).values_list('id', flat=True)
  266 + self._check_node_memberships(visible_page_ids, restricted_pages, public_page_ids)
  267 +
  268 + def get_request(self, user=None):
1
Jonas Obrist Collaborator
ojii added a note

This should probably be replaced with or use https://github.com/divio/django-cms/blob/develop/cms/test_utils/util/request_factory.py which is a backport of Djangos RequestFactory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/tests/menu_page_viewperm.py
((276 lines not shown))
  276 +
  277 +class ViewPermissionComplexMenuAllNodesTests(ViewPermissionTests):
  278 + """
  279 + Test CMS_PUBLIC_FOR=all group access and menu nodes rendering
  280 + """
  281 + settings_overrides = {
  282 + 'CMS_MODERATOR': False,
  283 + 'CMS_PERMISSION': True,
  284 + 'CMS_PUBLIC_FOR': 'all',
  285 + }
  286 + def setUp(self):
  287 + super(ViewPermissionComplexMenuAllNodesTests, self).setUp()
  288 + self.site = Site()
  289 + self.site.pk = 1
  290 +
  291 + def tearDown(self):
1
Jonas Obrist Collaborator
ojii added a note

needed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/tests/menu_page_viewperm.py
((286 lines not shown))
  286 + def setUp(self):
  287 + super(ViewPermissionComplexMenuAllNodesTests, self).setUp()
  288 + self.site = Site()
  289 + self.site.pk = 1
  290 +
  291 + def tearDown(self):
  292 + super(ViewPermissionComplexMenuAllNodesTests, self).tearDown()
  293 +
  294 + def test_public_pages_anonymous_norestrictions(self):
  295 + """
  296 + All pages are visible to an anonymous user
  297 + """
  298 + all_pages = self._setup_tree_pages()
  299 + request = self.get_request()
  300 + visible_page_ids = get_visible_pages(request, all_pages, self.site)
  301 + is_same = (len(all_pages) == len(visible_page_ids))
1
Jonas Obrist Collaborator
ojii added a note

should use assertEqual(len(all_pages), len(visible_page_ids)).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/tests/menu_page_viewperm.py
((329 lines not shown))
  329 + granted = ['page_a',
  330 + 'page_c',
  331 + #group_1
  332 + 'page_b', #page_id b has page_id and children restricted - group 1
  333 + 'page_b_a',
  334 + 'page_b_b', #page_id b_b children restricted - group 2
  335 + 'page_b_c',
  336 + 'page_b_d',
  337 + # not restricted
  338 + 'page_d_a',
  339 + 'page_d_b',
  340 + 'page_d_c',
  341 + 'page_d_d'
  342 + ]
  343 + self._check_grant_visiblity(all_pages, granted,username='user_1')
  344 + login_ok = self.client.login(username='user_1', password='user_1')
1
Jonas Obrist Collaborator
ojii added a note

this potentially leaks state (logged in users), so you should create a new instance of django.test.client.Client (and don't put it on self) to prevent that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/tests/menu_page_viewperm.py
((330 lines not shown))
  330 + 'page_c',
  331 + #group_1
  332 + 'page_b', #page_id b has page_id and children restricted - group 1
  333 + 'page_b_a',
  334 + 'page_b_b', #page_id b_b children restricted - group 2
  335 + 'page_b_c',
  336 + 'page_b_d',
  337 + # not restricted
  338 + 'page_d_a',
  339 + 'page_d_b',
  340 + 'page_d_c',
  341 + 'page_d_d'
  342 + ]
  343 + self._check_grant_visiblity(all_pages, granted,username='user_1')
  344 + login_ok = self.client.login(username='user_1', password='user_1')
  345 + self.assertEqual(login_ok , True)
1
Jonas Obrist Collaborator
ojii added a note

self.assertTrue(login_ok)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cms/tests/menu_page_viewperm.py
((563 lines not shown))
  563 + """
  564 + Setup group_6_ACCESS_PAGE view restriction
  565 + """
  566 + page = Page.objects.get(title_set__title="page_6")
  567 + group = Group.objects.get(name__iexact=self.GROUPNAME_6)
  568 + PagePermission.objects.create(can_view=True, group=group, page=page, grant_on=ACCESS_PAGE)
  569 +
  570 + def test_pageforbug(self):
  571 + all_pages=self._setup_pages()
  572 + self._setup_user()
  573 + self._setup_permviewbug()
  574 + for page in all_pages:
  575 + perm = PagePermission.objects.for_page(page=page)
  576 + # only page_6 has a permission assigned
  577 + if page.get_title() == 'page_6':
  578 + self.assertTrue(len(perm)==1)
1
Jonas Obrist Collaborator
ojii added a note

here and on 580 again assertEqual(len(perm), 1) and assertEqual(len(perm), 0)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
FrankBie2

fixed

Jonas Obrist
Collaborator

alright:

+1 for merge into develop and develop-2.2.1

Chris Glass chrisglass merged commit 2297552 into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 22 unique commits by 2 authors.

Nov 28, 2011
FrankBie2 FrankBie2 test various combinations of viewpermission
considering grant_on types, groups, anonymous user and page access and rendered menus
18513b2
Nov 29, 2011
FrankBie2 FrankBie2 menu rendering tests for grant options
ACCESS_PAGE_AND_CHILDREN
ACCESS_CHILDREN
ACCESS_PAGE_AND_DESCENDANTS
ACCESS_DESCENDANTS
ACCESS_PAGE

for logged in users and anonymous users
18ac28d
FrankBie2 FrankBie2 menu rendering tests for grant options
ACCESS_PAGE_AND_CHILDREN
ACCESS_CHILDREN
ACCESS_PAGE_AND_DESCENDANTS
ACCESS_DESCENDANTS
ACCESS_PAGE

for logged in users and anonymous users
7dd2341
Nov 30, 2011
Frank Bieniek FrankBie menu shows now right pages for different group and granttypes 35c0b88
Dec 01, 2011
FrankBie2 FrankBie2 refactor 9d1202b
FrankBie2 FrankBie2 first staff tests add - still failing dde1df2
Dec 02, 2011
FrankBie2 FrankBie2 more staff tests 118385d
FrankBie2 FrankBie2 cms public for staff : but no staff user test, menu not rendered corr…
…ectly yet
0aa3a1a
FrankBie2 FrankBie2 cms public for staff : but no staff user test, menu not rendered corr…
…ectly yet
16f9660
FrankBie2 FrankBie2 less queries 16f9934
FrankBie2 FrankBie2 removed edgecases e9e36e6
FrankBie2 FrankBie2 show the real restricted pages 57d35f1
FrankBie2 FrankBie2 some refactor 1b9d6c4
FrankBie2 FrankBie2 group 5 staff tests e3f7488
FrankBie2 FrankBie2 removed some debug c79d51a
FrankBie2 FrankBie2 improved tests for staff global and none global 37c9766
Jan 02, 2012
FrankBie2 FrankBie2 code cleanup a5a7092
Jan 04, 2012
FrankBie2 FrankBie2 add test for issue #1113 7107eb4
FrankBie2 FrankBie2 fixed tests that had page.level = None db0fdae
FrankBie2 FrankBie2 pep8 fixes 942cf4f
Jan 05, 2012
FrankBie2 FrankBie2 code cleanup fc4de6d
Jan 06, 2012
FrankBie2 FrankBie2 update testcase "View permissions for != 'Current Page' may not work …
…as expected."

issue divio#1113
a9f6a3b
This page is out of date. Refresh to see the latest.
145 cms/menu.py
@@ -2,51 +2,99 @@
2 2 from collections import defaultdict
3 3 from cms.apphook_pool import apphook_pool
4 4 from cms.models.moderatormodels import (ACCESS_DESCENDANTS,
5   - ACCESS_PAGE_AND_DESCENDANTS, ACCESS_CHILDREN, ACCESS_PAGE_AND_CHILDREN)
  5 + ACCESS_PAGE_AND_DESCENDANTS, ACCESS_CHILDREN, ACCESS_PAGE_AND_CHILDREN, ACCESS_PAGE)
6 6 from cms.models.permissionmodels import PagePermission, GlobalPagePermission
7 7 from cms.models.titlemodels import Title
8 8 from cms.utils import get_language_from_request
9 9 from cms.utils.i18n import get_fallback_languages
10 10 from cms.utils.moderator import get_page_queryset, get_title_queryset
11 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 12 from menus.base import Menu, NavigationNode, Modifier
16 13 from menus.menu_pool import menu_pool
17 14
  15 +from django.conf import settings
  16 +from django.contrib.sites.models import Site
  17 +from django.db.models.query_utils import Q
  18 +from django.contrib.auth.models import Permission
18 19
19 20 def get_visible_pages(request, pages, site=None):
20   - # This code is basically a many-pages-at-once version of
21   - # Page.has_view_permission, check there to see wtf is going on here.
22   - if request.user.is_staff and settings.CMS_PUBLIC_FOR in ('staff', 'all'):
23   - return [page.pk for page in pages]
24   - page_ids = []
  21 + """
  22 + This code is basically a many-pages-at-once version of
  23 + Page.has_view_permission.
  24 + pages contains all published pages
  25 + check if there is ANY restriction
  26 + that needs a permission page visibility calculation
  27 + """
  28 + is_setting_public_all = settings.CMS_PUBLIC_FOR == 'all'
  29 + is_setting_public_staff = settings.CMS_PUBLIC_FOR == 'staff'
  30 + is_auth_user = request.user.is_authenticated()
25 31
  32 + visible_page_ids = []
  33 + restricted_pages = defaultdict(list)
26 34 pages_perms_q = Q()
  35 +
27 36 for page in pages:
  37 + # taken from for_page as multiple at once version
28 38 page_q = Q(page__tree_id=page.tree_id) & (
29 39 Q(page=page)
30 40 | (Q(page__level__lt=page.level) & (Q(grant_on=ACCESS_DESCENDANTS) | Q(grant_on=ACCESS_PAGE_AND_DESCENDANTS)))
31 41 | (Q(page__level=page.level - 1) & (Q(grant_on=ACCESS_CHILDREN) | Q(grant_on=ACCESS_PAGE_AND_CHILDREN)))
32 42 )
33 43 pages_perms_q |= page_q
  44 +
  45 +
34 46 pages_perms_q &= Q(can_view=True)
35 47 page_permissions = PagePermission.objects.filter(pages_perms_q).select_related('page', 'group__users')
36   -
37   - restricted_pages = defaultdict(list)
  48 +
38 49 for perm in page_permissions:
39   - restricted_pages[perm.page.pk].append(perm)
  50 + # collect the pages that are affected by permissions
  51 + if perm is not None and perm not in restricted_pages[perm.page.pk]:
  52 + # affective restricted pages gathering
  53 + # using mptt functions
  54 + # add the page with the perm itself
  55 + if perm.grant_on in [ACCESS_PAGE, ACCESS_PAGE_AND_CHILDREN ,ACCESS_PAGE_AND_DESCENDANTS]:
  56 + restricted_pages[perm.page.pk].append(perm)
  57 + # add children
  58 + if perm.grant_on in [ACCESS_CHILDREN, ACCESS_PAGE_AND_CHILDREN]:
  59 + child_ids = perm.page.get_children().values_list('id', flat=True)
  60 + for id in child_ids:
  61 + restricted_pages[id].append(perm)
  62 + # add descendants
  63 + elif perm.grant_on in [ACCESS_DESCENDANTS, ACCESS_PAGE_AND_DESCENDANTS]:
  64 + child_ids = perm.page.get_descendants().values_list('id', flat=True)
  65 + for id in child_ids:
  66 + restricted_pages[id].append(perm)
  67 + # anonymous
  68 + # no restriction applied at all
  69 + if (not is_auth_user and
  70 + is_setting_public_all and
  71 + not restricted_pages):
  72 + return [page.pk for page in pages]
40 73
  74 +
41 75 if site is None:
42 76 site = current_site(request)
43 77
44   - if request.user.is_authenticated():
45   - #return self.filter(Q(user=user) | Q(group__user=user))
  78 + # authenticated user and global permission
  79 + if is_auth_user:
46 80 global_page_perm_q = Q(
47 81 Q(user=request.user) | Q(group__user=request.user)
48 82 ) & Q(can_view=True) & Q(Q(sites__in=[site.pk]) | Q(sites__isnull=True))
49 83 global_view_perms = GlobalPagePermission.objects.filter(global_page_perm_q).exists()
  84 +
  85 + #no page perms edgcase - all visible
  86 + if ((is_setting_public_all or (
  87 + is_setting_public_staff and request.user.is_staff))and
  88 + not restricted_pages and
  89 + not global_view_perms):
  90 + return [page.pk for page in pages]
  91 + #no page perms edgcase - none visible
  92 + elif (is_setting_public_staff and
  93 + not request.user.is_staff and
  94 + not restricted_pages and
  95 + not global_view_perms):
  96 + return []
  97 +
50 98
51 99 def has_global_perm():
52 100 if has_global_perm.cache < 0:
@@ -54,38 +102,56 @@ def has_global_perm():
54 102 return bool(has_global_perm.cache)
55 103 has_global_perm.cache = -1
56 104
57   - def has_permission(page):
  105 + def has_permission_membership(page):
58 106 """
59   - PagePermission tests
  107 + PagePermission user group membership tests
60 108 """
61   - for perm in restricted_pages[page.pk]:
62   - if perm.user_id == request.user.pk:
63   - return True
64   - for perm in restricted_pages[page.pk]:
  109 + user_pk = request.user.pk
  110 + page_pk = page.pk
  111 + has_perm = False
  112 + for perm in restricted_pages[page_pk]:
  113 + if perm.user_id == user_pk:
  114 + has_perm = True
65 115 if not perm.group_id:
66 116 continue
67   - if request.user.pk in perm.group.user_set.values_list('id', flat=True):
68   - return True
69   - return False
  117 + group_user_ids = perm.group.user_set.values_list('pk', flat=True)
  118 + if user_pk in group_user_ids:
  119 + has_perm = True
  120 + return has_perm
70 121
71 122 for page in pages:
  123 + to_add = False
  124 + # default to false, showing a restricted page is bad
  125 + # explicitly check all the conditions
  126 + # of settings and permissions
72 127 is_restricted = page.pk in restricted_pages
73   -
74   - if request.user.is_authenticated():
  128 + # restricted_pages contains as key any page.pk that is
  129 + # affected by a permission grant_on
  130 + if is_auth_user:
75 131 # a global permission was given to the request's user
76 132 if global_view_perms:
77   - page_ids.append(page.pk)
78   - # authenticated user, no restriction and public for all
79   - elif settings.CMS_PUBLIC_FOR == 'all':
80   - page_ids.append(page.pk)
81   - elif has_permission(page):
82   - page_ids.append(page.pk)
  133 + to_add = True
  134 + # setting based handling of unrestricted pages
  135 + elif not is_restricted and (
  136 + is_setting_public_all or (
  137 + is_setting_public_staff and request.user.is_staff)
  138 + ):
  139 + # authenticated user, no restriction and public for all
  140 + # or
  141 + # authenticated staff user, no restriction and public for staff
  142 + to_add = True
  143 + # check group and user memberships to restricted pages
  144 + elif is_restricted and has_permission_membership(page):
  145 + to_add = True
83 146 elif has_global_perm():
84   - page_ids.append(page.pk)
85   - elif not is_restricted and settings.CMS_PUBLIC_FOR == 'all':
86   - # anonymous user, no restriction saved in database
87   - page_ids.append(page.pk)
88   - return page_ids
  147 + to_add = True
  148 + # anonymous user, no restriction
  149 + elif not is_restricted and is_setting_public_all:
  150 + to_add = True
  151 + # store it
  152 + if to_add:
  153 + visible_page_ids.append(page.pk)
  154 + return visible_page_ids
89 155
90 156 def page_to_node(page, home, cut):
91 157 '''
@@ -178,7 +244,6 @@ def get_nodes(self, request):
178 244
179 245 # cache view perms
180 246 visible_pages = get_visible_pages(request, pages, site)
181   -
182 247 for page in pages:
183 248 # Pages are ordered by tree_id, therefore the first page is the root
184 249 # of the page tree (a.k.a "home")
@@ -190,8 +255,6 @@ def get_nodes(self, request):
190 255 page.home_pk_cache = home.pk
191 256 if first and page.pk != home.pk:
192 257 home_cut = True
193   - elif not settings.CMS_PUBLIC_FOR == 'all':
194   - continue
195 258 if (page.parent_id == home.pk or page.parent_id in home_children) and home_cut:
196 259 home_children.append(page.pk)
197 260 if (page.pk == home.pk and home.in_navigation) or page.pk != home.pk:
@@ -295,7 +358,7 @@ class SoftRootCutter(Modifier):
295 358 usually don’t want to present site visitors with deep menus of nested
296 359 items.
297 360
298   - For example, you’re on the page “Introduction to Bleeding”, so the menu
  361 + For example, you’re on the page -Introduction to Bleeding-?, so the menu
299 362 might look like this:
300 363
301 364 School of Medicine
@@ -326,7 +389,7 @@ class SoftRootCutter(Modifier):
326 389
327 390 which is frankly overwhelming.
328 391
329   - By making “Department of Mediaeval Surgery” a soft root, the menu
  392 + By making -Department of Mediaeval Surgery-? a soft root, the menu
330 393 becomes much more manageable:
331 394
332 395 Department of Mediaeval Surgery
20 cms/models/managers.py
@@ -343,13 +343,23 @@ def for_page(self, page):
343 343 """
344 344 from cms.models import ACCESS_DESCENDANTS, ACCESS_CHILDREN,\
345 345 ACCESS_PAGE_AND_CHILDREN, ACCESS_PAGE_AND_DESCENDANTS
  346 + # code taken from
  347 + # https://github.com/divio/django-cms/issues/1113#issuecomment-3376790
  348 + q_tree = Q(page__tree_id=page.tree_id)
  349 + q_page = Q(page=page)
346 350
347   - q = Q(page__tree_id=page.tree_id) & (
348   - Q(page=page)
349   - | (Q(page__level__lt=page.level) & (Q(grant_on=ACCESS_DESCENDANTS) | Q(grant_on=ACCESS_PAGE_AND_DESCENDANTS)))
350   - | (Q(page__level=page.level - 1) & (Q(grant_on=ACCESS_CHILDREN) | Q(grant_on=ACCESS_PAGE_AND_CHILDREN)))
351   - )
  351 + # NOTE: '... or 0' is used for test cases,
  352 + # if the page is not saved through mptt
  353 + left_right = {
  354 + 'page__%s__lte' % page._meta.left_attr: getattr(page, page._meta.left_attr) or 0,
  355 + 'page__%s__gte' % page._meta.right_attr: getattr(page, page._meta.right_attr) or 0,
  356 + }
  357 + q_parents = Q(**left_right)
  358 + q_desc = (Q(page__level__lt=page.level) & (Q(grant_on=ACCESS_DESCENDANTS) | Q(grant_on=ACCESS_PAGE_AND_DESCENDANTS)))
  359 + q_kids = (Q(page__level=page.level - 1) & (Q(grant_on=ACCESS_CHILDREN) | Q(grant_on=ACCESS_PAGE_AND_CHILDREN)))
  360 + q = q_tree & q_parents & (q_page | q_desc | q_kids)
352 361 return self.filter(q).order_by('page__level')
  362 +
353 363
354 364 class PagePermissionsPermissionManager(models.Manager):
355 365 """Page permissions permission manager.
30 cms/models/pagemodel.py
@@ -680,17 +680,12 @@ def get_template_name(self):
680 680 def has_view_permission(self, request):
681 681 from cms.models.permissionmodels import PagePermission, GlobalPagePermission
682 682 from cms.utils.plugins import current_site
683   - # staff is allowed to see everything
684   - if request.user.is_staff and settings.CMS_PUBLIC_FOR in ('staff', 'all'):
685   - return True
686   -
  683 +
687 684 if not self.publisher_is_draft and self.publisher_public:
688 685 return self.publisher_public.has_view_permission(request)
689 686 # does any restriction exist?
690   - # direct
691 687 # inherited and direct
692   - is_restricted = PagePermission.objects.for_page(self).filter(can_view=True).exists()
693   -
  688 + is_restricted = PagePermission.objects.for_page(page=self).filter(can_view=True).exists()
694 689 if request.user.is_authenticated():
695 690 site = current_site(request)
696 691 global_perms_q = Q(can_view=True) & Q(
@@ -698,13 +693,26 @@ def has_view_permission(self, request):
698 693 )
699 694 global_view_perms = GlobalPagePermission.objects.with_user(
700 695 request.user).filter(global_perms_q).exists()
  696 +
701 697 # a global permission was given to the request's user
702 698 if global_view_perms:
703 699 return True
704   - # authenticated user, no restriction and public for all
705   - if (not is_restricted and not global_view_perms and
706   - settings.CMS_PUBLIC_FOR == 'all'):
707   - return True
  700 +
  701 + elif not is_restricted:
  702 + if ((settings.CMS_PUBLIC_FOR == 'all') or
  703 + (settings.CMS_PUBLIC_FOR == 'staff' and
  704 + request.user.is_staff)):
  705 + return True
  706 +
  707 + # a restricted page and an authenticated user
  708 + elif is_restricted:
  709 + opts = self._meta
  710 + codename = '%s.view_%s' % (opts.app_label, opts.object_name.lower())
  711 + user_perm = request.user.has_perm(codename)
  712 + generic_perm = self.has_generic_permission(request, "view")
  713 + return (user_perm or generic_perm)
  714 +
  715 +
708 716 else:
709 717 #anonymous user
710 718 if is_restricted or not settings.CMS_PUBLIC_FOR == 'all':
4 cms/tests/__init__.py
@@ -27,4 +27,6 @@
27 27 from cms.tests.toolbar import *
28 28 from cms.tests.urlutils import *
29 29 from cms.tests.views import *
30   -from cms.tests.management import *
  30 +from cms.tests.management import *
  31 +from cms.tests.menu_page_viewperm import *
  32 +from cms.tests.menu_page_viewperm_staff import *
43 cms/tests/menu.py
@@ -10,14 +10,15 @@
10 10 from cms.test_utils.util.context_managers import (SettingsOverride,
11 11 LanguageOverride)
12 12 from cms.test_utils.util.mock import AttributeObject
  13 +from menus.base import NavigationNode
  14 +from menus.menu_pool import menu_pool, _build_nodes_inner_for_one_menu
  15 +from menus.utils import mark_descendants, find_selected, cut_levels
  16 +
13 17 from django.conf import settings
14 18 from django.contrib.auth.models import AnonymousUser, User, Permission, Group
15 19 from django.contrib.contenttypes.models import ContentType
16 20 from django.contrib.sites.models import Site
17 21 from django.template import Template, TemplateSyntaxError
18   -from menus.base import NavigationNode
19   -from menus.menu_pool import menu_pool, _build_nodes_inner_for_one_menu
20   -from menus.utils import mark_descendants, find_selected, cut_levels
21 22
22 23
23 24 class BaseMenuTest(SettingsOverrideTestCase):
@@ -762,6 +763,8 @@ def test_public_for_all_staff(self):
762 763 request.user.is_staff = True
763 764 page = Page()
764 765 page.pk = 1
  766 + page.level = 0
  767 + page.tree_id = 1
765 768 pages = [page]
766 769 result = get_visible_pages(request, pages)
767 770 self.assertEqual(result, [1])
@@ -771,8 +774,14 @@ def test_public_for_all_staff_assert_num_queries(self):
771 774 request.user.is_staff = True
772 775 page = Page()
773 776 page.pk = 1
  777 + page.level = 0
  778 + page.tree_id = 1
774 779 pages = [page]
775   - with self.assertNumQueries(0):
  780 + with self.assertNumQueries(1):
  781 + """
  782 + The queries are:
  783 + PagePermission count query
  784 + """
776 785 get_visible_pages(request, pages)
777 786
778 787 def test_public_for_all(self):
@@ -834,7 +843,10 @@ def test_unauthed_num_queries(self):
834 843
835 844 def test_authed_basic_perm(self):
836 845 with SettingsOverride(CMS_PUBLIC_FOR='staff'):
837   - user = User.objects.create_user('user', 'user@domain.com', 'user')
  846 + user = User()
  847 + user.username="test"
  848 + user.is_staff = True
  849 + user.save()
838 850 user.user_permissions.add(Permission.objects.get(codename='view_page'))
839 851 request = self.get_request(user)
840 852 page = Page()
@@ -849,7 +861,10 @@ def test_authed_basic_perm_num_queries(self):
849 861 site = Site()
850 862 site.pk = 1
851 863 with SettingsOverride(CMS_PUBLIC_FOR='staff'):
852   - user = User.objects.create_user('user', 'user@domain.com', 'user')
  864 + user = User()
  865 + user.username="test"
  866 + user.is_staff = True
  867 + user.save()
853 868 user.user_permissions.add(Permission.objects.get(codename='view_page'))
854 869 request = self.get_request(user)
855 870 page = Page()
@@ -857,13 +872,11 @@ def test_authed_basic_perm_num_queries(self):
857 872 page.level = 0
858 873 page.tree_id = 1
859 874 pages = [page]
860   - with self.assertNumQueries(4):
  875 + with self.assertNumQueries(2):
861 876 """
862 877 The queries are:
863   - PagePermission query for affected pages
864   - GlobalpagePermission query for user
865   - Generic django permission lookup
866   - content type lookup by permission lookup
  878 + PagePermission count query
  879 + GlobalpagePermission count query
867 880 """
868 881 get_visible_pages(request, pages, site)
869 882
@@ -890,13 +903,11 @@ def test_authed_no_access_num_queries(self):
890 903 page.level = 0
891 904 page.tree_id = 1
892 905 pages = [page]
893   - with self.assertNumQueries(4):
  906 + with self.assertNumQueries(2):
894 907 """
895 908 The queries are:
896   - PagePermission query for affected pages
897   - GlobalpagePermission query for user
898   - Generic django permission lookup
899   - content type lookup by permission lookup
  909 + View Permission Calculation Query
  910 + globalpagepermissino calculation
900 911 """
901 912 get_visible_pages(request, pages, site)
902 913
596 cms/tests/menu_page_viewperm.py
... ... @@ -0,0 +1,596 @@
  1 +# -*- coding: utf-8 -*-
  2 +from __future__ import with_statement
  3 +
  4 +from cms.api import create_page
  5 +from cms.menu import CMSMenu, get_visible_pages
  6 +from cms.models import Page
  7 +from cms.models import ACCESS_DESCENDANTS, ACCESS_CHILDREN, ACCESS_PAGE
  8 +from cms.models import ACCESS_PAGE_AND_CHILDREN, ACCESS_PAGE_AND_DESCENDANTS
  9 +from cms.models.permissionmodels import GlobalPagePermission, PagePermission
  10 +from cms.models.titlemodels import Title
  11 +from cms.test_utils.testcases import SettingsOverrideTestCase
  12 +
  13 +from django.contrib.sites.models import Site
  14 +from django.contrib.auth.models import AnonymousUser, User, Permission, Group
  15 +from django.db.models import Q
  16 +from django.test.client import Client
  17 +
  18 +
  19 +class ViewPermissionTests(SettingsOverrideTestCase):
  20 + """
  21 + Test various combinations of view permissions pages and menus
  22 + Focus on the different grant types and inheritance options of grant on
  23 + Given the tree:
  24 +
  25 + |- Page_a
  26 + |- Page_b
  27 + | |- Page_b_a
  28 + | |- Page_b_b
  29 + | | |- Page_b_b_a
  30 + | | | |- Page_b_b_a_a
  31 + | | |- Page_b_b_b
  32 + | | |- Page_b_b_c
  33 + | |- Page_b_c
  34 + | |- Page_b_d
  35 + | | |- Page_b_d_a
  36 + | | |- Page_b_d_b
  37 + | | |- Page_b_d_c
  38 + |- Page_c
  39 + |- Page_d
  40 + | |- Page_d_a
  41 + | |- Page_d_b
  42 + | |- Page_d_c
  43 + """
  44 + GROUPNAME_1 = 'group_b_ACCESS_PAGE_AND_CHILDREN'
  45 + GROUPNAME_2 = 'group_b_b_ACCESS_CHILDREN'
  46 + GROUPNAME_3 = 'group_b_ACCESS_PAGE_AND_DESCENDANTS'
  47 + GROUPNAME_4 = 'group_b_b_ACCESS_DESCENDANTS'
  48 + GROUPNAME_5 = 'group_d_ACCESS_PAGE'
  49 +
  50 + def setUp(self):
  51 + self.site = Site()
  52 + self.site.pk = 1
  53 + super(ViewPermissionTests, self).setUp()
  54 +
  55 + def tearDown(self):
  56 + super(ViewPermissionTests, self).tearDown()
  57 +
  58 + def _setup_tree_pages(self):
  59 + stdkwargs = {
  60 + 'template': 'nav_playground.html',
  61 + 'language': 'en',
  62 + 'published': True,
  63 + 'in_navigation': True,
  64 + }
  65 + page_a = create_page("page_a", **stdkwargs) # first page slug is /
  66 + page_b = create_page("page_b", **stdkwargs)
  67 + page_c = create_page("page_c", **stdkwargs)
  68 + page_d = create_page("page_d", **stdkwargs)
  69 +
  70 + page_b_a = create_page("page_b_a", parent=page_b, **stdkwargs)
  71 + page_b_b = create_page("page_b_b", parent=page_b, **stdkwargs)
  72 + page_b_b_a = create_page("page_b_b_a", parent=page_b_b, **stdkwargs)
  73 + page_b_b_b = create_page("page_b_b_b", parent=page_b_b, **stdkwargs)
  74 + page_b_b_c = create_page("page_b_b_c", parent=page_b_b, **stdkwargs)
  75 + page_b_b_a_a = create_page("page_b_b_a_a", parent=page_b_b_a, **stdkwargs)
  76 +
  77 + page_b_c = create_page("page_b_c", parent=page_b, **stdkwargs)
  78 + page_b_d = create_page("page_b_d", parent=page_b, **stdkwargs)
  79 + page_b_d_a = create_page("page_b_d_a", parent=page_b_d, **stdkwargs)
  80 + page_b_d_b = create_page("page_b_d_b", parent=page_b_d, **stdkwargs)
  81 + page_b_d_c = create_page("page_b_d_c", parent=page_b_d, **stdkwargs)
  82 +
  83 + page_d_a = create_page("page_d_a", parent=page_d, **stdkwargs)
  84 + page_d_b = create_page("page_d_b", parent=page_d, **stdkwargs)
  85 + page_d_c = create_page("page_d_c", parent=page_d, **stdkwargs)
  86 + page_d_d = create_page("page_d_d", parent=page_d, **stdkwargs)
  87 +
  88 + return [page_a,
  89 + page_b,
  90 + page_b_a,
  91 + page_b_b,
  92 + page_b_b_a,
  93 + page_b_b_a_a,
  94 + page_b_b_b,
  95 + page_b_b_c,
  96 + page_b_c,
  97 + page_b_d,
  98 + page_b_d_a,
  99 + page_b_d_b,
  100 + page_b_d_c,
  101 + page_c,
  102 + page_d,
  103 + page_d_a,
  104 + page_d_b,
  105 + page_d_c,
  106 + page_d_d,
  107 + ]
  108 +
  109 + def _setup_user_groups(self):
  110 + """
  111 + Setup a group for every grant on ACCESS TYPE
  112 + """
  113 + user = User.objects.create(username='user_1', email='user_1@domain.com', is_active=True, is_staff=True)
  114 + user.set_password(user.username)
  115 + user.save()
  116 + group = Group.objects.create(name=self.GROUPNAME_1)
  117 + group.user_set.add(user)
  118 + group.save()
  119 +
  120 + user = User.objects.create(username='user_1_nostaff', email='user_1_nostaff@domain.com', is_active=True, is_staff=False)
  121 + user.set_password(user.username)
  122 + user.save()
  123 + group.user_set.add(user)
  124 + group.save()
  125 +
  126 + user = User.objects.create(username='user_2', email='user_2@domain.com', is_active=True, is_staff=True)
  127 + user.set_password(user.username)
  128 + user.save()
  129 + group = Group.objects.create(name=self.GROUPNAME_2)
  130 + group.user_set.add(user)
  131 + group.save()
  132 +
  133 + user = User.objects.create(username='user_2_nostaff', email='user_2_nostaff@domain.com', is_active=True, is_staff=False)
  134 + user.set_password(user.username)
  135 + user.save()
  136 + group.user_set.add(user)
  137 + group.save()
  138 +
  139 + user = User.objects.create(username='user_3', email='user_3@domain.com', is_active=True, is_staff=True)
  140 + user.set_password(user.username)
  141 + user.save()
  142 + group = Group.objects.create(name=self.GROUPNAME_3)
  143 + group.user_set.add(user)
  144 + group.save()
  145 +
  146 + user = User.objects.create(username='user_3_nostaff', email='user_3_nostaff@domain.com', is_active=True, is_staff=False)
  147 + user.set_password(user.username)
  148 + user.save()
  149 + group.user_set.add(user)
  150 + group.save()
  151 +
  152 + user = User.objects.create(username='user_4', email='user_4@domain.com', is_active=True, is_staff=True)
  153 + user.set_password(user.username)
  154 + user.save()
  155 + group = Group.objects.create(name=self.GROUPNAME_4)
  156 + group.user_set.add(user)
  157 + group.save()
  158 +
  159 + user = User.objects.create(username='user_4_nostaff', email='user_4_nostaff@domain.com', is_active=True, is_staff=False)
  160 + user.set_password(user.username)
  161 + user.save()
  162 + group.user_set.add(user)
  163 + group.save()
  164 +
  165 + user = User.objects.create(username='user_5', email='user_5@domain.com', is_active=True, is_staff=True)
  166 + user.set_password(user.username)
  167 + user.save()
  168 + group = Group.objects.create(name=self.GROUPNAME_5)
  169 + group.user_set.add(user)
  170 + group.save()
  171 +
  172 + user = User.objects.create(username='user_5_nostaff', email='user_5_nostaff@domain.com', is_active=True, is_staff=False)
  173 + user.set_password(user.username)
  174 + user.save()
  175 + group.user_set.add(user)
  176 + group.save()
  177 +
  178 + user = User.objects.create(username='user_staff', email='user_staff@domain.com', is_active=True, is_staff=True)
  179 + user.set_password(user.username)
  180 + user.save()
  181 +
  182 + self.assertEquals(11, User.objects.all().count())
  183 +
  184 +
  185 + def _setup_view_restrictions(self):
  186 + """
  187 + Setup a view restriction with every type of the grant_on ACCESS_*
  188 + 'group_b_ACCESS_PAGE_AND_CHILDREN'
  189 + 'group_b_b_ACCESS_CHILDREN'
  190 + 'group_b_ACCESS_PAGE_AND_DESCENDANTS'
  191 + 'group_b_b_ACCESS_DESCENDANTS'
  192 + 'group_d_ACCESS_PAGE'
  193 + """
  194 +
  195 + page = Page.objects.get(title_set__title="page_b")
  196 + group = Group.objects.get(name__iexact=self.GROUPNAME_1)
  197 + PagePermission.objects.create(can_view=True, group=group, page=page, grant_on=ACCESS_PAGE_AND_CHILDREN)
  198 +
  199 + page = Page.objects.get(title_set__title="page_b_b")
  200 + group = Group.objects.get(name__iexact=self.GROUPNAME_2)
  201 + PagePermission.objects.create(can_view=True, group=group, page=page, grant_on=ACCESS_CHILDREN)
  202 +
  203 + page = Page.objects.get(title_set__title="page_b")
  204 + group = Group.objects.get(name__iexact=self.GROUPNAME_3)
  205 + PagePermission.objects.create(can_view=True, group=group, page=page, grant_on=ACCESS_PAGE_AND_DESCENDANTS)
  206 +
  207 + page = Page.objects.get(title_set__title="page_b_b")
  208 + group = Group.objects.get(name__iexact=self.GROUPNAME_4)
  209 + PagePermission.objects.create(can_view=True, group=group, page=page, grant_on=ACCESS_DESCENDANTS)
  210 +
  211 + page = Page.objects.get(title_set__title="page_d")
  212 + group = Group.objects.get(name__iexact=self.GROUPNAME_5)
  213 + PagePermission.objects.create(can_view=True, group=group, page=page, grant_on=ACCESS_PAGE)
  214 +
  215 + self.assertEquals(5, PagePermission.objects.all().count())
  216 + self.assertEquals(0, GlobalPagePermission.objects.all().count())
  217 +
  218 + def assertPageFound(self, url, client=None):
  219 + if not client:
  220 + client = Client()
  221 + response = client.get(url)
  222 + self.assertEquals(response.status_code, 200)
  223 +
  224 + def assertPageNotFound(self, url, client=None):
  225 + if not client:
  226 + client = Client()
  227 + response = client.get(url)
  228 + self.assertEquals(response.status_code, 404)
  229 +
  230 + def assertNodeMemberships(self, visible_page_ids, restricted_pages, public_page_ids):
  231 + """
  232 + test all visible page ids are either in_public and not in_restricted
  233 + or not in_public and in_restricted
  234 + """
  235 + for page_id in visible_page_ids:
  236 + in_restricted = False
  237 + in_public = False
  238 + if page_id in restricted_pages:
  239 + in_restricted = True
  240 + if page_id in public_page_ids:
  241 + in_public = True
  242 + self.assertTrue((in_public and not in_restricted) or
  243 + (not in_public and in_restricted),
  244 + msg="page_id %s in_public: %s, in_restricted: %s" % (page_id, in_public, in_restricted))
  245 +
  246 + def assertGrantedVisibility(self, all_pages, expected_granted_pages, username=None):
  247 + """
  248 + helper function to check the expected_granted_pages are
  249 + not in the restricted_pages list and
  250 + all visible pages are in the expected_granted_pages
  251 + """
  252 + # log the user in if present
  253 + user = None
  254 + if username is not None:
  255 + user = User.objects.get(username__iexact=username)
  256 + request = self.get_request(user)
  257 + visible_page_ids = get_visible_pages(request, all_pages, self.site)
  258 + self.assertEquals(len(visible_page_ids), len(expected_granted_pages))
  259 + public_page_ids = Page.objects.filter(title_set__title__in=expected_granted_pages).values_list('id', flat=True)
  260 + restricted_pages = Page.objects.exclude(title_set__title__in=expected_granted_pages).values_list('id', flat=True)
  261 + self.assertNodeMemberships(visible_page_ids, restricted_pages, public_page_ids)
  262 +
  263 + def get_request(self, user=None):
  264 + # see tests/menu.py line 753
  265 + attrs = {
  266 + 'user': user or AnonymousUser(),
  267 + 'REQUEST': {},
  268 + 'session': {},
  269 + }
  270 + return type('Request', (object,), attrs)
  271 +
  272 +
  273 +class ViewPermissionComplexMenuAllNodesTests(ViewPermissionTests):
  274 + """
  275 + Test CMS_PUBLIC_FOR=all group access and menu nodes rendering
  276 + """
  277 + settings_overrides = {
  278 + 'CMS_MODERATOR': False,
  279 + 'CMS_PERMISSION': True,
  280 + 'CMS_PUBLIC_FOR': 'all',
  281 + }
  282 +
  283 + def test_public_pages_anonymous_norestrictions(self):
  284 + """
  285 + All pages are visible to an anonymous user
  286 + """