From 283994c615e235281086d582755afa4d429ec735 Mon Sep 17 00:00:00 2001
From: Todd Dembrey <todd.dembrey@torchbox.com>
Date: Mon, 18 Dec 2017 16:40:48 +0000
Subject: [PATCH] Defer the addition of forms to the workflow within the view

---
 opentech/apply/tests/factories.py     | 30 +++++++++++++++++++++++++--
 opentech/apply/tests/test_workflow.py | 14 +++++++------
 opentech/apply/views.py               | 10 ++++++---
 opentech/apply/workflow.py            | 23 ++++++++++++++------
 4 files changed, 60 insertions(+), 17 deletions(-)

diff --git a/opentech/apply/tests/factories.py b/opentech/apply/tests/factories.py
index bd237ca95..cbaf9722b 100644
--- a/opentech/apply/tests/factories.py
+++ b/opentech/apply/tests/factories.py
@@ -49,6 +49,12 @@ class PhaseFactory(factory.Factory):
         new_class = type(model_class.__name__, (model_class,), {'actions': actions})
         return new_class(*args, **kwargs)
 
+    @classmethod
+    def _build(cls, model_class, *args, **kwargs):
+        # defer to create because parent uses build
+        return cls._create(model_class, *args, **kwargs)
+
+
 class StageFactory(factory.Factory):
     class Meta:
         model = Stage
@@ -63,18 +69,38 @@ class StageFactory(factory.Factory):
 
     @classmethod
     def _create(cls, model_class, *args, **kwargs):
+        # Returns a new class
+        phases = kwargs.pop('phases')
+        name = kwargs.pop('name')
+        return type(model_class.__name__, (model_class,), {'phases': phases, 'name': name})
+
+    @classmethod
+    def _build(cls, model_class, *args, **kwargs):
+        # returns an instance of the stage class
         phases = kwargs.pop('phases')
-        new_class = type(model_class.__name__, (model_class,), {'phases': phases})
+        name = kwargs.pop('name')
+        new_class = type(model_class.__name__, (model_class,), {'phases': phases, 'name': name})
         return new_class(*args, **kwargs)
 
 
 class WorkflowFactory(factory.Factory):
     class Meta:
         model = Workflow
-        inline_args = ('name', 'stages',)
+        rename = {'stages': 'stage_classes'}
 
     class Params:
         num_stages = factory.Faker('random_int', min=1, max=3)
 
     name = factory.Faker('word')
     stages = ListSubFactory(StageFactory, count=factory.SelfAttribute('num_stages'))
+
+    @factory.LazyAttribute
+    def forms(self):
+        return [Form() for _ in range(self.num_stages)]
+
+    @classmethod
+    def _create(cls, model_class, *args, **kwargs):
+        name = kwargs.pop('name')
+        stages = kwargs.pop('stage_classes')
+        new_class = type(model_class.__name__, (model_class,), {'name': name, 'stage_classes': stages})
+        return new_class(*args, **kwargs)
diff --git a/opentech/apply/tests/test_workflow.py b/opentech/apply/tests/test_workflow.py
index 5770b0d66..54b5caa6e 100644
--- a/opentech/apply/tests/test_workflow.py
+++ b/opentech/apply/tests/test_workflow.py
@@ -8,11 +8,13 @@ from .factories import ActionFactory, PhaseFactory, StageFactory, WorkflowFactor
 
 class TestWorkflowCreation(SimpleTestCase):
     def test_can_create_workflow(self):
-        name = 'single_stage'
         stage = StageFactory()
-        workflow = Workflow(name, [stage])
-        self.assertEqual(workflow.name, name)
-        self.assertCountEqual(workflow.stages, [stage])
+        class NewWorkflow(Workflow):
+            name = 'single_stage'
+            stage_classes = [stage]
+        workflow = NewWorkflow([Form()])
+        self.assertEqual(workflow.name, NewWorkflow.name)
+        self.assertEqual(len(workflow.stages), 1)
 
     def test_returns_first_phase_if_no_arg(self):
         workflow = WorkflowFactory(num_stages=1, stages__num_phases=1)
@@ -49,11 +51,11 @@ class TestStageCreation(SimpleTestCase):
         self.assertEqual(stage.form, form)
 
     def test_can_get_next_phase(self):
-        stage = StageFactory(num_phases=2)
+        stage = StageFactory.build(num_phases=2)
         self.assertEqual(stage.next(stage.phases[0]), stage.phases[1])
 
     def test_get_none_if_no_next_phase(self):
-        stage = StageFactory(num_phases=1)
+        stage = StageFactory.build(num_phases=1)
         self.assertEqual(stage.next(stage.phases[0]), None)
 
 
diff --git a/opentech/apply/views.py b/opentech/apply/views.py
index 8d8d5097c..a2b1c043a 100644
--- a/opentech/apply/views.py
+++ b/opentech/apply/views.py
@@ -1,14 +1,18 @@
+from django.forms import Form
 from django.shortcuts import render
 from django.template.response import TemplateResponse
 
-from .workflow import single_stage, two_stage
+from .workflow import SingleStage, DoubleStage
 
 
-workflows = [single_stage, two_stage]
+workflows = [SingleStage, DoubleStage]
 
 
 def demo_workflow(request, wf_id):
-    workflow = workflows[int(wf_id)-1]
+    wf = int(wf_id)
+    workflow_class = workflows[wf-1]
+    workflow = workflow_class([Form()] * wf)
+
     current_phase = request.POST.get('current')
     if request.POST:
         phase = workflow.process(current_phase, request.POST['action'])
diff --git a/opentech/apply/workflow.py b/opentech/apply/workflow.py
index c7ff5b158..7ab64787b 100644
--- a/opentech/apply/workflow.py
+++ b/opentech/apply/workflow.py
@@ -1,15 +1,20 @@
 import copy
 
-from typing import Dict, Iterator, Iterable, List, Sequence, Tuple, Union
+from typing import List, Sequence, Type, Union
 
 from django.forms import Form
 from django.utils.text import slugify
 
 
 class Workflow:
-    def __init__(self, name: str, stages: Sequence['Stage']) -> None:
-        self.name = name
-        self.stages = stages
+    name: str = ''
+    stage_classes: Sequence[Type['Stage']] = list()
+
+    def __init__(self, forms: Sequence[Form]) -> None:
+        if len(self.stage_classes) != len(forms):
+            raise ValueError('Number of forms does not equal the number of stages')
+
+        self.stages = [stage(form) for stage, form in zip(self.stage_classes, forms)]
 
     def current(self, current_phase: Union[str, 'Phase']) -> Union['Phase', None]:
         if isinstance(current_phase, Phase):
@@ -230,6 +235,12 @@ class ProposalStage(Stage):
         rejected,
     ]
 
-single_stage = Workflow('Single Stage', [ConceptStage(Form())])
 
-two_stage = Workflow('Two Stage', [ConceptStage(Form()), ProposalStage(Form())])
+class SingleStage(Workflow):
+    name = 'Single Stage'
+    stage_classes = [ConceptStage]
+
+
+class DoubleStage(Workflow):
+    name = 'Two Stage'
+    stage_classes = [ConceptStage, ProposalStage]
-- 
GitLab