Skip to content

Commit

Permalink
feat: Provide configurable Moderation Request Changelist fields and a…
Browse files Browse the repository at this point in the history
…ctions (#194)
  • Loading branch information
Aiky30 authored Sep 21, 2021
1 parent dbd820e commit 1fd928e
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 19 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ Changelog

Unreleased
==========
* Configuration options added to the cms_config to allow a third party to add fields and actions to the Moderation Request Changelist admin view.

1.0.27 (2021-03-10)
==================
===================
* Wrapped the publish view logic in a transaction to prevent inconsistent ModerationRequest states in the future.
* Added a new management command: "moderation_fix_states" to repair any ModerationRequests left in an inconsistent state where the the is_active state is True, the ModerationRequest version object is published, and the collection is Archived.
52 changes: 44 additions & 8 deletions djangocms_moderation/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import unicode_literals

from django import forms
from django.apps import apps
from django.conf.urls import url
from django.contrib import admin, messages
from django.contrib.auth import get_user_model
Expand Down Expand Up @@ -117,6 +118,7 @@ class ModerationRequestTreeAdmin(TreeAdmin):
"""
class Media:
js = ("djangocms_moderation/js/actions.js",)
css = {"all": ("djangocms_moderation/css/actions.css",)}

actions = [ # filtered out in `self.get_actions`
delete_selected,
Expand Down Expand Up @@ -158,6 +160,7 @@ def get_urls(self):
] + super().get_urls()

def get_list_display(self, request):
additional_fields = self._get_configured_fields(request)
list_display = [
'get_id',
'get_content_type',
Expand All @@ -166,11 +169,44 @@ def get_list_display(self, request):
'get_preview_link',
'get_status',
'get_reviewer',
*additional_fields,
'list_display_actions',
]
if conf.REQUEST_COMMENTS_ENABLED:
list_display.append("get_comments_link")
return list_display

def list_display_actions(self, obj):
"""Display links to state change endpoints
"""
return format_html_join(
"", "{}", ((action(obj),) for action in self.get_list_display_actions())
)

list_display_actions.short_description = _("actions")

def get_list_display_actions(self):
actions = []
if conf.REQUEST_COMMENTS_ENABLED:
actions.append(self.get_comments_link)

# Get any configured additional actions
moderation_config = apps.get_app_config("djangocms_moderation")
additional_actions = moderation_config.cms_extension.moderation_request_changelist_actions
if additional_actions:
actions += additional_actions

return actions

def _get_configured_fields(self, request):
fields = []
moderation_config = apps.get_app_config("djangocms_moderation")
additional_fields = moderation_config.cms_extension.moderation_request_changelist_fields

for field in additional_fields:
fields.append(field.__name__)
setattr(self, field.__name__, field)

return fields

def get_id(self, obj):
return format_html(
'<a href="{url}">{id}</a>',
Expand Down Expand Up @@ -255,14 +291,14 @@ def get_status(self, obj):
return status

def get_comments_link(self, obj):
return format_html(
'<a href="{}?moderation_request__id__exact={}">{}</a>',
reverse('admin:djangocms_moderation_requestcomment_changelist'),
comments_endpoint = format_html(
"{}?moderation_request__id__exact={}",
reverse("admin:djangocms_moderation_requestcomment_changelist"),
obj.moderation_request.id,
_('View')
)

get_comments_link.short_description = _("Comments")
return render_to_string(
"djangocms_moderation/comment_icon.html", {"url": comments_endpoint}
)

def get_actions(self, request):
"""
Expand Down
15 changes: 15 additions & 0 deletions djangocms_moderation/cms_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
class ModerationExtension(CMSAppExtension):
def __init__(self):
self.moderated_models = []
self.moderation_request_changelist_actions = []
self.moderation_request_changelist_fields = []

def handle_moderation_request_changelist_actions(self, moderation_request_changelist_actions):
self.moderation_request_changelist_actions.extend(moderation_request_changelist_actions)

def handle_moderation_request_changelist_fields(self, moderation_request_changelist_fields):
self.moderation_request_changelist_fields.extend(moderation_request_changelist_fields)

def configure_app(self, cms_config):
versioning_enabled = getattr(cms_config, "djangocms_versioning_enabled", False)
Expand All @@ -17,9 +25,16 @@ def configure_app(self, cms_config):

self.moderated_models.extend(moderated_models)

if hasattr(cms_config, "moderation_request_changelist_actions"):
self.handle_moderation_request_changelist_actions(cms_config.moderation_request_changelist_actions)

if hasattr(cms_config, "moderation_request_changelist_fields"):
self.handle_moderation_request_changelist_fields(cms_config.moderation_request_changelist_fields)


class CoreCMSAppConfig(CMSAppConfig):
djangocms_moderation_enabled = True
djangocms_versioning_enabled = True
moderated_models = [PageContent]
versioning = []

18 changes: 15 additions & 3 deletions docs/moderation_integration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ Moderation depends on `Versioning <https://github.com/divio/djangocms-versioning
# generate url as required
return obj.get_absolute_url()
def stories_about_intelligent_cats(request, version, *args, **kwargs):
return version.content.cat_stories
def get_blog_additional_changelist_action(obj):
return "Custom moderation action"
def get_blog_additional_changelist_field(obj):
return "Custom moderation field"
get_poll_additional_changelist_field.short_description = "Custom Field"
class BlogCMSConfig(CMSAppConfig):
class BlogCMSConfig(CMSAppConfig):
djangocms_versioning_enabled = True # -- 1
djangocms_moderation_enabled = True # -- 2
versioning = [
Expand All @@ -37,8 +40,17 @@ Moderation depends on `Versioning <https://github.com/divio/djangocms-versioning
moderated_models = [ # -- 4
PostContent,
]
moderation_request_changelist_actions = [ # -- 5
get_blog_additional_changelist_action
]
moderation_request_changelist_fields = [ # -- 6
get_blog_additional_changelist_field
]
1. This must be set to True for Versioning to read app's CMS config.
2. This must be set to True for Moderation to read app's CMS config.
3. `versioning` attribute takes a list of `VersionableItem` objects. See `djangocms_versioning` documentation for details.
4. `moderated_models` attribute takes a list of moderatable model objects.
5. `moderation_request_changelist_actions` attribute takes a list of actions that are added to the action field in the Moderation Request Changelist admin view
6. `moderation_request_changelist_fields` attribute takes a list of admin fields that are added to the display list in the Moderation Request Changelist admin view
74 changes: 68 additions & 6 deletions tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .utils.base import BaseTestCase, MockRequest
from .utils.factories import (
ModerationCollectionFactory,
PollVersionFactory,
RootModerationRequestTreeNodeFactory,
WorkflowFactory,
)
Expand Down Expand Up @@ -213,26 +214,28 @@ def test_change_list_view_should_respect_conf(self):

# test ModerationRequests
conf.REQUEST_COMMENTS_ENABLED = False
list_display = self.mr_tree_admin.get_list_display(mock_request)
self.assertNotIn("get_comments_link", list_display)
mra_buttons = []
for action in self.mr_tree_admin.get_list_display_actions():
mra_buttons.append(action.__name__)
self.assertNotIn("get_comments_link", mra_buttons)

conf.REQUEST_COMMENTS_ENABLED = True
list_display = self.mr_tree_admin.get_list_display(mock_request)
self.assertIn("get_comments_link", list_display)
mra_buttons = []
for action in self.mr_tree_admin.get_list_display_actions():
mra_buttons.append(action.__name__)
self.assertIn("get_comments_link", mra_buttons)

# test ModerationCollections
conf.COLLECTION_COMMENTS_ENABLED = False
mca_buttons = []
for action in self.mca.get_list_display_actions():
mca_buttons.append(action.__name__)
list_display = self.mca.get_list_display_actions()
self.assertNotIn("get_comments_link", mca_buttons)

conf.COLLECTION_COMMENTS_ENABLED = True
mca_buttons = []
for action in self.mca.get_list_display_actions():
mca_buttons.append(action.__name__)
list_display = self.mca.get_list_display_actions()
self.assertIn("get_comments_link", mca_buttons)

def test_change_moderation_collection_author_permission(self):
Expand Down Expand Up @@ -350,3 +353,62 @@ def test_tree_admin_list_links_to_moderation_request_change_view(self):
self.mr1.pk,
)
self.assertHTMLEqual(result, expected)


