새 작업 유형 추가

워크플로 시스템을 통해 사용자는 중재 단계를 나타내는 작업을 생성할 수 있습니다.

Wagtail은 특정 그룹의 모든 사용자가 중재를 승인하거나 거부할 수 있도록 하는 GroupApprovalTask 라는 내장 작업 유형을 제공합니다.

그러나 자신만의 작업 유형을 구현하는 것도 가능합니다. 그런 다음 Wagtail 관리자의 워크플로 작업 섹션에서 사용자 지정 작업 인스턴스를 생성할 수 있습니다.

작업 모델

모든 사용자 지정 작업은 wagtailcore.Task 에서 상속되는 모델이어야 합니다.

내장 GroupApprovalTask 의 동작을 사용자 지정해야 하는 경우, AbstractGroupApprovalTask 를 상속하는 사용자 지정 작업을 생성하고 거기에 사용자 지정을 추가하십시오. 동작을 사용자 지정하는 방법에 대한 자세한 내용은 아래를 참조하십시오.

이 예제 세트에서는 특정 사용자 한 명만 승인할 수 있는 작업을 설정합니다.

# <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']

    # 작업 생성 후 `user` 편집 방지
    # 기본적으로 이 속성에는 작업 이름 변경을 방지하기 위한 'name' 필드가 포함됩니다.
    admin_form_readonly_on_edit_fields = Task.admin_form_readonly_on_edit_fields + ['name']

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

동작 사용자 지정

TaskTaskState 모두 사용자 지정 동작을 구현하기 위해 재정의할 수 있는 여러 메서드를 가지고 있습니다. 다음은 가장 유용한 몇 가지입니다.

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

이 메서드는 일반적으로 권한이 없는 사용자가 편집기에 액세스하고 객체를 잠그거나 잠금 해제할 수 있는지 여부를 True 또는 False를 반환하여 결정합니다. False 를 반환해도 일반적으로 해당 작업을 수행할 수 있는 사용자를 막지는 않습니다. 예를 들어, UserApprovalTask 의 경우:

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

Task.locked_for_user(obj, user):

이것은 객체가 사용자에게 잠겨 있고 편집할 수 없는 경우 True 를 반환합니다. GroupApprovalTask 는 승인 그룹에 없는 사용자에게 객체를 잠그는 데 사용합니다.

def locked_for_user(self, obj, user):
    return user != self.user

Task.get_actions(obj, user):

이것은 편집 뷰 메뉴에서 작업에 사용할 수 있는 작업에 해당하는 (action_name, action_verbose_name, action_requires_additional_data_from_modal) 튜플 목록을 반환합니다. action_requires_additional_data_from_modal 은 작업 선택 시 추가 데이터 입력을 위한 모달을 열어야 하는 경우 True 를 반환하는 부울이어야 합니다(예: 댓글 입력).

예를 들어:

def get_actions(self, obj, user):
    if user == self.user:
        return [
            ('approve', "승인", False),
            ('reject', "거부", False),
            ('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(obj, user) 에 지정된 작업을 수행합니다. 예를 들어 approve 와 같은 작업 이름과 관련 작업 상태가 전달됩니다. 기본적으로 해당 작업 이름이 전달될 때 작업 상태에서 approvereject 메서드를 호출합니다. 모달에 입력된 추가 데이터( get_form_for_actionget_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(또는 서브클래스)의 QuerySet을 반환합니다. 이는 현재 사용자의 대시보드에 표시할 객체를 선택하는 데 사용됩니다.

예를 들어:

def get_task_states_user_can_moderate(self, user, **kwargs):
    if user == self.user:
        # 현재 작업(기본 클래스)에 연결된 모든 작업 상태 가져오기
        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 _("선택된 Wagtail 그룹의 구성원은 이 작업을 승인할 수 있습니다.")

알림 추가

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):
    """UserApprovalTask 이벤트에 대한 업데이트를 보내는 기본 알림기"""

    def __init__(self):
        # UserApprovalTaskState 및 TaskState가 알림을 보낼 수 있도록 허용
        super().__init__((UserApprovalTaskState, TaskState))

    def can_handle(self, instance, **kwargs):
        if super().can_handle(instance, **kwargs) and isinstance(instance.task.specific, UserApprovalTask):
            # 작업이 취소되었다가 다시 시작된 경우(객체가 새 개정판으로 업데이트된 경우) 알림을 보내지 않습니다.
            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['object'] = task_state.workflow_state.content_object
        context['task'] = task_state.task.specific
        return context

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

        # 작업에 할당된 사용자에게 이메일 보내기
        approving_user = task_state.task.specific.user

        recipients = {approving_user}

        return recipients


class UserApprovalTaskStateSubmissionEmailNotifier(BaseUserApprovalTaskStateEmailNotifier):
    """UserApprovalTask 제출 이벤트에 대한 업데이트를 보내는 알림기"""

    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() 는 앱 로드 시 실행되어야 합니다. 예를 들어, AppConfigready() 메서드에 추가하여 실행할 수 있습니다.

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


class MyAppConfig(AppConfig):
    name = 'myappname'
    label = 'myapplabel'
    verbose_name = '내 상세 앱 이름'

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