kopia lustrzana https://github.com/wagtail/wagtail
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.scsspull/6257/head
rodzic
238d7d7b1b
commit
a80c34983b
|
@ -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);
|
||||
|
|
|
@ -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']
|
||||
)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 %}
|
||||
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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 %}
|
|
@ -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):
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue