additional workflow with rejected state
moderation view and approved comments view: buttons for reject and approve
This commit is contained in:
		
							parent
							
								
									875409daff
								
							
						
					
					
						commit
						f7b8335d27
					
				| @ -100,6 +100,15 @@ | ||||
|         permission="plone.app.discussion.ReviewComments" | ||||
|         /> | ||||
| 
 | ||||
|     <!-- Reject comment view --> | ||||
|     <browser:page | ||||
|         for="plone.app.discussion.interfaces.IComment" | ||||
|         name="moderate-reject-comment" | ||||
|         layer="..interfaces.IDiscussionLayer" | ||||
|         class=".moderation.RejectComment" | ||||
|         permission="plone.app.discussion.ReviewComments" | ||||
|         /> | ||||
| 
 | ||||
|     <!-- Comments viewlet --> | ||||
|     <browser:viewlet | ||||
|         name="plone.comments" | ||||
|  | ||||
| @ -67,10 +67,12 @@ require([  // jshint ignore:line | ||||
|             var path = $(row).find("[name='selected_obj_paths:list']").attr("value"); | ||||
|             var auth_key = $('input[name="_authenticator"]').val(); | ||||
|             var target = path + "/@@moderate-publish-comment?_authenticator=" + auth_key; | ||||
|             var moderate = $(this).closest("fieldset").attr("id") == "fieldset-moderate-comments"; | ||||
|             $.ajax({ | ||||
|                 type: "GET", | ||||
|                 url: target, | ||||
|                 success: function (msg) {  // jshint ignore:line
 | ||||
|                     if (moderate) { | ||||
|                         // fade out row
 | ||||
|                         $(row).fadeOut("normal", function () { | ||||
|                             $(this).remove(); | ||||
| @ -80,6 +82,44 @@ require([  // jshint ignore:line | ||||
|                         if (comments.length === 1) { | ||||
|                             location.reload(); | ||||
|                         } | ||||
|                     } else { | ||||
|                         location.reload(); | ||||
|                     } | ||||
|                 }, | ||||
|                 error: function (msg) {  // jshint ignore:line
 | ||||
|                     alert("Error sending AJAX request:" + target); | ||||
|                 } | ||||
|             }); | ||||
|         }); | ||||
| 
 | ||||
| 
 | ||||
|         /********************************************************************** | ||||
|          * Reject a single comment. | ||||
|          **********************************************************************/ | ||||
|         $("input[name='form.button.Reject']").click(function (e) { | ||||
|             e.preventDefault(); | ||||
|             var row = $(this).parent().parent(); | ||||
|             var path = $(row).find("[name='selected_obj_paths:list']").attr("value"); | ||||
|             var auth_key = $('input[name="_authenticator"]').val(); | ||||
|             var target = path + "/@@moderate-reject-comment?_authenticator=" + auth_key; | ||||
|             var moderate = $(this).closest("fieldset").attr("id") == "fieldset-moderate-comments"; | ||||
|             $.ajax({ | ||||
|                 type: "GET", | ||||
|                 url: target, | ||||
|                 success: function (msg) {  // jshint ignore:line
 | ||||
|                     if (moderate) { | ||||
|                         // fade out row
 | ||||
|                         $(row).fadeOut("normal", function () { | ||||
|                             $(this).remove(); | ||||
|                         }); | ||||
|                         // reload page if all comments have been removed
 | ||||
|                         var comments = $("table#review-comments > tbody > tr"); | ||||
|                         if (comments.length === 1) { | ||||
|                             location.reload(); | ||||
|                         } | ||||
|                     } else { | ||||
|                         location.reload(); | ||||
|                     } | ||||
|                 }, | ||||
|                 error: function (msg) {  // jshint ignore:line
 | ||||
|                     alert("Error sending AJAX request:" + target); | ||||
| @ -171,7 +211,8 @@ require([  // jshint ignore:line | ||||
|          **********************************************************************/ | ||||
|         $(".last-history-entry").each(function() { | ||||
|             $(this).load($(this).attr("data-href") + " .historyByLine", function() { | ||||
|                 $(this).children(".historyByLine").last().remove(); | ||||
|                 let currententry = $(this).children(".historyByLine").first(); | ||||
|                 $(this).html(currententry); | ||||
|             }); | ||||
|         }); | ||||
| 
 | ||||
|  | ||||
| @ -142,7 +142,16 @@ | ||||
|                                                      name="form.button.Publish" | ||||
|                                                      i18n:attributes="value label_publish;" | ||||
|                                                      tal:attributes="id item/id" | ||||
|                                                      tal:condition="python:item.review_state == 'pending'" | ||||
|                                                      tal:condition="python:item.review_state in ['pending',]" | ||||
|                                                      /> | ||||
|                                               <input id="" | ||||
|                                                      class="context comment-reject-button" | ||||
|                                                      type="submit" | ||||
|                                                      value="Reject" | ||||
|                                                      name="form.button.Reject" | ||||
|                                                      i18n:attributes="value label_reject;" | ||||
|                                                      tal:attributes="id item/id" | ||||
|                                                      tal:condition="python:view.moderation_3state and item.review_state == 'pending'" | ||||
|                                                      /> | ||||
|                                               <input id="" | ||||
|                                                      class="destructive comment-delete-button" | ||||
| @ -167,7 +176,7 @@ | ||||
|                   Approved comments</legend> | ||||
|               <div> | ||||
|                   <form tal:condition="not:items_approved_or_rejected"> | ||||
|                       <fieldset id="fieldset-moderate-comments" class="formPanel"> | ||||
|                       <fieldset id="fieldset-moderated-comments" class="formPanel"> | ||||
|                           <p id="no-comments-message" i18n:translate="message_no_comments_approved"> | ||||
|                               No comments approved | ||||
|                           </p> | ||||
| @ -179,7 +188,7 @@ | ||||
|                         tal:condition="items_approved_or_rejected" | ||||
|                         tal:define="batch python:Batch(items_approved_or_rejected, b_size, int(b_start), orphan=1);"> | ||||
| 
 | ||||
|                       <fieldset id="fieldset-moderate-comments" class="formPanel"> | ||||
|                       <fieldset id="fieldset-moderated-comments" class="formPanel"> | ||||
| 
 | ||||
|                           <div metal:use-macro="here/batch_macros/macros/navigation" /> | ||||
| 
 | ||||
| @ -190,7 +199,7 @@ | ||||
|                                       <th class="nosort" i18n:translate="heading_date">Date</th> | ||||
|                                       <th class="nosort" i18n:translate="heading_in_reponse_to">In Response To</th> | ||||
|                                       <th class="nosort" i18n:translate="heading_comment">Comment</th> | ||||
|                                       <th class="nosort" i18n:translate="heading_approvedby">Approved by</th> | ||||
|                                       <th class="nosort" i18n:translate="heading_changedby">Last Action</th> | ||||
|                                       <th class="nosort" i18n:translate="heading_action">Action</th> | ||||
|                                   </tr> | ||||
|                               </thead> | ||||
| @ -215,8 +224,8 @@ | ||||
|                                               <a tal:attributes="href item_url" | ||||
|                                                  tal:content="item/in_response_to" /> | ||||
|                                           </td> | ||||
|                                           <td> | ||||
|                                               <span tal:replace="item/Description" /> | ||||
|                                           <td tal:attributes="class  python:item.review_state=='rejected' and 'state-private'"> | ||||
|                                               <span tal:replace="item/Description"/> | ||||
|                                               <a href="" | ||||
|                                                  tal:attributes="href string:$item_url/getText" | ||||
|                                                  tal:condition="python:item.Description.endswith('[...]')" | ||||
| @ -229,10 +238,27 @@ | ||||
|                                                   last history entry | ||||
|                                               </span> | ||||
|                                           </td> | ||||
| 
 | ||||
|                                           <td class="actions"> | ||||
|                                               <input type="hidden" name="selected_obj_paths:list" value="#" | ||||
|                                                      tal:attributes="value item/getURL" /> | ||||
|                                               <input id="" | ||||
|                                                      class="context comment-publish-button" | ||||
|                                                      type="submit" | ||||
|                                                      value="Approve" | ||||
|                                                      name="form.button.Publish" | ||||
|                                                      i18n:attributes="value label_publish;" | ||||
|                                                      tal:attributes="id item/id" | ||||
|                                                      tal:condition="python:item.review_state in ['pending', 'rejected']" | ||||
|                                                      /> | ||||
|                                               <input id="" | ||||
|                                                      class="context comment-reject-button" | ||||
|                                                      type="submit" | ||||
|                                                      value="Reject" | ||||
|                                                      name="form.button.Reject" | ||||
|                                                      i18n:attributes="value label_reject;" | ||||
|                                                      tal:attributes="id item/id" | ||||
|                                                      tal:condition="python:view.moderation_3state and item.review_state == 'published'" | ||||
|                                                      /> | ||||
|                                               <input id="" | ||||
|                                                      class="destructive comment-delete-button" | ||||
|                                                      type="submit" | ||||
|  | ||||
| @ -36,7 +36,7 @@ class View(BrowserView): | ||||
|                                 sort_on='created', | ||||
|                                 sort_order='reverse') | ||||
|         self.comments_approved = catalog(object_provides=IComment.__identifier__, | ||||
|                                 review_state='published', | ||||
|                                 review_state=['published', 'rejected'], | ||||
|                                 sort_on='created', | ||||
|                                 sort_order='reverse') | ||||
|         return self.template() | ||||
| @ -57,6 +57,23 @@ class View(BrowserView): | ||||
|                 return True | ||||
|         return False | ||||
| 
 | ||||
|     @property | ||||
|     def moderation_3state(self): | ||||
|         """Returns true if a 'review 3 state workflow' is enabled on 'Discussion Item' | ||||
|            content type. A 'review 3 state workflow' is characterized by implementing | ||||
|            a 'rejected' workflow state. | ||||
|         """ | ||||
|         context = aq_inner(self.context) | ||||
|         workflowTool = getToolByName(context, 'portal_workflow') | ||||
|         comment_workflow = workflowTool.getChainForPortalType( | ||||
|             'Discussion Item') | ||||
|         if comment_workflow: | ||||
|             comment_workflow = comment_workflow[0] | ||||
|             comment_workflow = workflowTool[comment_workflow] | ||||
|             if 'rejected' in comment_workflow.states: | ||||
|                 return True | ||||
|         return False | ||||
| 
 | ||||
| 
 | ||||
| class ModerateCommentsEnabled(BrowserView): | ||||
| 
 | ||||
| @ -189,10 +206,11 @@ class PublishComment(BrowserView): | ||||
|         alsoProvides(self.request, IDisableCSRFProtection) | ||||
|         comment = aq_inner(self.context) | ||||
|         content_object = aq_parent(aq_parent(comment)) | ||||
|         print("*** called: PublishComment for ", comment.Description) | ||||
|         workflowTool = getToolByName(comment, 'portal_workflow', None) | ||||
|         workflow_action = self.request.form.get('workflow_action', 'publish') | ||||
|         review_state = workflowTool.getInfoFor(comment, 'review_state', '') | ||||
|         if review_state == "pending": | ||||
|         if review_state != "published": | ||||
|             workflowTool.doActionFor(comment, workflow_action) | ||||
|             comment.reindexObject() | ||||
|             content_object.reindexObject(idxs=['total_comments']) | ||||
| @ -214,6 +232,41 @@ class PublishComment(BrowserView): | ||||
|         return self.context.REQUEST.RESPONSE.redirect(came_from) | ||||
| 
 | ||||
| 
 | ||||
| class RejectComment(BrowserView): | ||||
|     """Reject a comment. | ||||
| 
 | ||||
|        see PublishComment for more information | ||||
|     """ | ||||
| 
 | ||||
|     def __call__(self): | ||||
|         alsoProvides(self.request, IDisableCSRFProtection) | ||||
|         comment = aq_inner(self.context) | ||||
|         content_object = aq_parent(aq_parent(comment)) | ||||
|         print("*** called: RejectComment for ", comment.Description) | ||||
|         workflowTool = getToolByName(comment, 'portal_workflow', None) | ||||
|         workflow_action = self.request.form.get('workflow_action', 'reject') | ||||
|         review_state = workflowTool.getInfoFor(comment, 'review_state', '') | ||||
|         if review_state != 'rejected': | ||||
|             workflowTool.doActionFor(comment, workflow_action) | ||||
|             comment.reindexObject() | ||||
|             content_object.reindexObject(idxs=['total_comments']) | ||||
|             notify(CommentPublishedEvent(self.context, comment)) | ||||
|             IStatusMessage(self.context.REQUEST).addStatusMessage( | ||||
|                 _('Comment rejected.'), | ||||
|                 type='info') | ||||
|         else: | ||||
|             IStatusMessage(self.context.REQUEST).addStatusMessage( | ||||
|                 _('Comment already rejected.'), | ||||
|                 type='info') | ||||
|         came_from = self.context.REQUEST.HTTP_REFERER | ||||
|         # if the referrer already has a came_from in it, don't redirect back | ||||
|         if (len(came_from) == 0 or 'came_from=' in came_from or | ||||
|                 not getToolByName( | ||||
|                 content_object, 'portal_url').isURLInPortal(came_from)): | ||||
|             came_from = content_object.absolute_url() | ||||
|         return self.context.REQUEST.RESPONSE.redirect(came_from) | ||||
| 
 | ||||
| 
 | ||||
| class BulkActionsView(BrowserView): | ||||
|     """Bulk actions (unapprove, approve, delete, mark as spam). | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| <?xml version="1.0"?> | ||||
| <object name="portal_workflow" meta_type="Plone Workflow Tool"> | ||||
|   <object name="comment_review_workflow" meta_type="Workflow"/> | ||||
|   <object name="comment_3state_review_workflow" meta_type="Workflow"/> | ||||
|   <object name="comment_one_state_workflow" meta_type="Workflow"/> | ||||
|   <bindings> | ||||
|     <type type_id="Discussion Item"> | ||||
|  | ||||
| @ -0,0 +1,105 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <dc-workflow workflow_id="comment_3state_review_workflow" title="Comment Review Workflow" description="A simple review workflow for comments" state_variable="review_state" initial_state="pending" manager_bypass="False"> | ||||
|  <permission>Access contents information</permission> | ||||
|  <permission>Modify portal content</permission> | ||||
|  <permission>Reply to item</permission> | ||||
|  <permission>View</permission> | ||||
|  <state state_id="pending" title="Pending"> | ||||
|   <description>Submitted, pending review.</description> | ||||
|   <exit-transition transition_id="publish"/> | ||||
|   <exit-transition transition_id="reject"/> | ||||
|   <permission-map name="Access contents information" acquired="False"> | ||||
|    <permission-role>Manager</permission-role> | ||||
|    <permission-role>Owner</permission-role> | ||||
|    <permission-role>Reviewer</permission-role> | ||||
|   </permission-map> | ||||
|   <permission-map name="Modify portal content" acquired="False"> | ||||
|    <permission-role>Manager</permission-role> | ||||
|    <permission-role>Owner</permission-role> | ||||
|    <permission-role>Reviewer</permission-role> | ||||
|   </permission-map> | ||||
|   <permission-map name="Reply to item" acquired="False"> | ||||
|   </permission-map> | ||||
|   <permission-map name="View" acquired="False"> | ||||
|    <permission-role>Manager</permission-role> | ||||
|    <permission-role>Owner</permission-role> | ||||
|    <permission-role>Reviewer</permission-role> | ||||
|   </permission-map> | ||||
|  </state> | ||||
|  <state state_id="published" title="Published"> | ||||
|   <description>Visible to everyone, non-editable.</description> | ||||
|   <exit-transition transition_id="reject"/> | ||||
|   <permission-map name="Access contents information" acquired="True"> | ||||
|   </permission-map> | ||||
|   <permission-map name="Modify portal content" acquired="False"> | ||||
|    <permission-role>Manager</permission-role> | ||||
|   </permission-map> | ||||
|   <permission-map name="Reply to item" acquired="True"> | ||||
|   </permission-map> | ||||
|   <permission-map name="View" acquired="True"> | ||||
|   </permission-map> | ||||
|  </state> | ||||
|  <state state_id="rejected" title=""> | ||||
|   <exit-transition transition_id="publish"/> | ||||
|  </state> | ||||
|  <transition transition_id="publish" title="Reviewer approves content" new_state="published" trigger="USER" before_script="" after_script=""> | ||||
|   <description>Approving the comment makes it visible to other users.</description> | ||||
|   <action url="%(content_url)s/content_status_modify?workflow_action=publish" category="workflow" icon="">Approve</action> | ||||
|   <guard> | ||||
|    <guard-permission>Review comments</guard-permission> | ||||
|   </guard> | ||||
|  </transition> | ||||
|  <transition transition_id="reject" title="Reject" new_state="rejected" trigger="USER" before_script="" after_script=""> | ||||
|   <action url="%(content_url)s/content_status_modify?workflow_action=rejected" category="workflow" icon="">Reject</action> | ||||
|   <guard> | ||||
|    <guard-permission>Review comments</guard-permission> | ||||
|   </guard> | ||||
|  </transition> | ||||
|  <variable variable_id="action" for_catalog="False" for_status="True" update_always="True"> | ||||
|   <description>Previous transition</description> | ||||
|   <default> | ||||
| 
 | ||||
|    <expression>transition/getId|nothing</expression> | ||||
|   </default> | ||||
|   <guard> | ||||
|   </guard> | ||||
|  </variable> | ||||
|  <variable variable_id="actor" for_catalog="False" for_status="True" update_always="True"> | ||||
|   <description>The ID of the user who performed the previous transition</description> | ||||
|   <default> | ||||
| 
 | ||||
|    <expression>user/getUserName</expression> | ||||
|   </default> | ||||
|   <guard> | ||||
|   </guard> | ||||
|  </variable> | ||||
|  <variable variable_id="comments" for_catalog="False" for_status="True" update_always="True"> | ||||
|   <description>Comment about the last transition</description> | ||||
|   <default> | ||||
| 
 | ||||
|    <expression>python:state_change.kwargs.get('comment', '')</expression> | ||||
|   </default> | ||||
|   <guard> | ||||
|   </guard> | ||||
|  </variable> | ||||
|  <variable variable_id="review_history" for_catalog="False" for_status="False" update_always="False"> | ||||
|   <description>Provides access to workflow history</description> | ||||
|   <default> | ||||
| 
 | ||||
|    <expression>state_change/getHistory</expression> | ||||
|   </default> | ||||
|   <guard> | ||||
|    <guard-permission>Request review</guard-permission> | ||||
|    <guard-permission>Review portal content</guard-permission> | ||||
|   </guard> | ||||
|  </variable> | ||||
|  <variable variable_id="time" for_catalog="False" for_status="True" update_always="True"> | ||||
|   <description>When the previous transition was performed</description> | ||||
|   <default> | ||||
| 
 | ||||
|    <expression>state_change/getDateTime</expression> | ||||
|   </default> | ||||
|   <guard> | ||||
|   </guard> | ||||
|  </variable> | ||||
| </dc-workflow> | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user