根据指定页面字段的值分支工作流

Branching Workflows based on value of specified Page field

我有一个带有 reflection_date 字段的 DailyReflectionPage 模型,该字段构成了 Page 的 slug 的基础,其形式为 YYYY-MM-DD。这是我的页面模型的摘录:

class DailyReflectionPage(Page):
    """
    The Daily Reflection Model
    """
    ...
    ...

    reflection_date = models.DateField("Reflection Date", max_length=254)
    ...
    ...
    @cached_property
    def date(self):
        """
        Returns the Reflection's date as a string in %Y-%m-%d format
        """
        fmt = "%Y-%m-%d"
        date_as_string = (self.reflection_date).strftime(fmt)
        return date_as_string      
    ...
    ...
    def full_clean(self, *args, **kwargs):
        # first call the built-in cleanups (including default slug generation)
        super(DailyReflectionPage, self).full_clean(*args, **kwargs)

        # now make your additional modifications
        if self.slug is not self.date:
            self.slug = self.date
    ...
    ...

这些日常反思由不同的作者撰写,作为年底出版的小册子的一部分,供来年使用。我想有一个工作流程,例如,1 月到 6 月的每日反思由一个小组审查,7 月到 12 月的每日反思由另一个小组审查,如下图所示:

如何实现?

这应该可以通过创建一个新的工作流 Task 类型来实现,该类型与两组用户 Group 有关系(例如 a/b 或 before/after, 最好在模型定义中保留这个泛型。

这个新 Task 可以作为 Wagtail 管理中新 Workflow 的一部分创建,并且每个组都链接到版主组 1 / 2。

Wagtail 在 Task 上的方法允许您根据 Page 模型为任何已创建的工作流程 return 批准选项,从这里您可以寻找一个方法class 并从那里分配组。

拥有更多通用方法的好处是,您可以利用它来将主持人分配的任何拆分作为未来工作流任务的一部分。

实施概述

  • 1 - 阅读 Wagatail Docs on how to add a new Task Type and the Task model reference 以了解此过程。
  • 2 - 通读内置 GroupApprovalTask.
  • 代码中的完整实现
  • 3 - 在 GroupApprovalTask 中,您可以看到具有覆盖的方法都依赖于 self.groups 的检查,但它们都将 page 作为参数传递给那些方法方法。
  • 4 - 创建一个新的 Task 来扩展 Wagtail Task class 并在此模型上创建两个 ManyToManyField 以允许链接两组用户组(注意:您不必将此作为两个字段,您可以在中间放置一个模型,但下面的示例只是到达监狱的最简单方法)。
  • 5 - 在 DailyReflectionPage 模型上创建一个方法 get_approval_group_key,它将 return 可能是一个简单的布尔值或 'A' 或 'B' 基于您在上面描述的业务需求(检查模型的日期等)
  • 6 - 在您的自定义 Task 中创建一个方法,抽象化检查 Page 此方法和 return 任务的用户组。您可能想要添加一些错误处理和默认值。例如。 get_approval_groups
  • 7 - 为每个 'start'、'user_can_access_editor'、page_locked_for_useruser_can_lockuser_can_unlockget_task_states_user_can_moderate 添加自定义方法调用 get_approval_group 页面和 return 值的方法(请参阅代码 GroupApprovalTask 以了解它们应该做什么。

示例代码片段

models.py


class DailyReflectionPage(Page):
    """
    The Daily Reflection Model
    """
    def get_approval_group_key(self):
        # custom logic here that checks all the date stuff
        if date_is_after_foo:
            return 'A'
        return 'B'    


class SplitGroupApprovalTask(Task):

    ## note: this is the simplest approach, two fields of linked groups, you could further refine this approach as needed.

    groups_a = models.ManyToManyField(
        Group,
        help_text="Pages at this step in a workflow will be moderated or approved by these groups of users",
        related_name="split_task_group_a",
    )
    groups_b = models.ManyToManyField(
        Group,
        help_text="Pages at this step in a workflow will be moderated or approved by these groups of users",
        related_name="split_task_group_b",
    )

    admin_form_fields = Task.admin_form_fields + ["groups_a", "groups_b"]
    admin_form_widgets = {
        "groups_a": forms.CheckboxSelectMultiple,
        "groups_b": forms.CheckboxSelectMultiple,
    }

    def get_approval_groups(self, page):
       """This method gets used by all checks when determining what group to allow/assign this Task to"""
        
        # recommend some checks here, what if `get_approval_group` is not on the Page?
        approval_group = page.specific.get_approval_group_key()

        if (approval_group == 'A'):
            return self.group_a

        return self.group_b

    # each of the following methods will need to be implemented, all checking for the correct groups for the Page when called
    # def start(self, ...etc)
    # def user_can_access_editor(self, ...etc)
    # def page_locked_for_user(self, ...etc)
    # def user_can_lock(self, ...etc)
    # def user_can_unlock(self, ...etc)


    def get_task_states_user_can_moderate(self, user, **kwargs):
        # Note: this has not been tested, however as this method does not get `page` we must find all the tasks allowed indirectly via their TaskState pages

        tasks = TaskState.objects.filter(status=TaskState.STATUS_IN_PROGRESS, task=self.task_ptr)

        filtered_tasks = []
        for task in tasks:
            page = task.select_related('page_revision', 'task', 'page_revision__page')
            groups = self.get_approval_groups(page)
            if groups.filter(id__in=user.groups.all()).exists() or user.is_superuser:
                filtered_tasks.append(task)

        return TaskState.objects.filter(pk__in=[task.pk for task in filtered_tasks])

    def get_actions(self, page, user):
        # essentially a copy of this method on `GroupApprovalTask` but with the ability to have a dynamic 'group' returned.
        approval_groups = self.get_approval_groups(page)

        if approval_groups.filter(id__in=user.groups.all()).exists() or user.is_superuser:
            return [
                ('reject', "Request changes", True),
                ('approve', "Approve", False),
                ('approve', "Approve with comment", True),
            ]

        return super().get_actions(page, user)