From bf40cd3082ac3030d33b0bf67f71df28a4a5991f Mon Sep 17 00:00:00 2001
From: Todd Dembrey <todd.dembrey@torchbox.com>
Date: Thu, 14 Jun 2018 10:45:02 +0100
Subject: [PATCH] Add basic revision comparison

---
 .../funds/applicationsubmission_detail.html   | 26 +-----------
 .../funds/includes/rendered_answers.html      | 26 ++++++++++++
 .../templates/funds/revisions_compare.html    | 13 ++++++
 .../funds/templates/funds/revisions_list.html |  4 +-
 opentech/apply/funds/urls.py                  |  4 +-
 opentech/apply/funds/views.py                 | 42 +++++++++++++++++++
 6 files changed, 88 insertions(+), 27 deletions(-)
 create mode 100644 opentech/apply/funds/templates/funds/includes/rendered_answers.html
 create mode 100644 opentech/apply/funds/templates/funds/revisions_compare.html

diff --git a/opentech/apply/funds/templates/funds/applicationsubmission_detail.html b/opentech/apply/funds/templates/funds/applicationsubmission_detail.html
index ee58bbbfb..e8aec6e0f 100644
--- a/opentech/apply/funds/templates/funds/applicationsubmission_detail.html
+++ b/opentech/apply/funds/templates/funds/applicationsubmission_detail.html
@@ -54,31 +54,7 @@
                         {% endif %}
                     </h6>
 
-                    <h3>Proposal Information</h3>
-                    <div class="grid grid--proposal-info">
-                        <div>
-                            <h5>Requested Funding</h5>
-                            <p>{{ object.value }}</p>
-                        </div>
-
-                        <div>
-                            <h5>Project Duration</h5>
-                            <p>{{ object.value }}</p>
-                        </div>
-
-                        <div>
-                            <h5>Legal Name</h5>
-                            <p>{{ object.full_name }}</p>
-                        </div>
-
-                        <div>
-                            <h5>Email</h5>
-                            <p>{{ object.email }}</p>
-                        </div>
-                    </div>
-                    <div class="rich-text rich-text--answers">
-                        {{ object.render_answers }}
-                    </div>
+                    {% include "funds/includes/rendered_answers.html" %}
                 </div>
             {% endif %}
 
diff --git a/opentech/apply/funds/templates/funds/includes/rendered_answers.html b/opentech/apply/funds/templates/funds/includes/rendered_answers.html
new file mode 100644
index 000000000..e5b7f88b2
--- /dev/null
+++ b/opentech/apply/funds/templates/funds/includes/rendered_answers.html
@@ -0,0 +1,26 @@
+{% load workflow_tags %}
+<h3>Proposal Information</h3>
+<div class="grid grid--proposal-info">
+    <div>
+        <h5>Requested Funding</h5>
+        <p>{{ object.value }}</p>
+    </div>
+
+    <div>
+        <h5>Project Duration</h5>
+        <p>{{ object.value }}</p>
+    </div>
+
+    <div>
+        <h5>Legal Name</h5>
+        <p>{{ object.full_name }}</p>
+    </div>
+
+    <div>
+        <h5>Email</h5>
+        <p>{{ object.email }}</p>
+    </div>
+</div>
+<div class="rich-text rich-text--answers">
+    {{ object.render_answers }}
+</div>
diff --git a/opentech/apply/funds/templates/funds/revisions_compare.html b/opentech/apply/funds/templates/funds/revisions_compare.html
new file mode 100644
index 000000000..8b07bf11c
--- /dev/null
+++ b/opentech/apply/funds/templates/funds/revisions_compare.html
@@ -0,0 +1,13 @@
+{% extends "base-apply.html" %}
+{% block content %}
+<div class="wrapper wrapper--breakout wrapper--admin">
+    <div class="wrapper wrapper--medium">
+        <h2 class="heading heading--no-margin">Comparing Revisions</h2>
+        <h5>For <a href="{% url "funds:submission" submission.id %}">{{ submission.title }}</a></h5>
+    </div>
+</div>
+
+<div class="wrapper wrapper--medium wrapper--tabs">
+    {% include "funds/includes/rendered_answers.html" with object=diff %}
+</div>
+{% endblock %}
diff --git a/opentech/apply/funds/templates/funds/revisions_list.html b/opentech/apply/funds/templates/funds/revisions_list.html
index ff39b2099..e4ba90161 100644
--- a/opentech/apply/funds/templates/funds/revisions_list.html
+++ b/opentech/apply/funds/templates/funds/revisions_list.html
@@ -8,13 +8,15 @@
 </div>
 
 <div class="wrapper wrapper--medium wrapper--tabs">
