Workflow pages formset (#6171)

* Give "Create task" its own template
* Implement workflow pages formset
* Delete 'add workflow to page' view
* Moved $object-title-height to variables.scss
pull/6257/head
Karl Hobley 2020-06-25 11:54:40 +01:00 zatwierdzone przez Matt Westcott
rodzic 238d7d7b1b
commit a80c34983b
14 zmienionych plików z 445 dodań i 207 usunięć

Wyświetl plik

@ -103,6 +103,8 @@ $focus-outline-width: 3px;
$nav-wrapper-inner-z-index: 26;
$draftail-editor-z-index: $nav-wrapper-inner-z-index + 1;
$object-title-height: 40px;
// Nav
$nav-grey-1: darken($color-white, 80);
$nav-grey-2: darken($color-white, 60);

Wyświetl plik

@ -71,11 +71,7 @@ class TaskChooserSearchForm(forms.Form):
return self.task_model is not Task
class AddWorkflowToPageForm(forms.Form):
"""
A form to assign a Workflow instance to a Page. It is designed to work with a confirmation step if a the chosen Page
is assigned to an existing Workflow - the result of which is stored in overwrite_existing.
"""
class WorkflowPageForm(forms.ModelForm):
page = forms.ModelChoiceField(
queryset=Page.objects.all(),
widget=widgets.AdminPageChooser(
@ -83,26 +79,65 @@ class AddWorkflowToPageForm(forms.Form):
can_choose_root=True
)
)
workflow = forms.ModelChoiceField(queryset=Workflow.objects.active(), widget=forms.HiddenInput())
overwrite_existing = forms.BooleanField(widget=forms.HiddenInput(), initial=False, required=False)
class Meta:
model = WorkflowPage
fields = ['page']
def clean(self):
page = self.cleaned_data.get('page')
try:
existing_workflow = page.workflowpage.workflow
if not self.errors and existing_workflow != self.cleaned_data['workflow'] and not self.cleaned_data['overwrite_existing']:
if not self.errors and existing_workflow != self.cleaned_data['workflow']:
# If the form has no errors, Page has an existing Workflow assigned, that Workflow is not
# the selected Workflow, and overwrite_existing is not True, add a new error. This should be used to
# trigger the confirmation message in the view. This is why this error is only added if there are no
# other errors - confirmation should be the final step.
self.add_error('page', ValidationError(_("This page already has workflow '{0}' assigned. Do you want to overwrite the existing workflow?").format(existing_workflow), code='needs_confirmation'))
self.add_error('page', ValidationError(_("This page already has workflow '{0}' assigned.").format(existing_workflow), code='existing_workflow'))
except AttributeError:
pass
def save(self):
def save(self, commit=False):
page = self.cleaned_data['page']
workflow = self.cleaned_data['workflow']
WorkflowPage.objects.update_or_create(
page=page,
defaults={'workflow': workflow},
)
if commit:
WorkflowPage.objects.update_or_create(
page=page,
defaults={'workflow': self.cleaned_data['workflow']},
)
class BaseWorkflowPagesFormSet(forms.BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for form in self.forms:
form.fields['DELETE'].widget = forms.HiddenInput()
@property
def empty_form(self):
empty_form = super().empty_form
empty_form.fields['DELETE'].widget = forms.HiddenInput()
return empty_form
def clean(self):
"""Checks that no two forms refer to the same page object"""
if any(self.errors):
# Don't bother validating the formset unless each form is valid on its own
return
pages = [
form.cleaned_data['page']
for form in self.forms
# need to check for presence of 'page' in cleaned_data,
# because a completely blank form passes validation
if form not in self.deleted_forms and 'page' in form.cleaned_data
]
if len(set(pages)) != len(pages):
# pages list contains duplicates
raise forms.ValidationError(_("You cannot assign this workflow to the same page multiple times."))
WorkflowPagesFormSet = forms.inlineformset_factory(
Workflow, WorkflowPage, form=WorkflowPageForm, formset=BaseWorkflowPagesFormSet, extra=1, can_delete=True, fields=['page']
)

Wyświetl plik

@ -42,8 +42,6 @@
}
}
$object-title-height: 40px;
// An object is the basic wrapper around any field or group of fields in the editor interface
.object {
@include nice-padding();

Wyświetl plik

@ -0,0 +1,31 @@
@import '../../../../../../client/scss/tools/mixins.general';
@import '../../../../../../client/scss/settings/variables';
.listing {
.field label {
@include visuallyhidden;
}
input,
select,
textarea {
font-size: 1em;
}
select + span:after {
// stylelint-disable-next-line declaration-no-important
font-size: 2.5em !important;
}
}
.workflow-pages-listing {
max-width: 1024px - 50px;
.admin_page_chooser .field-content {
width: 100%; // so that 'choose another page' button displays in its entirety
}
}
.top-padding {
padding-top: $object-title-height + 12px;
}

Wyświetl plik

@ -1,61 +0,0 @@
{% extends "wagtailadmin/base.html" %}
{% load i18n %}
{% block titletag %}Add Workflow to Page{% endblock %}
{% block extra_css %}
{% include "wagtailadmin/pages/_editor_css.html" %}
{{ form.media.css }}
{{ view.media.css }}
{% endblock %}
{% block extra_js %}
{% include "wagtailadmin/pages/_editor_js.html" %}
{{ form.media.js }}
{{ html_declarations }}
{{ view.media.js }}
{% endblock %}
{% block content %}
{% block header %}
{% include "wagtailadmin/shared/header.html" with icon="clipboard-list" %}
{% endblock %}
<form action="{% url 'wagtailadmin_workflows:add_to_page' workflow.pk %}" method="POST" enctype="multipart/form-data" novalidate>
{% csrf_token %}
<div class="nice-padding">
<ul class="fields">
{{ form.non_field_errors }}
{{ form.override_existing.errors }}
{{ form.workflow.errors }}
<li class="field">
{{ form.page.errors }}
{% if not confirm %}
<label for="{{ form.page.id_for_label }}">Page:</label>
{{ form.page }}
{% else %}
{{ form.page.as_hidden }}
{% endif %}
</li>
{{ form.workflow }}
{% if not confirm %}
{{ form.override_existing }}
{% endif %}
</ul>
<div>
{% if confirm %}
<button type="submit" name="overwrite_existing" value="True" class="button">{% trans 'Overwrite Existing Workflow' %}</button>
<a href="." class="button button-secondary">{% trans 'Cancel' %}</a>
{% else %}
<button type="submit" class="button">{% trans 'Add to Page' %}</button>
<a href="{% url 'wagtailadmin_workflows:edit' workflow.pk %}" class="button button-secondary">{% trans 'Return to Workflow' %}</a>
{% endif %}
</div>
</div>
</form>
{% endblock %}

Wyświetl plik

@ -1,11 +1,13 @@
{% extends "wagtailadmin/base.html" %}
{% load i18n %}
{% load i18n wagtailadmin_tags %}
{% block titletag %}{{ view.page_title }}{% endblock %}
{% block extra_css %}
{% include "wagtailadmin/pages/_editor_css.html" %}
<link rel="stylesheet" href="{% versioned_static 'wagtailadmin/css/layouts/workflow-edit.css' %}" type="text/css" />
{{ edit_handler.form.media.css }}
{{ pages_formset.media.css }}
{{ view.media.css }}
{% endblock %}
@ -13,6 +15,7 @@
{% block extra_js %}
{% include "wagtailadmin/pages/_editor_js.html" %}
{{ edit_handler.form.media.js }}
{{ pages_formset.media.js }}
{{ edit_handler.html_declarations }}
{{ view.media.js }}
@ -27,6 +30,21 @@
{% block form %}{{ edit_handler.render_form_content }}{% endblock %}
<ul class="objects">
<li class="object">
<div class="title-wrapper">
<label>
{% trans "Assign your workflow to pages" %}
</label>
</div>
<div class="object-layout">
<div class="object-layout_big-part">
{% include "wagtailadmin/workflows/includes/workflow_pages_formset.html" with formset=pages_formset %}
</div>
</div>
</li>
</ul>
{% block footer %}
<footer role="contentinfo">
<ul>

Wyświetl plik

@ -1 +1,46 @@
{% extends "wagtailadmin/workflows/create.html" %}
{% extends "wagtailadmin/base.html" %}
{% load i18n %}
{% block titletag %}{{ view.page_title }}{% endblock %}
{% block extra_css %}
{% include "wagtailadmin/pages/_editor_css.html" %}
{{ edit_handler.form.media.css }}
{{ view.media.css }}
{% endblock %}
{% block extra_js %}
{% include "wagtailadmin/pages/_editor_js.html" %}
{{ edit_handler.form.media.js }}
{{ edit_handler.html_declarations }}
{{ view.media.js }}
{% endblock %}
{% block content %}
{% include "wagtailadmin/shared/header.html" with title=view.page_title icon=view.header_icon merged=1 %}
<form action="{{ view.get_add_url }}" enctype="multipart/form-data" method="POST" novalidate>
{% csrf_token %}
{% block form %}{{ edit_handler.render_form_content }}{% endblock %}
{% block footer %}
<footer role="contentinfo">
<ul>
<li class="actions">
{% block form_actions %}
<div class="dropdown dropup dropdown-button match-width">
<button type="submit" class="button action-save button-longrunning" data-clicked-text="{% trans 'Creating…' %}">
<span class="icon icon-spinner"></span><em>{% trans 'Create' %}</em>
</button>
</div>
{% endblock %}
</li>
</ul>
</footer>
{% endblock %}
</form>
{% endblock %}

Wyświetl plik

@ -6,13 +6,16 @@
{% block extra_css %}
{% include "wagtailadmin/pages/_editor_css.html" %}
{{ edit_handler.form.media.css }}
{{ pages_formset.media.css }}
{{ view.media.css }}
<link rel="stylesheet" href="{% versioned_static 'wagtailadmin/css/layouts/workflow-edit.css' %}" type="text/css" />
{% endblock %}
{% block extra_js %}
{% include "wagtailadmin/pages/_editor_js.html" %}
{{ edit_handler.form.media.js }}
{{ pages_formset.media.js }}
{{ edit_handler.html_declarations }}
{{ view.media.js }}
@ -22,11 +25,35 @@
{% include "wagtailadmin/shared/header.html" with title=view.page_title icon=view.header_icon merged=1 %}
<form action="{% block form_action %}{{ view.edit_url }}{% endblock %}"{% if is_multipart %} enctype="multipart/form-data"{% endif %} method="POST" novalidate>
{% csrf_token %}
{% block form %}{{ edit_handler.render_form_content }}{% endblock %}
<ul class="objects">
<li class="object">
<div class="title-wrapper">
<label>
{% trans "Assign your workflow to pages" %}
</label>
</div>
<div class="object-layout">
<div class="object-layout_big-part">
{% if workflow.active %}
{% include "wagtailadmin/workflows/includes/workflow_pages_formset.html" with formset=pages_formset %}
{% else %}
<div class="top-padding">
<p class="help-block help-info">
{% trans "This workflow is disabled so it cannot be assigned to any pages." %}
</p>
</div>
{% endif %}
</div>
</div>
</li>
</ul>
<div class="object">
<ul class="object-layout">
<li class="object-layout_big-part">
@ -39,6 +66,7 @@
</ul>
</div>
</form>
{% if can_enable or can_disable %}
<div class="nice-padding">
<form action="{{ view.get_enable_url }}" method="POST" novalidate>
@ -54,21 +82,4 @@
</form>
</div>
{% endif %}
<div class="object">
<div class="title-wrapper">{% trans 'Select page(s) to link workflow to' %}</div>
<div class="object-layout">
<div class="object-layout_big-part">
<div class="multiple">
<p>{% trans 'Child pages will also use this workflow' %}</p>
{% if pages %}
{% include "wagtailadmin/workflows/listing/_list_workflow_pages.html" %}
{% paginate pages base_url=request.path %}
{% endif %}
<p class="add">
<a class="button bicolor icon icon-plus" href="{% url 'wagtailadmin_workflows:add_to_page' workflow.pk %}">Add</a>
</p>
</div>
</div>
</div>
</div>
{% endblock %}

Wyświetl plik

@ -0,0 +1,9 @@
{% load i18n %}
<td>
{% include "wagtailadmin/shared/field.html" with field=form.page only %}
{{ form.DELETE }}
</td>
<td>
<button class="button button-secondary button-small no" type="button" id="{{ form.DELETE.id_for_label }}-button">{% trans "Delete" %}</button>
</td>

Wyświetl plik

@ -0,0 +1,69 @@
{% load i18n wagtailadmin_tags %}
<h2>{% trans "Assigned pages" %}</h2>
{{ formset.management_form }}
{% if formset.non_form_errors %}
<p class="error-message">
{% for error in formset.non_form_errors %}
<span>{{ error }}</span>
{% endfor %}
</p>
{% endif %}
<table class="listing workflow-pages-listing">
<col />
<col />
<thead>
<tr>
<th>{% trans "Page" %}</th>
{% for identifier, short_label, long_label in formset.permission_types %}
<th title="{{ long_label }}">{{ short_label }}</th>
{% endfor %}
<th></th>
</tr>
</thead>
<tbody id="id_{{ formset.prefix }}-FORMS">
{% for form in formset.forms %}
<tr id="inline_child_{{ form.prefix }}"{% if form.DELETE.value %} style="display: none;"{% endif %}>
{% if form.non_field_errors %}
<p class="error-message">
{% for error in form.non_field_errors %}
<span>{{ error|escape }}</span>
{% endfor %}
</p>
{% endif %}
{% include "wagtailadmin/workflows/includes/workflow_pages_form.html" with form=form only %}
</tr>
{% endfor %}
</tbody>
</table>
<script type="text/django-form-template" id="id_{{ formset.prefix }}-EMPTY_FORM_TEMPLATE">
{% escapescript %}
<tr id="inline_child_{{ formset.empty_form.prefix }}">
{% include "wagtailadmin/workflows/includes/workflow_pages_form.html" with form=formset.empty_form only %}
</tr>
{% endescapescript %}
</script>
<p class="add">
<a class="button bicolor icon icon-plus" id="id_{{ formset.prefix }}-ADD" value="Add">{% trans "Assign to another page" %}</a>
</p>
<script>
$(function() {
buildExpandingFormset('id_{{ formset.prefix|escapejs }}', {
onInit: function(index) {
var deleteInputId = 'id_{{ formset.prefix|escapejs }}-' + index + '-DELETE';
var childId = 'inline_child_{{ formset.prefix|escapejs }}-' + index;
$('#' + deleteInputId + '-button').on('click', function() {
/* set 'deleted' form field to true */
$('#' + deleteInputId).val('1');
$('#' + childId).fadeOut();
});
}
});
});
</script>

Wyświetl plik

@ -1,13 +0,0 @@
{% extends "wagtailadmin/pages/listing/_list_explore.html" %}
{% load i18n wagtailadmin_tags %}
{% block page_navigation %}
<td class="remove-workflow" valign="top">
<form action="{% url 'wagtailadmin_workflows:remove' page.id workflow.pk %}" method="post">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.path }}">
<button type="submit" class="button button-secondary button-small">{% trans 'Remove' %}</button>
</form>
</td>
{% endblock %}

