Spec-Zone.ru › Wagtail

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

Система 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):

Возвращает QuerySet из 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 - when 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/stable/extending/custom_tasks.html

Spec-Zone.ru

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