class ModerationAdminChangelistConfigurationTestCase(BaseTestCase):
def setUp(self):
self.wf = WorkflowFactory(name="Workflow Test")
self.collection = ModerationCollectionFactory(
author=self.user,
name="Collection Admin Actions",
workflow=self.wf,
status=constants.IN_REVIEW,
)

poll1_version = PollVersionFactory()

self.mr1n = RootModerationRequestTreeNodeFactory(
moderation_request__version=poll1_version,
moderation_request__collection=self.collection,
moderation_request__is_active=True,
)
self.mr1 = self.mr1n.moderation_request

self.wfst = self.wf.steps.create(role=self.role2, is_required=True, order=1)

# this moderation request is approved
self.mr1.actions.create(
to_user=self.user2, by_user=self.user, action=constants.ACTION_STARTED
)
self.mr1action2 = self.mr1.actions.create(
by_user=self.user,
to_user=self.user2,
action=constants.ACTION_APPROVED,
step_approved=self.wfst,
)

self.url = reverse("admin:djangocms_moderation_moderationrequest_changelist")
self.url_with_filter = "{}?moderation_request__collection__id={}".format(
self.url, self.collection.pk
)
self.mr_tree_admin = ModerationRequestTreeAdmin(
ModerationRequest, admin.AdminSite()
)
self.mra = ModerationRequestAdmin(ModerationRequest, admin.AdminSite())
self.mca = ModerationCollectionAdmin(ModerationCollection, admin.AdminSite())

def test_tree_admin_change_list_shows_additional_configured_actions(self):
mra_buttons = []

for action in self.mr_tree_admin.get_list_display_actions():
mra_buttons.append(action.__name__)

self.assertIn("get_poll_additional_changelist_action", mra_buttons)

def test_tree_admin_change_list_shows_additional_configured_fields(self):
mock_request = RequestFactory()
mock_request.user = self.user
mock_request._collection = self.collection

self.assertIn("get_poll_additional_changelist_field",
self.mr_tree_admin.get_list_display(mock_request))
15 changes: 14 additions & 1 deletion tests/utils/moderated_polls/cms_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,23 @@
from .models import PollContent


def get_poll_additional_changelist_action(obj):
return f"Custom poll action {obj.pk}"


def get_poll_additional_changelist_field(obj):
return f"Custom poll link {obj.pk}"
get_poll_additional_changelist_field.short_description = "Custom Link"


class PollsCMSConfig(CMSAppConfig):
djangocms_moderation_enabled = True
djangocms_versioning_enabled = True
# Moderation configuration
moderated_models = (PollContent,)
moderation_request_changelist_actions = [get_poll_additional_changelist_action]
moderation_request_changelist_fields = [get_poll_additional_changelist_field]
# Versioning configuration
djangocms_versioning_enabled = True
versioning = [
VersionableItem(
content_model=PollContent,
Expand Down

0 comments on commit 1fd928e

Please sign in to comment.