diff --git a/opentech/apply/activity/messaging.py b/opentech/apply/activity/messaging.py
index 718726ce8c66c685eb8b2195a17c02d54d2579fd..c8e6c41b1d5c79129c119f0670e628e5d479722b 100644
--- a/opentech/apply/activity/messaging.py
+++ b/opentech/apply/activity/messaging.py
@@ -49,6 +49,7 @@ neat_related = {
     MESSAGES.EDIT: 'revision',
     MESSAGES.COMMENT: 'comment',
     MESSAGES.SCREENING: 'old_status',
+    MESSAGES.REVIEW_OPINION: 'opinion',
 }
 
 
@@ -202,7 +203,8 @@ class ActivityAdapter(AdapterBase):
         MESSAGES.BATCH_REVIEWERS_UPDATED: 'batch_reviewers_updated',
         MESSAGES.NEW_REVIEW: 'Submitted a review',
         MESSAGES.OPENED_SEALED: 'Opened the submission while still sealed',
-        MESSAGES.SCREENING: 'Screening status from {old_status} to {submission.screening_status}'
+        MESSAGES.SCREENING: 'Screening status from {old_status} to {submission.screening_status}',
+        MESSAGES.REVIEW_OPINION: '{user} {opinion.opinion_display}s with {opinion.review.author}''s review of {submission}'
     }
 
     def recipients(self, message_type, **kwargs):
@@ -311,8 +313,10 @@ class SlackAdapter(AdapterBase):
         MESSAGES.INVITED_TO_PROPOSAL: '<{link}|{submission.title}> by {submission.user} has been invited to submit a proposal',
         MESSAGES.NEW_REVIEW: '{user} has submitted a review for <{link}|{submission.title}>. Outcome: {review.outcome},  Score: {review.score}',
         MESSAGES.READY_FOR_REVIEW: 'notify_reviewers',
+        MESSAGES.OPENED_SEALED: '{user} has opened the sealed submission: <{link}|{submission.title}>',
+        MESSAGES.REVIEW_OPINION: '{user} {opinion.opinion_display}s with {opinion.review.author}''s review of {submission}',
         MESSAGES.BATCH_READY_FOR_REVIEW: 'batch_notify_reviewers',
-        MESSAGES.OPENED_SEALED: '{user} has opened the sealed submission: <{link}|{submission.title}>'
+
     }
 
     def __init__(self):
diff --git a/opentech/apply/activity/migrations/0017_add_review_opinion.py b/opentech/apply/activity/migrations/0017_add_review_opinion.py
new file mode 100644
index 0000000000000000000000000000000000000000..c59a1458a577d05df0686a5b52dd5d9cfbb954c7
--- /dev/null
+++ b/opentech/apply/activity/migrations/0017_add_review_opinion.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.0.9 on 2019-02-18 08:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('activity', '0016_add_batch_ready'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='event',
+            name='type',
+            field=models.CharField(choices=[('UPDATE_LEAD', 'Update Lead'), ('EDIT', 'Edit'), ('APPLICANT_EDIT', 'Applicant Edit'), ('NEW_SUBMISSION', 'New Submission'), ('SCREENING', 'Screening'), ('TRANSITION', 'Transition'), ('BATCH_TRANSITION', 'Batch Transition'), ('DETERMINATION_OUTCOME', 'Determination Outcome'), ('INVITED_TO_PROPOSAL', 'Invited To Proposal'), ('REVIEWERS_UPDATED', 'Reviewers Updated'), ('BATCH_REVIEWERS_UPDATED', 'Batch Reviewers Updated'), ('READY_FOR_REVIEW', 'Ready For Review'), ('BATCH_READY_FOR_REVIEW', 'Batch Ready For Review'), ('NEW_REVIEW', 'New Review'), ('COMMENT', 'Comment'), ('PROPOSAL_SUBMITTED', 'Proposal Submitted'), ('OPENED_SEALED', 'Opened Sealed Submission'), ('REVIEW_OPINION', 'Review Opinion')], max_length=50),
+        ),
+    ]
diff --git a/opentech/apply/activity/options.py b/opentech/apply/activity/options.py
index d1817bbfc52d4f7bbe858b8c3eeef37b0152b94e..7041ce9e343aa4bbf7782417d2c4786e5cbc9e7e 100644
--- a/opentech/apply/activity/options.py
+++ b/opentech/apply/activity/options.py
@@ -19,6 +19,7 @@ class MESSAGES(Enum):
     COMMENT = 'Comment'
     PROPOSAL_SUBMITTED = 'Proposal Submitted'
     OPENED_SEALED = 'Opened Sealed Submission'
