From ed7937e1c6140e27ba84db9a596adfe956c4d4e7 Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Thu, 10 Oct 2019 15:42:11 +0200 Subject: [PATCH 1/5] Add a way to execute unpublishing --- djangocms_moderation/admin.py | 98 ++++++++++++------- djangocms_moderation/admin_actions.py | 19 +--- .../0017_workflow_is_unpublishing.py | 20 ++++ djangocms_moderation/models.py | 9 ++ djangocms_moderation/signals.py | 8 +- 5 files changed, 103 insertions(+), 51 deletions(-) create mode 100644 djangocms_moderation/migrations/0017_workflow_is_unpublishing.py diff --git a/djangocms_moderation/admin.py b/djangocms_moderation/admin.py index 306a2ed8..902b45cf 100644 --- a/djangocms_moderation/admin.py +++ b/djangocms_moderation/admin.py @@ -7,7 +7,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.db import transaction -from django.http import Http404, HttpResponseRedirect +from django.http import Http404, HttpResponseRedirect, HttpResponseNotAllowed from django.shortcuts import get_object_or_404, render from django.template.loader import render_to_string from django.urls import reverse @@ -27,7 +27,6 @@ delete_selected, post_bulk_actions, publish_selected, - publish_version, reject_selected, resubmit_selected, ) @@ -552,6 +551,7 @@ def resubmit_view(self, request): moderation_requests=resubmitted_requests, user=request.user, rework=True, + workflow=collection.workflow ) messages.success( @@ -565,6 +565,49 @@ def resubmit_view(self, request): ) return HttpResponseRedirect(redirect_url) + def _publish_flow(self, request, queryset): + """Handles the published workflow""" + published_moderation_requests = [] + for mr in queryset.all(): + if mr.version_can_be_published(): + mr.version.publish(request.user) + published_moderation_requests.append(mr) + mr.update_status( + action=constants.ACTION_FINISHED, by_user=request.user + ) + + messages.success( + request, + ungettext( + "%(count)d request successfully published", + "%(count)d requests successfully published", + len(published_moderation_requests), + ) + % {"count": len(published_moderation_requests)}, + ) + return published_moderation_requests + + def _unpublish_flow(self, request, queryset): + unpublished_moderation_requests = [] + for mr in queryset.all(): + if mr.version_can_be_unpublished(): + mr.version.unpublish(request.user) + unpublished_moderation_requests.append(mr) + mr.update_status( + action=constants.ACTION_FINISHED, by_user=request.user + ) + + messages.success( + request, + ungettext( + "%(count)d request successfully unpublished", + "%(count)d requests successfully unpublished", + len(unpublished_moderation_requests), + ) + % {"count": len(unpublished_moderation_requests)}, + ) + return unpublished_moderation_requests + def published_view(self, request): collection_id = request.GET.get('collection_id') treenodes = self._get_selected_tree_nodes(request) @@ -578,46 +621,30 @@ def published_view(self, request): if request.user != collection.author: raise PermissionDenied - if request.method != 'POST': + if request.method not in ['POST', 'GET']: + return HttpResponseNotAllowed + + if request.method == 'GET': context = self._custom_view_context(request) return render( request, "admin/djangocms_moderation/moderationrequest/publish_confirmation.html", context, ) - else: - published_moderation_requests = [] - for node in treenodes.all(): - mr = node.moderation_request - if mr.version_can_be_published(): - if publish_version(mr.version, request.user): - published_moderation_requests.append(mr) - mr.update_status( - action=constants.ACTION_FINISHED, by_user=request.user - ) - else: - # TODO provide some feedback back to the user? - pass - - messages.success( - request, - ungettext( - "%(count)d request successfully published", - "%(count)d requests successfully published", - len(published_moderation_requests), - ) - % {"count": len(published_moderation_requests)}, - ) - - post_bulk_actions(collection) - signals.published.send( - sender=self.model, - collection=collection, - moderator=collection.author, - moderation_requests=published_moderation_requests, - workflow=collection.workflow - ) + if collection.workflow.is_unpublishing: + moderation_requests = self._unpublish_flow(request, treenodes) + else: + moderation_requests = self._publish_flow(request, treenodes) + + post_bulk_actions(collection) + signals.published.send( + sender=self.model, + collection=collection, + moderator=collection.author, + moderation_requests=moderation_requests, + workflow=collection.workflow + ) return HttpResponseRedirect(redirect_url) def rework_view(self, request): @@ -955,6 +982,7 @@ class WorkflowAdmin(admin.ModelAdmin): fields = [ "name", "is_default", + "is_unpublishing", "identifier", "requires_compliance_number", "compliance_number_backend", diff --git a/djangocms_moderation/admin_actions.py b/djangocms_moderation/admin_actions.py index d1585c23..a8044538 100644 --- a/djangocms_moderation/admin_actions.py +++ b/djangocms_moderation/admin_actions.py @@ -12,7 +12,6 @@ from djangocms_versioning.models import Version -from django_fsm import TransitionNotAllowed from djangocms_moderation import constants from .utils import get_admin_url @@ -115,9 +114,9 @@ def convert_queryset_to_version_queryset(queryset): m for m in reversed(model.mro()) if ( - isinstance(m, tuple(model_bases)) - and m not in model_bases - and not m._meta.abstract + isinstance(m, tuple(model_bases)) and + m not in model_bases and not + m._meta.abstract ) ) @@ -153,20 +152,10 @@ def add_items_to_collection(modeladmin, request, queryset): return HttpResponseRedirect(request.META.get("HTTP_REFERER")) -add_items_to_collection.short_description = _( - "Add to moderation collection" -) # noqa: E305 +add_items_to_collection.short_description = _("Add to moderation collection") def post_bulk_actions(collection): if collection.should_be_archived(): collection.status = constants.ARCHIVED collection.save(update_fields=["status"]) - - -def publish_version(version, user): - try: - version.publish(user) - except TransitionNotAllowed: - return False - return True diff --git a/djangocms_moderation/migrations/0017_workflow_is_unpublishing.py b/djangocms_moderation/migrations/0017_workflow_is_unpublishing.py new file mode 100644 index 00000000..7c501f1c --- /dev/null +++ b/djangocms_moderation/migrations/0017_workflow_is_unpublishing.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-10-11 09:23 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('djangocms_moderation', '0016_moderationrequesttreenode'), + ] + + operations = [ + migrations.AddField( + model_name='workflow', + name='is_unpublishing', + field=models.BooleanField(default=False, verbose_name='unpublishing workflow'), + ), + ] diff --git a/djangocms_moderation/models.py b/djangocms_moderation/models.py index 2b840e33..e341e655 100644 --- a/djangocms_moderation/models.py +++ b/djangocms_moderation/models.py @@ -118,6 +118,11 @@ def get_users_queryset(self): class Workflow(models.Model): name = models.CharField(verbose_name=_("name"), max_length=120, unique=True) is_default = models.BooleanField(verbose_name=_("is default"), default=False) + is_unpublishing = models.BooleanField( + verbose_name=_("unpublishing workflow"), + default=False, + help_text=_("This workflow will unpublish assets at the end") + ) identifier = models.CharField( verbose_name=_("identifier"), max_length=128, @@ -295,6 +300,7 @@ def submit_for_review(self, by_user, to_user=None): moderation_requests=list(self.moderation_requests.all()), user=by_user, rework=False, + workflow=self.workflow ) def is_cancellable(self, user): @@ -462,6 +468,9 @@ def is_approved(self): def version_can_be_published(self): return self.is_approved() and self.version.can_be_published() + def version_can_be_unpublished(self): + return self.is_approved() and self.version.can_be_unpublished() + def is_rejected(self): last_action = self.get_last_action() return last_action and last_action.action == constants.ACTION_REJECTED diff --git a/djangocms_moderation/signals.py b/djangocms_moderation/signals.py index 46c28975..67356293 100644 --- a/djangocms_moderation/signals.py +++ b/djangocms_moderation/signals.py @@ -6,7 +6,13 @@ ) submitted_for_review = django.dispatch.Signal( - providing_args=["collection", "moderation_requests", "user", "rework"] + providing_args=[ + "collection", + "moderation_requests", + "user", + "rework", + "workflow" + ] ) published = django.dispatch.Signal( From 0ba4d10d03b48cf8abd86e6439187ddf74589cd6 Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Mon, 14 Oct 2019 13:26:59 +0200 Subject: [PATCH 2/5] Add unpublish_selected among actions --- djangocms_moderation/admin.py | 112 +++++++++++------- djangocms_moderation/admin_actions.py | 9 +- .../publish_confirmation.html | 4 + tests/test_admin.py | 19 ++- tests/test_admin_actions.py | 81 ++++++++++++- 5 files changed, 176 insertions(+), 49 deletions(-) diff --git a/djangocms_moderation/admin.py b/djangocms_moderation/admin.py index 902b45cf..194da776 100644 --- a/djangocms_moderation/admin.py +++ b/djangocms_moderation/admin.py @@ -29,6 +29,7 @@ publish_selected, reject_selected, resubmit_selected, + unpublish_selected, ) from .emails import notify_collection_author, notify_collection_moderators from .filters import ModeratorFilter, ReviewerFilter @@ -119,6 +120,7 @@ class Media: actions = [ # filtered out in `self.get_actions` delete_selected, + unpublish_selected, publish_selected, approve_selected, reject_selected, @@ -289,31 +291,50 @@ def get_actions(self, request): ) # publish_selected, approve_selected, reject_selected, resubmit_selected else: # If the collection is archived, then no other action than - # `publish_selected` is possible. + # `publish_selected` or `unpublish_selected` is possible. _max_to_keep = 1 # publish_selected for mr in collection.moderation_requests.all().select_related("version"): if len(actions_to_keep) == _max_to_keep: break # We have found all the actions, so no need to loop anymore - if "publish_selected" not in actions_to_keep: - if ( - request.user == collection.author - and mr.version_can_be_published() - ): - actions_to_keep.append("publish_selected") - if ( - collection.status == constants.IN_REVIEW - and "approve_selected" not in actions_to_keep - ): - if mr.user_can_take_moderation_action(request.user): - actions_to_keep.append("approve_selected") - actions_to_keep.append("reject_selected") - if ( - collection.status == constants.IN_REVIEW - and "resubmit_selected" not in actions_to_keep - ): - if mr.user_can_resubmit(request.user): - actions_to_keep.append("resubmit_selected") + + publish_condition = all([ + "publish_selected" not in actions_to_keep, + request.user == collection.author, + collection.workflow.is_unpublishing is False, + mr.version_can_be_published() + ]) + + unpublish_condition = all([ + "unpublish_selected" not in actions_to_keep, + collection.workflow.is_unpublishing is True, + mr.version_can_be_unpublished() + ]) + + approve_condition = all([ + "approve_selected" not in actions_to_keep, + collection.status == constants.IN_REVIEW, + mr.user_can_take_moderation_action(request.user) + ]) + + resubmit_condition = all([ + "resubmit_selected" not in actions_to_keep, + collection.status == constants.IN_REVIEW, + mr.user_can_resubmit(request.user) + ]) + + if unpublish_condition: + actions_to_keep.append("unpublish_selected") + + if publish_condition: + actions_to_keep.append("publish_selected") + + if approve_condition: + actions_to_keep.append("approve_selected") + actions_to_keep.append("reject_selected") + + if resubmit_condition: + actions_to_keep.append("resubmit_selected") # Only collection author can delete moderation requests if collection.author == request.user: @@ -495,31 +516,30 @@ def _get_selected_tree_nodes(self, request): ).select_related('moderation_request') return treenodes - def _custom_view_context(self, request): + def _custom_view_context(self, request, collection): treenodes = self._get_selected_tree_nodes(request) - collection_id = request.GET.get('collection_id') - redirect_url = self._redirect_to_changeview_url(collection_id) + redirect_url = self._redirect_to_changeview_url(collection.pk) return dict( ids=request.GET.getlist("ids"), back_url=redirect_url, - queryset=[n.moderation_request for n in treenodes] + queryset=[n.moderation_request for n in treenodes], + collection=collection ) def resubmit_view(self, request): collection_id = request.GET.get('collection_id') - treenodes = self._get_selected_tree_nodes(request) - redirect_url = self._redirect_to_changeview_url(collection_id) - try: collection = ModerationCollection.objects.get(id=int(collection_id)) except (ValueError, ModerationCollection.DoesNotExist): raise Http404 + treenodes = self._get_selected_tree_nodes(request) + redirect_url = self._redirect_to_changeview_url(collection_id) if collection.author != request.user: raise PermissionDenied if request.method != 'POST': - context = self._custom_view_context(request) + context = self._custom_view_context(request, collection) return render( request, 'admin/djangocms_moderation/moderationrequest/resubmit_confirmation.html', @@ -527,7 +547,6 @@ def resubmit_view(self, request): ) else: resubmitted_requests = [] - for node in treenodes.all(): mr = node.moderation_request if mr.user_can_resubmit(request.user): @@ -568,7 +587,8 @@ def resubmit_view(self, request): def _publish_flow(self, request, queryset): """Handles the published workflow""" published_moderation_requests = [] - for mr in queryset.all(): + for node in queryset.all(): + mr = node.moderation_request if mr.version_can_be_published(): mr.version.publish(request.user) published_moderation_requests.append(mr) @@ -589,7 +609,8 @@ def _publish_flow(self, request, queryset): def _unpublish_flow(self, request, queryset): unpublished_moderation_requests = [] - for mr in queryset.all(): + for node in queryset.all(): + mr = node.moderation_request if mr.version_can_be_unpublished(): mr.version.unpublish(request.user) unpublished_moderation_requests.append(mr) @@ -610,13 +631,12 @@ def _unpublish_flow(self, request, queryset): def published_view(self, request): collection_id = request.GET.get('collection_id') - treenodes = self._get_selected_tree_nodes(request) - redirect_url = self._redirect_to_changeview_url(collection_id) - try: collection = ModerationCollection.objects.get(id=int(collection_id)) except (ValueError, ModerationCollection.DoesNotExist): raise Http404 + treenodes = self._get_selected_tree_nodes(request) + redirect_url = self._redirect_to_changeview_url(collection_id) if request.user != collection.author: raise PermissionDenied @@ -625,7 +645,7 @@ def published_view(self, request): return HttpResponseNotAllowed if request.method == 'GET': - context = self._custom_view_context(request) + context = self._custom_view_context(request, collection) return render( request, "admin/djangocms_moderation/moderationrequest/publish_confirmation.html", @@ -649,24 +669,24 @@ def published_view(self, request): def rework_view(self, request): collection_id = request.GET.get('collection_id') + try: + collection = ModerationCollection.objects.get(id=int(collection_id)) + except (ValueError, ModerationCollection.DoesNotExist): + raise Http404 + treenodes = self._get_selected_tree_nodes(request) redirect_url = self._redirect_to_changeview_url(collection_id) if request.method != 'POST': - context = self._custom_view_context(request) + context = self._custom_view_context(request, collection) return render( request, "admin/djangocms_moderation/moderationrequest/rework_confirmation.html", context, ) else: - try: - collection = ModerationCollection.objects.get(id=int(collection_id)) - except (ValueError, ModerationCollection.DoesNotExist): - raise Http404 rejected_requests = [] - for node in treenodes.all(): moderation_request = node.moderation_request if moderation_request.user_can_take_moderation_action(request.user): @@ -698,11 +718,15 @@ def rework_view(self, request): def approved_view(self, request): collection_id = request.GET.get('collection_id') + try: + collection = ModerationCollection.objects.get(id=int(collection_id)) + except (ValueError, ModerationCollection.DoesNotExist): + raise Http404 treenodes = self._get_selected_tree_nodes(request) redirect_url = self._redirect_to_changeview_url(collection_id) if request.method != 'POST': - context = self._custom_view_context(request) + context = self._custom_view_context(request, collection) return render( request, "admin/djangocms_moderation/moderationrequest/approve_confirmation.html", @@ -723,10 +747,6 @@ def approved_view(self, request): and some in the second, then the reviewers we need to notify are different per request, depending on which stage the request is in """ - try: - collection = ModerationCollection.objects.get(id=int(collection_id)) - except (ValueError, ModerationCollection.DoesNotExist): - raise Http404 approved_requests = [] # Variable we are using to group the requests by action.step_approved diff --git a/djangocms_moderation/admin_actions.py b/djangocms_moderation/admin_actions.py index a8044538..0eaaa519 100644 --- a/djangocms_moderation/admin_actions.py +++ b/djangocms_moderation/admin_actions.py @@ -1,4 +1,5 @@ from collections import defaultdict +from functools import partial from django.contrib import admin from django.contrib.contenttypes.models import ContentType @@ -77,7 +78,7 @@ def delete_selected(modeladmin, request, queryset): delete_selected.short_description = _("Remove selected") -def publish_selected(modeladmin, request, queryset): +def base_publish(modeladmin, request, queryset): if request.user != request._collection.author: raise PermissionDenied @@ -90,8 +91,14 @@ def publish_selected(modeladmin, request, queryset): return HttpResponseRedirect(url) +publish_selected = partial(base_publish) +publish_selected.__name__ = 'publish_selected' publish_selected.short_description = _("Publish selected requests") +unpublish_selected = partial(base_publish) +unpublish_selected.__name__ = 'unpublish_selected' +unpublish_selected.short_description = _("Unpublish selected requests") + def convert_queryset_to_version_queryset(queryset): if not queryset: diff --git a/djangocms_moderation/templates/admin/djangocms_moderation/moderationrequest/publish_confirmation.html b/djangocms_moderation/templates/admin/djangocms_moderation/moderationrequest/publish_confirmation.html index c7b3122c..331a6281 100644 --- a/djangocms_moderation/templates/admin/djangocms_moderation/moderationrequest/publish_confirmation.html +++ b/djangocms_moderation/templates/admin/djangocms_moderation/moderationrequest/publish_confirmation.html @@ -11,7 +11,11 @@ {% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation{% endblock %} {% block content %} +{% if collection.workflow.is_unpublishing %} +

