import django_filters from django import forms from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.core.paginator import Paginator from django.db import transaction from django.db.models import Count, OuterRef, Prefetch from django.db.models.functions import Lower from django.http import Http404, HttpResponseBadRequest from django.shortcuts import get_object_or_404, redirect, render from django.template.loader import render_to_string from django.urls import reverse from django.utils.functional import cached_property from django.utils.http import url_has_allowed_host_and_scheme from django.utils.text import capfirst from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext from django.views.decorators.http import require_POST from django.views.generic import TemplateView from wagtail.admin import messages from wagtail.admin.auth import PermissionPolicyChecker from wagtail.admin.filters import MultipleContentTypeFilter, WagtailFilterSet from wagtail.admin.forms.workflows import ( TaskChooserSearchForm, WorkflowContentTypeForm, WorkflowPagesFormSet, get_task_form_class, get_workflow_edit_handler, ) from wagtail.admin.modal_workflow import render_modal_workflow from wagtail.admin.ui.tables import BaseColumn, Column, TitleColumn from wagtail.admin.views.generic import CreateView, DeleteView, EditView, IndexView from wagtail.coreutils import resolve_model_string from wagtail.models import ( Page, Task, TaskState, Workflow, WorkflowContentType, WorkflowState, WorkflowTask, ) from wagtail.permissions import ( page_permission_policy, task_permission_policy, workflow_permission_policy, ) from wagtail.snippets.models import get_workflow_enabled_models from wagtail.workflows import get_task_types task_permission_checker = PermissionPolicyChecker(task_permission_policy) class WorkflowTitleColumn(TitleColumn): cell_template_name = "wagtailadmin/workflows/includes/workflow_title_cell.html" class WorkflowUsedByColumn(TitleColumn): cell_template_name = "wagtailadmin/workflows/includes/workflow_used_by_cell.html" def get_cell_context_data(self, instance, parent_context): context = super().get_cell_context_data(instance, parent_context) context["workflow_enabled_models"] = get_workflow_enabled_models() return context class WorkflowTasksColumn(BaseColumn): cell_template_name = "wagtailadmin/workflows/includes/workflow_tasks_cell.html" num_tasks = 5 def get_cell_context_data(self, instance, parent_context): context = super().get_cell_context_data(instance, parent_context) context["tasks"] = instance.workflow_tasks.all()[: self.num_tasks] context["extra_count"] = instance.workflow_tasks.count() - self.num_tasks return context class BaseWorkflowFilterSet(WagtailFilterSet): show_disabled = django_filters.ChoiceFilter( label=_("Show disabled"), method="filter_show_disabled", choices=(("true", _("Yes")), ("false", _("No"))), widget=forms.RadioSelect, empty_label=None, initial="false", ) def __init__(self, data=None, queryset=None, *, request=None, prefix=None): if data is not None: if data.get("show_disabled") is None: filter = self.base_filters["show_disabled"] data = data.copy() data["show_disabled"] = filter.extra["initial"] super().__init__(data, queryset, request=request, prefix=prefix) def filter_show_disabled(self, queryset, name, value): if value == "true": return queryset return queryset.filter(active=True) class WorkflowFilterSet(BaseWorkflowFilterSet): class Meta: model = Workflow fields = [] class Index(IndexView): permission_policy = workflow_permission_policy model = Workflow context_object_name = "workflows" template_name = "wagtailadmin/workflows/index.html" results_template_name = "wagtailadmin/workflows/index_results.html" add_url_name = "wagtailadmin_workflows:add" edit_url_name = "wagtailadmin_workflows:edit" index_url_name = "wagtailadmin_workflows:index" index_results_url_name = "wagtailadmin_workflows:index_results" page_title = _("Workflows") add_item_label = _("Add a workflow") header_icon = "tasks" columns = [ WorkflowTitleColumn( "name", label=_("Name"), url_name="wagtailadmin_workflows:edit", width="25%", sort_key="name", ), WorkflowUsedByColumn( "usage", label=_("Used by"), url_name="wagtailadmin_workflows:usage", width="15%", ), WorkflowTasksColumn("tasks", label=_("Tasks")), ] default_ordering = "name" search_fields = ["name"] filterset_class = WorkflowFilterSet _show_breadcrumbs = True paginate_by = 20 def show_disabled(self): return self.filters.form.cleaned_data.get("show_disabled") == "true" def get_base_queryset(self): queryset = super().get_base_queryset() content_types = WorkflowContentType.objects.filter( workflow=OuterRef("pk") ).values_list("pk", flat=True) queryset = queryset.annotate(content_types=Count(content_types)) return queryset.prefetch_related( "workflow_pages", "workflow_pages__page", "workflow_tasks", "workflow_tasks__task", ) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["showing_disabled"] = self.show_disabled() return context class Create(CreateView): permission_policy = workflow_permission_policy model = Workflow page_title = _("New workflow") template_name = "wagtailadmin/workflows/create.html" success_message = _("Workflow '%(object)s' created.") add_url_name = "wagtailadmin_workflows:add" edit_url_name = "wagtailadmin_workflows:edit" index_url_name = "wagtailadmin_workflows:index" header_icon = "tasks" edit_handler = None _show_breadcrumbs = True def get_edit_handler(self): if not self.edit_handler: self.edit_handler = get_workflow_edit_handler() return self.edit_handler def get_form_class(self): return self.get_edit_handler().get_form_class() def get_pages_formset(self): if self.request.method == "POST": return WorkflowPagesFormSet( self.request.POST, instance=self.object, prefix="pages" ) else: return WorkflowPagesFormSet(instance=self.object, prefix="pages") def get_content_type_form(self): if self.request.method == "POST": return WorkflowContentTypeForm(self.request.POST, workflow=self.object) else: return WorkflowContentTypeForm(workflow=self.object) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) form = context["form"] bound_panel = self.edit_handler.get_bound_panel( form=form, instance=form.instance, request=self.request ) pages_formset = self.get_pages_formset() context["edit_handler"] = bound_panel context["pages_formset"] = pages_formset context["has_workflow_enabled_models"] = bool(get_workflow_enabled_models()) context["content_type_form"] = self.get_content_type_form() context["media"] = form.media + bound_panel.media + pages_formset.media return context def form_valid(self, form): self.form = form with transaction.atomic(): self.object = self.save_instance() pages_formset = self.get_pages_formset() content_type_form = self.get_content_type_form() if pages_formset.is_valid() and content_type_form.is_valid(): pages_formset.save() content_type_form.save() success_message = self.get_success_message(self.object) if success_message is not None: messages.success( self.request, success_message, buttons=[ messages.button( reverse(self.edit_url_name, args=(self.object.id,)), _("Edit"), ) ], ) return redirect(self.get_success_url()) else: transaction.set_rollback(True) return self.form_invalid(form) class Edit(EditView): permission_policy = workflow_permission_policy model = Workflow page_title = _("Editing workflow") template_name = "wagtailadmin/workflows/edit.html" success_message = _("Workflow '%(object)s' updated.") add_url_name = "wagtailadmin_workflows:add" edit_url_name = "wagtailadmin_workflows:edit" delete_url_name = "wagtailadmin_workflows:disable" delete_item_label = _("Disable") index_url_name = "wagtailadmin_workflows:index" enable_item_label = _("Enable") enable_url_name = "wagtailadmin_workflows:enable" header_icon = "tasks" edit_handler = None MAX_PAGES = 5 _show_breadcrumbs = True def get_edit_handler(self): if not self.edit_handler: self.edit_handler = get_workflow_edit_handler() return self.edit_handler def get_form_class(self): return self.get_edit_handler().get_form_class() def get_pages_formset(self): if self.request.method == "POST": return WorkflowPagesFormSet( self.request.POST, instance=self.get_object(), prefix="pages" ) else: return WorkflowPagesFormSet(instance=self.get_object(), prefix="pages") def get_content_type_form(self): if self.request.method == "POST": return WorkflowContentTypeForm(self.request.POST, workflow=self.object) else: return WorkflowContentTypeForm(workflow=self.object) def get_paginated_pages(self): # Get the (paginated) list of Pages to which this Workflow is assigned. pages = Page.objects.filter(workflowpage__workflow=self.get_object()) pages.paginator = Paginator(pages, self.MAX_PAGES) page_number = int(self.request.GET.get("p", 1)) paginated_pages = pages.paginator.page(page_number) return paginated_pages def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) form = context["form"] bound_panel = self.edit_handler.get_bound_panel( form=form, instance=form.instance, request=self.request ) pages_formset = self.get_pages_formset() context["edit_handler"] = bound_panel context["pages"] = self.get_paginated_pages() context["pages_formset"] = pages_formset context["has_workflow_enabled_models"] = bool(get_workflow_enabled_models()) context["content_type_form"] = self.get_content_type_form() context["can_disable"] = ( self.permission_policy is None or self.permission_policy.user_has_permission(self.request.user, "delete") ) and self.object.active context["can_enable"] = ( self.permission_policy is None or self.permission_policy.user_has_permission(self.request.user, "add") ) and not self.object.active context["media"] = bound_panel.media + form.media + pages_formset.media return context @property def get_enable_url(self): return reverse(self.enable_url_name, args=(self.object.pk,)) @transaction.atomic() def form_valid(self, form): self.form = form with transaction.atomic(): self.object = self.save_instance() successful = True # Save pages formset and content type form # Note: These are hidden when the workflow is inactive if self.object.active: pages_formset = self.get_pages_formset() content_type_form = self.get_content_type_form() if pages_formset.is_valid() and content_type_form.is_valid(): pages_formset.save() content_type_form.save() else: transaction.set_rollback(True) successful = False if successful: success_message = self.get_success_message() if success_message is not None: messages.success( self.request, success_message, buttons=[ messages.button( reverse(self.edit_url_name, args=(self.object.id,)), _("Edit"), ) ], ) return redirect(self.get_success_url()) return self.form_invalid(form) class Disable(DeleteView): permission_policy = workflow_permission_policy model = Workflow page_title = _("Disable workflow") template_name = "wagtailadmin/workflows/confirm_disable.html" success_message = _("Workflow '%(object)s' disabled.") add_url_name = "wagtailadmin_workflows:add" edit_url_name = "wagtailadmin_workflows:edit" delete_url_name = "wagtailadmin_workflows:disable" index_url_name = "wagtailadmin_workflows:index" header_icon = "tasks" @property def get_edit_url(self): return reverse(self.edit_url_name, args=(self.kwargs["pk"],)) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) states_in_progress = WorkflowState.objects.filter( workflow=self.object, status=WorkflowState.STATUS_IN_PROGRESS ).count() if states_in_progress: context["warning_message"] = ngettext( "This workflow is in progress on %(states_in_progress)d page/snippet. Disabling this workflow will cancel moderation on this page/snippet.", "This workflow is in progress on %(states_in_progress)d pages/snippets. Disabling this workflow will cancel moderation on these pages/snippets.", states_in_progress, ) % { "states_in_progress": states_in_progress, } return context def delete_action(self): self.object.deactivate(user=self.request.user) def usage(request, pk): workflow = get_object_or_404(Workflow, id=pk) editable_pages = page_permission_policy.instances_user_has_permission_for( request.user, "change" ) pages = workflow.all_pages() & editable_pages paginator = Paginator(pages, per_page=10) pages = paginator.get_page(request.GET.get("p")) return render( request, "wagtailadmin/workflows/usage.html", { "workflow": workflow, "used_by": pages, }, ) @require_POST def enable_workflow(request, pk): # Reactivate an inactive workflow workflow = get_object_or_404(Workflow, id=pk) # Check permissions if not workflow_permission_policy.user_has_permission(request.user, "add"): raise PermissionDenied # Set workflow to active if inactive if not workflow.active: workflow.active = True workflow.save() messages.success( request, _("Workflow '%(workflow_name)s' enabled.") % {"workflow_name": workflow.name}, ) # Redirect redirect_to = request.POST.get("next", None) if redirect_to and url_has_allowed_host_and_scheme( url=redirect_to, allowed_hosts={request.get_host()} ): return redirect(redirect_to) else: return redirect("wagtailadmin_workflows:edit", workflow.id) @require_POST def remove_workflow(request, page_pk, workflow_pk=None): # Remove a workflow from a page (specifically a single workflow if workflow_pk is set) # Get the page page = get_object_or_404(Page, id=page_pk) # Check permissions if not workflow_permission_policy.user_has_permission(request.user, "change"): raise PermissionDenied if hasattr(page, "workflowpage"): # If workflow_pk is set, this will only remove the workflow if it its pk matches - this prevents accidental # removal of the wrong workflow via a workflow edit page if the page listing is out of date if not workflow_pk or workflow_pk == page.workflowpage.workflow.pk: page.workflowpage.delete() messages.success( request, _("Workflow removed from Page '%(page_title)s'.") % {"page_title": page.get_admin_display_title()}, ) # Redirect redirect_to = request.POST.get("next", None) if redirect_to and url_has_allowed_host_and_scheme( url=redirect_to, allowed_hosts={request.get_host()} ): return redirect(redirect_to) else: return redirect("wagtailadmin_explore", page.id) class TaskTitleColumn(TitleColumn): cell_template_name = "wagtailadmin/workflows/includes/task_title_cell.html" class TaskUsageColumn(Column): cell_template_name = "wagtailadmin/workflows/includes/task_usage_cell.html" class TaskFilterSet(BaseWorkflowFilterSet): def __init__(self, data=None, queryset=None, *, request=None, prefix=None): super().__init__(data, queryset, request=request, prefix=prefix) task_types = get_task_types() ct_ids = [ ct.id for ct in ContentType.objects.get_for_models(*task_types).values() ] if len(task_types) > 1: self.filters["content_type"] = MultipleContentTypeFilter( label=_("Type"), widget=forms.CheckboxSelectMultiple, queryset=lambda request: ContentType.objects.filter(pk__in=ct_ids), field_name="content_type", ) class Meta: model = Task fields = [] class TaskIndex(IndexView): permission_policy = task_permission_policy model = Task context_object_name = "tasks" template_name = "wagtailadmin/workflows/task_index.html" results_template_name = "wagtailadmin/workflows/task_index_results.html" add_url_name = "wagtailadmin_workflows:select_task_type" edit_url_name = "wagtailadmin_workflows:edit_task" index_url_name = "wagtailadmin_workflows:task_index" index_results_url_name = "wagtailadmin_workflows:task_index_results" page_title = _("Workflow tasks") add_item_label = _("New workflow task") header_icon = "thumbtack" columns = [ TaskTitleColumn( "name", label=_("Name"), url_name="wagtailadmin_workflows:edit_task", sort_key="name", ), Column("type", label=_("Type"), accessor="get_verbose_name", width="25%"), TaskUsageColumn( "usage", label=_("Used on"), accessor="_active_workflows", width="25%" ), ] default_ordering = "name" search_fields = ["name"] filterset_class = TaskFilterSet _show_breadcrumbs = True paginate_by = 50 def show_disabled(self): return self.filters.form.cleaned_data.get("show_disabled") == "true" def get_queryset(self): return ( super() .get_queryset() .specific() .prefetch_related( Prefetch( "workflow_tasks", queryset=WorkflowTask.objects.filter( workflow__active=True ).select_related("workflow"), to_attr="_active_workflows", ) ) ) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["showing_disabled"] = self.show_disabled() return context def select_task_type(request): if not task_permission_policy.user_has_permission(request.user, "add"): raise PermissionDenied task_types = [ ( model.get_verbose_name(), model._meta.app_label, model._meta.model_name, model.get_description(), ) for model in get_task_types() ] # sort by lower-cased version of verbose name task_types.sort(key=lambda task_type: task_type[0].lower()) if len(task_types) == 1: # Only one task type is available - redirect straight to the create form rather than # making the user choose verbose_name, app_label, model_name, description = task_types[0] return redirect("wagtailadmin_workflows:add_task", app_label, model_name) return render( request, "wagtailadmin/workflows/select_task_type.html", { "task_types": task_types, "icon": "thumbtack", "title": _("Workflows"), }, ) class CreateTask(CreateView): permission_policy = task_permission_policy model = None page_title = _("New workflow task") template_name = "wagtailadmin/workflows/create_task.html" success_message = _("Task '%(object)s' created.") add_url_name = "wagtailadmin_workflows:add_task" edit_url_name = "wagtailadmin_workflows:edit_task" index_url_name = "wagtailadmin_workflows:task_index" header_icon = "thumbtack" _show_breadcrumbs = True @cached_property def model(self): try: content_type = ContentType.objects.get_by_natural_key( self.kwargs["app_label"], self.kwargs["model_name"] ) except (ContentType.DoesNotExist, AttributeError): raise Http404 # Get class model = content_type.model_class() # Make sure the class is a descendant of Task if not issubclass(model, Task) or model is Task: raise Http404 return model def get_form_class(self): return get_task_form_class(self.model) def get_add_url(self): return reverse( self.add_url_name, kwargs={ "app_label": self.kwargs.get("app_label"), "model_name": self.kwargs.get("model_name"), }, ) def get_breadcrumbs_items(self): # Use the base Task class instead of the specific class for the index view items = [ { "url": reverse(self.index_url_name), "label": capfirst(Task._meta.verbose_name_plural), }, { "url": "", "label": _("New: %(model_name)s") % {"model_name": capfirst(self.model._meta.verbose_name)}, }, ] return self.breadcrumbs_items + items class EditTask(EditView): permission_policy = task_permission_policy model = None page_title = _("Editing workflow task") template_name = "wagtailadmin/workflows/edit_task.html" success_message = _("Task '%(object)s' updated.") add_url_name = "wagtailadmin_workflows:select_task_type" edit_url_name = "wagtailadmin_workflows:edit_task" delete_url_name = "wagtailadmin_workflows:disable_task" index_url_name = "wagtailadmin_workflows:task_index" delete_item_label = _("Disable") enable_item_label = _("Enable") enable_url_name = "wagtailadmin_workflows:enable_task" header_icon = "thumbtack" _show_breadcrumbs = True @cached_property def model(self): return type(self.get_object()) @cached_property def page_title(self): return _("Editing %(task_type)s") % { "task_type": self.get_object().content_type.name } def get_queryset(self): if self.queryset is None: return Task.objects.all() def get_object(self, queryset=None): return super().get_object().specific def get_form_class(self): return get_task_form_class(self.model, for_edit=True) def get_breadcrumbs_items(self): # Use the base Task class instead of the specific class items = [ { "url": reverse(self.index_url_name), "label": capfirst(Task._meta.verbose_name_plural), }, {"url": "", "label": str(self.object)}, ] return self.breadcrumbs_items + items def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["can_disable"] = ( self.permission_policy is None or self.permission_policy.user_has_permission(self.request.user, "delete") ) and self.object.active context["can_enable"] = ( self.permission_policy is None or self.permission_policy.user_has_permission(self.request.user, "add") ) and not self.object.active # TODO: add warning msg when there are pages/snippets currently on this task in a workflow, add interaction like resetting task state when saved return context @property def get_enable_url(self): return reverse(self.enable_url_name, args=(self.object.pk,)) class DisableTask(DeleteView): permission_policy = task_permission_policy model = Task page_title = _("Disable task") template_name = "wagtailadmin/workflows/confirm_disable_task.html" success_message = _("Task '%(object)s' disabled.") add_url_name = "wagtailadmin_workflows:add_task" edit_url_name = "wagtailadmin_workflows:edit_task" delete_url_name = "wagtailadmin_workflows:disable_task" index_url_name = "wagtailadmin_workflows:task_index" header_icon = "thumbtack" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) states_in_progress = TaskState.objects.filter( status=TaskState.STATUS_IN_PROGRESS, task=self.get_object().pk ).count() if states_in_progress: context["warning_message"] = ngettext( "This task is in progress on %(states_in_progress)d page/snippet. Disabling this task will cause it to be skipped in the moderation workflow and not be listed for selection when editing a workflow.", "This task is in progress on %(states_in_progress)d pages/snippets. Disabling this task will cause it to be skipped in the moderation workflow and not be listed for selection when editing a workflow.", states_in_progress, ) % { "states_in_progress": states_in_progress, } return context @property def get_edit_url(self): return reverse(self.edit_url_name, args=(self.kwargs["pk"],)) def delete_action(self): self.object.deactivate(user=self.request.user) @require_POST def enable_task(request, pk): # Reactivate an inactive task task = get_object_or_404(Task, id=pk) # Check permissions if not task_permission_policy.user_has_permission(request.user, "add"): raise PermissionDenied # Set workflow to active if inactive if not task.active: task.active = True task.save() messages.success( request, _("Task '%(task_name)s' enabled.") % {"task_name": task.name} ) # Redirect redirect_to = request.POST.get("next", None) if redirect_to and url_has_allowed_host_and_scheme( url=redirect_to, allowed_hosts={request.get_host()} ): return redirect(redirect_to) else: return redirect("wagtailadmin_workflows:edit_task", task.id) def get_task_chosen_response(request, task): """ helper function: given a task, return the response indicating that it has been chosen """ result_data = { "id": task.id, "name": task.name, "edit_url": reverse("wagtailadmin_workflows:edit_task", args=[task.id]), } return render_modal_workflow( request, None, None, None, json_data={"step": "task_chosen", "result": result_data}, ) class BaseTaskChooserView(TemplateView): def dispatch(self, request): self.task_models = get_task_types() self.can_create = ( task_permission_policy.user_has_permission(request.user, "add") and len(self.task_models) != 0 ) return super().dispatch(request) def get_create_model(self): """ To be called after dispatch(); returns the model to use for a new task if one is known (either from being the only available task mode, or from being specified in the URL as create_model) """ if self.can_create: if len(self.task_models) == 1: return self.task_models[0] elif "create_model" in self.request.GET: create_model = resolve_model_string(self.request.GET["create_model"]) if create_model not in self.task_models: raise Http404 return create_model def get_create_form_class(self): """ To be called after dispatch(); returns the form class for creating a new task """ self.create_model = self.get_create_model() if self.create_model: return get_task_form_class(self.create_model) else: return None def get_create_form(self): """ To be called after dispatch(); returns a blank create form, or None if not available """ create_form_class = self.get_create_form_class() if create_form_class: return create_form_class(prefix="create-task") def get_task_type_options(self): """ To be called after dispatch(); returns the task types list for the "select task type" view """ task_types = [ ( model.get_verbose_name(), model._meta.app_label, model._meta.model_name, model.get_description(), ) for model in self.task_models ] # sort by lower-cased version of verbose name task_types.sort(key=lambda task_type: task_type[0].lower()) return task_types def get_task_type_filter_choices(self): """ To be called after dispatch(); returns the list of task type choices for filter on "existing task" tab """ task_type_choices = [ (model, model.get_verbose_name()) for model in self.task_models ] task_type_choices.sort(key=lambda task_type: task_type[1].lower()) return task_type_choices def get_form_js_context(self): return {} def get_task_listing_context_data(self): search_form = TaskChooserSearchForm( self.request.GET, task_type_choices=self.get_task_type_filter_choices() ) tasks = all_tasks = search_form.task_model.objects.filter(active=True).order_by( Lower("name") ) q = "" if search_form.is_searching(): # Note: I decided not to use wagtailsearch here. This is because # wagtailsearch creates a new index for each model you make # searchable and this might affect someone's quota. I doubt there # would ever be enough tasks to require using anything more than # an icontains anyway. q = search_form.cleaned_data["q"] tasks = tasks.filter(name__icontains=q) # Pagination paginator = Paginator(tasks, per_page=10) tasks = paginator.get_page(self.request.GET.get("p")) return { "search_form": search_form, "tasks": tasks, "all_tasks": all_tasks, "query_string": q, "can_create": self.can_create, } def get_create_tab_context_data(self): return { "create_form": self.create_form, "add_url": reverse("wagtailadmin_workflows:task_chooser_create") + "?" + self.request.GET.urlencode() if self.create_model else None, "task_types": self.get_task_type_options(), } class TaskChooserView(BaseTaskChooserView): def get(self, request): self.create_form = self.get_create_form() return super().get(request) def get_context_data(self, **kwargs): context = { "can_create": self.can_create, } context.update(self.get_task_listing_context_data()) context.update(self.get_create_tab_context_data()) return context def render_to_response(self, context): js_context = self.get_form_js_context() js_context["step"] = "chooser" return render_modal_workflow( self.request, "wagtailadmin/workflows/task_chooser/chooser.html", None, context, json_data=js_context, ) class TaskChooserCreateView(BaseTaskChooserView): def get(self, request): self.create_form = self.get_create_form() return super().get(request) def post(self, request): create_form_class = self.get_create_form_class() if not create_form_class: return HttpResponseBadRequest() self.create_form = create_form_class( request.POST, request.FILES, prefix="create-task" ) if self.create_form.is_valid(): task = self.create_form.save() return get_task_chosen_response(request, task) else: context = self.get_context_data() return self.render_to_response(context) def get_context_data(self, **kwargs): return self.get_create_tab_context_data() def render_to_response(self, context): tab_html = render_to_string( "wagtailadmin/workflows/task_chooser/includes/create_tab.html", context, self.request, ) js_context = self.get_form_js_context() js_context["step"] = "reshow_create_tab" js_context["htmlFragment"] = tab_html return render_modal_workflow( self.request, None, None, None, json_data=js_context ) class TaskChooserResultsView(BaseTaskChooserView): template_name = "wagtailadmin/workflows/task_chooser/includes/results.html" def get_context_data(self, **kwargs): return self.get_task_listing_context_data() def task_chosen(request, task_id): task = get_object_or_404(Task, id=task_id) return get_task_chosen_response(request, task)