diff --git a/opentech/apply/dashboard/templates/dashboard/applicant_dashboard.html b/opentech/apply/dashboard/templates/dashboard/applicant_dashboard.html
index 5093f482c4cfb93f329f3c5c865521eb6f848aef..2a9ad9a7cb3fc84adcf0b340912cae36d681b3c7 100644
--- a/opentech/apply/dashboard/templates/dashboard/applicant_dashboard.html
+++ b/opentech/apply/dashboard/templates/dashboard/applicant_dashboard.html
@@ -26,7 +26,7 @@
                 <h5 class="heading heading--no-margin"><a class="link link--underlined" href="{% url 'funds:submission' submission.id %}">{{ submission.title }}</a></h5>
                 <h6 class="heading heading--no-margin heading--submission-meta"><span>Submitted:</span> {{ submission.submit_time.date }} by {{ submission.user.get_full_name }}</h6>
             </div>
-            {% include "funds/includes/status_bar.html" with phases=submission.workflow status=submission.phase class="status-bar--small" %}
+            {% include "funds/includes/status_bar.html" with phases=submission.workflow status=submission.status current_phase=submission.phase class="status-bar--small" %}
             {% if request.user|has_edit_perm:submission %}
                 <a class="button button--primary" href="{% url 'funds:edit_submission' submission.id %}">Start your {{ submission.stage }} application</a>
             {% endif %}
diff --git a/opentech/apply/funds/forms.py b/opentech/apply/funds/forms.py
index dcbf9371aabc0277c8c079e5520a8e9a2c3e932e..91383a729f4625820c1b2d74f664e7f65043773e 100644
--- a/opentech/apply/funds/forms.py
+++ b/opentech/apply/funds/forms.py
@@ -16,14 +16,14 @@ class ProgressSubmissionForm(forms.ModelForm):
     def __init__(self, *args, **kwargs):
         kwargs.pop('user')
         super().__init__(*args, **kwargs)
-        choices = [(action, action) for action in self.instance.phase.action_names]
+        choices = [(name, action) for name, action in self.instance.phase.get('transitions', {}).items()]
         action_field = self.fields['action']
         action_field.choices = choices
         self.should_show = bool(choices)
 
     def save(self, *args, **kwargs):
-        new_phase = self.instance.workflow.process(self.instance.phase, self.cleaned_data['action'])
-        self.instance.status = str(new_phase)
+        transition = getattr(self.instance, self.cleaned_data['action'])
+        transition(self.instance)
         return super().save(*args, **kwargs)
 
 
diff --git a/opentech/apply/funds/migrations/0033_use_django_fsm.py b/opentech/apply/funds/migrations/0033_use_django_fsm.py
new file mode 100644
index 0000000000000000000000000000000000000000..b27a4eb6d86742c014f2ba0d6e2c970032154426
--- /dev/null
+++ b/opentech/apply/funds/migrations/0033_use_django_fsm.py
@@ -0,0 +1,39 @@
+# Generated by Django 2.0.2 on 2018-06-11 16:14
+
+from django.db import migrations, models
+import django_fsm
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('funds', '0032_make_reviewers_optional_in_all_instances'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='applicationsubmission',
+            name='status',
+            field=django_fsm.FSMField(default='in_discussion', max_length=50, protected=True),
+        ),
+        migrations.AlterField(
+            model_name='applicationsubmission',
+            name='workflow_name',
+            field=models.CharField(choices=[('single', 'Request'), ('double', 'Concept & Proposal')], default='single', max_length=100, verbose_name='Workflow'),
+        ),
+        migrations.AlterField(
+            model_name='fundtype',
+            name='workflow_name',
+            field=models.CharField(choices=[('single', 'Request'), ('double', 'Concept & Proposal')], default='single', max_length=100, verbose_name='Workflow'),
+        ),
+        migrations.AlterField(
+            model_name='labtype',
+            name='workflow_name',
+            field=models.CharField(choices=[('single', 'Request'), ('double', 'Concept & Proposal')], default='single', max_length=100, verbose_name='Workflow'),
+        ),
+        migrations.AlterField(
+            model_name='round',
+            name='workflow_name',
+            field=models.CharField(choices=[('single', 'Request'), ('double', 'Concept & Proposal')], default='single', max_length=100, verbose_name='Workflow'),
+        ),
+    ]
diff --git a/opentech/apply/funds/models.py b/opentech/apply/funds/models.py
index 0a8076d6e40e2610edb262138aef31568753db76..30db10a27e0cffb3cae58912afd784f3341ce42d 100644
--- a/opentech/apply/funds/models.py
+++ b/opentech/apply/funds/models.py
@@ -16,6 +16,7 @@ from django.urls import reverse
 from django.utils.text import mark_safe
 from django.utils.translation import ugettext_lazy as _
 