Wyświetl plik

@ -121,6 +121,7 @@ class TestWorkflowsCreateView(TestCase, WagtailTestUtils):
moderators.user_set.add(self.moderator)
moderators.permissions.add(Permission.objects.get(codename="add_workflow"))
self.root_page = Page.objects.get(depth=1)
def get(self, params={}):
return self.client.get(reverse('wagtailadmin_workflows:add'), params)
@ -135,11 +136,29 @@ class TestWorkflowsCreateView(TestCase, WagtailTestUtils):
def test_post(self):
response = self.post({
'name': ['test_workflow'], 'active': ['on'], 'workflow_tasks-TOTAL_FORMS': ['2'],
'workflow_tasks-INITIAL_FORMS': ['0'], 'workflow_tasks-MIN_NUM_FORMS': ['0'],
'workflow_tasks-MAX_NUM_FORMS': ['1000'], 'workflow_tasks-0-task': [str(self.task_1.id)], 'workflow_tasks-0-id': [''],
'workflow_tasks-0-ORDER': ['1'], 'workflow_tasks-0-DELETE': [''], 'workflow_tasks-1-task': [str(self.task_2.id)],
'workflow_tasks-1-id': [''], 'workflow_tasks-1-ORDER': ['2'], 'workflow_tasks-1-DELETE': ['']})
'name': ['test_workflow'],
'active': ['on'],
'workflow_tasks-TOTAL_FORMS': ['2'],
'workflow_tasks-INITIAL_FORMS': ['0'],
'workflow_tasks-MIN_NUM_FORMS': ['0'],
'workflow_tasks-MAX_NUM_FORMS': ['1000'],
'workflow_tasks-0-task': [str(self.task_1.id)],
'workflow_tasks-0-id': [''],
'workflow_tasks-0-ORDER': ['1'],
'workflow_tasks-0-DELETE': [''],
'workflow_tasks-1-task': [str(self.task_2.id)],
'workflow_tasks-1-id': [''],
'workflow_tasks-1-ORDER': ['2'],
'workflow_tasks-1-DELETE': [''],
'pages-TOTAL_FORMS': ['2'],
'pages-INITIAL_FORMS': ['1'],
'pages-MIN_NUM_FORMS': ['0'],
'pages-MAX_NUM_FORMS': ['1000'],
'pages-0-page': [str(self.root_page.id)],
'pages-0-DELETE': [''],
'pages-1-page': [''],
'pages-1-DELETE': [''],
})
# Should redirect back to index
@ -169,6 +188,38 @@ class TestWorkflowsCreateView(TestCase, WagtailTestUtils):
response = self.get()
self.assertEqual(response.status_code, 200)
def test_page_already_has_workflow_check(self):
workflow = Workflow.objects.create(name="existing_workflow")
WorkflowPage.objects.create(workflow=workflow, page=self.root_page)
response = self.post({
'name': ['test_workflow'],
'active': ['on'],
'workflow_tasks-TOTAL_FORMS': ['2'],
'workflow_tasks-INITIAL_FORMS': ['0'],
'workflow_tasks-MIN_NUM_FORMS': ['0'],
'workflow_tasks-MAX_NUM_FORMS': ['1000'],
'workflow_tasks-0-task': [str(self.task_1.id)],
'workflow_tasks-0-id': [''],
'workflow_tasks-0-ORDER': ['1'],
'workflow_tasks-0-DELETE': [''],
'workflow_tasks-1-task': [str(self.task_2.id)],
'workflow_tasks-1-id': [''],
'workflow_tasks-1-ORDER': ['2'],
'workflow_tasks-1-DELETE': [''],
'pages-TOTAL_FORMS': ['2'],
'pages-INITIAL_FORMS': ['1'],
'pages-MIN_NUM_FORMS': ['0'],
'pages-MAX_NUM_FORMS': ['1000'],
'pages-0-page': [str(self.root_page.id)],
'pages-0-DELETE': [''],
'pages-1-page': [''],
'pages-1-DELETE': [''],
})
self.assertEqual(response.status_code, 200)
self.assertFormsetError(response, 'pages_formset', 0, 'page', ["This page already has workflow 'existing_workflow' assigned."])
class TestWorkflowsEditView(TestCase, WagtailTestUtils):
@ -230,7 +281,16 @@ class TestWorkflowsEditView(TestCase, WagtailTestUtils):
'workflow_tasks-1-task': [str(self.task_2.id)],
'workflow_tasks-1-id': [''],
'workflow_tasks-1-ORDER': ['2'],
'workflow_tasks-1-DELETE': ['']})
'workflow_tasks-1-DELETE': [''],
'pages-TOTAL_FORMS': ['2'],
'pages-INITIAL_FORMS': ['1'],
'pages-MIN_NUM_FORMS': ['0'],
'pages-MAX_NUM_FORMS': ['1000'],
'pages-0-page': [str(self.page.id)],
'pages-0-DELETE': [''],
'pages-1-page': [''],
'pages-1-DELETE': [''],
})
# Should redirect back to index
@ -260,68 +320,71 @@ class TestWorkflowsEditView(TestCase, WagtailTestUtils):
response = self.get()
self.assertEqual(response.status_code, 200)
def test_duplicate_page_check(self):
response = self.post({
'name': [str(self.workflow.name)],
'active': ['on'],
'workflow_tasks-TOTAL_FORMS': ['2'],
'workflow_tasks-INITIAL_FORMS': ['1'],
'workflow_tasks-MIN_NUM_FORMS': ['0'],
'workflow_tasks-MAX_NUM_FORMS': ['1000'],
'workflow_tasks-0-task': [str(self.task_1.id)],
'workflow_tasks-0-id': [str(self.workflow_task.id)],
'workflow_tasks-0-ORDER': ['1'],
'workflow_tasks-0-DELETE': [''],
'workflow_tasks-1-task': [str(self.task_2.id)],
'workflow_tasks-1-id': [''],
'workflow_tasks-1-ORDER': ['2'],
'workflow_tasks-1-DELETE': [''],
'pages-TOTAL_FORMS': ['2'],
'pages-INITIAL_FORMS': ['1'],
'pages-MIN_NUM_FORMS': ['0'],
'pages-MAX_NUM_FORMS': ['1000'],
'pages-0-page': [str(self.page.id)],
'pages-0-DELETE': [''],
'pages-1-page': [str(self.page.id)],
'pages-1-DELETE': [''],
})
class TestAddWorkflowToPage(TestCase, WagtailTestUtils):
fixtures = ['test.json']
def setUp(self):
delete_existing_workflows()
self.login()
self.workflow = Workflow.objects.create(name="workflow")
self.page = Page.objects.first()
self.other_workflow = Workflow.objects.create(name="other_workflow")
self.other_page = Page.objects.last()
WorkflowPage.objects.create(workflow=self.other_workflow, page=self.other_page)
self.editor = get_user_model().objects.create_user(
username='editor',
email='editor@email.com',
password='password',
)
editors = Group.objects.get(name='Editors')
editors.user_set.add(self.editor)
self.moderator = get_user_model().objects.create_user(
username='moderator',
email='moderator@email.com',
password='password',
)
moderators = Group.objects.get(name='Moderators')
moderators.user_set.add(self.moderator)
moderators.permissions.add(Permission.objects.get(codename="change_workflow"))
def get(self, params={}):
return self.client.get(reverse('wagtailadmin_workflows:add_to_page', args=[self.workflow.id]), params)
def post(self, post_data={}):
return self.client.post(reverse('wagtailadmin_workflows:add_to_page', args=[self.workflow.id]), post_data)
def test_get(self):
response = self.get()
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'wagtailadmin/workflows/add_to_page.html')
self.assertFormsetError(response, 'pages_formset', None, None, ['You cannot assign this workflow to the same page multiple times.'])
def test_post(self):
# Check that a WorkflowPage instance is created correctly when a page with no existing workflow is created
self.post({'page': str(self.page.id), 'workflow': str(self.workflow.id)})
self.assertEqual(WorkflowPage.objects.filter(workflow=self.workflow, page=self.page).count(), 1)
def test_pages_ignored_if_workflow_disabled(self):
self.workflow.active = False
self.workflow.save()
self.workflow.workflow_pages.all().delete()
# Check that trying to add a WorkflowPage for a page with an existing workflow does not create
self.post({'page': str(self.other_page.id), 'workflow': str(self.workflow.id)})
self.assertEqual(WorkflowPage.objects.filter(workflow=self.workflow, page=self.other_page).count(), 0)
response = self.post({
'name': [str(self.workflow.name)],
'active': ['on'],
'workflow_tasks-TOTAL_FORMS': ['2'],
'workflow_tasks-INITIAL_FORMS': ['1'],
'workflow_tasks-MIN_NUM_FORMS': ['0'],
'workflow_tasks-MAX_NUM_FORMS': ['1000'],
'workflow_tasks-0-task': [str(self.task_1.id)],
'workflow_tasks-0-id': [str(self.workflow_task.id)],
'workflow_tasks-0-ORDER': ['1'],
'workflow_tasks-0-DELETE': [''],
'workflow_tasks-1-task': [str(self.task_2.id)],
'workflow_tasks-1-id': [''],
'workflow_tasks-1-ORDER': ['2'],
'workflow_tasks-1-DELETE': [''],
'pages-TOTAL_FORMS': ['2'],
'pages-INITIAL_FORMS': ['1'],
'pages-MIN_NUM_FORMS': ['0'],
'pages-MAX_NUM_FORMS': ['1000'],
'pages-0-page': [str(self.page.id)],
'pages-0-DELETE': [''],
'pages-1-page': [''],
'pages-1-DELETE': [''],
})
# Check that this can be overridden by setting overwrite_existing to true
self.post({'page': str(self.other_page.id), 'overwrite_existing': 'True', 'workflow': str(self.workflow.id)})
self.assertEqual(WorkflowPage.objects.filter(workflow=self.workflow, page=self.other_page).count(), 1)
# Should redirect back to index
self.assertRedirects(response, reverse('wagtailadmin_workflows:index'))
def test_permissions(self):
self.login(user=self.editor)
response = self.get()
self.assertEqual(response.status_code, 403)
self.login(user=self.moderator)
response = self.get()
self.assertEqual(response.status_code, 200)
# Check that the pages weren't added to the workflow
self.workflow.refresh_from_db()
self.assertFalse(self.workflow.workflow_pages.exists())
class TestRemoveWorkflow(TestCase, WagtailTestUtils):

