diff --git a/CHANGES.txt b/CHANGES.txt index d029a41..3071d6f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,11 @@ Changelog 2.1.5 (unreleased) ------------------ +- Redirect to "/view" for Image, File and anything listed as requiring + a view in the url to properly display comments. + [eleddy] + + - Make comments and controlpanel views more robust, so they don't break if no workflow is assigned to the 'Discussion Item' content type. [timo] diff --git a/plone/app/discussion/browser/comment.py b/plone/app/discussion/browser/comment.py index 10a94d9..97eec23 100644 --- a/plone/app/discussion/browser/comment.py +++ b/plone/app/discussion/browser/comment.py @@ -1,6 +1,7 @@ from Acquisition import aq_inner, aq_parent from Products.Five.browser import BrowserView +from Products.CMFCore.utils import getToolByName class View(BrowserView): @@ -22,7 +23,17 @@ class View(BrowserView): def __call__(self): context = aq_inner(self.context) - self.request.response.redirect( - aq_parent(aq_parent(context)).absolute_url() + - '#' + str(context.id) - ) + ptool = getToolByName(context, 'portal_properties') + view_action_types = ptool.site_properties.typesUseViewActionInListings + obj = aq_parent(aq_parent(context)) + url = obj.absolute_url() + + """ + Image and File types, as well as many other customized archetypes + require /view be appended to the url to see the comments, otherwise it + will redirect right to the binary object, bypassing comments. + """ + if obj.portal_type in view_action_types: + url = "%s/view" % url + + self.request.response.redirect('%s#%s' % (url, context.id)) diff --git a/plone/app/discussion/tests/test_comment.py b/plone/app/discussion/tests/test_comment.py index 75ba549..309086a 100644 --- a/plone/app/discussion/tests/test_comment.py +++ b/plone/app/discussion/tests/test_comment.py @@ -186,6 +186,156 @@ class CommentTest(unittest.TestCase): self.assertEqual('http://nohost/plone/doc1/++conversation++default/' + str(new_comment1_id), comment.absolute_url()) + def test_view_blob_types(self): + """ + Make sure that traversal to images/files redirects to the + version of the url with a /view in it. + """ + self.portal.invokeFactory(id='image1', + title='Image', + type_name='Image') + conversation = IConversation(self.portal.image1) + + comment1 = createObject('plone.Comment') + comment1.text = 'Comment text' + new_comment1_id = conversation.addComment(comment1) + comment = self.portal.image1.restrictedTraverse( + '++conversation++default/%s' % new_comment1_id) + + view = View(comment, self.request) + View.__call__(view) + response = self.request.response + self.assertIn("/view", response.headers['location']) + + def test_workflow(self): + """Basic test for the 'comment_review_workflow' + """ + self.portal.portal_workflow.setChainForPortalTypes( + ('Discussion Item',), + ('comment_review_workflow,')) + + conversation = IConversation(self.portal.doc1) + comment1 = createObject('plone.Comment') + new_comment1_id = conversation.addComment(comment1) + + comment = conversation[new_comment1_id] + + # Make sure comments use the 'comment_review_workflow' + chain = self.portal.portal_workflow.getChainFor(comment) + self.assertEqual(('comment_review_workflow',), chain) + + # Ensure the initial state was entered and recorded + self.assertEqual(1, + len(comment.workflow_history['comment_review_workflow'])) + self.assertEqual(None, + comment.workflow_history['comment_review_workflow'][0]['action']) + self.assertEqual('pending', + self.portal.portal_workflow.getInfoFor(comment, 'review_state')) + + def test_fti(self): + # test that we can look up an FTI for Discussion Item + + self.assertTrue("Discussion Item" in + self.portal.portal_types.objectIds()) + + comment1 = createObject('plone.Comment') + + fti = self.portal.portal_types.getTypeInfo(comment1) + self.assertEqual('Discussion Item', fti.getTypeInfo(comment1).getId()) + + def test_view(self): + # make sure that the comment view is there and redirects to the right + # URL + + # Create a conversation. In this case we doesn't assign it to an + # object, as we just want to check the Conversation object API. + conversation = IConversation(self.portal.doc1) + + # Create a comment + comment1 = createObject('plone.Comment') + comment1.text = 'Comment text' + + # Add comment to the conversation + new_comment1_id = conversation.addComment(comment1) + + comment = self.portal.doc1.restrictedTraverse( + '++conversation++default/%s' % new_comment1_id) + + # make sure the view is there + self.assertTrue(getMultiAdapter((comment, self.request), + name='view')) + + # make sure the HTTP redirect (status code 302) works when a comment + # is called directly + view = View(comment, self.request) + View.__call__(view) + self.assertEqual(self.request.response.status, 302) + + def test_workflow(self): + """Basic test for the 'comment_review_workflow' + """ + self.portal.portal_workflow.setChainForPortalTypes( + ('Discussion Item',), + ('comment_review_workflow,')) + + conversation = IConversation(self.portal.doc1) + comment1 = createObject('plone.Comment') + new_comment1_id = conversation.addComment(comment1) + + comment = conversation[new_comment1_id] + + # Make sure comments use the 'comment_review_workflow' + chain = self.portal.portal_workflow.getChainFor(comment) + self.assertEqual(('comment_review_workflow',), chain) + + # Ensure the initial state was entered and recorded + self.assertEqual(1, + len(comment.workflow_history['comment_review_workflow'])) + self.assertEqual(None, + comment.workflow_history['comment_review_workflow'][0]['action']) + self.assertEqual('pending', + self.portal.portal_workflow.getInfoFor(comment, 'review_state')) + + def test_fti(self): + # test that we can look up an FTI for Discussion Item + + self.assertTrue("Discussion Item" in + self.portal.portal_types.objectIds()) + + comment1 = createObject('plone.Comment') + + fti = self.portal.portal_types.getTypeInfo(comment1) + self.assertEqual('Discussion Item', fti.getTypeInfo(comment1).getId()) + + def test_view(self): + # make sure that the comment view is there and redirects to the right + # URL + + # Create a conversation. In this case we doesn't assign it to an + # object, as we just want to check the Conversation object API. + conversation = IConversation(self.portal.doc1) + + # Create a comment + comment1 = createObject('plone.Comment') + comment1.text = 'Comment text' + + # Add comment to the conversation + new_comment1_id = conversation.addComment(comment1) + + comment = self.portal.doc1.restrictedTraverse( + '++conversation++default/%s' % new_comment1_id) + + # make sure the view is there + self.assertTrue(getMultiAdapter((comment, self.request), + name='view')) + + # make sure the HTTP redirect (status code 302) works when a comment + # is called directly + view = View(comment, self.request) + View.__call__(view) + + + def test_workflow(self): """Basic test for the 'comment_review_workflow' """