However, it is possible to implement your own task types. Instances of your custom task can then be created in the `Tasks` section of the Wagtail Admin.
All custom tasks must be models inheriting from `wagtailcore.Task`. In this set of examples, we'll set up a task that can be approved by only one specific user.
Subclassed Tasks follow the same approach as Pages: they are concrete models, with the specific subclass instance accessible by calling `Task.specific()`.
You can now add any custom fields. To make these editable in the admin, add the names of the fields into the `admin_form_fields` attribute:
For example:
```python
# <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)
Any fields that shouldn't be edited after task creation - for example, anything that would fundamentally change the meaning of the task in any history logs - can be added to `admin_form_readonly_on_edit_fields`. For example:
```python
# <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)
Wagtail will choose a default form widget to use based on the field type. But you can override the form widget using the `admin_form_widgets` attribute:
```python
# <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)
Note that returning `False` will not prevent users who would normally be able to perform those actions. For example, for our `UserApprovalTask`:
```python
def user_can_access_editor(self, page, user):
return user == self.user
```
`Task.page_locked_for_user(page, user)`:
This returns `True` if the page should be locked and uneditable by the user. It is used by `GroupApprovalTask` to lock the page to any users not in the approval group.
```python
def page_locked_for_user(self, page, user):
return user != self.user
```
`Task.get_actions(page, user)`:
This returns a list of `(action_name, action_verbose_name, action_requires_additional_data_from_modal)` tuples, corresponding to the actions available for the task in the edit view menu.
`action_requires_additional_data_from_modal` should be a boolean, returning `True` if choosing the action should open a modal for additional data input - for example, entering a comment.
For example:
```python
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)`:
Returns a form to be used for additional data input for the given action modal. By default, returns `TaskStateCommentForm`, with a single comment field. The form data returned in `form.cleaned_data` must be fully serializable as JSON.
`Task.get_template_for_action(action)`:
Returns the name of a custom template to be used in rendering the data entry modal for that action.
This performs the actions specified in `Task.get_actions(page, user)`: it is passed an action name, for example, `approve`, and the relevant task state. By default, it calls `approve` and `reject` methods on the task state when the corresponding action names are passed through. Any additional data entered in a modal (see `get_form_for_action` and `get_actions`) is supplied as kwargs.
This returns a QuerySet of `TaskStates` (or subclasses) the given user can moderate - this is currently used to select pages to display on the user's dashboard.
`register_signal_handlers()` should then be run on loading the app: for example, by adding it to the `ready()` method in your `AppConfig`.
```python
# <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()
```
```{note}
In Django versions before 3.2 your `AppConfig` subclass needs to be set as `default_app_config` in `<project>/__init__.py`.
See the [relevant section in the Django docs](https://docs.djangoproject.com/en/3.1/ref/applications/#for-application-authors) for the version you are using.