{% trans "Are you sure you want to unpublish these items?" %}

+{% else %}

{% trans "Are you sure you want to publish these items?" %}

+{% endif %}
diff --git a/tests/test_admin.py b/tests/test_admin.py index 50780d9e..36de9b94 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -113,7 +113,7 @@ def test_publish_selected_action_visibility(self): # mr1 request is approved, so user1 can see the publish selected option self.assertIn("publish_selected", actions) - # user2 should not be able to see it + # user2 should not be able to see it as user2 is not the author mock_request.user = self.user2 actions = self.mr_tree_admin.get_actions(request=mock_request) self.assertNotIn("publish_selected", actions) @@ -124,6 +124,23 @@ def test_publish_selected_action_visibility(self): actions = self.mr_tree_admin.get_actions(request=mock_request) self.assertNotIn("publish_selected", actions) + def test_unpublish_selected_action_visibility(self): + self.collection.workflow.is_unpublishing = True + self.collection.workflow.save() + self.mr1.version.publish(self.user) + mock_request = MockRequest() + mock_request.user = self.user + mock_request._collection = self.collection + actions = self.mr_tree_admin.get_actions(request=mock_request) + # mr1 request is approved, so user1 can see the unpublish selected option + self.assertIn("unpublish_selected", actions) + + # if there are no approved requests, user can't see the button either + mock_request.user = self.user + self.mr1.get_last_action().delete() + actions = self.mr_tree_admin.get_actions(request=mock_request) + self.assertNotIn("unpublish_selected", actions) + def test_approve_and_reject_selected_action_visibility(self): mock_request = MockRequest() mock_request.user = self.user diff --git a/tests/test_admin_actions.py b/tests/test_admin_actions.py index e45475ce..ef21880b 100644 --- a/tests/test_admin_actions.py +++ b/tests/test_admin_actions.py @@ -9,7 +9,7 @@ from cms.test_utils.testcases import CMSTestCase from cms.test_utils.util.context_managers import signal_tester -from djangocms_versioning.constants import DRAFT, PUBLISHED +from djangocms_versioning.constants import DRAFT, PUBLISHED, UNPUBLISHED from djangocms_versioning.models import Version from djangocms_moderation import constants @@ -551,6 +551,84 @@ def test_view_doesnt_reject_when_user_cant_take_moderation_action( self.assertFalse(notify_author_mock.called) +class UnpublishSelectedTest(CMSTestCase): + def setUp(self): + self.user = factories.UserFactory(is_staff=True, is_superuser=True) + + # TODO approving all MR will put the collection in archived status. + # So that is the current assumption that it should be correct. + self.collection = factories.ModerationCollectionFactory( + author=self.user, status=constants.ARCHIVED + ) + self.collection.workflow.is_unpublishing = True + self.collection.workflow.save() + self.role1 = Role.objects.create(name="Role 1", user=self.user) + self.role2 = Role.objects.create(name="Role 2", user=factories.UserFactory(is_staff=True, is_superuser=True)) + self.collection.workflow.steps.create(role=self.role1, is_required=True, order=1) + self.collection.workflow.steps.create(role=self.role2, is_required=True, order=1) + + self.mr1 = factories.ModerationRequestFactory( + id=1, collection=self.collection + ) + self.mr2 = factories.ModerationRequestFactory( + id=2, collection=self.collection + ) + self.root1 = factories.RootModerationRequestTreeNodeFactory( + id=4, moderation_request=self.mr1 + ) + factories.ChildModerationRequestTreeNodeFactory( + id=5, moderation_request=self.mr2, parent=self.root1 + ) + self.root2 = factories.RootModerationRequestTreeNodeFactory( + id=6, moderation_request=self.mr2 + ) + + # Request 1 is approved, request 2 is started + self.mr1.actions.create(by_user=self.user, action=constants.ACTION_STARTED) + self.mr2.actions.create(by_user=self.user, action=constants.ACTION_STARTED) + self.mr1.update_status(constants.ACTION_APPROVED, self.role1.user) + self.mr1.update_status(constants.ACTION_APPROVED, self.role2.user) + + self.mr1.version.publish(self.user) + self.mr2.version.publish(self.user) + + # we need to refresh the db so that version state is reflected correctly + self.mr1.refresh_from_db() + self.mr2.refresh_from_db() + + # Login as the collection author + self.client.force_login(self.user) + + self.url = reverse("admin:djangocms_moderation_moderationrequesttreenode_changelist") + self.url += "?moderation_request__collection__id={}".format(self.collection.pk) + + @mock.patch("django.contrib.messages.success") + def test_unpublish_selected_unpublishes_approved_request(self, message_mock): + + # resp = self.client.get(self.url) + # import pdb; pdb.set_trace() + + print('correct flow') + data = get_url_data(self, "unpublish_selected") + response = self.client.post(self.url, data) + # And now go to the view the action redirects to + response = self.client.post(response.url) + + # Response is correct + self.assertEqual(response.status_code, 302) + self.assertEqual(response.url, self.url) + self.assertEqual(message_mock.call_args[0][1], '1 request successfully unpublished') + + version1 = Version.objects.get(pk=self.mr1.version.pk) + version2 = Version.objects.get(pk=self.mr2.version.pk) + self.mr1.refresh_from_db() + self.mr2.refresh_from_db() + self.assertEqual(version1.state, UNPUBLISHED) + self.assertEqual(version2.state, PUBLISHED) + self.assertFalse(self.mr1.is_active) + self.assertTrue(self.mr2.is_active) + + class PublishSelectedTest(CMSTestCase): def setUp(self): @@ -647,6 +725,7 @@ def test_signal_when_published(self): self.assertEquals(published_mr[0], self.moderation_request1) self.assertEquals(kwargs['workflow'], self.collection.workflow) + # TODO Andrew, how is it intended that collection status should change? @unittest.skip("Skip until collection status bugs fixed") @mock.patch("django.contrib.messages.success") def test_publish_selected_sets_collection_to_archived_if_all_requests_published(self, messages_mock): From 9370420d3a13321a3494e480aec3c1c0463f17fe Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Mon, 21 Oct 2019 15:48:36 +0200 Subject: [PATCH 3/5] Fix isort errors --- djangocms_moderation/admin.py | 2 +- djangocms_moderation/forms.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/djangocms_moderation/admin.py b/djangocms_moderation/admin.py index 194da776..cb02672e 100644 --- a/djangocms_moderation/admin.py +++ b/djangocms_moderation/admin.py @@ -7,7 +7,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.db import transaction -from django.http import Http404, HttpResponseRedirect, HttpResponseNotAllowed +from django.http import Http404, HttpResponseNotAllowed, HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.template.loader import render_to_string from django.urls import reverse diff --git a/djangocms_moderation/forms.py b/djangocms_moderation/forms.py index 4b7c2317..f56a0eef 100644 --- a/djangocms_moderation/forms.py +++ b/djangocms_moderation/forms.py @@ -10,6 +10,7 @@ from adminsortable2.admin import CustomInlineFormSet from djangocms_versioning.models import Version + from .constants import ACTION_CANCELLED, ACTION_REJECTED, ACTION_RESUBMITTED, COLLECTING from .helpers import ( get_active_moderation_request, From 7ce61de05e1a7525b634e1a11a1fc7f0894e4eda Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Mon, 21 Oct 2019 16:00:46 +0200 Subject: [PATCH 4/5] Add a separate signal for when something is being unpublished --- djangocms_moderation/admin.py | 23 ++++++++++++++++------- djangocms_moderation/signals.py | 9 +++++++++ tests/test_admin_actions.py | 28 ++++++++++++++++++++++------ 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/djangocms_moderation/admin.py b/djangocms_moderation/admin.py index cb02672e..869328ac 100644 --- a/djangocms_moderation/admin.py +++ b/djangocms_moderation/admin.py @@ -658,13 +658,22 @@ def published_view(self, request): moderation_requests = self._publish_flow(request, treenodes) post_bulk_actions(collection) - signals.published.send( - sender=self.model, - collection=collection, - moderator=collection.author, - moderation_requests=moderation_requests, - workflow=collection.workflow - ) + if collection.workflow.is_unpublishing: + signals.unpublished.send( + sender=self.model, + collection=collection, + moderator=collection.author, + moderation_requests=moderation_requests, + workflow=collection.workflow + ) + else: + signals.published.send( + sender=self.model, + collection=collection, + moderator=collection.author, + moderation_requests=moderation_requests, + workflow=collection.workflow + ) return HttpResponseRedirect(redirect_url) def rework_view(self, request): diff --git a/djangocms_moderation/signals.py b/djangocms_moderation/signals.py index 67356293..8c23daba 100644 --- a/djangocms_moderation/signals.py +++ b/djangocms_moderation/signals.py @@ -23,3 +23,12 @@ "workflow" ] ) + +unpublished = django.dispatch.Signal( + providing_args=[ + "collection", + "moderator", + "moderation_requests", + "workflow" + ] +) diff --git a/tests/test_admin_actions.py b/tests/test_admin_actions.py index ef21880b..d99e25f9 100644 --- a/tests/test_admin_actions.py +++ b/tests/test_admin_actions.py @@ -20,7 +20,7 @@ ModerationRequestTreeNode, Role, ) -from djangocms_moderation.signals import published +from djangocms_moderation.signals import published, unpublished from .utils import factories @@ -604,11 +604,6 @@ def setUp(self): @mock.patch("django.contrib.messages.success") def test_unpublish_selected_unpublishes_approved_request(self, message_mock): - - # resp = self.client.get(self.url) - # import pdb; pdb.set_trace() - - print('correct flow') data = get_url_data(self, "unpublish_selected") response = self.client.post(self.url, data) # And now go to the view the action redirects to @@ -628,6 +623,27 @@ def test_unpublish_selected_unpublishes_approved_request(self, message_mock): self.assertFalse(self.mr1.is_active) self.assertTrue(self.mr2.is_active) + def test_signal_when_unpublished(self): + """ + A signal should be sent so further action can be taken when a moderation + collection is being unpublished. + """ + data = get_url_data(self, "unpublish_selected") + response = self.client.post(self.url, data) + + with signal_tester(unpublished) as signal: + # And now go to the view the action redirects to + self.client.post(response.url) + args, kwargs = signal.calls[0] + published_mr = kwargs['moderation_requests'] + self.assertEquals(signal.call_count, 1) + self.assertEquals(kwargs['sender'], ModerationRequest) + self.assertEquals(kwargs['collection'], self.collection) + self.assertEquals(kwargs['moderator'], self.collection.author) + self.assertEquals(len(published_mr), 1) + self.assertEquals(published_mr[0], self.mr1) + self.assertEquals(kwargs['workflow'], self.collection.workflow) + class PublishSelectedTest(CMSTestCase): From 4a5d60b0039fcc034cdbfff400b7600888ad9801 Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Tue, 22 Oct 2019 12:20:45 +0200 Subject: [PATCH 5/5] Fighting isort... --- djangocms_moderation/forms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/djangocms_moderation/forms.py b/djangocms_moderation/forms.py index f56a0eef..4b7c2317 100644 --- a/djangocms_moderation/forms.py +++ b/djangocms_moderation/forms.py @@ -10,7 +10,6 @@ from adminsortable2.admin import CustomInlineFormSet from djangocms_versioning.models import Version - from .constants import ACTION_CANCELLED, ACTION_REJECTED, ACTION_RESUBMITTED, COLLECTING from .helpers import ( get_active_moderation_request,