+    REVIEW_OPINION = 'Review Opinion'
 
     @classmethod
     def choices(cls):
diff --git a/opentech/apply/review/forms.py b/opentech/apply/review/forms.py
index 9987745338e627a82da14fb1981f4110b1d0c625..0d29c09ea311ac510dadc7422f527ce0ad0dfc38 100644
--- a/opentech/apply/review/forms.py
+++ b/opentech/apply/review/forms.py
@@ -1,10 +1,12 @@
 from django import forms
 from django.core.exceptions import NON_FIELD_ERRORS
+from django.utils.html import escape
 
 from opentech.apply.review.options import NA
 from opentech.apply.stream_forms.forms import StreamBaseForm
 
-from .models import Review
+from .models import Review, ReviewOpinion
+from .options import OPINION_CHOICES
 
 
 class MixedMetaClass(type(StreamBaseForm), type(forms.ModelForm)):
@@ -84,3 +86,54 @@ class ReviewModelForm(StreamBaseForm, forms.ModelForm, metaclass=MixedMetaClass)
             return sum(scores) / len(scores)
         except ZeroDivisionError:
             return NA
+
+
+class SubmitButtonWidget(forms.Widget):
+    def render(self, name, value, attrs=None):
+        disabled = 'disabled' if attrs.get('disabled') else ''
+        return '<input type="submit" name="{name}" value="{value}" class="button button--primary button--bottom-space" {disabled}>'.format(
+            disabled=disabled,
+            name=escape(name),
+            value=escape(name.title()),
+        )
+
+
+class OpinionField(forms.IntegerField):
+    def __init__(self, *args, opinion, **kwargs):
+        kwargs["widget"] = SubmitButtonWidget
+        self.opinion = opinion
+        kwargs['label'] = ''
+        super().__init__(*args, **kwargs)
+
+    def clean(self, value):
+        if value:
+            return self.opinion
+
+
+class ReviewOpinionForm(forms.ModelForm):
+    opinion = forms.IntegerField(required=False, widget=forms.HiddenInput())
+
+    class Meta:
+        model = ReviewOpinion
+        fields = ('opinion',)
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        for value, opinion in OPINION_CHOICES:
+            self.fields[opinion.lower()] = OpinionField(
+                label=opinion.title(),
+                opinion=value,
+                disabled=self.instance.opinion == value,
+            )
+
+    def clean(self):
+        cleaned_data = super().clean()
+        opinions = [cleaned_data.get(opinion.lower()) for _, opinion in OPINION_CHOICES]
+        valid_opinions = [opinion for opinion in opinions if opinion is not None]
+        if len(valid_opinions) > 1:
+            self.add_error(None, 'Cant submit both an agreement and disagreement')
+        cleaned_data = {'opinion': valid_opinions[0]}
+        return cleaned_data
+
+    def save(self, *args, **kwargs):
+        return super().save(*args, **kwargs)
diff --git a/opentech/apply/review/migrations/0015_review_opinion.py b/opentech/apply/review/migrations/0015_review_opinion.py
new file mode 100644
index 0000000000000000000000000000000000000000..d96d7c561190ae58bcc49d06a69afbd1de38f4f2
--- /dev/null
+++ b/opentech/apply/review/migrations/0015_review_opinion.py
@@ -0,0 +1,29 @@
+# Generated by Django 2.0.10 on 2019-02-16 12:19
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('review', '0014_add_markdown'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ReviewOpinion',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('opinion', models.IntegerField(choices=[(1, 'Agree'), (0, 'Disagree')])),
+                ('author', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
+                ('review', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opinions', to='review.Review')),
+            ],
+        ),
+        migrations.AlterUniqueTogether(
+            name='reviewopinion',
+            unique_together={('author', 'review')},
+        ),
+    ]
diff --git a/opentech/apply/review/models.py b/opentech/apply/review/models.py
index f7f746918e1f4aaf5f3f458b082955be31f9112d..294b622e86c225e0ef4d4bb4e72d8e6d1f234ee4 100644
--- a/opentech/apply/review/models.py
+++ b/opentech/apply/review/models.py
@@ -11,7 +11,7 @@ from wagtail.core.fields import StreamField
 
 from opentech.apply.funds.models import AssignedReviewers
 from opentech.apply.funds.models.mixins import AccessFormData
-from opentech.apply.review.options import YES, NO, MAYBE, RECOMMENDATION_CHOICES
+from opentech.apply.review.options import YES, NO, MAYBE, RECOMMENDATION_CHOICES, OPINION_CHOICES
 from opentech.apply.stream_forms.models import BaseStreamForm
 from opentech.apply.users.models import User
 
