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"
|
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 -->
|
<!-- Comments viewlet -->
|
||||||
<browser:viewlet
|
<browser:viewlet
|
||||||
name="plone.comments"
|
name="plone.comments"
|
||||||
|
@ -67,17 +67,57 @@ require([ // jshint ignore:line
|
|||||||
var path = $(row).find("[name='selected_obj_paths:list']").attr("value");
|
var path = $(row).find("[name='selected_obj_paths:list']").attr("value");
|
||||||
var auth_key = $('input[name="_authenticator"]').val();
|
var auth_key = $('input[name="_authenticator"]').val();
|
||||||
var target = path + "/@@moderate-publish-comment?_authenticator=" + auth_key;
|
var target = path + "/@@moderate-publish-comment?_authenticator=" + auth_key;
|
||||||
|
var moderate = $(this).closest("fieldset").attr("id") == "fieldset-moderate-comments";
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
url: target,
|
url: target,
|
||||||
success: function (msg) { // jshint ignore:line
|
success: function (msg) { // jshint ignore:line
|
||||||
// fade out row
|
if (moderate) {
|
||||||
$(row).fadeOut("normal", function () {
|
// fade out row
|
||||||
$(this).remove();
|
$(row).fadeOut("normal", function () {
|
||||||
});
|
$(this).remove();
|
||||||
// reload page if all comments have been removed
|
});
|
||||||
var comments = $("table#review-comments > tbody > tr");
|
// reload page if all comments have been removed
|
||||||
if (comments.length === 1) {
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
* 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();
|
location.reload();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -171,7 +211,8 @@ require([ // jshint ignore:line
|
|||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
$(".last-history-entry").each(function() {
|
$(".last-history-entry").each(function() {
|
||||||
$(this).load($(this).attr("data-href") + " .historyByLine", 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"
|
name="form.button.Publish"
|
||||||
i18n:attributes="value label_publish;"
|
i18n:attributes="value label_publish;"
|
||||||
tal:attributes="id item/id"
|
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=""
|
<input id=""
|
||||||
class="destructive comment-delete-button"
|
class="destructive comment-delete-button"
|
||||||
@ -167,7 +176,7 @@
|
|||||||
Approved comments</legend>
|
Approved comments</legend>
|
||||||
<div>
|
<div>
|
||||||
<form tal:condition="not:items_approved_or_rejected">
|
<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">
|
<p id="no-comments-message" i18n:translate="message_no_comments_approved">
|
||||||
No comments approved
|
No comments approved
|
||||||
</p>
|
</p>
|
||||||
@ -179,7 +188,7 @@
|
|||||||
tal:condition="items_approved_or_rejected"
|
tal:condition="items_approved_or_rejected"
|
||||||
tal:define="batch python:Batch(items_approved_or_rejected, b_size, int(b_start), orphan=1);">
|
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" />
|
<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_date">Date</th>
|
||||||
<th class="nosort" i18n:translate="heading_in_reponse_to">In Response To</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_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>
|
<th class="nosort" i18n:translate="heading_action">Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -215,8 +224,8 @@
|
|||||||
<a tal:attributes="href item_url"
|
<a tal:attributes="href item_url"
|
||||||
tal:content="item/in_response_to" />
|
tal:content="item/in_response_to" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td tal:attributes="class python:item.review_state=='rejected' and 'state-private'">
|
||||||
<span tal:replace="item/Description" />
|
<span tal:replace="item/Description"/>
|
||||||
<a href=""
|
<a href=""
|
||||||
tal:attributes="href string:$item_url/getText"
|
tal:attributes="href string:$item_url/getText"
|
||||||
tal:condition="python:item.Description.endswith('[...]')"
|
tal:condition="python:item.Description.endswith('[...]')"
|
||||||
@ -229,10 +238,27 @@
|
|||||||
last history entry
|
last history entry
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
<input type="hidden" name="selected_obj_paths:list" value="#"
|
<input type="hidden" name="selected_obj_paths:list" value="#"
|
||||||
tal:attributes="value item/getURL" />
|
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=""
|
<input id=""
|
||||||
class="destructive comment-delete-button"
|
class="destructive comment-delete-button"
|
||||||
type="submit"
|
type="submit"
|
||||||
|
@ -36,7 +36,7 @@ class View(BrowserView):
|
|||||||
sort_on='created',
|
sort_on='created',
|
||||||
sort_order='reverse')
|
sort_order='reverse')
|
||||||
self.comments_approved = catalog(object_provides=IComment.__identifier__,
|
self.comments_approved = catalog(object_provides=IComment.__identifier__,
|
||||||
review_state='published',
|
review_state=['published', 'rejected'],
|
||||||
sort_on='created',
|
sort_on='created',
|
||||||
sort_order='reverse')
|
sort_order='reverse')
|
||||||
return self.template()
|
return self.template()
|
||||||
@ -57,6 +57,23 @@ class View(BrowserView):
|
|||||||
return True
|
return True
|
||||||
return False
|
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):
|
class ModerateCommentsEnabled(BrowserView):
|
||||||
|
|
||||||
@ -189,10 +206,11 @@ class PublishComment(BrowserView):
|
|||||||
alsoProvides(self.request, IDisableCSRFProtection)
|
alsoProvides(self.request, IDisableCSRFProtection)
|
||||||
comment = aq_inner(self.context)
|
comment = aq_inner(self.context)
|
||||||
content_object = aq_parent(aq_parent(comment))
|
content_object = aq_parent(aq_parent(comment))
|
||||||
|
print("*** called: PublishComment for ", comment.Description)
|
||||||
workflowTool = getToolByName(comment, 'portal_workflow', None)
|
workflowTool = getToolByName(comment, 'portal_workflow', None)
|
||||||
workflow_action = self.request.form.get('workflow_action', 'publish')
|
workflow_action = self.request.form.get('workflow_action', 'publish')
|
||||||
review_state = workflowTool.getInfoFor(comment, 'review_state', '')
|
review_state = workflowTool.getInfoFor(comment, 'review_state', '')
|
||||||
if review_state == "pending":
|
if review_state != "published":
|
||||||
workflowTool.doActionFor(comment, workflow_action)
|
workflowTool.doActionFor(comment, workflow_action)
|
||||||
comment.reindexObject()
|
comment.reindexObject()
|
||||||
content_object.reindexObject(idxs=['total_comments'])
|
content_object.reindexObject(idxs=['total_comments'])
|
||||||
@ -214,6 +232,41 @@ class PublishComment(BrowserView):
|
|||||||
return self.context.REQUEST.RESPONSE.redirect(came_from)
|
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):
|
class BulkActionsView(BrowserView):
|
||||||
"""Bulk actions (unapprove, approve, delete, mark as spam).
|
"""Bulk actions (unapprove, approve, delete, mark as spam).
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<object name="portal_workflow" meta_type="Plone Workflow Tool">
|
<object name="portal_workflow" meta_type="Plone Workflow Tool">
|
||||||
<object name="comment_review_workflow" meta_type="Workflow"/>
|
<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"/>
|
<object name="comment_one_state_workflow" meta_type="Workflow"/>
|
||||||
<bindings>
|
<bindings>
|
||||||
<type type_id="Discussion Item">
|
<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…
Reference in New Issue
Block a user