(custom_tasks)= # 새 작업 유형 추가 워크플로 시스템을 통해 사용자는 중재 단계를 나타내는 작업을 생성할 수 있습니다. Wagtail은 특정 그룹의 모든 사용자가 중재를 승인하거나 거부할 수 있도록 하는 `GroupApprovalTask` 라는 내장 작업 유형을 제공합니다. 그러나 자신만의 작업 유형을 구현하는 것도 가능합니다. 그런 다음 Wagtail 관리자의 워크플로 작업 섹션에서 사용자 지정 작업 인스턴스를 생성할 수 있습니다. ## 작업 모델 모든 사용자 지정 작업은 `wagtailcore.Task` 에서 상속되는 모델이어야 합니다. 내장 `GroupApprovalTask` 의 동작을 사용자 지정해야 하는 경우, `AbstractGroupApprovalTask` 를 상속하는 사용자 지정 작업을 생성하고 거기에 사용자 지정을 추가하십시오. 동작을 사용자 지정하는 방법에 대한 자세한 내용은 아래를 참조하십시오. 이 예제 세트에서는 특정 사용자 한 명만 승인할 수 있는 작업을 설정합니다. ```python # /models.py from wagtail.models import Task class UserApprovalTask(Task): pass ``` 서브클래스화된 작업은 페이지와 동일한 접근 방식을 따릅니다. 즉, 구체적인 모델이며, `Task.specific()` 을 호출하여 특정 서브클래스 인스턴스에 액세스할 수 있습니다. 이제 사용자 지정 필드를 추가할 수 있습니다. 관리자에서 편집 가능하게 하려면 필드 이름을 `admin_form_fields` 속성에 추가하십시오. 예를 들어: ```python # /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` 에 추가할 수 있습니다. 예를 들어: ```python # /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` 속성을 사용하여 폼 위젯을 재정의할 수 있습니다. ```python # /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` 와 동일하게 서브클래스화할 수도 있습니다. ```python # /models.py from wagtail.models import TaskState class UserApprovalTaskState(TaskState): pass ``` 그런 다음 사용자 지정 작업은 시작 시 일반 `TaskState` 인스턴스 대신 사용자 지정 작업 상태 인스턴스를 생성하도록 지시해야 합니다. ```python # /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 ``` (custom_tasks_behavior)= ## 동작 사용자 지정 `Task` 와 `TaskState` 모두 사용자 지정 동작을 구현하기 위해 재정의할 수 있는 여러 메서드를 가지고 있습니다. 다음은 가장 유용한 몇 가지입니다. `Task.user_can_access_editor(obj, user)`, `Task.user_can_lock(obj, user)`, `Task.user_can_unlock(obj, user)`: 이 메서드는 일반적으로 권한이 없는 사용자가 편집기에 액세스하고 객체를 잠그거나 잠금 해제할 수 있는지 여부를 True 또는 False를 반환하여 결정합니다. `False` 를 반환해도 일반적으로 해당 작업을 수행할 수 있는 사용자를 막지는 않습니다. 예를 들어, `UserApprovalTask` 의 경우: ```python def user_can_access_editor(self, obj, user): return user == self.user ``` `Task.locked_for_user(obj, user)`: 이것은 객체가 사용자에게 잠겨 있고 편집할 수 없는 경우 `True` 를 반환합니다. `GroupApprovalTask` 는 승인 그룹에 없는 사용자에게 객체를 잠그는 데 사용합니다. ```python 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` 를 반환하는 부울이어야 합니다(예: 댓글 입력). 예를 들어: ```python 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` 와 같은 작업 이름과 관련 작업 상태가 전달됩니다. 기본적으로 해당 작업 이름이 전달될 때 작업 상태에서 `approve` 및 `reject` 메서드를 호출합니다. 모달에 입력된 추가 데이터( `get_form_for_action` 및 `get_actions` 참조)는 kwargs로 제공됩니다. 예를 들어, 전체 워크플로를 취소하는 추가 옵션을 추가하고 싶다고 가정해 봅시다. ```python 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을 반환합니다. 이는 현재 사용자의 대시보드에 표시할 객체를 선택하는 데 사용됩니다. 예를 들어: ```python 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()` 작업에 대한 사람이 읽을 수 있는 설명을 반환하는 클래스 메서드입니다. 예를 들어: ```python @classmethod def get_description(cls): return _("선택된 Wagtail 그룹의 구성원은 이 작업을 승인할 수 있습니다.") ``` ## 알림 추가 Wagtail의 알림은 `wagtail.admin.mail.Notifier` 서브클래스(신호에 연결될 호출 가능)에 의해 전송됩니다. 기본적으로 워크플로 제출, 승인 및 거부 시, 그리고 그룹 승인 작업에 제출 시 이메일 알림이 전송됩니다. 예를 들어, 새 작업이 시작될 때 이메일 알림을 추가합니다. ```python # /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` 신호에 연결해야 합니다. ```python # /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()` 는 앱 로드 시 실행되어야 합니다. 예를 들어, `AppConfig` 의 `ready()` 메서드에 추가하여 실행할 수 있습니다. ```python # /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() ```