@@ -168,3 +168,19 @@ def update_submission_reviewers_list(sender, **kwargs):
         submission=review.submission,
         reviewer=review.author,
     )
+
+
+class ReviewOpinion(models.Model):
+    review = models.ForeignKey(Review, on_delete=models.CASCADE, related_name='opinions')
+    author = models.ForeignKey(
+        settings.AUTH_USER_MODEL,
+        on_delete=models.PROTECT,
+    )
+    opinion = models.IntegerField(choices=OPINION_CHOICES)
+
+    class Meta:
+        unique_together = ('author', 'review')
+
+    @property
+    def opinion_display(self):
+        return self.get_opinion_display()
diff --git a/opentech/apply/review/options.py b/opentech/apply/review/options.py
index 2d58d84393ab0bbc580dc1defb4f7adc08de8ff9..84cec661736272f372f528805055a4c2fd0868fb 100644
--- a/opentech/apply/review/options.py
+++ b/opentech/apply/review/options.py
@@ -21,3 +21,11 @@ RECOMMENDATION_CHOICES = (
     (MAYBE, 'Maybe'),
     (YES, 'Yes'),
 )
+
+DISAGREE = 0
+AGREE = 1
+
+OPINION_CHOICES = (
+    (AGREE, 'Agree'),
+    (DISAGREE, 'Disagree'),
+)
diff --git a/opentech/apply/review/templates/review/review_detail.html b/opentech/apply/review/templates/review/review_detail.html
index 4a82a07322bf04ed897c272dc3a90bba5ee25c7a..0e0c909c50804e9b8fe05d60a4a7e5092d08b68e 100644
--- a/opentech/apply/review/templates/review/review_detail.html
+++ b/opentech/apply/review/templates/review/review_detail.html
@@ -33,4 +33,12 @@
 
     {{ object.output_answers|submission_links }}
 </div>
+
+{% if form %}
+    <form method="post">
+        {% csrf_token %}
+        {{ form }}
+    </form>
+{% endif %}
+
 {% endblock %}
diff --git a/opentech/apply/review/tests/test_views.py b/opentech/apply/review/tests/test_views.py
index d9d3b9453e746c27b9dbec7511f5306bcd3b603b..d58724ff3f442a40caa974033a3cbd9c7bc92570 100644
--- a/opentech/apply/review/tests/test_views.py
+++ b/opentech/apply/review/tests/test_views.py
@@ -1,12 +1,13 @@
 from django.urls import reverse
 
+from opentech.apply.activity.models import Activity
 from opentech.apply.funds.tests.factories.models import ApplicationSubmissionFactory
 from opentech.apply.users.tests.factories import StaffFactory, UserFactory
 from opentech.apply.utils.testing.tests import BaseViewTestCase
 
 from .factories import ReviewFactory, ReviewFormFieldsFactory, ReviewFormFactory
-from ..models import Review
-from ..options import NA
+from ..models import Review, ReviewOpinion
+from ..options import NA, AGREE
 
 
 class StaffReviewsTestCase(BaseViewTestCase):
@@ -206,3 +207,36 @@ class ReviewDetailTestCase(BaseViewTestCase):
         response = self.get_page(review)
         self.assertContains(response, submission.title)
         self.assertContains(response, "<p>Yes</p>")
+
+
+class StaffReviewOpinionCase(BaseViewTestCase):
+    user_factory = StaffFactory
+    url_name = 'funds:submissions:reviews:{}'
+    base_view_name = 'review'
+
+    @classmethod
+    def setUpTestData(cls):
+        super().setUpTestData()
+        cls.submission = ApplicationSubmissionFactory(status='draft_proposal', workflow_stages=2)
+
+    def get_kwargs(self, instance):
+        return {'pk': instance.id, 'submission_pk': instance.submission.id}
+
+    def test_can_see_opinion_buttons_on_others_review(self):
+        staff = StaffFactory()
+        review = ReviewFactory(submission=self.submission, author=staff, recommendation_yes=True)
+        response = self.get_page(review)
+        self.assertContains(response, 'name="agree"')
+
+    def test_cant_see_opinion_buttons_on_self_review(self):
+        review = ReviewFactory(submission=self.submission, author=self.user, recommendation_yes=True)
+        response = self.get_page(review)
+        self.assertNotContains(response, 'name="agree"')
+
+    def test_can_add_opinion_to_others_review(self):
+        staff = StaffFactory()
+        review = ReviewFactory(submission=self.submission, author=staff, recommendation_yes=True)
+        self.post_page(review, {'agree': AGREE})
+        self.assertTrue(review.opinions.first().opinion_display in Activity.objects.first().message)
+        self.assertEqual(ReviewOpinion.objects.all().count(), 1)
+        self.assertEqual(ReviewOpinion.objects.first().opinion, AGREE)
diff --git a/opentech/apply/review/views.py b/opentech/apply/review/views.py
index 05c38204f8df6a57115df6e20f0871a95c581d99..c8605b2eb93cd743e650bd6efec6689df71117cb 100644
--- a/opentech/apply/review/views.py
+++ b/opentech/apply/review/views.py
@@ -6,14 +6,14 @@ from django.http import HttpResponseRedirect
 from django.shortcuts import get_object_or_404
 from django.urls import reverse_lazy
 from django.utils.decorators import method_decorator