+    {% with base_revision=revisions.0.id %}
     {% for revision in revisions %}
         <div>
             {{ revision.date }} by {{ revision.author }}
             {% if not forloop.first %}
-                <a class="button button--primary" href="#">Compare</a>
+                <a class="button button--primary" href="{% url 'apply:revisions:compare' submission_pk=submission.id from=revision.id to=base_revision %}">Compare</a>
             {% endif %}
         </div>
     {% endfor %}
+    {% endwith %}
 </div>
 {% endblock %}
diff --git a/opentech/apply/funds/urls.py b/opentech/apply/funds/urls.py
index 60bc05307..ede94849c 100644
--- a/opentech/apply/funds/urls.py
+++ b/opentech/apply/funds/urls.py
@@ -1,6 +1,7 @@
 from django.urls import include, path
 
 from .views import (
+    RevisionCompareView,
     RevisionListView,
     SubmissionDetailView,
     SubmissionEditView,
@@ -10,7 +11,8 @@ from .views import (
 
 
 revision_urls = ([
-    path('', RevisionListView.as_view(), name='list')
+    path('', RevisionListView.as_view(), name='list'),
+    path('compare/<int:to>/<int:from>', RevisionCompareView.as_view(), name='compare'),
 ], 'revisions')
 
 
diff --git a/opentech/apply/funds/views.py b/opentech/apply/funds/views.py
index 457164ae2..7eb7feb73 100644
--- a/opentech/apply/funds/views.py
+++ b/opentech/apply/funds/views.py
@@ -1,3 +1,5 @@
+from difflib import SequenceMatcher
+
 from django.contrib.auth.decorators import login_required
 from django.core.exceptions import PermissionDenied
 from django.http import HttpResponseRedirect
@@ -257,3 +259,43 @@ class RevisionListView(ListView):
             submission=self.submission,
             **kwargs,
         )
+
+
+class RevisionCompareView(TemplateView):
+    template_name = 'funds/revisions_compare.html'
+
+    def compare_answer(self, answer_a, answer_b):
+        if not answer_a and not answer_b:
+            return answer_b
+        diff = SequenceMatcher(answer_a, answer_b)
+        output = []
+        for opcode, a0, a1, b0, b1 in diff.get_opcodes():
+            if opcode == 'equal':
+                output.append(diff.a[a0:a1])
+            elif opcode == 'insert':
+                output.append('<span class="added">' + diff.b[b0:b1] + '</span>')
+            elif opcode == 'delete':
+                output.append('<span class="deleted">' + diff.a[a0:a1] + "</span>")
+            elif opcode == 'replace':
+                raise NotImplementedError("what to do with 'replace' opcode?")
+            else:
+                raise RuntimeError("unexpected opcode")
+        return ''.join(output)
+
+    def compare(self, from_data, to_data):
+        diffed_form_data = {
+            field: self.compare_answer(from_data.form_data.get(field), to_data.form_data[field])
+            for field in to_data.form_data
+        }
+        to_data.form_data = diffed_form_data
+        return to_data
+
+    def get_context_data(self, **kwargs):
+        from_revision = ApplicationSubmission.objects.get(id=self.kwargs['from'])
+        to_revision = ApplicationSubmission.objects.get(id=self.kwargs['to'])
+        diff = self.compare(from_revision, to_revision)
+        return super().get_context_data(
+            submission = ApplicationSubmission.objects.get(id=self.kwargs['submission_pk']),
+            diff=diff,
+            **kwargs,
+        )
-- 
GitLab