Добавление новых типов задач
Система Workflow позволяет пользователям создавать задачи, которые представляют этапы модерации.
Wagtail предоставляет один встроенный тип задачи: GroupApprovalTask, который позволяет любому пользователю в определенных группах утверждать или отклонять модерацию.
Однако, можно добавить собственные типы задач в коде. Экземпляры ваших пользовательских задач затем можно создать в разделе Tasks администрирования Wagtail.
Модели задач
Все пользовательские задачи должны быть моделями, унаследованными от wagtailcore.Task. В этом наборе примеров мы настроим задачу, которую может утвердить только один конкретный пользователь.
# <project>/models.py
from wagtail.core.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.core.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.core.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.core.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.core.models import TaskState
class UserApprovalTaskState(TaskState):
pass
Затем вашей пользовательской задаче необходимо будет указать на создание экземпляра вашего пользовательского состояния задачи при запуске вместо простого экземпляра TaskState:
# <project>/models.py
from django.conf import settings
from django.db import models
from wagtail.core.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.core.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.core.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/v2.16.3/extending/custom_tasks.html