From 5fb6968fcad4e40c25f6ae65a331f015bbd30ce0 Mon Sep 17 00:00:00 2001 From: Kees Hink Date: Fri, 10 May 2013 14:59:32 +0200 Subject: [PATCH 1/8] Add a test for Acquisition in comments. --- plone/app/discussion/testing.py | 4 + plone/app/discussion/tests/configure.zcml | 16 ++ plone/app/discussion/tests/profile/types.xml | 4 + .../profile/types/sample_content_type.xml | 47 ++++++ .../discussion/tests/profile/workflows.xml | 4 + .../definition.xml | 75 +++++++++ .../app/discussion/tests/test_acquisition.py | 145 ++++++++++++++++++ 7 files changed, 295 insertions(+) create mode 100644 plone/app/discussion/tests/configure.zcml create mode 100644 plone/app/discussion/tests/profile/types.xml create mode 100644 plone/app/discussion/tests/profile/types/sample_content_type.xml create mode 100644 plone/app/discussion/tests/profile/workflows.xml create mode 100644 plone/app/discussion/tests/profile/workflows/comment_workflow_acquired_view/definition.xml create mode 100644 plone/app/discussion/tests/test_acquisition.py diff --git a/plone/app/discussion/testing.py b/plone/app/discussion/testing.py index cf1066c..02b8fa3 100644 --- a/plone/app/discussion/testing.py +++ b/plone/app/discussion/testing.py @@ -35,10 +35,14 @@ class PloneAppDiscussion(PloneSandboxLayer): xmlconfig.file('configure.zcml', plone.app.discussion, context=configurationContext) + xmlconfig.file('configure.zcml', + plone.app.discussion.tests, + context=configurationContext) def setUpPloneSite(self, portal): # Install into Plone site using portal_setup applyProfile(portal, 'plone.app.discussion:default') + applyProfile(portal, 'plone.app.discussion.tests:testing') # Creates some users acl_users = getToolByName(portal, 'acl_users') diff --git a/plone/app/discussion/tests/configure.zcml b/plone/app/discussion/tests/configure.zcml new file mode 100644 index 0000000..ee79b95 --- /dev/null +++ b/plone/app/discussion/tests/configure.zcml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/plone/app/discussion/tests/profile/types.xml b/plone/app/discussion/tests/profile/types.xml new file mode 100644 index 0000000..0aec569 --- /dev/null +++ b/plone/app/discussion/tests/profile/types.xml @@ -0,0 +1,4 @@ + + + + diff --git a/plone/app/discussion/tests/profile/types/sample_content_type.xml b/plone/app/discussion/tests/profile/types/sample_content_type.xml new file mode 100644 index 0000000..7869638 --- /dev/null +++ b/plone/app/discussion/tests/profile/types/sample_content_type.xml @@ -0,0 +1,47 @@ + + + + + sample_content_type + Sample Content + document_icon.png + True + True + + + True + + plone.dexterity.content.Item + + cmf.AddPortalContent + + + + + + view + False + + + + + + + + + + + + + + + + + + + diff --git a/plone/app/discussion/tests/profile/workflows.xml b/plone/app/discussion/tests/profile/workflows.xml new file mode 100644 index 0000000..500f444 --- /dev/null +++ b/plone/app/discussion/tests/profile/workflows.xml @@ -0,0 +1,4 @@ + + + + diff --git a/plone/app/discussion/tests/profile/workflows/comment_workflow_acquired_view/definition.xml b/plone/app/discussion/tests/profile/workflows/comment_workflow_acquired_view/definition.xml new file mode 100644 index 0000000..89a9fb2 --- /dev/null +++ b/plone/app/discussion/tests/profile/workflows/comment_workflow_acquired_view/definition.xml @@ -0,0 +1,75 @@ + + + Access contents information + Change portal events + Modify portal content + View + + Visible to everyone, editable by the owner. + + Anonymous + + + Editor + Manager + Owner + Site Administrator + + + Editor + Manager + Owner + Site Administrator + + + + + + Previous transition + + transition/getId|nothing + + + + + + The ID of the user who performed the previous transition + + user/getId + + + + + + Comment about the last transition + + python:state_change.kwargs.get('comment', '') + + + + + + Provides access to workflow history + + state_change/getHistory + + + Request review + Review portal content + + + + When the previous transition was performed + + state_change/getDateTime + + + + + diff --git a/plone/app/discussion/tests/test_acquisition.py b/plone/app/discussion/tests/test_acquisition.py new file mode 100644 index 0000000..31d5f81 --- /dev/null +++ b/plone/app/discussion/tests/test_acquisition.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +from Acquisition import aq_chain +from plone.app.discussion.testing import \ + PLONE_APP_DISCUSSION_INTEGRATION_TESTING +from plone.app.discussion.interfaces import IConversation +from plone.app.testing import TEST_USER_ID, setRoles +from Products.CMFCore.utils import getToolByName +from zope.component import createObject + +import unittest2 as unittest + + +dexterity_type_name = 'sample_content_type' +dexterity_object_id = 'instance-of-dexterity-type' +archetypes_object_id = 'instance-of-archetypes-type' +one_state_workflow = 'one_state_workflow' +comment_workflow_acquired_view = 'comment_workflow_acquired_view' + + +def _anonymousCanView(obj): + """Use rolesOfPermission() to sees if Anonymous has View permission on an + object""" + roles_of_view_permission = obj.rolesOfPermission("View") + # rolesOfPermission returns a list of dictionaries that have the key + # 'name' for role. + anon_views = [r for r in roles_of_view_permission + if r['name'] == 'Anonymous'] + # only one entry per role should be present + anon_view = anon_views[0] + # if this role has the permission, 'selected' is set to 'SELECTED' + return anon_view['selected'] == 'SELECTED' + + +class DexterityAcquisitionTest(unittest.TestCase): + """See test_view_permission.""" + + layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING + + def setUp(self): + self.portal = self.layer['portal'] + self.request = self.layer['request'] + setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.wftool = getToolByName(self.portal, 'portal_workflow') + + # Use customized workflow for comments. + self.wftool.setChainForPortalTypes( + ['Discussion Item'], + (comment_workflow_acquired_view,), + ) + + # Use one_state_workflow for Document and sample_content_type, + # so they're always published. + self.wftool.setChainForPortalTypes( + ['Document', dexterity_type_name], + (one_state_workflow,), + ) + + # Create a dexterity item and add a comment. + self.portal.invokeFactory( + id=dexterity_object_id, + title='Instance Of Dexterity Type', + type_name=dexterity_type_name, + ) + self.dexterity_object = self.portal.get(dexterity_object_id) + dx_conversation = IConversation(self.dexterity_object) + self.dexterity_conversation = dx_conversation + comment1 = createObject('plone.Comment') + dx_conversation.addComment(comment1) + self.dexterity_comment = comment1 + + # Create an Archetypes item and add a comment. + self.portal.invokeFactory( + id=archetypes_object_id, + title='Instance Of Archetypes Type', + type_name='Document', + ) + self.archetypes_object = self.portal.get(archetypes_object_id) + at_conversation = IConversation(self.archetypes_object) + self.archetypes_conversation = at_conversation + comment2 = createObject('plone.Comment') + at_conversation.addComment(comment2) + self.archetypes_comment = comment2 + + def test_workflows_installed(self): + """Check that the new comment workflow has been installed properly. + (Just a test to check our test setup.) + """ + workflows = self.wftool.objectIds() + self.assertTrue('comment_workflow_acquired_view' in workflows) + + def test_workflows_applied(self): + """Check that all objects have the workflow that we expect. + (Just a test to check our test setup.)""" + self.assertEqual( + self.wftool.getChainFor(self.archetypes_object), + (one_state_workflow,) + ) + self.assertEqual( + self.wftool.getChainFor(self.dexterity_object), + (one_state_workflow,) + ) + self.assertEqual( + self.wftool.getChainFor(self.archetypes_comment), + (comment_workflow_acquired_view,) + ) + self.assertEqual( + self.wftool.getChainFor(self.dexterity_comment), + (comment_workflow_acquired_view,) + ) + + def test_view_permission(self): + """Test that if the View permission on Discussion Items is acquired, + Anonymous can view comments on published items.""" + + # Anonymous has View permission on commented objects. + self.assertTrue(_anonymousCanView(self.archetypes_object)) + self.assertTrue(_anonymousCanView(self.dexterity_object)) + + # Fails: Anonymous should therefore have View permission on the + # comments. + self.assertTrue(_anonymousCanView(self.archetypes_comment)) + self.assertTrue(_anonymousCanView(self.dexterity_comment)) + + def test_acquisition_chain(self): + """The acquisition chain for the comment should contain the same items + as that of the conversation. + + Note that the list returned by aq_inner has the innermost object + first.""" + + # Fails: list index out of range + at_comment_chain = aq_chain(self.archetypes_comment) + at_conversation_chain = aq_chain(self.archetypes_conversation) + for (index, item) in enumerate(at_conversation_chain): + self.assertEqual(item, at_comment_chain[index + 1]) + + # Fails: list index out of range + dx_comment_chain = aq_chain(self.dexterity_comment) + dx_conversation_chain = aq_chain(self.dexterity_conversation) + for (index, item) in enumerate(dx_conversation_chain): + self.assertEqual(item, dx_comment_chain[index + 1]) + + +def test_suite(): + return unittest.defaultTestLoader.loadTestsFromName(__name__) From 8e56b99638f48170725a29425312ae6f3966aec8 Mon Sep 17 00:00:00 2001 From: vmaksymiv Date: Tue, 24 Sep 2013 08:14:05 +0000 Subject: [PATCH 2/8] added test acquisition chain for unwrapped object Conflicts: plone/app/discussion/tests/test_acquisition.py --- .../app/discussion/tests/test_acquisition.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/plone/app/discussion/tests/test_acquisition.py b/plone/app/discussion/tests/test_acquisition.py index 31d5f81..4f4d01b 100644 --- a/plone/app/discussion/tests/test_acquisition.py +++ b/plone/app/discussion/tests/test_acquisition.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- -from Acquisition import aq_chain +from AccessControl.User import User # before SpecialUsers +from AccessControl.SpecialUsers import nobody as user_nobody +from AccessControl.PermissionRole import rolesForPermissionOn +from Acquisition import aq_chain, aq_base from plone.app.discussion.testing import \ PLONE_APP_DISCUSSION_INTEGRATION_TESTING from plone.app.discussion.interfaces import IConversation @@ -140,6 +143,25 @@ class DexterityAcquisitionTest(unittest.TestCase): for (index, item) in enumerate(dx_conversation_chain): self.assertEqual(item, dx_comment_chain[index + 1]) + def test_acquisition_base_object_chain(self): + """ The acquisition chain for the object without wrappers should return + list which contains only the object. + """ + + at_object_base_chain = aq_chain(aq_base(self.archetypes_object)) + dx_object_base_chain = aq_chain(aq_base(self.dexterity_object)) + + # Fails: acquisition chain has more than one object + self.assertTrue(len(at_object_base_chain) == 1) + self.assertTrue(len(dx_object_base_chain) == 1) + + at_comment_base_chain = aq_chain(aq_base(self.archetypes_comment)) + dx_comment_base_chain = aq_chain(aq_base(self.dexterity_comment)) + + # Fails: acquisition chain has more than one object + self.assertTrue(len(at_comment_base_chain) == 1) + self.assertTrue(len(dx_comment_base_chain) == 1) + def test_suite(): return unittest.defaultTestLoader.loadTestsFromName(__name__) From 4be4c03c49f478e7339d59b008fc21ba52576e9e Mon Sep 17 00:00:00 2001 From: Alan Hoey Date: Thu, 21 Nov 2013 18:13:31 +0000 Subject: [PATCH 3/8] Fix total comments index --- plone/app/discussion/conversation.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plone/app/discussion/conversation.py b/plone/app/discussion/conversation.py index ba27c0f..16a3cec 100644 --- a/plone/app/discussion/conversation.py +++ b/plone/app/discussion/conversation.py @@ -50,8 +50,14 @@ from plone.app.discussion.comment import Comment from AccessControl.SpecialUsers import nobody as user_nobody +from ComputedAttribute import ComputedAttribute + ANNOTATION_KEY = 'plone.app.discussion:conversation' +def computed_attribute_decorator(level=0): + def computed_attribute_wrapper(func): + return ComputedAttribute(func, level) + return computed_attribute_wrapper class Conversation(Traversable, Persistent, Explicit): """A conversation is a container for all comments on a content object. @@ -87,10 +93,10 @@ class Conversation(Traversable, Persistent, Explicit): parent = aq_inner(self.__parent__) return parent.restrictedTraverse('@@conversation_view').enabled() - @property + @computed_attribute_decorator(level=1) def total_comments(self): public_comments = [ - x for x in self._comments.values() + x for x in self.values() if user_nobody.has_permission('View', x) ] return len(public_comments) From 7269dfca6568e9d5c8554aaac5228c957efc1c9a Mon Sep 17 00:00:00 2001 From: Jess Henderson Date: Fri, 22 Nov 2013 11:51:47 +0000 Subject: [PATCH 4/8] Add dev.delib-da.1 to version pin --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index af74549..7a91fc2 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_packages -version = '2.3.0dev' +version = '2.3.0dev.delib-da.1' install_requires = [ 'setuptools', From 4804868ab25a5c5c38f73fed25065ab6c849e6c3 Mon Sep 17 00:00:00 2001 From: Alan Hoey Date: Mon, 25 Nov 2013 12:06:05 +0000 Subject: [PATCH 5/8] Refactor acquisition tests to define the expected behaviour when dealing with wrapped and unwrapped comments. --- .../app/discussion/tests/test_acquisition.py | 153 ++++++++++-------- 1 file changed, 82 insertions(+), 71 deletions(-) diff --git a/plone/app/discussion/tests/test_acquisition.py b/plone/app/discussion/tests/test_acquisition.py index 4f4d01b..72d68fe 100644 --- a/plone/app/discussion/tests/test_acquisition.py +++ b/plone/app/discussion/tests/test_acquisition.py @@ -2,7 +2,7 @@ from AccessControl.User import User # before SpecialUsers from AccessControl.SpecialUsers import nobody as user_nobody from AccessControl.PermissionRole import rolesForPermissionOn -from Acquisition import aq_chain, aq_base +from Acquisition import aq_chain from plone.app.discussion.testing import \ PLONE_APP_DISCUSSION_INTEGRATION_TESTING from plone.app.discussion.interfaces import IConversation @@ -19,23 +19,8 @@ archetypes_object_id = 'instance-of-archetypes-type' one_state_workflow = 'one_state_workflow' comment_workflow_acquired_view = 'comment_workflow_acquired_view' - -def _anonymousCanView(obj): - """Use rolesOfPermission() to sees if Anonymous has View permission on an - object""" - roles_of_view_permission = obj.rolesOfPermission("View") - # rolesOfPermission returns a list of dictionaries that have the key - # 'name' for role. - anon_views = [r for r in roles_of_view_permission - if r['name'] == 'Anonymous'] - # only one entry per role should be present - anon_view = anon_views[0] - # if this role has the permission, 'selected' is set to 'SELECTED' - return anon_view['selected'] == 'SELECTED' - - -class DexterityAcquisitionTest(unittest.TestCase): - """See test_view_permission.""" +class AcquisitionTest(unittest.TestCase): + """ Define the expected behaviour of wrapped and unwrapped comments. """ layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING @@ -64,12 +49,14 @@ class DexterityAcquisitionTest(unittest.TestCase): title='Instance Of Dexterity Type', type_name=dexterity_type_name, ) + self.dexterity_object = self.portal.get(dexterity_object_id) dx_conversation = IConversation(self.dexterity_object) self.dexterity_conversation = dx_conversation - comment1 = createObject('plone.Comment') - dx_conversation.addComment(comment1) - self.dexterity_comment = comment1 + dx_comment = createObject('plone.Comment') + dx_conversation.addComment(dx_comment) + self.unwrapped_dexterity_comment = dx_comment + self.wrapped_dexterity_comment = dx_conversation[dx_comment.id] # Create an Archetypes item and add a comment. self.portal.invokeFactory( @@ -77,12 +64,15 @@ class DexterityAcquisitionTest(unittest.TestCase): title='Instance Of Archetypes Type', type_name='Document', ) + self.archetypes_object = self.portal.get(archetypes_object_id) at_conversation = IConversation(self.archetypes_object) self.archetypes_conversation = at_conversation - comment2 = createObject('plone.Comment') - at_conversation.addComment(comment2) - self.archetypes_comment = comment2 + at_comment = createObject('plone.Comment') + at_conversation.addComment(at_comment) + self.unwrapped_archetypes_comment = at_comment + self.wrapped_archetypes_comment = at_conversation[at_comment.id] + def test_workflows_installed(self): """Check that the new comment workflow has been installed properly. @@ -103,64 +93,85 @@ class DexterityAcquisitionTest(unittest.TestCase): (one_state_workflow,) ) self.assertEqual( - self.wftool.getChainFor(self.archetypes_comment), + self.wftool.getChainFor(self.unwrapped_archetypes_comment), (comment_workflow_acquired_view,) ) self.assertEqual( - self.wftool.getChainFor(self.dexterity_comment), + self.wftool.getChainFor(self.unwrapped_dexterity_comment), (comment_workflow_acquired_view,) ) - def test_view_permission(self): - """Test that if the View permission on Discussion Items is acquired, - Anonymous can view comments on published items.""" + def test_comment_acquisition_chain(self): + """ Test that the acquisition chains for wrapped and unwrapped + comments are as expected. """ + + # Unwrapped comments rely on __parent__ attributes to determine + # parentage. Frustratingly there is no guarantee that __parent__ + # is always set, so the computed acquisition chain may be short. + # In this case the unwrapped AT and DX objects stored as the + # conversation parents don't have a __parent__, preventing the portal + # from being included in the chain. + self.assertNotIn(self.portal, + aq_chain(self.unwrapped_archetypes_comment)) + self.assertNotIn(self.portal, + aq_chain(self.unwrapped_dexterity_comment)) - # Anonymous has View permission on commented objects. - self.assertTrue(_anonymousCanView(self.archetypes_object)) - self.assertTrue(_anonymousCanView(self.dexterity_object)) + # Wrapped comments however have a complete chain and thus can find the + # portal object reliably. + self.assertIn(self.portal,aq_chain(self.wrapped_archetypes_comment)) + self.assertIn(self.portal,aq_chain(self.wrapped_dexterity_comment)) - # Fails: Anonymous should therefore have View permission on the - # comments. - self.assertTrue(_anonymousCanView(self.archetypes_comment)) - self.assertTrue(_anonymousCanView(self.dexterity_comment)) + + def test_acquiring_comment_permissions(self): + """ Unwrapped comments should not be able to acquire permissions + controlled by unreachable objects """ + + # We use the "Allow sendto" permission as by default it is + # controlled by the portal, which is unreachable via __parent__ + # attributes on the comments. + permission = "Allow sendto" - def test_acquisition_chain(self): - """The acquisition chain for the comment should contain the same items - as that of the conversation. + # Unwrapped comments can't find the portal so just return manager + self.assertNotIn("Anonymous", + rolesForPermissionOn(permission, + self.unwrapped_archetypes_comment)) + self.assertNotIn("Anonymous", + rolesForPermissionOn(permission, + self.unwrapped_dexterity_comment)) + + # Wrapped objects can find the portal and correctly return the + # anonymous role. + self.assertIn("Anonymous", + rolesForPermissionOn(permission, + self.wrapped_archetypes_comment)) + self.assertIn("Anonymous", + rolesForPermissionOn(permission, + self.wrapped_dexterity_comment)) + + def test_acquiring_comment_permissions_via_user_nobody(self): + """ The current implementation uses user_nobody.has_permission to + check whether anonymous can view comments. This confirms it also + works. """ + + # Again we want to use a permission that's not managed by any of our + # content objects so it must be acquired from the portal. + permission = "Allow sendto" + + self.assertFalse( + user_nobody.has_permission(permission, + self.unwrapped_archetypes_comment)) + + self.assertFalse( + user_nobody.has_permission(permission, + self.unwrapped_dexterity_comment)) - Note that the list returned by aq_inner has the innermost object - first.""" + self.assertTrue( + user_nobody.has_permission(permission, + self.wrapped_archetypes_comment)) - # Fails: list index out of range - at_comment_chain = aq_chain(self.archetypes_comment) - at_conversation_chain = aq_chain(self.archetypes_conversation) - for (index, item) in enumerate(at_conversation_chain): - self.assertEqual(item, at_comment_chain[index + 1]) - - # Fails: list index out of range - dx_comment_chain = aq_chain(self.dexterity_comment) - dx_conversation_chain = aq_chain(self.dexterity_conversation) - for (index, item) in enumerate(dx_conversation_chain): - self.assertEqual(item, dx_comment_chain[index + 1]) - - def test_acquisition_base_object_chain(self): - """ The acquisition chain for the object without wrappers should return - list which contains only the object. - """ - - at_object_base_chain = aq_chain(aq_base(self.archetypes_object)) - dx_object_base_chain = aq_chain(aq_base(self.dexterity_object)) - - # Fails: acquisition chain has more than one object - self.assertTrue(len(at_object_base_chain) == 1) - self.assertTrue(len(dx_object_base_chain) == 1) - - at_comment_base_chain = aq_chain(aq_base(self.archetypes_comment)) - dx_comment_base_chain = aq_chain(aq_base(self.dexterity_comment)) - - # Fails: acquisition chain has more than one object - self.assertTrue(len(at_comment_base_chain) == 1) - self.assertTrue(len(dx_comment_base_chain) == 1) + self.assertTrue( + user_nobody.has_permission(permission, + self.wrapped_dexterity_comment)) def test_suite(): From e6ec6ebe164b68f465fe87ea3dda8e7136411053 Mon Sep 17 00:00:00 2001 From: Alan Hoey Date: Mon, 25 Nov 2013 12:52:31 +0000 Subject: [PATCH 6/8] Add tests to confirm that some methods of a conversation incorrectly use an unwrapped comment to determine whether anonymous users can view. --- .../app/discussion/tests/test_acquisition.py | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/plone/app/discussion/tests/test_acquisition.py b/plone/app/discussion/tests/test_acquisition.py index 72d68fe..c6a96db 100644 --- a/plone/app/discussion/tests/test_acquisition.py +++ b/plone/app/discussion/tests/test_acquisition.py @@ -2,7 +2,7 @@ from AccessControl.User import User # before SpecialUsers from AccessControl.SpecialUsers import nobody as user_nobody from AccessControl.PermissionRole import rolesForPermissionOn -from Acquisition import aq_chain +from Acquisition import aq_chain, aq_base from plone.app.discussion.testing import \ PLONE_APP_DISCUSSION_INTEGRATION_TESTING from plone.app.discussion.interfaces import IConversation @@ -173,6 +173,71 @@ class AcquisitionTest(unittest.TestCase): user_nobody.has_permission(permission, self.wrapped_dexterity_comment)) +class AcquiredPermissionTest(unittest.TestCase): + """ Test methods of a conversation which rely on acquired permissions """ + + layer = PLONE_APP_DISCUSSION_INTEGRATION_TESTING + + def setUp(self): + self.portal = self.layer['portal'] + self.request = self.layer['request'] + setRoles(self.portal, TEST_USER_ID, ['Manager']) + self.wftool = getToolByName(self.portal, 'portal_workflow') + + # Disable workflow for comments and content. + self.wftool.setChainForPortalTypes(["Discussion Item"],[]) + self.wftool.setChainForPortalTypes([dexterity_type_name],[]) + + # Create a dexterity item. + self.portal.invokeFactory( + id=dexterity_object_id, + title='Instance Of Dexterity Type', + type_name=dexterity_type_name, + ) + + self.content = self.portal.get(dexterity_object_id) + + # Absolutely make sure that we're replicating the case of an + # incomplete chain correctly. + aq_base(self.content).__parent__ = None + + self.conversation = IConversation(self.content) + + # Add a comment + comment = createObject('plone.Comment') + self.conversation.addComment(comment) + self.comment = comment + + def test_view_permission_is_only_available_on_portal(self): + """ Check that the test setup is correct """ + + content_roles = rolesForPermissionOn("View",aq_base(self.content)) + self.assertNotIn("Anonymous",content_roles) + + comment_roles = rolesForPermissionOn("View",aq_base(self.comment)) + self.assertNotIn("Anonymous",comment_roles) + + # This actually acquires view from the app root, but we don't really + # care, we just need to confirm that something above our content + # object will give us View. + portal_roles = rolesForPermissionOn("View",self.portal) + self.assertIn("Anonymous",portal_roles) + + # The following tests fail when the conversation uses unwrapped comment + # objects to determine whether an anonymous user has the view permission. + + def test_total_comments(self): + self.assertEqual(self.conversation.total_comments,1) + + def test_last_comment_date(self): + self.assertEqual(self.conversation.last_comment_date, + self.comment.creation_date) + + def test_public_commentators(self): + self.assertEqual(self.conversation.public_commentators, + (self.comment.author_username,)) + + def test_suite(): return unittest.defaultTestLoader.loadTestsFromName(__name__) From 8161dc14dae8cd8b7c9d796066d302b8a4a153c9 Mon Sep 17 00:00:00 2001 From: Alan Hoey Date: Mon, 25 Nov 2013 12:54:49 +0000 Subject: [PATCH 7/8] Fixing test failures. --- plone/app/discussion/conversation.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plone/app/discussion/conversation.py b/plone/app/discussion/conversation.py index 16a3cec..ca22c05 100644 --- a/plone/app/discussion/conversation.py +++ b/plone/app/discussion/conversation.py @@ -101,13 +101,13 @@ class Conversation(Traversable, Persistent, Explicit): ] return len(public_comments) - @property + @computed_attribute_decorator(level=1) def last_comment_date(self): # self._comments is an Instance of a btree. The keys # are always ordered comment_keys = self._comments.keys() for comment_key in reversed(comment_keys): - comment = self._comments[comment_key] + comment = self[comment_key] if user_nobody.has_permission('View', comment): return comment.creation_date return None @@ -116,10 +116,10 @@ class Conversation(Traversable, Persistent, Explicit): def commentators(self): return self._commentators - @property + @computed_attribute_decorator(level=1) def public_commentators(self): retval = set() - for comment in self._comments.values(): + for comment in self.values(): if not user_nobody.has_permission('View', comment): continue retval.add(comment.author_username) From 1bf4dacc1d116e8c33691d01dbc1083fd004514a Mon Sep 17 00:00:00 2001 From: Alan Hoey Date: Mon, 25 Nov 2013 13:04:24 +0000 Subject: [PATCH 8/8] Back out internal version number --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7a91fc2..af74549 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_packages -version = '2.3.0dev.delib-da.1' +version = '2.3.0dev' install_requires = [ 'setuptools',