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; $nav-wrapper-inner-z-index: 26;
$draftail-editor-z-index: $nav-wrapper-inner-z-index + 1; $draftail-editor-z-index: $nav-wrapper-inner-z-index + 1;
$object-title-height: 40px;
// Nav // Nav
$nav-grey-1: darken($color-white, 80); $nav-grey-1: darken($color-white, 80);
$nav-grey-2: darken($color-white, 60); $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 return self.task_model is not Task
class AddWorkflowToPageForm(forms.Form): class WorkflowPageForm(forms.ModelForm):
"""
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.
"""
page = forms.ModelChoiceField( page = forms.ModelChoiceField(
queryset=Page.objects.all(), queryset=Page.objects.all(),
widget=widgets.AdminPageChooser( widget=widgets.AdminPageChooser(
@ -83,26 +79,65 @@ class AddWorkflowToPageForm(forms.Form):
can_choose_root=True 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): def clean(self):
page = self.cleaned_data.get('page') page = self.cleaned_data.get('page')
try: try:
existing_workflow = page.workflowpage.workflow 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 # 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 # 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 # 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. # 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: except AttributeError:
pass pass
def save(self): def save(self, commit=False):
page = self.cleaned_data['page'] page = self.cleaned_data['page']
workflow = self.cleaned_data['workflow']
WorkflowPage.objects.update_or_create( if commit:
page=page, WorkflowPage.objects.update_or_create(
defaults={'workflow': workflow}, 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 // An object is the basic wrapper around any field or group of fields in the editor interface
.object { .object {
@include nice-padding(); @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" %} {% extends "wagtailadmin/base.html" %}
{% load i18n %} {% load i18n wagtailadmin_tags %}
{% block titletag %}{{ view.page_title }}{% endblock %} {% block titletag %}{{ view.page_title }}{% endblock %}
{% block extra_css %} {% block extra_css %}
{% include "wagtailadmin/pages/_editor_css.html" %} {% 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 }} {{ edit_handler.form.media.css }}
{{ pages_formset.media.css }}
{{ view.media.css }} {{ view.media.css }}
{% endblock %} {% endblock %}
@ -13,6 +15,7 @@
{% block extra_js %} {% block extra_js %}
{% include "wagtailadmin/pages/_editor_js.html" %} {% include "wagtailadmin/pages/_editor_js.html" %}
{{ edit_handler.form.media.js }} {{ edit_handler.form.media.js }}
{{ pages_formset.media.js }}
{{ edit_handler.html_declarations }} {{ edit_handler.html_declarations }}
{{ view.media.js }} {{ view.media.js }}
@ -27,6 +30,21 @@
{% block form %}{{ edit_handler.render_form_content }}{% endblock %} {% 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 %} {% block footer %}
<footer role="contentinfo"> <footer role="contentinfo">
<ul> <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 %} {% block extra_css %}
{% include "wagtailadmin/pages/_editor_css.html" %} {% include "wagtailadmin/pages/_editor_css.html" %}
{{ edit_handler.form.media.css }} {{ edit_handler.form.media.css }}
{{ pages_formset.media.css }}
{{ view.media.css }} {{ view.media.css }}
<link rel="stylesheet" href="{% versioned_static 'wagtailadmin/css/layouts/workflow-edit.css' %}" type="text/css" />
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
{% include "wagtailadmin/pages/_editor_js.html" %} {% include "wagtailadmin/pages/_editor_js.html" %}
{{ edit_handler.form.media.js }} {{ edit_handler.form.media.js }}
{{ pages_formset.media.js }}
{{ edit_handler.html_declarations }} {{ edit_handler.html_declarations }}
{{ view.media.js }} {{ view.media.js }}
@ -22,11 +25,35 @@
{% include "wagtailadmin/shared/header.html" with title=view.page_title icon=view.header_icon merged=1 %} {% 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> <form action="{% block form_action %}{{ view.edit_url }}{% endblock %}"{% if is_multipart %} enctype="multipart/form-data"{% endif %} method="POST" novalidate>
{% csrf_token %} {% csrf_token %}
{% block form %}{{ edit_handler.render_form_content }}{% endblock %} {% 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"> <div class="object">
<ul class="object-layout"> <ul class="object-layout">
<li class="object-layout_big-part"> <li class="object-layout_big-part">
@ -39,6 +66,7 @@
</ul> </ul>
</div> </div>
</form> </form>
{% if can_enable or can_disable %} {% if can_enable or can_disable %}
<div class="nice-padding"> <div class="nice-padding">
<form action="{{ view.get_enable_url }}" method="POST" novalidate> <form action="{{ view.get_enable_url }}" method="POST" novalidate>
@ -54,21 +82,4 @@
</form> </form>
</div> </div>
{% endif %} {% 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 %} {% 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.user_set.add(self.moderator)
moderators.permissions.add(Permission.objects.get(codename="add_workflow")) moderators.permissions.add(Permission.objects.get(codename="add_workflow"))
self.root_page = Page.objects.get(depth=1)
def get(self, params={}): def get(self, params={}):
return self.client.get(reverse('wagtailadmin_workflows:add'), params) return self.client.get(reverse('wagtailadmin_workflows:add'), params)
@ -135,11 +136,29 @@ class TestWorkflowsCreateView(TestCase, WagtailTestUtils):
def test_post(self): def test_post(self):
response = self.post({ response = self.post({
'name': ['test_workflow'], 'active': ['on'], 'workflow_tasks-TOTAL_FORMS': ['2'], 'name': ['test_workflow'],
'workflow_tasks-INITIAL_FORMS': ['0'], 'workflow_tasks-MIN_NUM_FORMS': ['0'], 'active': ['on'],
'workflow_tasks-MAX_NUM_FORMS': ['1000'], 'workflow_tasks-0-task': [str(self.task_1.id)], 'workflow_tasks-0-id': [''], 'workflow_tasks-TOTAL_FORMS': ['2'],
'workflow_tasks-0-ORDER': ['1'], 'workflow_tasks-0-DELETE': [''], 'workflow_tasks-1-task': [str(self.task_2.id)], 'workflow_tasks-INITIAL_FORMS': ['0'],
'workflow_tasks-1-id': [''], 'workflow_tasks-1-ORDER': ['2'], 'workflow_tasks-1-DELETE': ['']}) '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 # Should redirect back to index
@ -169,6 +188,38 @@ class TestWorkflowsCreateView(TestCase, WagtailTestUtils):
response = self.get() response = self.get()
self.assertEqual(response.status_code, 200) 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): class TestWorkflowsEditView(TestCase, WagtailTestUtils):
@ -230,7 +281,16 @@ class TestWorkflowsEditView(TestCase, WagtailTestUtils):
'workflow_tasks-1-task': [str(self.task_2.id)], 'workflow_tasks-1-task': [str(self.task_2.id)],
'workflow_tasks-1-id': [''], 'workflow_tasks-1-id': [''],
'workflow_tasks-1-ORDER': ['2'], '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 # Should redirect back to index
@ -260,68 +320,71 @@ class TestWorkflowsEditView(TestCase, WagtailTestUtils):
response = self.get() response = self.get()
self.assertEqual(response.status_code, 200) 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.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): def test_pages_ignored_if_workflow_disabled(self):
# Check that a WorkflowPage instance is created correctly when a page with no existing workflow is created self.workflow.active = False
self.post({'page': str(self.page.id), 'workflow': str(self.workflow.id)}) self.workflow.save()
self.assertEqual(WorkflowPage.objects.filter(workflow=self.workflow, page=self.page).count(), 1) self.workflow.workflow_pages.all().delete()
# Check that trying to add a WorkflowPage for a page with an existing workflow does not create response = self.post({
self.post({'page': str(self.other_page.id), 'workflow': str(self.workflow.id)}) 'name': [str(self.workflow.name)],
self.assertEqual(WorkflowPage.objects.filter(workflow=self.workflow, page=self.other_page).count(), 0) '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 # Should redirect back to index
self.post({'page': str(self.other_page.id), 'overwrite_existing': 'True', 'workflow': str(self.workflow.id)}) self.assertRedirects(response, reverse('wagtailadmin_workflows:index'))
self.assertEqual(WorkflowPage.objects.filter(workflow=self.workflow, page=self.other_page).count(), 1)
def test_permissions(self): # Check that the pages weren't added to the workflow
self.login(user=self.editor) self.workflow.refresh_from_db()
response = self.get() self.assertFalse(self.workflow.workflow_pages.exists())
self.assertEqual(response.status_code, 403)
self.login(user=self.moderator)
response = self.get()
self.assertEqual(response.status_code, 200)
class TestRemoveWorkflow(TestCase, WagtailTestUtils): class TestRemoveWorkflow(TestCase, WagtailTestUtils):

Wyświetl plik

@ -12,7 +12,6 @@ urlpatterns = [
path('edit/<int:pk>/', workflows.Edit.as_view(), name='edit'), 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>/', workflows.remove_workflow, name='remove'),
path('remove/<int:page_pk>/<int:workflow_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/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/select_type/', workflows.select_task_type, name='select_task_type'),
path('tasks/index/', workflows.TaskIndex.as_view(), name='task_index'), 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.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.db import transaction
from django.db.models.functions import Lower from django.db.models.functions import Lower
from django.http import Http404, HttpResponseBadRequest from django.http import Http404, HttpResponseBadRequest
from django.shortcuts import get_object_or_404, redirect, render 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 import messages
from wagtail.admin.auth import PermissionPolicyChecker from wagtail.admin.auth import PermissionPolicyChecker
from wagtail.admin.edit_handlers import Workflow 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.modal_workflow import render_modal_workflow
from wagtail.admin.views.generic import CreateView, DeleteView, EditView, IndexView 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.models import Page, Task, TaskState, WorkflowState
from wagtail.core.permissions import task_permission_policy, workflow_permission_policy from wagtail.core.permissions import task_permission_policy, workflow_permission_policy
from wagtail.core.utils import resolve_model_string 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) self.edit_handler = self.edit_handler.bind_to(form=form)
return 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): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['edit_handler'] = self.edit_handler context['edit_handler'] = self.edit_handler
context['pages_formset'] = self.get_pages_formset()
return context 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): class Edit(EditView):
permission_policy = workflow_permission_policy permission_policy = workflow_permission_policy
@ -126,6 +155,12 @@ class Edit(EditView):
self.edit_handler = self.edit_handler.bind_to(form=form) self.edit_handler = self.edit_handler.bind_to(form=form)
return 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): def get_paginated_pages(self):
# Get the (paginated) list of Pages to which this Workflow is assigned. # Get the (paginated) list of Pages to which this Workflow is assigned.
pages = Page.objects.filter(workflowpage__workflow=self.get_object()) pages = Page.objects.filter(workflowpage__workflow=self.get_object())
@ -138,6 +173,7 @@ class Edit(EditView):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['edit_handler'] = self.edit_handler context['edit_handler'] = self.edit_handler
context['pages'] = self.get_paginated_pages() 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_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( context['can_enable'] = (self.permission_policy is None or self.permission_policy.user_has_permission(
self.request.user, 'create')) and not self.object.active self.request.user, 'create')) and not self.object.active
@ -147,6 +183,34 @@ class Edit(EditView):
def get_enable_url(self): def get_enable_url(self):
return reverse(self.enable_url_name, args=(self.object.pk,)) 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): class Disable(DeleteView):
permission_policy = workflow_permission_policy permission_policy = workflow_permission_policy
@ -231,38 +295,6 @@ def remove_workflow(request, page_pk, workflow_pk=None):
return redirect('wagtailadmin_explore', page.id) 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): class TaskIndex(IndexView):
permission_policy = task_permission_policy permission_policy = task_permission_policy
model = Task model = Task