+from django_fsm import FSMField, transition
 from modelcluster.fields import ParentalKey, ParentalManyToManyField
 from wagtail.admin.edit_handlers import (
     FieldPanel,
@@ -39,12 +40,12 @@ from opentech.apply.users.groups import REVIEWER_GROUP_NAME, STAFF_GROUP_NAME
 from .admin_forms import WorkflowFormAdminForm
 from .blocks import CustomFormFieldsBlock, MustIncludeFieldBlock, REQUIRED_BLOCK_NAMES
 from .edit_handlers import FilteredFieldPanel, ReadOnlyPanel, ReadOnlyInlinePanel
-from .workflow import SingleStage, DoubleStage, active_statuses, get_review_statuses, review_statuses
+from .workflow import SingleStage, DoubleStage, active_statuses, get_review_statuses, review_statuses, INITAL_STATE
 
 
 WORKFLOW_CLASS = {
-    SingleStage.name: SingleStage,
-    DoubleStage.name: DoubleStage,
+    'Request': SingleStage,
+    'Concept & Proposal': DoubleStage,
 }
 
 
@@ -88,15 +89,15 @@ class WorkflowHelpers(models.Model):
         abstract = True
 
     WORKFLOWS = {
-        'single': SingleStage.name,
-        'double': DoubleStage.name,
+        'single': 'Request',
+        'double': 'Concept & Proposal',
     }
 
     workflow_name = models.CharField(choices=WORKFLOWS.items(), max_length=100, default='single', verbose_name="Workflow")
 
     @property
     def workflow(self):
-        return self.workflow_class()
+        return self.workflow_class
 
     @property
     def workflow_class(self):
@@ -514,6 +515,18 @@ class ApplicationSubmissionQueryset(JSONOrderable):
 
 
 class ApplicationSubmission(WorkflowHelpers, BaseStreamForm, AbstractFormSubmission):
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        fsm_field = self._meta.get_field('status')
+        for transition_name in self.phase['transitions'].keys():
+            def transition_state(self):
+                # TODO include state change methods
+                pass
+            transition_func = transition(fsm_field, source=self.status, target=transition_name)(transition_state)
+
+            setattr(self, transition_name, transition_func)
+
     field_template = 'funds/includes/submission_field.html'
 
     form_data = JSONField(encoder=DjangoJSONEncoder)
@@ -537,7 +550,7 @@ class ApplicationSubmission(WorkflowHelpers, BaseStreamForm, AbstractFormSubmiss
     search_data = models.TextField()
 
     # Workflow inherited from WorkflowHelpers
-    status = models.CharField(max_length=254)
+    status = FSMField(default=INITAL_STATE, protected=True)
 
     # Meta: used for migration purposes only
     drupal_id = models.IntegerField(null=True, blank=True, editable=False)
@@ -546,19 +559,19 @@ class ApplicationSubmission(WorkflowHelpers, BaseStreamForm, AbstractFormSubmiss
 
     @property
     def status_name(self):
-        return self.phase.name
+        return self.status
 
     @property
     def stage(self):
-        return self.phase.stage
+        return self.phase['stage']
 
     @property
     def phase(self):
-        return self.workflow.current(self.status)
+        return self.workflow.get(self.status) or self.workflow.get(list(self.workflow.keys())[0])
 
     @property
     def active(self):
-        return self.phase.active
+        return True
 
     def ensure_user_has_account(self):
         if self.user and self.user.is_authenticated:
@@ -648,17 +661,17 @@ class ApplicationSubmission(WorkflowHelpers, BaseStreamForm, AbstractFormSubmiss
             self.reviewers.set(self.get_from_parent('reviewers').all())
 
         # Check to see if we should progress to the next stage
-        if self.phase.can_proceed and not self.next:
-            submission_in_db = ApplicationSubmission.objects.get(id=self.id)
+        # if self.phase.can_proceed and not self.next:
+        #     submission_in_db = ApplicationSubmission.objects.get(id=self.id)
 
-            self.id = None
-            self.status = str(self.workflow.next(self.status))
-            self.form_fields = self.get_from_parent('get_defined_fields')(self.stage)
+        #     self.id = None
+        #     self.status = str(self.workflow.next(self.status))
+        #     self.form_fields = self.get_from_parent('get_defined_fields')(self.stage)
 
-            super().save(*args, **kwargs)
+        #     super().save(*args, **kwargs)
 
-            submission_in_db.next = self
-            submission_in_db.save()
+        #     submission_in_db.next = self
+        #     submission_in_db.save()
 
     @property
     def missing_reviewers(self):
diff --git a/opentech/apply/funds/tables.py b/opentech/apply/funds/tables.py
index 7269eb3b333b7e8cf5bbeafd171a2a1c74a71dd9..55081847434c5ece54c107ac1b8772a730f7741e 100644
--- a/opentech/apply/funds/tables.py
+++ b/opentech/apply/funds/tables.py
@@ -27,7 +27,7 @@ class SubmissionsTable(tables.Table):
     title = tables.LinkColumn('funds:submission', args=[A('pk')], orderable=True)
     submit_time = tables.DateColumn(verbose_name="Submitted")
     status_name = tables.Column(verbose_name="Status")
-    stage = tables.Column(verbose_name="Type", order_by=('status',))
+    stage = tables.Column(verbose_name="Type", accessor='stage.name', order_by=('status',))
     page = tables.Column(verbose_name="Fund")
     comments = tables.Column(accessor='activities.comments.all', verbose_name="Comments")
     last_update = tables.DateColumn(accessor="activities.last.timestamp", verbose_name="Last updated")
diff --git a/opentech/apply/funds/templates/funds/applicationsubmission_detail.html b/opentech/apply/funds/templates/funds/applicationsubmission_detail.html
index 0dbf3bcd39338c07e65f0709bf14d53d4b1b199c..421b7cb2ba83c9fc0149e122f56f01b80aecd42d 100644
--- a/opentech/apply/funds/templates/funds/applicationsubmission_detail.html
+++ b/opentech/apply/funds/templates/funds/applicationsubmission_detail.html
@@ -7,12 +7,12 @@
     <div class="wrapper wrapper--medium">
         <h2 class="heading heading--no-margin">{{ object.title }}</h2>
         <h5 class="heading heading--meta">
-            <span>{{ object.stage }}</span>
+            <span>{{ object.stage.name }}</span>
             <span>{{ object.page }}</span>
             <span>{{ object.round }}</span>
             <span>Lead: {{ object.lead }}</span>
         </h5>
-        {% include "funds/includes/status_bar.html" with phases=object.phase.stage status=object.phase %}
+        {% include "funds/includes/status_bar.html" with phases=object.workflow status=object.status current_phase=object.phase %}
 
         <div class="tabs js-tabs">
             <div class="tabs__container">
diff --git a/opentech/apply/funds/templates/funds/includes/status_bar.html b/opentech/apply/funds/templates/funds/includes/status_bar.html
index dfbf334700749dd847447c0e38c3c5cc795eeb6d..e24b9661d21d77e39454a912e1edd8bcce555564 100644
--- a/opentech/apply/funds/templates/funds/includes/status_bar.html
+++ b/opentech/apply/funds/templates/funds/includes/status_bar.html
@@ -1,27 +1,18 @@
 <div class="status-bar {{ class }}">
-    {% for phase in phases %}
+    {% for phase_name, phase in phases.items %}
         <div class="status-bar__item
-                    {% if phase == status %}
+                    {% if phase_name == status %}
                         status-bar__item--is-current
-                    {% elif phase < status %}
+                    {% elif current_phase.step > phase.step %}
                         status-bar__item--is-complete
                     {% endif %}">
             <span class="status-bar__tooltip"
-                {% if phase == status %}
-                    {# We want to display the status explicitly in case phase is a MultiStep (displays "Outcome" for name) #}
-                    data-title="{{ status.name }}" aria-label="{{ status.name }}"
-                {% else %}
-                    data-title="{{ phase.name }}" aria-label="{{ phase.name }}"
-                {% endif %}
+                data-title="{{ phase.display }}" aria-label="{{ phase.display }}"
             ></span>
             <svg class="status-bar__icon"><use xlink:href="#tick-alt"></use></svg>
         </div>
     {% endfor %}
 </div>
 <div class="status-bar--mobile">
-    {% for phase in status.stage %}
-        {% if phase == status %}
-            <h6 class="status-bar__subheading">{{ status.name }}</h6>
-        {% endif %}
-    {% endfor %}
+    <h6 class="status-bar__subheading">{{ current_phase.display }}</h6>
 </div>
diff --git a/opentech/apply/funds/templatetags/workflow_tags.py b/opentech/apply/funds/templatetags/workflow_tags.py
index 87adb9ad8d8edc74fa0e3766e09d03adbd3b7505..224f0ae697c673da4b140925bb9e1d3d14619020 100644
--- a/opentech/apply/funds/templatetags/workflow_tags.py
+++ b/opentech/apply/funds/templatetags/workflow_tags.py
@@ -4,7 +4,8 @@ register = template.Library()
 
 
 def check_permission(user, perm, submission):
-    return submission.phase.has_perm(user, perm)
+    perm_method = getattr(submission.phase['permissions'], f'can_{perm}', lambda x: False)
+    return perm_method(user)
 
 
 @register.filter
diff --git a/opentech/apply/funds/views.py b/opentech/apply/funds/views.py
index 129b3a978845601cdb37a36fd86ac96ceb2c45b1..932b6f8dd8310f8ea8eea9f96190282fc4da6041 100644
--- a/opentech/apply/funds/views.py
+++ b/opentech/apply/funds/views.py
@@ -72,9 +72,9 @@ class ProgressSubmissionView(DelegatedViewMixin, UpdateView):
     context_name = 'progress_form'
 
     def form_valid(self, form):
-        old_phase = form.instance.phase.name
+        old_phase = form.instance.phase['display']
         response = super().form_valid(form)
-        new_phase = form.instance.phase.name
+        new_phase = form.instance.phase['display']
         Activity.actions.create(
             user=self.request.user,
             submission=self.kwargs['submission'],
diff --git a/opentech/apply/funds/workflow.py b/opentech/apply/funds/workflow.py
index 89232f813f037c9d28838b12859c73bd40b1647f..81f8d8dc6d6fcea92abe9fa036e6234d40b53c80 100644
--- a/opentech/apply/funds/workflow.py
+++ b/opentech/apply/funds/workflow.py
@@ -1,4 +1,4 @@
-from collections import defaultdict
+from collections import defaultdict, namedtuple
 import copy
 import itertools
 
@@ -10,6 +10,102 @@ if TYPE_CHECKING:
     from opentech.apply.users.models import User  # NOQA
 
 
+class Permission:
+    def can_edit(self, user: 'User') -> bool:
+        return False
+
+    def can_staff_review(self, user: 'User') -> bool:
+        return False
+
+    def can_reviewer_review(self, user: 'User') -> bool:
+        return False
+
+    def can_review(self, user: 'User') -> bool:
+        return self.can_staff_review(user) or self.can_reviewer_review(user)
+
+
+class StaffReviewPermission(Permission):
+    def can_staff_review(self, user: 'User') -> bool:
+        return user.is_apply_staff
+
+
+class ReviewerReviewPermission(Permission):
+    def can_reviewer_review(self, user: 'User') -> bool:
+        return user.is_reviewer
+
+
+class CanEditPermission(Permission):
+    def can_edit(self, user: 'User') -> bool:
+        return True
+
+
+
+Stage = namedtuple('Stage', ['name', 'has_external_review'])
+
+Request = Stage('Request', False)
+
+INITAL_STATE = 'in_discussion'
+
+SingleStage = {
+    'in_discussion' : {
+        'transitions': {
+            'internal_review' : 'Open Review',
+            'rejected' : 'Reject',
+        },
+        'display': 'Under Discussion',
+        'stage': Request,
+        'permissions': Permission(),
+        'step': 0,
+    },
+    'internal_review' : {
+        'transitions': {
+            'in_discussion_2' : 'Close Review',
+        },
+        'display': 'Internal Review',
+        'stage': Request,
+        'permissions': StaffReviewPermission(),
+        'step': 1,
+    },
+    'post_review_discussion': {
+        'transitions': {
+            'accepted': 'Accept',
+            'rejected': 'Reject',
+        },
+        'display': 'Under Discussion',
+        'stage': Request,
+        'permissions': Permission(),
+        'step': 2,
+    },
+    'accepted': {
+        'display': 'Accepted',
+        'stage': Request,
+        'permissions': Permission(),
+        'step': 3,
+    },
+    'rejected': {
+        'display': 'Rejected',
+        'stage': Request,
+        'permissions': Permission(),
+        'step': 3,
+    },
+}
+
+DoubleStage = {
+    'in_discussion' : {
+        'transitions': {
+            'internal_review' : 'Open Review',
+            'rejected' : 'Reject',
+        },
+        'display': 'Under Discussion',
+        'stage': Request,
+        'permissions': Permission(),
+        'step': 0,
+    },
+}
+
+
+status_options = [(key, value['display']) for key, value in SingleStage.items()]
+
 """
 This file defines classes which allow you to compose workflows based on the following structure:
 
@@ -240,35 +336,6 @@ class PhaseIterator(Iterator):
         return self.Step(self.phases[self.current - 1])
 
 
-class Permission:
-    def can_edit(self, user: 'User') -> bool:
-        return False
-
-    def can_staff_review(self, user: 'User') -> bool:
-        return False
-
-    def can_reviewer_review(self, user: 'User') -> bool:
-        return False
-
-    def can_review(self, user: 'User') -> bool:
-        return self.can_staff_review(user) or self.can_reviewer_review(user)
-
-
-class StaffReviewPermission(Permission):
-    def can_staff_review(self, user: 'User') -> bool:
-        return user.is_apply_staff
-
-
-class ReviewerReviewPermission(Permission):
-    def can_reviewer_review(self, user: 'User') -> bool:
-        return user.is_reviewer
-
-
-class CanEditPermission(Permission):
-    def can_edit(self, user: 'User') -> bool:
-        return True
-
-
 class Phase:
     """
     Holds the Actions which a user can perform at each stage. A Phase with no actions is
@@ -450,53 +517,54 @@ class ProposalStage(Stage):
     ]
 
 
-class SingleStage(Workflow):
-    name = 'Single Stage'
-    stage_classes = [RequestStage]
+# class SingleStage(Workflow):
+#     name = 'Single Stage'
+#     stage_classes = [RequestStage]
 
 
-class DoubleStage(Workflow):
-    name = 'Two Stage'
-    stage_classes = [ConceptStage, ProposalStage]
+# class DoubleStage(Workflow):
+#     name = 'Two Stage'
+#     stage_classes = [ConceptStage, ProposalStage]
 
 
-statuses = set(phase.name for phase in Phase.__subclasses__())
-status_options = [(slugify(opt), opt) for opt in statuses]
+# statuses = set(phase.name for phase in Phase.__subclasses__())
+# status_options = [(slugify(opt), opt) for opt in statuses]
 
 
-def get_active_statuses() -> Set[str]:
-    active = set()
+# def get_active_statuses() -> Set[str]:
+#     active = set()
 
-    def add_if_active(phase: 'Phase') -> None:
-        if phase.active:
-            active.add(str(phase))
+#     def add_if_active(phase: 'Phase') -> None:
+#         if phase.active:
+#             active.add(str(phase))
 
-    for phase in itertools.chain(SingleStage(), DoubleStage()):
-        try:
-            add_if_active(phase)
-        except AttributeError:
-            # it is actually a step
-            step = phase
-            for phase in step.phases:
-                add_if_active(phase)
-    return active
+#     for phase in itertools.chain(SingleStage(), DoubleStage()):
+#         try:
+#             add_if_active(phase)
+#         except AttributeError:
+#             # it is actually a step
+#             step = phase
+#             for phase in step.phases:
+#                 add_if_active(phase)
+#     return active
 
 
-active_statuses = get_active_statuses()
+active_statuses = [] #get_active_statuses()
 
 
 def get_review_statuses(user: Union[None, 'User']=None) -> Set[str]:
-    reviews = set()
+    return []
+#     reviews = set()
 
-    for step in itertools.chain(SingleStage(), DoubleStage()):
-        for phase in step.phases:
-            if isinstance(phase, ReviewPhase):
-                if user is None:
-                    reviews.add(str(phase))
-                elif phase.has_perm(user, 'review'):
-                    reviews.add(str(phase))
+#     for step in itertools.chain(SingleStage(), DoubleStage()):
+#         for phase in step.phases:
+#             if isinstance(phase, ReviewPhase):
+#                 if user is None:
+#                     reviews.add(str(phase))
+#                 elif phase.has_perm(user, 'review'):
+#                     reviews.add(str(phase))
 
-    return reviews
+#     return reviews
 
 
-review_statuses = get_review_statuses()
+review_statuses = [] #get_review_statuses()
diff --git a/opentech/settings/base.py b/opentech/settings/base.py
index f1616a9236d877f782fc67e165304051f98a6d8d..2b69ec1644edc17b5d265d91e0adfd4edf9283d8 100644
--- a/opentech/settings/base.py
+++ b/opentech/settings/base.py
@@ -61,6 +61,7 @@ INSTALLED_APPS = [
     'django_select2',
     'addressfield',
     'django_bleach',
+    'django_fsm',
 
     'django.contrib.admin',
     'django.contrib.auth',
diff --git a/requirements.txt b/requirements.txt
index 75dc42283b3326f9ef9e65b9c245a6eafd102372..ae8d0744c6275c7be92a07bd39900a4d24f87880 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,6 @@
 Django==2.0.2
 djangorestframework==3.7.4
+django-fsm==2.6.0
 wagtail==2.0
 psycopg2==2.7.3.1
 Pillow==4.3.0