diff --git a/opentech/apply/funds/migrations/0034_create_revisions_model.py b/opentech/apply/funds/migrations/0034_create_revisions_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..ef28257185557786b6402f30c5a86f74358920a9
--- /dev/null
+++ b/opentech/apply/funds/migrations/0034_create_revisions_model.py
@@ -0,0 +1,34 @@
+# Generated by Django 2.0.2 on 2018-06-21 09:31
+
+import django.contrib.postgres.fields.jsonb
+import django.core.serializers.json
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('funds', '0033_use_django_fsm'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ApplicationRevision',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('form_data', django.contrib.postgres.fields.jsonb.JSONField(encoder=django.core.serializers.json.DjangoJSONEncoder)),
+                ('submission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='revisions', to='funds.ApplicationSubmission')),
+            ],
+        ),
+        migrations.AddField(
+            model_name='applicationsubmission',
+            name='draft_revision',
+            field=models.OneToOneField(editable=False, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='draft', to='funds.ApplicationRevision'),
+        ),
+        migrations.AddField(
+            model_name='applicationsubmission',
+            name='live_revision',
+            field=models.OneToOneField(editable=False, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='live', to='funds.ApplicationRevision'),
+        ),
+    ]
diff --git a/opentech/apply/funds/models.py b/opentech/apply/funds/models.py
index 321cfd1e37a1c3bcc4d3b231ff5e5afdae3afad1..59fdc1db8ed13d4af317567fbe308fc8a9dadcc9 100644
--- a/opentech/apply/funds/models.py
+++ b/opentech/apply/funds/models.py
@@ -606,6 +606,23 @@ class ApplicationSubmission(WorkflowHelpers, BaseStreamForm, AbstractFormSubmiss
     # Workflow inherited from WorkflowHelpers
     status = FSMField(default=INITIAL_STATE, protected=True)
 
+    is_draft = False
+
+    live_revision = models.OneToOneField(
+        'ApplicationRevision',
+        on_delete=models.PROTECT,
+        related_name='live',
+        null=True,
+        editable=False,
+    )
+    draft_revision = models.OneToOneField(
+        'ApplicationRevision',
+        on_delete=models.PROTECT,
+        related_name='draft',
+        null=True,
+        editable=False,
+    )
+
     # Meta: used for migration purposes only
     drupal_id = models.IntegerField(null=True, blank=True, editable=False)
 
@@ -719,10 +736,30 @@ class ApplicationSubmission(WorkflowHelpers, BaseStreamForm, AbstractFormSubmiss
         submission_in_db.next = self
         submission_in_db.save()
 
-    def create_revision(self):
-        pass
+    def from_draft(self):
+        self.is_draft = True
+        self.form_data = self.draft_revision.form_data
+        return self
+
+    def create_revision(self, draft=False):
+        self.clean_submission()
+        current_data = ApplicationSubmission.objects.get(id=self.id).form_data
+        if current_data != self.form_data:
+            revision = ApplicationRevision.objects.create(submission=self, form_data=self.form_data)
+            if draft:
+                self.form_data = self.live_revision.form_data
+            else:
+                self.live_revision = revision
+
+            self.draft_revision = revision
+            self.save()
 
-    def save(self, *args, **kwargs):
+    def clean_submission(self):
+        self.process_form_data()
+        self.ensure_user_has_account()
+        self.process_file_data()
+
+    def process_form_data(self):
         for field in self.form_fields:
             # Update the ids which are unique to use the unique name
             if isinstance(field.block, MustIncludeFieldBlock):
@@ -730,13 +767,18 @@ class ApplicationSubmission(WorkflowHelpers, BaseStreamForm, AbstractFormSubmiss
                 if response:
                     self.form_data[field.block.name] = response
 
-        self.ensure_user_has_account()
-
+    def process_file_data(self):
         for field in self.form_fields:
             if isinstance(field.block, UploadableMediaBlock):
                 file = self.form_data.get(field.id, {})
                 self.form_data[field.id] = self.handle_files(file)
 
+    def save(self, *args, **kwargs):
+        if self.is_draft:
+            raise ValueError('Cannot save with draft data')
+
+        self.clean_submission()
+
         creating = not self.id
         if creating:
             # We are creating the object default to first stage
@@ -751,6 +793,10 @@ class ApplicationSubmission(WorkflowHelpers, BaseStreamForm, AbstractFormSubmiss
 
         if creating:
             self.reviewers.set(self.get_from_parent('reviewers').all())
+            first_revision = ApplicationRevision.objects.create(submission=self, form_data=self.form_data)
+            self.live_revision = first_revision
+            self.draft_revision = first_revision
+            self.save()
 
     @property
     def missing_reviewers(self):
@@ -852,3 +898,8 @@ class ApplicationSubmission(WorkflowHelpers, BaseStreamForm, AbstractFormSubmiss
 
     def __repr__(self):
         return f'<{self.__class__.__name__}: {self.user}, {self.round}, {self.page}>'
+
+
+class ApplicationRevision(models.Model):
+    submission = models.ForeignKey(ApplicationSubmission, related_name='revisions', on_delete=models.CASCADE)
+    form_data = JSONField(encoder=DjangoJSONEncoder)
diff --git a/opentech/apply/funds/tests/test_models.py b/opentech/apply/funds/tests/test_models.py
index 5626409f60cf4a85be2b72d00097cbf5fbbca844..9a41315381b6353fac6c2f5094c278e34fa124d0 100644
--- a/opentech/apply/funds/tests/test_models.py
+++ b/opentech/apply/funds/tests/test_models.py
@@ -311,6 +311,9 @@ class TestApplicationSubmission(TestCase):
     def make_submission(self, **kwargs):
         return ApplicationSubmissionFactory(**kwargs)
 
+    def refresh(self, instance):
+        return instance.__class__.objects.get(id=instance.id)
+
     def test_can_get_required_block_names(self):
         email = 'test@test.com'
         submission = self.make_submission(user__email=email)
@@ -364,3 +367,41 @@ class TestApplicationSubmission(TestCase):
         submission = self.make_submission(form_data__image__filename=filename)
         save_path = os.path.join(settings.MEDIA_ROOT, submission.save_path(filename))
         self.assertTrue(os.path.isfile(save_path))
+
+    def test_create_revision_on_create(self):
+        submission = ApplicationSubmissionFactory()
+        self.assertEqual(submission.revisions.count(), 1)
+        self.assertDictEqual(submission.live_revision.form_data, submission.form_data)
+
+    def test_create_revision_on_data_change(self):
+        submission = ApplicationSubmissionFactory()
+        new_data = {'title': 'My Awesome Title'}
+        submission.form_data = new_data
+        submission.create_revision()
+        submission = self.refresh(submission)
+        self.assertEqual(submission.revisions.count(), 2)
+        self.assertDictEqual(submission.live_revision.form_data, new_data)
+
+    def test_dont_create_revision_on_data_same(self):
+        submission = ApplicationSubmissionFactory()
+        submission.create_revision()
+        self.assertEqual(submission.revisions.count(), 1)
+        self.assertDictEqual(submission.live_revision.form_data, submission.form_data)
+
+    def test_can_get_draft_data(self):
+        submission = ApplicationSubmissionFactory()
+        title = 'My new title'
+        submission.form_data = {'title': title}
+        submission.create_revision(draft=True)
+        self.assertEqual(submission.revisions.count(), 2)
+
+        draft_submission = submission.from_draft()
+        self.assertDictEqual(draft_submission.form_data, submission.form_data)
+        self.assertEqual(draft_submission.title, title)
+        self.assertTrue(draft_submission.is_draft, True)
+
+        with self.assertRaises(ValueError):
+            draft_submission.save()
+
+        submission = self.refresh(submission)
+        self.assertNotEqual(submission.title, title)
diff --git a/opentech/apply/funds/tests/test_views.py b/opentech/apply/funds/tests/test_views.py
index 2fb9cb7726d212e1e97c4b2f0eb98ab19c75ef0b..e9fdf85119f156402909b12b40ae856b22aaea13 100644
--- a/opentech/apply/funds/tests/test_views.py
+++ b/opentech/apply/funds/tests/test_views.py
@@ -85,3 +85,27 @@ class TestApplicantSubmissionView(BaseSubmissionViewTestCase):
         submission = ApplicationSubmissionFactory(draft_proposal=True)
         response = self.get_page(submission, 'edit')
         self.assertEqual(response.status_code, 403)
+
+
+class TestRevisionsView(BaseSubmissionViewTestCase):
+    user_factory = UserFactory
+
+    def test_create_revisions_on_submit(self):
+        submission = ApplicationSubmissionFactory(status='draft_proposal', workflow_stages=2)
+        old_data = submission.form_data
+        self.post_page(submission, {'proposal_discussion': True}, 'edit')
+        submission = self.refresh(submission)
+        self.assertEqual(submission.status, 'proposal_discussion')
+        self.assertEqual(submission.revisions.count(), 2)
+        self.asswerEqual(submission.revisions.first().form_data, old_data)
+        self.assertEqual(submission.form_data, {})
+
+    def test_dont_update_live_revision_on_save(self):
+        submission = ApplicationSubmissionFactory(status='draft_proposal', workflow_stages=2)
+        old_data = submission.form_data
+        self.post_page(submission, {'save': True}, 'edit')
+        submission = self.refresh(submission)
+        self.assertEqual(submission.status, 'draft_proposal')
+        self.assertEqual(submission.revisions.count(), 2)
+        self.asswerEqual(submission.revisions.first().form_data, old_data)
+        self.assertEqual(submission.form_data, {})