diff --git a/opentech/apply/dashboard/tables.py b/opentech/apply/dashboard/tables.py index 2ef02e44fc5f59f114aeca526788283c690268c9..3ae000d46e2ae10636743f3e77b624528b0679cc 100644 --- a/opentech/apply/dashboard/tables.py +++ b/opentech/apply/dashboard/tables.py @@ -4,11 +4,13 @@ from opentech.apply.funds.models import ApplicationSubmission class DashboardTable(tables.Table): submit_time = tables.DateColumn(verbose_name="Submitted") + status_name = tables.Column(verbose_name="Status") + stage = tables.Column(verbose_name="Type") page = tables.Column(verbose_name="Fund") class Meta: model = ApplicationSubmission - fields = ('title', 'page', 'round', 'submit_time', 'user') + fields = ('title', 'status_name', 'stage', 'page', 'round', 'submit_time', 'user') template = "dashboard/tables/table.html" def render_user(self, value): diff --git a/opentech/apply/funds/forms.py b/opentech/apply/funds/forms.py index 675a7ccff7df6fd56dca40f503d10e7af665ba41..643ab0d9012941d53390c04c154df894918814d5 100644 --- a/opentech/apply/funds/forms.py +++ b/opentech/apply/funds/forms.py @@ -6,8 +6,7 @@ class WorkflowFormAdminForm(WagtailAdminPageForm): cleaned_data = super().clean() model = self._meta.model - from .models import WORKFLOW_CLASS - workflow = WORKFLOW_CLASS[model.WORKFLOWS[cleaned_data['workflow']]] + workflow = model.workflow_class_from_name(cleaned_data['workflow_name']) application_forms = self.formsets['forms'] self.validate_stages_equal_forms(workflow, application_forms) diff --git a/opentech/apply/funds/migrations/0020_add_workflow_and_status_to_submission.py b/opentech/apply/funds/migrations/0020_add_workflow_and_status_to_submission.py new file mode 100644 index 0000000000000000000000000000000000000000..9d64d2adf2343e766fe5082f49affed161b51708 --- /dev/null +++ b/opentech/apply/funds/migrations/0020_add_workflow_and_status_to_submission.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.8 on 2018-02-14 11:29 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('funds', '0019_protect_submission'), + ] + + operations = [ + migrations.AlterModelOptions( + name='applicationsubmission', + options={}, + ), + migrations.AddField( + model_name='applicationsubmission', + name='status', + field=models.CharField(default='', max_length=254), + preserve_default=False, + ), + migrations.AddField( + model_name='applicationsubmission', + name='workflow', + field=models.CharField(choices=[('single', 'Single Stage'), ('double', 'Two Stage')], default='single', max_length=100), + ), + ] diff --git a/opentech/apply/funds/migrations/0021_rename_workflow_field.py b/opentech/apply/funds/migrations/0021_rename_workflow_field.py new file mode 100644 index 0000000000000000000000000000000000000000..ea9dbb49fecfdd4348d4bcbac03b6ffece25b9b4 --- /dev/null +++ b/opentech/apply/funds/migrations/0021_rename_workflow_field.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.8 on 2018-02-14 11:41 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('funds', '0020_add_workflow_and_status_to_submission'), + ] + + operations = [ + migrations.RenameField( + model_name='applicationsubmission', + old_name='workflow', + new_name='workflow_name', + ), + migrations.RenameField( + model_name='fundtype', + old_name='workflow', + new_name='workflow_name', + ), + migrations.RenameField( + model_name='labtype', + old_name='workflow', + new_name='workflow_name', + ), + migrations.RenameField( + model_name='round', + old_name='workflow', + new_name='workflow_name', + ), + migrations.AlterField( + model_name='applicationsubmission', + name='workflow_name', + field=models.CharField(choices=[('single', 'Single Stage'), ('double', 'Two Stage')], default='single', max_length=100, verbose_name='Workflow'), + ), + migrations.AlterField( + model_name='fundtype', + name='workflow_name', + field=models.CharField(choices=[('single', 'Single Stage'), ('double', 'Two Stage')], default='single', max_length=100, verbose_name='Workflow'), + ), + migrations.AlterField( + model_name='labtype', + name='workflow_name', + field=models.CharField(choices=[('single', 'Single Stage'), ('double', 'Two Stage')], default='single', max_length=100, verbose_name='Workflow'), + ), + migrations.AlterField( + model_name='round', + name='workflow_name', + field=models.CharField(choices=[('single', 'Single Stage'), ('double', 'Two Stage')], default='single', max_length=100, verbose_name='Workflow'), + ), + ] diff --git a/opentech/apply/funds/models.py b/opentech/apply/funds/models.py index 7f115f11779149f3164c3bbc621d68ca28cda2ae..7a1ec0461e3d517d13358d44d33c28a5faf43590 100644 --- a/opentech/apply/funds/models.py +++ b/opentech/apply/funds/models.py @@ -91,9 +91,9 @@ class SubmittableStreamForm(AbstractStreamForm): return kwargs -class WorkflowStreamForm(AbstractStreamForm): +class WorkflowHelpers(models.Model): """ - Defines the common methods and fields for working with Workflows + Defines the common methods and fields for working with Workflows within Django models """ class Meta: abstract = True @@ -103,18 +103,36 @@ class WorkflowStreamForm(AbstractStreamForm): 'double': DoubleStage.name, } - workflow = models.CharField(choices=WORKFLOWS.items(), max_length=100, default='single') + workflow_name = models.CharField(choices=WORKFLOWS.items(), max_length=100, default='single', verbose_name="Workflow") - def get_defined_fields(self): - # Only return the first form, will need updating for when working with 2 stage WF - return self.forms.all()[0].fields + @property + def workflow(self): + # Pretend we have forms associated with the workflow. + # TODDO Confirm if we need forms on the workflow. + return self.workflow_class([None] * len(self.workflow_class.stage_classes)) @property def workflow_class(self): - return WORKFLOW_CLASS[self.get_workflow_display()] + return WORKFLOW_CLASS[self.get_workflow_name_display()] + + @classmethod + def workflow_class_from_name(cls, name): + return WORKFLOW_CLASS[cls.WORKFLOWS[name]] + + +class WorkflowStreamForm(WorkflowHelpers, AbstractStreamForm): # type: ignore + """ + Defines the common methods and fields for working with Workflows within Wagtail pages + """ + class Meta: + abstract = True + + def get_defined_fields(self): + # Only return the first form, will need updating for when working with 2 stage WF + return self.forms.all()[0].fields content_panels = AbstractStreamForm.content_panels + [ - FieldPanel('workflow'), + FieldPanel('workflow_name'), InlinePanel('forms', label="Forms"), ] @@ -270,7 +288,7 @@ class Round(WorkflowStreamForm, SubmittableStreamForm): # type: ignore FieldPanel('end_date'), ]), ], heading="Dates"), - ReadOnlyPanel('get_workflow_display', heading="Workflow"), + ReadOnlyPanel('get_workflow_name_display', heading="Workflow"), ReadOnlyInlinePanel('forms', help_text="Are copied from the parent fund."), ] @@ -283,13 +301,13 @@ class Round(WorkflowStreamForm, SubmittableStreamForm): # type: ignore super().__init__(*args, **kwargs) # We attached the parent page as part of the before_create_hook if hasattr(self, 'parent_page'): - self.workflow = self.parent_page.workflow + self.workflow_name = self.parent_page.workflow_name def save(self, *args, **kwargs): is_new = not self.id if is_new and hasattr(self, 'parent_page'): # Ensure that the workflow hasn't changed - self.workflow = self.parent_page.workflow + self.workflow_name = self.parent_page.workflow_name super().save(*args, **kwargs) @@ -420,14 +438,41 @@ class JSONOrderable(models.QuerySet): return super().order_by(*field_ordering) -class ApplicationSubmission(AbstractFormSubmission): +class ApplicationSubmission(WorkflowHelpers, AbstractFormSubmission): form_data = JSONField(encoder=DjangoJSONEncoder) page = models.ForeignKey('wagtailcore.Page', on_delete=models.PROTECT) round = models.ForeignKey('wagtailcore.Page', on_delete=models.PROTECT, related_name='submissions', null=True) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True) + # Workflow inherited from WorkflowHelpers + status = models.CharField(max_length=254) + objects = JSONOrderable.as_manager() + @property + def status_name(self): + return self.phase.name + + @property + def stage(self): + return self.phase.stage + + @property + def phase(self): + return self.workflow.current(self.status) + + def save(self, *args, **kwargs): + if not self.id: + # We are creating the object default to first stage + try: + self.workflow_name = self.round.workflow_name + except AttributeError: + # We are a lab submission + self.workflow_name = self.page.workflow_name + self.status = str(self.workflow.first()) + + return super().save(*args, **kwargs) + def get_data(self): # Updated for JSONField form_data = self.form_data @@ -441,7 +486,7 @@ class ApplicationSubmission(AbstractFormSubmission): # fall back to values defined on the data if item in REQUIRED_BLOCK_NAMES: return self.get_data()[item] - return super().__getattr__(item) + raise AttributeError('{} has no attribute "{}"'.format(repr(self), item)) def __str__(self): return str(super().__str__()) diff --git a/opentech/apply/funds/tests/factories/models.py b/opentech/apply/funds/tests/factories/models.py index c42a9cc41d2bd2790b469b473b96c6c3ad2649a3..116b07094810dbda0b7bf52cc8cf1aff315337c1 100644 --- a/opentech/apply/funds/tests/factories/models.py +++ b/opentech/apply/funds/tests/factories/models.py @@ -36,7 +36,7 @@ class FundTypeFactory(wagtail_factories.PageFactory): workflow_stages = 1 # Will need to update how the stages are identified as Fund Page changes - workflow = factory.LazyAttribute(lambda o: list(FundType.WORKFLOWS.keys())[o.workflow_stages - 1]) + workflow_name = factory.LazyAttribute(lambda o: list(FundType.WORKFLOWS.keys())[o.workflow_stages - 1]) @factory.post_generation def forms(self, create, extracted, **kwargs): @@ -99,7 +99,7 @@ class LabFactory(wagtail_factories.PageFactory): number_forms = 1 # Will need to update how the stages are identified as Fund Page changes - workflow = factory.LazyAttribute(lambda o: list(FundType.WORKFLOWS.keys())[o.workflow_stages - 1]) + workflow_name = factory.LazyAttribute(lambda o: list(FundType.WORKFLOWS.keys())[o.workflow_stages - 1]) class LabFormFactory(AbstractRelatedFormFactory): diff --git a/opentech/apply/funds/tests/test_models.py b/opentech/apply/funds/tests/test_models.py index 993171c232219ac702825e4bedf92bd4f83d5b8f..86c173891275de97020ffb14b3543f80f4fb73e3 100644 --- a/opentech/apply/funds/tests/test_models.py +++ b/opentech/apply/funds/tests/test_models.py @@ -32,7 +32,7 @@ class TestFundModel(TestCase): self.fund = FundTypeFactory(parent=None) def test_can_access_workflow_class(self): - self.assertEqual(self.fund.workflow, 'single') + self.assertEqual(self.fund.workflow_name, 'single') self.assertEqual(self.fund.workflow_class, SingleStage) def test_no_open_rounds(self): @@ -152,7 +152,7 @@ class TestRoundModelWorkflowAndForms(TestCase): def test_workflow_is_copied_to_new_rounds(self): self.round.save() - self.assertEqual(self.round.workflow, self.fund.workflow) + self.assertEqual(self.round.workflow_name, self.fund.workflow_name) def test_forms_are_copied_to_new_rounds(self): self.round.save() @@ -216,6 +216,22 @@ class TestFormSubmission(TestCase): except AttributeError: return page.serve(request) + def test_workflow_and_status_assigned(self): + self.submit_form() + submission = ApplicationSubmission.objects.first() + first_phase = self.round_page.workflow.first() + self.assertEqual(submission.workflow_name, self.round_page.workflow_name) + self.assertEqual(submission.status, str(first_phase)) + self.assertEqual(submission.status_name, first_phase.name) + + def test_workflow_and_status_assigned_lab(self): + self.submit_form(page=self.lab_page) + submission = ApplicationSubmission.objects.first() + first_phase = self.lab_page.workflow.first() + self.assertEqual(submission.workflow_name, self.lab_page.workflow_name) + self.assertEqual(submission.status, str(first_phase)) + self.assertEqual(submission.status_name, first_phase.name) + def test_can_submit_if_new(self): self.submit_form()