from typing import Dict, Iterator, Iterable, List, Sequence, Tuple, Union from django.forms import Form class Workflow: def __init__(self, name: str, stages: Sequence['Stage']) -> None: self.name = name self.stages = stages def current(self, current_phase: Union[str, 'Phase']) -> Union['Phase', None]: if isinstance(current_phase, Phase): return current_phase if not current_phase: return self.first() stage_name, phase_name, _ = current_phase.split('__') for stage in self.stages: if stage.name == stage_name: return stage.current(phase_name) return None def first(self) -> 'Phase': return self.stages[0].next() def process(self, current_phase: str, action: str) -> Union['Phase', None]: phase = self.current(current_phase) new_phase = phase.process(action) if not new_phase: new_stage = self.next_stage(phase.stage) return new_stage.first() return new_phase def next_stage(self, current_stage: 'Stage') -> 'Stage': for i, stage in enumerate(self.stages): if stage == current_stage: try: return self.stages[i+1] except IndexError: pass return None def next(self, current_phase: Union[str, 'Phase']=None) -> Union['Phase', None]: if not current_phase: return self.first() phase = self.current(current_phase) for stage in self.stages: if stage == phase.stage: next_phase = stage.next(phase) if not next_phase: continue return next_phase next_stage = self.next_stage(phase.stage) if next_stage: return stage.next() return None def __str__(self) -> str: return self.name class Stage: def __init__(self, name: str, form: Form, phases: Sequence['Phase'], current_phase: Union['Phase', None]=None) -> None: self.name = name self.form = form for phase in phases: phase.stage = self self.phases = phases def __str__(self) -> str: return self.name def current(self, phase_name: str) -> 'Phase': for phase in self.phases: if phase.name == phase_name: return phase return None def first(self) -> 'Phase': return self.phases[0] def next(self, current_phase: 'Phase'=None) -> 'Phase': if not current_phase: return self.first() for i, phase in enumerate(self.phases): if phase == current_phase: try: return self.phases[i+1] except IndexError: pass return None class Phase: actions: Sequence['Action'] = list() def __init__(self, name: str) -> None: self.name = name self.stage: Union['Stage', None] = None self._actions = {action.name: action for action in self.actions} self.occurance: int = 0 @property def action_names(self) -> List[str]: return list(self._actions.keys()) def __str__(self) -> str: return '__'.join([self.stage.name, self.name, str(self.occurance)]) def __getitem__(self, value: str) -> 'Action': return self._actions[value] def process(self, action: str) -> Union['Phase', None]: return self[action]() class Action: def __init__(self, name: str) -> None: self.name = name def __call__(self) -> Union['Phase', None]: return self.process() def process(self) -> Union['Phase', None]: # Use this to define the behaviour of the action raise NotImplementedError # --- OTF Workflow --- class ChangePhaseAction(Action): def __init__(self, phase: Union['Phase', str], *args: str, **kwargs: str) -> None: self.target_phase = phase super().__init__(*args, **kwargs) def process(self) -> Union['Phase', None]: if isinstance(self.target_phase, str): phase = globals()[self.target_phase] else: phase = self.target_phase return phase reject_action = ChangePhaseAction('rejected', 'Reject') accept_action = ChangePhaseAction('accepted', 'Accept') progress_external = ChangePhaseAction('external_review', 'Progress') progress_stage = ChangePhaseAction(None, 'Progress Stage') class ReviewPhase(Phase): actions = [progress_stage, reject_action] class ProposalReviewPhase(Phase): actions = [progress_external, reject_action] class FinalReviewPhase(Phase): actions = [accept_action, reject_action] concept_review = ReviewPhase('Internal Review') proposal_review = ProposalReviewPhase('Internal Review') external_review = FinalReviewPhase('AC Review') rejected = Phase('Rejected') accepted = Phase('Accepted') concept_note = Stage('Concept', Form(), [concept_review, accepted, rejected]) proposal = Stage('Proposal', Form(), [proposal_review, external_review, accepted, rejected]) single_stage = Workflow('Single Stage', [proposal]) two_stage = Workflow('Two Stage', [concept_note, proposal])