-from django.views.generic import ListView, DetailView
+from django.views.generic import CreateView, ListView, DetailView
 
 from wagtail.core.blocks import RichTextBlock
 
 from opentech.apply.activity.messaging import messenger, MESSAGES
 from opentech.apply.funds.models import ApplicationSubmission
 from opentech.apply.review.blocks import RecommendationBlock, RecommendationCommentsBlock
-from opentech.apply.review.forms import ReviewModelForm
+from opentech.apply.review.forms import ReviewModelForm, ReviewOpinionForm
 from opentech.apply.stream_forms.models import BaseStreamForm
 from opentech.apply.users.decorators import staff_required
 from opentech.apply.utils.views import CreateOrUpdateView
@@ -24,7 +24,7 @@ from .models import Review
 class ReviewContextMixin:
     def get_context_data(self, **kwargs):
         assigned = self.object.assigned.order_by('role__order').select_related('reviewer')
-        reviews = self.object.reviews.all().select_related('author')
+        reviews = self.object.reviews.submitted().select_related('author')
 
         reviews_dict = {}
         for review in reviews:
@@ -138,10 +138,22 @@ class ReviewCreateOrUpdateView(BaseStreamForm, CreateOrUpdateView):
         return self.submission.get_absolute_url()
 
 
-@method_decorator(login_required, name='dispatch')
-class ReviewDetailView(DetailView):
+class ReviewDisplay(DetailView):
     model = Review
 
+    def get_context_data(self, **kwargs):
+        review = self.get_object()
+        if review.author != self.request.user:
+            consensus_form = ReviewOpinionForm(
+                instance=review.opinions.filter(author=self.request.user).first(),
+            )
+        else:
+            consensus_form = None
+        return super().get_context_data(
+            form=consensus_form,
+            **kwargs,
+        )
+
     def dispatch(self, request, *args, **kwargs):
         review = self.get_object()
         author = review.author
@@ -150,11 +162,53 @@ class ReviewDetailView(DetailView):
             raise PermissionDenied
 
         if review.is_draft:
-            return HttpResponseRedirect(reverse_lazy('apply:reviews:form', args=(review.submission.id,)))
+            return HttpResponseRedirect(reverse_lazy('apply:submissions:reviews:form', args=(review.submission.id,)))
 
         return super().dispatch(request, *args, **kwargs)
 
 
+class ReviewOpinionFormView(CreateView):
+    template_name = 'review/review_detail.html'
+    form_class = ReviewOpinionForm
+    model = Review
+
+    def get_form_kwargs(self):
+        self.object = self.get_object()
+        kwargs = super().get_form_kwargs()
+        instance = kwargs['instance']
+        kwargs['instance'] = instance.opinions.filter(author=self.request.user).first()
+        return kwargs
+
+    def form_valid(self, form):
+        self.review = self.get_object()
+        form.instance.author = self.request.user
+        form.instance.review = self.review
+        response = super().form_valid(form)
+
+        messenger(
+            MESSAGES.REVIEW_OPINION,
+            request=self.request,
+            user=self.request.user,
+            submission=self.review.submission,
+            related=form.instance,
+        )
+        return response
+
+    def get_success_url(self):
+        return self.review.get_absolute_url()
+
+
+@method_decorator(login_required, name='dispatch')
+class ReviewDetailView(DetailView):
+    def get(self, request, *args, **kwargs):
+        view = ReviewDisplay.as_view()
+        return view(request, *args, **kwargs)
+
+    def post(self, request, *args, **kwargs):
+        view = ReviewOpinionFormView.as_view()
+        return view(request, *args, **kwargs)
+
+
 @method_decorator(staff_required, name='dispatch')
 class ReviewListView(ListView):
     model = Review