Wyświetl plik

@ -12,7 +12,6 @@ urlpatterns = [
path('edit/<int:pk>/', workflows.Edit.as_view(), name='edit'),
path('remove/<int:page_pk>/', workflows.remove_workflow, name='remove'),
path('remove/<int:page_pk>/<int:workflow_pk>/', workflows.remove_workflow, name='remove'),
path('add_to_page/<int:workflow_pk>/', workflows.add_to_page, name='add_to_page'),
path('tasks/add/<str:app_label>/<str:model_name>/', workflows.CreateTask.as_view(), name='add_task'),
path('tasks/select_type/', workflows.select_task_type, name='select_task_type'),
path('tasks/index/', workflows.TaskIndex.as_view(), name='task_index'),

Wyświetl plik

@ -1,6 +1,7 @@
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.functions import Lower
from django.http import Http404, HttpResponseBadRequest
from django.shortcuts import get_object_or_404, redirect, render
@ -15,10 +16,9 @@ from django.views.decorators.http import require_POST
from wagtail.admin import messages
from wagtail.admin.auth import PermissionPolicyChecker
from wagtail.admin.edit_handlers import Workflow
from wagtail.admin.forms.workflows import AddWorkflowToPageForm, TaskChooserSearchForm
from wagtail.admin.forms.workflows import TaskChooserSearchForm, WorkflowPagesFormSet
from wagtail.admin.modal_workflow import render_modal_workflow
from wagtail.admin.views.generic import CreateView, DeleteView, EditView, IndexView
from wagtail.admin.views.pages import get_valid_next_url_from_request
from wagtail.core.models import Page, Task, TaskState, WorkflowState
from wagtail.core.permissions import task_permission_policy, workflow_permission_policy
from wagtail.core.utils import resolve_model_string
@ -83,11 +83,40 @@ class Create(CreateView):
self.edit_handler = self.edit_handler.bind_to(form=form)
return form
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_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['edit_handler'] = self.edit_handler
context['pages_formset'] = self.get_pages_formset()
return context
def form_valid(self, form):
self.form = form
with transaction.atomic():
self.object = self.save_instance()
pages_formset = self.get_pages_formset()
if pages_formset.is_valid():
pages_formset.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
@ -126,6 +155,12 @@ class Edit(EditView):
self.edit_handler = self.edit_handler.bind_to(form=form)
return form
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_paginated_pages(self):
# Get the (paginated) list of Pages to which this Workflow is assigned.
pages = Page.objects.filter(workflowpage__workflow=self.get_object())
@ -138,6 +173,7 @@ class Edit(EditView):
context = super().get_context_data(**kwargs)
context['edit_handler'] = self.edit_handler
context['pages'] = self.get_paginated_pages()
context['pages_formset'] = self.get_pages_formset()
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, 'create')) and not self.object.active
@ -147,6 +183,34 @@ class Edit(EditView):
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
# Note: The pages formset is hidden when the page is inactive
if self.object.active:
pages_formset = self.get_pages_formset()
if pages_formset.is_valid():
pages_formset.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
@ -231,38 +295,6 @@ def remove_workflow(request, page_pk, workflow_pk=None):
return redirect('wagtailadmin_explore', page.id)
def add_to_page(request, workflow_pk):
# Assign a workflow to a Page, including a confirmation step if the Page has a different Workflow assigned already.
if not workflow_permission_policy.user_has_permission(request.user, 'change'):
raise PermissionDenied
workflow = get_object_or_404(Workflow, pk=workflow_pk)
form_class = AddWorkflowToPageForm
next_url = get_valid_next_url_from_request(request)
if request.method == 'POST':
form = form_class(request.POST, request.FILES)
if form.is_valid():
form.save()
messages.success(request, _("Workflow '{0}' added to Page '{1}'.").format(workflow, form.cleaned_data['page']))
form = form_class(initial={'workflow': workflow.pk, 'overwrite_existing': False})
else:
form = form_class(initial={'workflow': workflow.pk, 'overwrite_existing': False})
confirm = form.has_error('page', 'needs_confirmation')
return render(request, 'wagtailadmin/workflows/add_to_page.html', {
'workflow': workflow,
'form': form,
'icon': 'clipboard-list',
'title': _("Workflows"),
'next': next_url,
'confirm': confirm
})
class TaskIndex(IndexView):
permission_policy = task_permission_policy
model = Task