Set blank=True on current_task_state, and address issue with MySQL backend not recogning conditional unique 'one in progress workflow' constraint by adding an additional check in pre-save validation.

pull/6257/head
jacobtoppm 2020-02-18 15:40:28 +00:00 zatwierdzone przez Matt Westcott
rodzic 514bc7de20
commit 8664d7cea7
3 zmienionych plików z 17 dodań i 6 usunięć

Wyświetl plik

@ -1,4 +1,4 @@
# Generated by Django 3.0.3 on 2020-02-17 10:19
# Generated by Django 3.0.3 on 2020-02-18 15:33
from django.conf import settings
from django.db import migrations, models
@ -10,9 +10,9 @@ import wagtail.core.models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('contenttypes', '0002_remove_content_type_name'),
('wagtailcore', '0046_site_name_remove_null'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('auth', '0011_update_proxy_permissions'),
]
@ -76,7 +76,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('in_progress', 'In progress'), ('approved', 'Approved'), ('rejected', 'Rejected'), ('cancelled', 'Cancelled')], default='in_progress', max_length=50, verbose_name='status')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')),
('current_task_state', models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='wagtailcore.TaskState', verbose_name='current task state')),
('current_task_state', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='wagtailcore.TaskState', verbose_name='current task state')),
('page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workflow_states', to='wagtailcore.Page', verbose_name='page')),
('requested_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='requested_workflows', to=settings.AUTH_USER_MODEL, verbose_name='requested by')),
('workflow', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workflow_states', to='wagtailcore.Workflow', verbose_name='workflow')),

Wyświetl plik

@ -2785,12 +2785,22 @@ class WorkflowState(models.Model):
editable=True,
on_delete=models.SET_NULL,
related_name='requested_workflows')
current_task_state = models.OneToOneField('TaskState', on_delete=models.SET_NULL, null=True, blank=False,
current_task_state = models.OneToOneField('TaskState', on_delete=models.SET_NULL, null=True, blank=True,
verbose_name=_("current task state"))
# allows a custom function to be called on finishing the Workflow successfully.
on_finish = import_string(getattr(settings, 'WAGTAIL_FINISH_WORKFLOW_ACTION', 'wagtail.core.workflows.publish_workflow_state'))
def clean(self):
super().clean()
# The unique constraint is conditional, and so not supported on the MySQL backend - so an additional check is done here
if WorkflowState.objects.filter(status=self.STATUS_IN_PROGRESS, page=self.page).exclude(pk=self.pk).exists():
raise ValidationError(_('There may only be one in progress workflow state per page.'))
def save(self, *args, **kwargs):
self.full_clean()
return super().save(*args, **kwargs)
def __str__(self):
return _("Workflow '{0}' on Page '{1}': {2}").format(self.workflow, self.page, self.status)
@ -2884,7 +2894,7 @@ class WorkflowState(models.Model):
class Meta:
verbose_name = _('Workflow state')
verbose_name_plural = _('Workflow states')
# prevent multiple STATUS_IN_PROGRESS workflows for the same page
# prevent multiple STATUS_IN_PROGRESS workflows for the same page. This is not supported by MySQL, so is checked additionally on save.
constraints = [
models.UniqueConstraint(fields=['page'], condition=Q(status='in_progress'), name='unique_in_progress_workflow')
]

Wyświetl plik

@ -3,6 +3,7 @@ import datetime
import pytz
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.core.exceptions import ValidationError
from django.db.utils import IntegrityError
from django.test import TestCase, override_settings
@ -115,7 +116,7 @@ class TestWorkflows(TestCase):
def test_error_when_starting_multiple_in_progress_workflows(self):
# test trying to start multiple status='in_progress' workflows on a single page will trigger an IntegrityError
self.start_workflow_on_homepage()
with self.assertRaises(IntegrityError):
with self.assertRaises((IntegrityError, ValidationError)):
self.start_workflow_on_homepage()
@freeze_time("2017-01-01 12:00:00")