Spec-Zone.ru › Wagtail 3

Добавление новых типов задач

Система Workflow позволяет пользователям создавать задачи, представляющие стадии модерации.

Wagtail предоставляет один встроенный тип задачи: GroupApprovalTask, который позволяет любому пользователю в определенных группах утверждать или отклонять модерацию.

Однако, можно добавить собственные типы задач в коде. Экземпляры ваших настраиваемых задач затем могут быть созданы в разделе Tasks административной панели Wagtail.

Модели задач

Все пользовательские задачи должны быть моделями, наследующими от wagtailcore.Task. В этом примере мы настроим задачу, которая может быть утверждена только одним конкретным пользователем.

# <project>/models.py

from wagtail.models import Task


class UserApprovalTask(Task):
    pass

Подклассы задач следуют тому же подходу, что и страницы: они являются конкретными моделями, с экземпляром конкретного подкласса, доступным с помощью вызова Task.specific().

Теперь вы можете добавить любые пользовательские поля. Чтобы сделать их редактируемыми в админке, добавьте имена полей в атрибут admin_form_fields.

Например:

# <project>/models.py

from django.conf import settings
from django.db import models
from wagtail.models import Task


class UserApprovalTask(Task):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=False)

    admin_form_fields = Task.admin_form_fields + ['user']

Любые поля, которые не должны редактироваться после создания задачи — например, любые поля, которые фундаментально изменят смысл задачи в любых журналах истории — могут быть добавлены в admin_form_readonly_on_edit_fields. Например:

# <project>/models.py

from django.conf import settings
from django.db import models
from wagtail.models import Task


class UserApprovalTask(Task):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=False)

    admin_form_fields = Task.admin_form_fields + ['user']

    # prevent editing of ``user`` after the task is created
    # by default, this attribute contains the 'name' field to prevent tasks from being renamed
    admin_form_readonly_on_edit_fields = Task.admin_form_readonly_on_edit_fields + ['user']

Wagtail выберет виджет формы по умолчанию на основе типа поля. Но вы можете переопределить виджет формы, используя атрибут admin_form_widgets:

# <project>/models.py

from django.conf import settings
from django.db import models
from wagtail.models import Task

from .widgets import CustomUserChooserWidget


class UserApprovalTask(Task):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=False)

    admin_form_fields = Task.admin_form_fields + ['user']

    admin_form_widgets = {
        'user': CustomUserChooserWidget,
    }

Настраиваемые модели TaskState

Возможно, вам также потребуется хранить пользовательскую информацию о состоянии задачи: например, рейтинг, оставленный утверждающим пользователем. Обычно это делается на экземпляре TaskState, который создается, когда страница начинает задачу. Однако это также может быть подклассом, эквивалентным Task:

# <project>/models.py

from wagtail.models import TaskState


class UserApprovalTaskState(TaskState):
    pass

Затем вашей настраиваемой задаче необходимо указать генерировать экземпляр вашего настраиваемого состояния задачи при запуске вместо обычного экземпляра TaskState:

# <project>/models.py

from django.conf import settings
from django.db import models
from wagtail.models import Task, TaskState


class UserApprovalTaskState(TaskState):
    pass


class UserApprovalTask(Task):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=False)

    admin_form_fields = Task.admin_form_fields + ['user']

    task_state_class = UserApprovalTaskState

Настройка поведения

Как Task, так и TaskState имеют ряд методов, которые можно переопределить для реализации пользовательского поведения. Вот некоторые из наиболее полезных:

Task.user_can_access_editor(page, user), Task.user_can_lock(page, user), Task.user_can_unlock(page, user):

Эти методы определяют, могут ли пользователи, обычно без разрешений, получить доступ к редактору, заблокировать или разблокировать страницу, возвращая True или False. Обратите внимание, что возвращение False не помешает пользователям, которые обычно могут выполнять эти действия. Например, для нашей UserApprovalTask:

def user_can_access_editor(self, page, user):
    return user == self.user

Task.page_locked_for_user(page, user):

Это возвращает True, если страница должна быть заблокирована и недоступна для редактирования пользователем. Используется GroupApprovalTask для блокировки страницы для всех пользователей, не входящих в группу утверждения.

def page_locked_for_user(self, page, user):
    return user != self.user

Task.get_actions(page, user):

Это возвращает список (action_name, action_verbose_name, action_requires_additional_data_from_modal) кортежей, соответствующих действиям, доступным для задачи в меню просмотра редактирования. action_requires_additional_data_from_modal должно быть булевым значением, возвращающим True, если при выборе действия следует открыть модальное окно для дополнительного ввода данных — например, для ввода комментария.

Например:

def get_actions(self, page, user):
    if user == self.user:
        return [
            ('approve', "Approve", False),
            ('reject', "Reject", False),
            ('cancel', "Cancel", False),
        ]
    else:
        return []

Task.get_form_for_action(action):

Возвращает форму для дополнительного ввода данных для модального окна данного действия. По умолчанию возвращает TaskStateCommentForm, с одним полем комментария. Данные формы, возвращаемые в form.cleaned_data, должны быть полностью сериализуемы в формате JSON.

Task.get_template_for_action(action):

Возвращает имя пользовательского шаблона, который будет использоваться при рендеринге модального окна ввода данных для этого действия.

Task.on_action(task_state, user, action_name, **kwargs):

Это выполняет действия, указанные в Task.get_actions(page, user): ему передается имя действия, например approve, и соответствующее состояние задачи. По умолчанию он вызывает методы approve и reject состояния задачи, когда соответствующие имена действий передаются через них. Любые дополнительные данные, введенные в модальном окне (см. get_form_for_action и get_actions), передаются в качестве kwargs.

Например, предположим, что мы хотели добавить дополнительный вариант: отмену всего рабочего процесса:

def on_action(self, task_state, user, action_name):
    if action_name == 'cancel':
        return task_state.workflow_state.cancel(user=user)
    else:
        return super().on_action(task_state, user, workflow_state)

Task.get_task_states_user_can_moderate(user, **kwargs):

Это возвращает набор запросов TaskStates (или подклассы) для данной задачи, которые может модератор — в настоящее время используется для выбора страниц для отображения на панели задач пользователя.

Например:

def get_task_states_user_can_moderate(self, user, **kwargs):
    if user == self.user:
        # get all task states linked to the (base class of) current task
        return TaskState.objects.filter(status=TaskState.STATUS_IN_PROGRESS, task=self.task_ptr)
    else:
        return TaskState.objects.none()

Task.get_description()

Метод класса, возвращающий удобочитаемое описание задачи.

Например:

@classmethod
def get_description(cls):
    return _("Members of the chosen Wagtail Groups can approve this task")

Добавление уведомлений

Уведомления Wagtail отправляются подклассами wagtail.admin.mail.Notifier: вызываемыми объектами, предназначенными для подключения к сигналу.

По умолчанию уведомления по электронной почте отправляются при отправке рабочего процесса, утверждении и отклонении, а также при отправке в задачу группового утверждения.

В качестве примера добавим уведомления по электронной почте, когда наша новая задача запущена.

# <project>/mail.py

from wagtail.admin.mail import EmailNotificationMixin, Notifier
from wagtail.models import TaskState

from .models import UserApprovalTaskState


class BaseUserApprovalTaskStateEmailNotifier(EmailNotificationMixin, Notifier):
    """A base notifier to send updates for UserApprovalTask events"""

    def __init__(self):
        # Allow UserApprovalTaskState and TaskState to send notifications
        super().__init__((UserApprovalTaskState, TaskState))

    def can_handle(self, instance, **kwargs):
        if super().can_handle(instance, **kwargs) and isinstance(instance.task.specific, UserApprovalTask):
            # Don't send notifications if a Task has been cancelled and then resumed - ie page was updated to a new revision
            return not TaskState.objects.filter(workflow_state=instance.workflow_state, task=instance.task, status=TaskState.STATUS_CANCELLED).exists()
        return False

    def get_context(self, task_state, **kwargs):
        context = super().get_context(task_state, **kwargs)
        context['page'] = task_state.workflow_state.page
        context['task'] = task_state.task.specific
        return context

    def get_recipient_users(self, task_state, **kwargs):

        # Send emails to the user assigned to the task
        approving_user = task_state.task.specific.user

        recipients = {approving_user}

        return recipients


class UserApprovalTaskStateSubmissionEmailNotifier(BaseUserApprovalTaskStateEmailNotifier):
    """A notifier to send updates for UserApprovalTask submission events"""

    notification = 'submitted'

Аналогично, можно определить подклассы нотификаторов для уведомлений об утверждении и отклонении.

Далее, необходимо создать экземпляр нотификатора и подключить его к сигналу task_submitted.

# <project>/signal_handlers.py

from wagtail.signals import task_submitted
from .mail import UserApprovalTaskStateSubmissionEmailNotifier


task_submission_email_notifier = UserApprovalTaskStateSubmissionEmailNotifier()

def register_signal_handlers():
    task_submitted.connect(user_approval_task_submission_email_notifier, dispatch_uid='user_approval_task_submitted_email_notification')

register_signal_handlers() должен выполняться при загрузке приложения: например, путем добавления его в метод ready() в вашем классе AppConfig.

# <project>/apps.py
from django.apps import AppConfig


class MyAppConfig(AppConfig):
    name = 'myappname'
    label = 'myapplabel'
    verbose_name = 'My verbose app name'

    def ready(self):
        from .signal_handlers import register_signal_handlers
        register_signal_handlers()

Примечание

В версиях Django до 3.2 ваш подкласс AppConfig должен быть установлен как default_app_config в <project>/__init__.py. См. соответствующий раздел в документации Django для используемой версии.

© 2014-present Torchbox Ltd and individual contributors.
All rights are reserved.
Licensed under the BSD License.
https://docs.wagtail.org/en/v3.0.3/extending/custom_tasks.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API