diff --git a/comments/factories.py b/comments/factories.py
new file mode 100644
index 0000000000000000000000000000000000000000..aaf4cc9698021923b66ba04444a4e612d0650240
--- /dev/null
+++ b/comments/factories.py
@@ -0,0 +1,14 @@
+import factory
+
+from django.utils import timezone
+
+from scipost.factories import ContributorFactory
+from .models import Comment
+
+
+class CommentFactory(factory.django.DjangoModelFactory):
+    class Meta:
+        model = Comment
+
+    comment_text = factory.Faker('text')
+    date_submitted = timezone.now()
diff --git a/comments/migrations/0006_auto_20170115_1750.py b/comments/migrations/0006_auto_20170115_1750.py
new file mode 100644
index 0000000000000000000000000000000000000000..9937694a13ff6a56d62b3a8a7d7da23a9331107b
--- /dev/null
+++ b/comments/migrations/0006_auto_20170115_1750.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2017-01-15 16:50
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('comments', '0005_merge_20161219_2126'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='comment',
+            name='created',
+            field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='comment',
+            name='latest_activity',
+            field=models.DateTimeField(auto_now=True),
+        ),
+    ]
diff --git a/comments/models.py b/comments/models.py
index 1e6c1c5d1da23a916f201a3e664b24e001ab0d7d..0c43a91ac3751dcd52e860481d7fca09dc30f12d 100644
--- a/comments/models.py
+++ b/comments/models.py
@@ -8,7 +8,7 @@ from django.utils.safestring import mark_safe
 from .models import *
 
 from commentaries.models import Commentary
-from scipost.models import Contributor
+from scipost.models import TimeStampedModel, Contributor
 from submissions.models import Submission, Report
 from theses.models import ThesisLink
 
@@ -34,7 +34,7 @@ COMMENT_STATUS = (
 comment_status_dict = dict(COMMENT_STATUS)
 
 
-class Comment(models.Model):
+class Comment(TimeStampedModel):
     """ A Comment is an unsollicited note, submitted by a Contributor,
     on a particular publication or in reply to an earlier Comment. """
 
@@ -42,7 +42,7 @@ class Comment(models.Model):
     vetted_by = models.ForeignKey(Contributor, blank=True, null=True,
                                   on_delete=models.CASCADE,
                                   related_name='comment_vetted_by')
-    # a Comment is either for a Commentary or Submission
+    # a Comment is either for a Commentary or Submission or a ThesisLink.
     commentary = models.ForeignKey(Commentary, blank=True, null=True, on_delete=models.CASCADE)
     submission = models.ForeignKey(Submission, blank=True, null=True, on_delete=models.CASCADE)
     thesislink = models.ForeignKey(ThesisLink, blank=True, null=True, on_delete=models.CASCADE)
diff --git a/comments/templates/comments/reply_to_comment.html b/comments/templates/comments/reply_to_comment.html
index 19cdddfbe573d4e6bbf83580e3ecf70311ed9652..89f725b9a82535c7f15bb1ecd4b86219741e4d21 100644
--- a/comments/templates/comments/reply_to_comment.html
+++ b/comments/templates/comments/reply_to_comment.html
@@ -32,21 +32,21 @@
   {% if comment.commentary %}
     <h1>The Commentary concerned:</h1>
     {{ commentary.header_as_table }}
-    
+
     <h3>Abstract:</h3>
     <p>{{ commentary.pub_abstract }}</p>
   {% endif %}
   {% if comment.submission %}
     <h1>The Submission concerned:</h1>
     {{ submission.header_as_table }}
-    
+
     <h3>Abstract:</h3>
     <p>{{ submission.abstract }}</p>
   {% endif %}
   {% if comment.thesislink %}
     <h1>The Thesis concerned:</h1>
-    {{ thesislink.header_as_table }}
-    
+    {% include "theses/_header_as_table.html" with thesislink=thesislink %}
+
     <h3>Abstract:</h3>
     <p>{{ thesislink.abstract }}</p>
   {% endif %}
diff --git a/common/helpers/__init__.py b/common/helpers/__init__.py
index a29cff832e8239e0ffc0cf1db98d51745c4c88c8..bf886509ae9e9ca883b2d8c5591811e5d355dbc1 100644
--- a/common/helpers/__init__.py
+++ b/common/helpers/__init__.py
@@ -27,4 +27,5 @@ def model_form_data(model, form_class):
 
 
 def filter_keys(dictionary, keys_to_keep):
-    return {key: dictionary[key] for key in keys_to_keep}
+    # Field is empty if not on model.
+    return {key: dictionary.get(key, "") for key in keys_to_keep}
diff --git a/scipost/factories.py b/scipost/factories.py
index df335f90e80a06400c418c988d7a21b3a773d388..5e22d34f51ad3ca28de962b1876cc840a56f8d13 100644
--- a/scipost/factories.py
+++ b/scipost/factories.py
@@ -16,6 +16,14 @@ class ContributorFactory(factory.django.DjangoModelFactory):
     vetted_by = factory.SubFactory('scipost.factories.ContributorFactory', vetted_by=None)
 
 
+class VettingEditorFactory(ContributorFactory):
+    @factory.post_generation
+    def add_to_vetting_editors(self, create, extracted, **kwargs):
+        if not create:
+            return
+        self.user.groups.add(Group.objects.get(name="Vetting Editors"))
+
+
 class UserFactory(factory.django.DjangoModelFactory):
     class Meta:
         model = get_user_model()
diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html
index d2645698fd4982108e18cd393f6bad8a3bee8bed..01045558655e17da7f9d6bdd07b11819aba58d26 100644
--- a/scipost/templates/scipost/personal_page.html
+++ b/scipost/templates/scipost/personal_page.html
@@ -273,7 +273,7 @@
       <li><a href="{% url 'comments:vet_submitted_comments' %}">Vet submitted Comments</a> ({{ nr_comments_to_vet }})</li>
       {% endif %}
       {% if perms.scipost.can_vet_thesislink_requests %}
-      <li><a href="{% url 'theses:vet_thesislink_requests' %}">Vet Thesis Link Requests</a> ({{ nr_thesislink_requests_to_vet }})</li>
+      <li><a href="{% url 'theses:unvetted_thesislinks' %}">Vet Thesis Link Requests</a> ({{ nr_thesislink_requests_to_vet }})</li>
       {% endif %}
       {% if perms.scipost.can_vet_authorship_claims %}
       <li><a href="{% url 'scipost:vet_authorship_claims' %}">Vet Authorship Claims</a> ({{ nr_authorship_claims_to_vet }})</li>
diff --git a/strings/__init__.py b/strings/__init__.py
index 82d957c9dde2ab9c1606aa3568f778e408e13a84..4aa362a9cb4689d7212cb18ff1284ed5cd7b6635 100644
--- a/strings/__init__.py
+++ b/strings/__init__.py
@@ -1 +1,5 @@
-acknowledge_request_thesis_link = "Thank you for your request for a Thesis Link. Your request will soon be handled by an editor"
+acknowledge_request_thesis_link = (
+    "Thank you for your request for a Thesis Link. Your request"
+    " will soon be handled by an editor."
+)
+acknowledge_vet_thesis_link = "Thesis Link request vetted."
diff --git a/theses/factories.py b/theses/factories.py
index bc57863b350e82579b8725bd27884bdeb1382c24..550d5a73e004b69b8e40ac52ed2dc4618e9309c9 100644
--- a/theses/factories.py
+++ b/theses/factories.py
@@ -18,7 +18,7 @@ class ThesisLinkFactory(factory.django.DjangoModelFactory):
     author = factory.Faker('name')
     supervisor = factory.Faker('name')
     institution = factory.Faker('company')
-    defense_date = factory.Faker('date_time_this_century')
+    defense_date = factory.Faker('date')
     abstract = factory.Faker('text')
     domain = 'ET'
 
@@ -28,4 +28,3 @@ class VetThesisLinkFormFactory(FormFactory):
         model = VetThesisLinkForm
 
     action_option = VetThesisLinkForm.ACCEPT
-    # justification = factory.Faker('lorem')
diff --git a/theses/forms.py b/theses/forms.py
index 05963cb3221ca5cadcc553f653090d316395470b..5b8b8db4cecc499e9d8a4006b1b98face3e2d0d8 100644
--- a/theses/forms.py
+++ b/theses/forms.py
@@ -1,4 +1,5 @@
 from django import forms
+from django.core.mail import EmailMessage
 
 from .models import *
 from .helpers import past_years
@@ -16,7 +17,7 @@ class RequestThesisLinkForm(forms.ModelForm):
         }
 
 
-class VetThesisLinkForm(forms.Form):
+class VetThesisLinkForm(RequestThesisLinkForm):
     MODIFY = 0
     ACCEPT = 1
     REFUSE = 2
@@ -41,10 +42,62 @@ class VetThesisLinkForm(forms.Form):
     justification = forms.CharField(widget=forms.Textarea(
         attrs={'rows': 5, 'cols': 40}), label='Justification (optional)', required=False)
 
-    def vet_request(self, thesis_link):
-        print(self.cleaned_data)
-        if self.cleaned_data['action_option'] == VetThesisLinkForm.ACCEPT:
-            print('hoi')
+    def vet_request(self, thesislink, user):
+        if int(self.cleaned_data['action_option']) == VetThesisLinkForm.ACCEPT:
+            thesislink.vetted = True
+            thesislink.vetted_by = Contributor.objects.get(user=user)
+            thesislink.save()
+
+            email_text = ('Dear ' + title_dict[thesislink.requested_by.title] + ' '
+                          + thesislink.requested_by.user.last_name
+                          + ', \n\nThe Thesis Link you have requested, concerning thesis with title '
+                          + thesislink.title + ' by ' + thesislink.author
+                          + ', has been activated at https://scipost.org/thesis/'
+                          + str(thesislink.id) + '.'
+                          + '\n\nThank you for your contribution, \nThe SciPost Team.')
+            emailmessage = EmailMessage('SciPost Thesis Link activated', email_text,
+                                        'SciPost Theses <theses@scipost.org>',
+                                        [thesislink.requested_by.user.email],
+                                        ['theses@scipost.org'],
+                                        reply_to=['theses@scipost.org'])
+            emailmessage.send(fail_silently=False)
+        elif int(self.cleaned_data['action_option']) == VetThesisLinkForm.REFUSE:
+            email_text = ('Dear ' + title_dict[thesislink.requested_by.title] + ' '
+                          + thesislink.requested_by.user.last_name
+                          + ', \n\nThe Thesis Link you have requested, concerning thesis with title '
+                          + thesislink.title + ' by ' + thesislink.author
+                          + ', has not been activated for the following reason: '
+                          + self.cleaned_data['refusal_reason']
+                          + '.\n\nThank you for your interest, \nThe SciPost Team.')
+            if self.cleaned_data['justification']:
+                email_text += '\n\nFurther explanations: ' + \
+                    self.cleaned_data['justification']
+            emailmessage = EmailMessage('SciPost Thesis Link', email_text,
+                                        'SciPost Theses <theses@scipost.org>',
+                                        [thesislink.requested_by.user.email],
+                                        ['theses@scipost.org'],
+                                        reply_to=['theses@scipost.org'])
+            emailmessage.send(fail_silently=False)
+            thesislink.delete()
+
+        elif int(self.cleaned_data['action_option']) == VetThesisLinkForm.MODIFY:
+            thesislink.vetted = True
+            thesislink.vetted_by = Contributor.objects.get(user=user)
+            thesislink.save()
+            email_text = ('Dear ' + title_dict[thesislink.requested_by.title] + ' '
+                          + thesislink.requested_by.user.last_name
+                          + ', \n\nThe Thesis Link you have requested, concerning thesis with title '
+                          + thesislink.title + ' by ' + thesislink.author_list
+                          + ', has been activated '
+                          '(with slight modifications to your submitted details) at '
+                          'https://scipost.org/thesis/' + str(thesislink.id) + '.'
+                          '\n\nThank you for your contribution, \nThe SciPost Team.')
+            emailmessage = EmailMessage('SciPost Thesis Link activated', email_text,
+                                        'SciPost Theses <theses@scipost.org>',
+                                        [thesislink.requested_by.user.email],
+                                        ['theses@scipost.org'],
+                                        reply_to=['theses@scipost.org'])
+            emailmessage.send(fail_silently=False)
 
 
 class ThesisLinkSearchForm(forms.Form):
diff --git a/theses/models.py b/theses/models.py
index 50ec6b1f47548e9f43d6f53d31d091883f4ed091..fbd5f370d030f2fe3c7e163082cefa39a258efe8 100644
--- a/theses/models.py
+++ b/theses/models.py
@@ -69,45 +69,6 @@ class ThesisLink(models.Model):
     def __str__(self):
         return self.title
 
-    def header_as_table(self):
-        context = Context({
-            'title': self.title, 'author': self.author,
-            'pub_link': self.pub_link, 'institution': self.institution,
-            'supervisor': self.supervisor, 'defense_date': self.defense_date})
-        header = (
-            '<table>'
-            '<tr><td>Title: </td><td>&nbsp;</td><td>{{ title }}</td></tr>'
-            '<tr><td>Author: </td><td>&nbsp;</td><td>{{ author }}</td></tr>'
-            '<tr><td>As Contributor: </td><td>&nbsp;</td>')
-        if self.author_as_cont.all():
-            for auth in self.author_as_cont.all():
-                header += (
-                    '<td><a href="/contributor/' + str(auth.id) + '">' +
-                    auth.user.first_name + ' ' + auth.user.last_name +
-                    '</a></td>')
-        else:
-            header += '<td>(not claimed)</td>'
-        header += (
-            '</tr>'
-            '<tr><td>Type: </td><td></td><td>' + self.THESIS_TYPES_DICT[self.type] +
-            '</td></tr>'
-            '<tr><td>Discipline: </td><td></td><td>' +
-            disciplines_dict[self.discipline] + '</td></tr>'
-            '<tr><td>Domain: </td><td></td><td>' +
-            journals_domains_dict[self.domain] + '</td></tr>'
-            '<tr><td>Subject area: </td><td></td><td>' +
-            subject_areas_dict[self.subject_area] + '</td></tr>'
-            '<tr><td>URL: </td><td>&nbsp;</td><td><a href="{{ pub_link }}" '
-            'target="_blank">{{ pub_link }}</a></td></tr>'
-            '<tr><td>Degree granting institution: </td><td>&nbsp;</td>'
-            '<td>{{ institution }}</td></tr>'
-            '<tr><td>Supervisor(s): </td><td></td><td>{{ supervisor }}'
-            '</td></tr>' '<tr><td>Defense date: </td><td>&nbsp;</td>'
-            '<td>{{ defense_date }}</td></tr>'
-            '</table>')
-        template = Template(header)
-        return template.render(context)
-
     def header_as_li(self):
         context = Context({
             'id': self.id, 'title': self.title, 'author': self.author,
diff --git a/theses/templates/theses/_header_as_table.html b/theses/templates/theses/_header_as_table.html
new file mode 100644
index 0000000000000000000000000000000000000000..2a23414649882e3f50f6d2c1e7788ad254de5099
--- /dev/null
+++ b/theses/templates/theses/_header_as_table.html
@@ -0,0 +1,46 @@
+{% load theses_extras %}
+
+<table>
+    <tr>
+        <td>Title: </td><td>&nbsp;</td><td>{{ thesislink.title }}</td>
+    </tr>
+    <tr>
+        <td>Author: </td><td>&nbsp;</td><td>{{ thesislink.author }}</td>
+    </tr>
+    <tr>
+        <td>As Contributor: </td><td>&nbsp;</td>
+        {% if thesislink.author_as_cont.all %}
+            {% for author in thesislink.author_as_cont.all %}
+                <td><a href= {% url 'scipost:contributor_info' author.id %}>
+                        author.user.first_name author.user.last_name
+                </a></td>
+            {% endfor %}
+        {% else %}
+            <td>(not claimed)</td>
+        {% endif %}
+    </tr>
+    <tr>
+        <td>Type: </td><td></td><td>  {{ thesislink|type }}</td>
+    </tr>
+    <tr>
+        <td>Discipline: </td><td></td><td>{{ thesislink|discipline }}</td>
+    </tr>
+    <tr>
+        <td>Domain: </td><td></td><td>{{ thesislink|domain }}</td>
+    </tr>
+    <tr>
+        <td>Subject area: </td><td></td><td> {{ thesislink|subject_area }} </td>
+    </tr>
+    <tr>
+        <td>URL: </td><td>&nbsp;</td><td><a href="{{ pub_link }}" target="_blank">{{ thesislink.pub_link }}</a></td>
+    </tr>
+    <tr>
+        <td>Degree granting institution: </td><td>&nbsp;</td><td>{{ thesislink.institution }}</td>
+    </tr>
+    <tr>
+        <td>Supervisor(s): </td><td></td><td>{{ thesislink.supervisor }}</td>
+    </tr>
+    <tr>
+        <td>Defense date: </td><td>&nbsp;</td><td>{{ thesislink.defense_date }}</td>
+    </tr>
+</table>
diff --git a/theses/templates/theses/thesis_detail.html b/theses/templates/theses/thesis_detail.html
index 541f934e1009668a521c0839c0a54fa77f9748af..45b16160beaa66b2f6f1041d0255a65c99642f2a 100644
--- a/theses/templates/theses/thesis_detail.html
+++ b/theses/templates/theses/thesis_detail.html
@@ -44,7 +44,8 @@
       <h2>Thesis information: </h2>
     </div>
   </div>
-  {{ thesislink.header_as_table }}
+  {% include "./_header_as_table.html" with thesislink=thesislink %}
+  {# {{ thesislink.header_as_table }}#}
   <h3>Abstract:</h3>
   <p>{{ thesislink.abstract }}</p>
 
diff --git a/theses/templates/theses/unvetted_thesislinks.html b/theses/templates/theses/unvetted_thesislinks.html
new file mode 100644
index 0000000000000000000000000000000000000000..f2245e0a6f698670eb439883a4483b8eea19d28c
--- /dev/null
+++ b/theses/templates/theses/unvetted_thesislinks.html
@@ -0,0 +1,26 @@
+{% extends 'scipost/base.html' %}
+
+{% block pagetitle %}: Unvetted Thesis Links{% endblock pagetitle %}
+
+{% block headsup %}
+
+{% endblock headsup %}
+
+{% block bodysup %}
+
+
+<h1>Unvetted Thesis Links</h1>
+<ul>
+    {% for thesislink in thesislinks %}
+        <li>
+        {{ thesislink.author }} - {{ thesislink.title }} -
+        <a href = "{% url 'theses:vet_thesislink' pk=thesislink.id %}">vet</a>
+        </li>
+    {% empty %}
+        <li>
+            No unvetted thesis links.
+        </li>
+    {% endfor %}
+</ul>
+
+{% endblock bodysup %}
diff --git a/theses/templates/theses/vet_thesislink.html b/theses/templates/theses/vet_thesislink.html
new file mode 100644
index 0000000000000000000000000000000000000000..3694c92f0f94b02e57dd15fb4102e2ff4dbba7df
--- /dev/null
+++ b/theses/templates/theses/vet_thesislink.html
@@ -0,0 +1,16 @@
+{% extends 'scipost/base.html' %}
+
+{% block pagetitle %}: Unvetted Thesis Links{% endblock pagetitle %}
+
+{% block headsup %}
+
+{% endblock headsup %}
+
+{% block bodysup %}
+
+<form action="" method="post">{% csrf_token %}
+    {{ form.as_p }}
+    <input type="submit" value="Update" />
+</form>
+
+{% endblock bodysup %}
diff --git a/theses/templates/theses/vet_thesislink_requests.html b/theses/templates/theses/vet_thesislink_requests.html
index c49008776f23329e746b62c1e0d84c9da1f3a178..49172307273f713a950bb4355acb90c6ab8387ff 100644
--- a/theses/templates/theses/vet_thesislink_requests.html
+++ b/theses/templates/theses/vet_thesislink_requests.html
@@ -16,13 +16,13 @@
   <hr>
   <div class="row">
     <div class="col-8">
-      {{ thesislink_to_vet.header_as_table }}
+      {% include "./_header_as_table.html" with thesislink=thesislink_to_vet %}
       <br />
       <h4>Abstract:</h4>
       <p>{{ thesislink_to_vet.abstract }}</p>
     </div>
     <div class="col-4">
-      <form method="post">
+      <form action= {% url 'theses:vet_thesislink_request' thesislink_to_vet.id %} method="post">
         {% csrf_token %}
         {{ form.as_ul }}
         <input type="submit" value="Submit" />
diff --git a/theses/templatetags/__init__.py b/theses/templatetags/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/theses/templatetags/theses_extras.py b/theses/templatetags/theses_extras.py
new file mode 100644
index 0000000000000000000000000000000000000000..f3b441f41f64e3bb5a7ad23f852eb3e627fe9dec
--- /dev/null
+++ b/theses/templatetags/theses_extras.py
@@ -0,0 +1,26 @@
+from django import template
+
+from scipost.constants import SCIPOST_DISCIPLINES, subject_areas_dict, disciplines_dict
+from journals.models import journals_domains_dict
+
+register = template.Library()
+
+
+@register.filter
+def type(thesislink):
+    return thesislink.THESIS_TYPES_DICT[thesislink.type]
+
+
+@register.filter
+def discipline(thesislink):
+    return disciplines_dict[thesislink.discipline]
+
+
+@register.filter
+def domain(thesislink):
+    return journals_domains_dict[thesislink.domain]
+
+
+@register.filter
+def subject_area(thesislink):
+    return subject_areas_dict[thesislink.subject_area]
diff --git a/theses/test_forms.py b/theses/test_forms.py
index 1456184593dd64e0f63f12574fb5c59b87576e6a..73d294c75e3c7f0908828be27e06deb5920ef005 100644
--- a/theses/test_forms.py
+++ b/theses/test_forms.py
@@ -24,21 +24,3 @@ class TestRequestThesisLink(TestCase):
         form = RequestThesisLinkForm(form_data)
         form.is_valid()
         self.assertEqual(form.errors['domain'], ['This field is required.'])
-
-
-class TestVetThesisLinkRequests(TestCase):
-    fixtures = ['permissions', 'groups']
-
-    def test_thesislink_gets_vetted_when_accepted(self):
-        thesis_link = ThesisLinkFactory()
-        form = VetThesisLinkFormFactory()
-        form.is_valid()
-        form.vet_request(thesis_link)
-        self.assertTrue(thesis_link.vetted)
-
-    def test_thesislink_is_not_vetted_when_refused(self):
-        thesis_link = ThesisLinkFactory()
-        form = VetThesisLinkFormFactory(action_option=VetThesisLinkForm.REFUSE)
-        form.is_valid()
-        form.vet_request(thesis_link)
-        self.assertFalse(thesis_link.vetted)
diff --git a/theses/test_models.py b/theses/test_models.py
index 4308ab25f1d22c56e33cb5047cb6f571fe080bf7..9cda6172906f7feeb4a8789f014fb4a09f5f7bf9 100644
--- a/theses/test_models.py
+++ b/theses/test_models.py
@@ -8,6 +8,8 @@ from .factories import ThesisLinkFactory
 
 
 class ThesisLinkTestCase(TestCase):
+    fixtures = ['permissions', 'groups']
+
     def test_domain_cannot_be_blank(self):
         thesis_link = ThesisLinkFactory()
         thesis_link.domain = ""
diff --git a/theses/test_views.py b/theses/test_views.py
index faa7f14d5d621096441faa542af7984e8e629573..c3f1244b1865f5576fab264d50d7ab95ae428a69 100644
--- a/theses/test_views.py
+++ b/theses/test_views.py
@@ -1,27 +1,53 @@
 import re
 
+from django.core import mail
 from django.core.exceptions import PermissionDenied
 from django.test import TestCase, RequestFactory
 from django.test.client import Client
 from django.contrib.auth.models import Group
-from django.urls import reverse
+from django.urls import reverse, reverse_lazy
+from django.contrib.messages.storage.fallback import FallbackStorage
 
-from .views import RequestThesisLink, VetThesisLinkRequests
 from scipost.factories import UserFactory, ContributorFactory
+from comments.factories import CommentFactory
+from comments.forms import CommentForm
+from comments.models import Comment
+from .views import RequestThesisLink, VetThesisLink, thesis_detail
 from .factories import ThesisLinkFactory, VetThesisLinkFormFactory
 from .models import ThesisLink
+from .forms import VetThesisLinkForm
+from common.helpers import model_form_data
 
 
 class TestThesisDetail(TestCase):
     fixtures = ['groups', 'permissions']
 
     def test_visits_valid_thesis_detail(self):
+        """ A visitor does not have to be logged in to view a thesis link. """
         thesis_link = ThesisLinkFactory()
         client = Client()
         target = reverse('theses:thesis', kwargs={'thesislink_id': thesis_link.id})
         response = client.post(target)
         self.assertEqual(response.status_code, 200)
 
+    def test_submitting_comment_creates_comment(self):
+        """ Valid Comment gets saved """
+
+        contributor = ContributorFactory()
+        thesislink = ThesisLinkFactory()
+        valid_comment_data = model_form_data(CommentFactory.build(), CommentForm)
+        target = reverse('theses:thesis', kwargs={'thesislink_id': thesislink.id})
+
+        comment_count = Comment.objects.filter(author=contributor).count()
+        self.assertEqual(comment_count, 0)
+
+        request = RequestFactory().post(target, valid_comment_data)
+        request.user = contributor.user
+        response = thesis_detail(request, thesislink_id=thesislink.id)
+
+        comment_count = Comment.objects.filter(author=contributor).count()
+        self.assertEqual(comment_count, 1)
+
 
 class TestRequestThesisLink(TestCase):
     fixtures = ['groups', 'permissions']
@@ -47,7 +73,8 @@ class TestVetThesisLinkRequests(TestCase):
 
     def setUp(self):
         self.client = Client()
-        self.target = reverse('theses:vet_thesislink_requests')
+        self.thesislink = ThesisLinkFactory()
+        self.target = reverse('theses:vet_thesislink', kwargs={'pk': self.thesislink.id})
 
     def test_response_when_not_logged_in(self):
         response = self.client.get(self.target)
@@ -58,33 +85,59 @@ class TestVetThesisLinkRequests(TestCase):
         A Contributor needs to be in the Vetting Editors group to be able to
         vet submitted thesis links.
         '''
-        # Create ThesisLink to vet.
-        ThesisLinkFactory()
         request = RequestFactory().get(self.target)
         user = UserFactory()
         request.user = user
         self.assertRaises(
-            PermissionDenied, VetThesisLinkRequests.as_view(), request)
+            PermissionDenied, VetThesisLink.as_view(), request, pk=self.thesislink.id)
 
     def test_response_vetting_editor(self):
-        # Create ThesisLink to vet.
-        ThesisLinkFactory()
         request = RequestFactory().get(self.target)
         user = UserFactory()
         user.groups.add(Group.objects.get(name="Vetting Editors"))
         request.user = user
-        response = VetThesisLinkRequests.as_view()(request)
+        response = VetThesisLink.as_view()(request, pk=self.thesislink.id)
         self.assertEqual(response.status_code, 200)
 
-    def test_thesislink_is_vetted_by_correct_contributor(self):
-        # TODO: how to make sure we are vetting the right thesis link?
+    def test_thesislink_is_vetted_by_correct_contributor_and_mail_is_sent(self):
+        contributor = ContributorFactory()
+        contributor.user.groups.add(Group.objects.get(name="Vetting Editors"))
+        post_data = model_form_data(ThesisLinkFactory(), VetThesisLinkForm)
+        post_data["action_option"] = VetThesisLinkForm.ACCEPT
+        target = reverse('theses:vet_thesislink', kwargs={'pk': self.thesislink.id})
+
+        request = RequestFactory().post(target, post_data)
+        request.user = contributor.user
+
+        # I don't know what the following three lines do, but they help make a RequestFactory
+        # work with the messages middleware
+        setattr(request, 'session', 'session')
+        messages = FallbackStorage(request)
+        setattr(request, '_messages', messages)
+
+        response = VetThesisLink.as_view()(request, pk=self.thesislink.id)
+        self.thesislink.refresh_from_db()
+        self.assertEqual(self.thesislink.vetted_by, contributor)
+        self.assertEqual(len(mail.outbox), 1)
+        self.assertEqual(mail.outbox[0].subject, 'SciPost Thesis Link activated')
+
+    def test_thesislink_that_is_refused_is_deleted_and_mail_is_sent(self):
         contributor = ContributorFactory()
         contributor.user.groups.add(Group.objects.get(name="Vetting Editors"))
-        post_data = VetThesisLinkFormFactory().data
+        post_data = model_form_data(ThesisLinkFactory(), VetThesisLinkForm)
+        post_data["action_option"] = VetThesisLinkForm.REFUSE
+        target = reverse('theses:vet_thesislink', kwargs={'pk': self.thesislink.id})
 
-        request = RequestFactory().post(self.target, post_data)
+        request = RequestFactory().post(target, post_data)
         request.user = contributor.user
 
-        response = VetThesisLinkRequests.as_view()(request)
+        # I don't know what the following three lines do, but they help make a RequestFactory
+        # work with the messages middleware
+        setattr(request, 'session', 'session')
+        messages = FallbackStorage(request)
+        setattr(request, '_messages', messages)
 
-        self.assertTrue(False)
+        response = VetThesisLink.as_view()(request, pk=self.thesislink.id)
+        self.assertEqual(ThesisLink.objects.filter(id=self.thesislink.id).count(), 0)
+        self.assertEqual(len(mail.outbox), 1)
+        self.assertEqual(mail.outbox[0].subject, 'SciPost Thesis Link')
diff --git a/theses/urls.py b/theses/urls.py
index 3d3c530e497a0ca6794259b9626530d6a8e8fbc9..a0734fdb52195d0ffaa5864a8ed649fe7129630d 100644
--- a/theses/urls.py
+++ b/theses/urls.py
@@ -9,8 +9,6 @@ urlpatterns = [
     url(r'^browse/(?P<discipline>[a-z]+)/(?P<nrweeksback>[0-9]+)/$', views.browse, name='browse'),
     url(r'^(?P<thesislink_id>[0-9]+)/$', views.thesis_detail, name='thesis'),
     url(r'^request_thesislink$', views.RequestThesisLink.as_view(), name='request_thesislink'),
-    url(r'^vet_thesislink_requests$', views.VetThesisLinkRequests.as_view(),
-        name='vet_thesislink_requests'),
-    url(r'^vet_thesislink_request_ack/(?P<thesislink_id>[0-9]+)$',
-        views.vet_thesislink_request_ack, name='vet_thesislink_request_ack'),
+    url(r'^unvetted_thesislinks$', views.UnvettedThesisLinks.as_view(), name='unvetted_thesislinks'),
+    url(r'^vet_thesislink/(?P<pk>[0-9]+)/$', views.VetThesisLink.as_view(), name='vet_thesislink'),
 ]
diff --git a/theses/views.py b/theses/views.py
index 7e782a4b88b20052e4c52217c4544106c246ced6..34c9f035e737fb1aea998b2530b806184b105746 100644
--- a/theses/views.py
+++ b/theses/views.py
@@ -11,7 +11,8 @@ from django.core.urlresolvers import reverse, reverse_lazy
 from django.http import HttpResponse, HttpResponseRedirect
 from django.views.decorators.csrf import csrf_protect
 from django.db.models import Avg
-from django.views.generic.edit import CreateView, FormView
+from django.views.generic.edit import CreateView, FormView, UpdateView
+from django.views.generic.list import ListView
 from django.utils.decorators import method_decorator
 
 from .models import *
@@ -20,7 +21,7 @@ from .forms import *
 from comments.models import Comment
 from comments.forms import CommentForm
 from scipost.forms import TITLE_CHOICES, AuthenticationForm
-
+import strings
 
 title_dict = dict(TITLE_CHOICES)  # Convert titles for use in emails
 
@@ -44,108 +45,39 @@ class RequestThesisLink(CreateView):
 
 @method_decorator(permission_required(
     'scipost.can_vet_thesislink_requests', raise_exception=True), name='dispatch')
-class VetThesisLinkRequests(FormView):
-    form_class = VetThesisLinkForm
-    template_name = 'theses/vet_thesislink_requests.html'
-    # TODO: not right yet
-    success_url = reverse_lazy('theses:vet_thesislink_requests')
+class UnvettedThesisLinks(ListView):
+    model = ThesisLink
+    template_name = 'theses/unvetted_thesislinks.html'
+    context_object_name = 'thesislinks'
+    queryset = ThesisLink.objects.filter(vetted=False)
 
-    def get_context_data(self, **kwargs):
-        context = super(VetThesisLinkRequests, self).get_context_data(**kwargs)
-        context['thesislink_to_vet'] = self.thesislink_to_vet()
-        return context
 
-    def thesislink_to_vet(self):
-        return ThesisLink.objects.filter(vetted=False).first()
+@method_decorator(permission_required(
+    'scipost.can_vet_thesislink_requests', raise_exception=True), name='dispatch')
+class VetThesisLink(UpdateView):
+    model = ThesisLink
+    form_class = VetThesisLinkForm
+    template_name = "theses/vet_thesislink.html"
+    success_url = reverse_lazy('theses:unvetted_thesislinks')
 
     def form_valid(self, form):
-        form.vet_request(self.thesislink_to_vet())
-        return super(VetThesisLinkRequests, self).form_valid(form)
-
-
-# @permission_required('scipost.can_vet_thesislink_requests', raise_exception=True)
-# def vet_thesislink_requests(request):
-#     contributor = Contributor.objects.get(user=request.user)
-#     thesislink_to_vet = ThesisLink.objects.filter(
-#         vetted=False).first()  # only handle one at a time
-#     form = VetThesisLinkForm()
-#     context = {'contributor': contributor, 'thesislink_to_vet': thesislink_to_vet, 'form': form}
-#     return render(request, 'theses/vet_thesislink_requests.html', context)
-
-
-@permission_required('scipost.can_vet_thesislink_requests', raise_exception=True)
-def vet_thesislink_request_ack(request, thesislink_id):
-    if request.method == 'POST':
-        form = VetThesisLinkForm(request.POST)
-        thesislink = ThesisLink.objects.get(pk=thesislink_id)
-        if form.is_valid():
-            if form.cleaned_data['action_option'] == '1':
-                thesislink.vetted = True
-                thesislink.vetted_by = Contributor.objects.get(user=request.user)
-                thesislink.save()
-                email_text = ('Dear ' + title_dict[thesislink.requested_by.title] + ' '
-                              + thesislink.requested_by.user.last_name
-                              + ', \n\nThe Thesis Link you have requested, concerning thesis with title '
-                              + thesislink.title + ' by ' + thesislink.author
-                              + ', has been activated at https://scipost.org/thesis/'
-                              + str(thesislink.id) + '.'
-                              + '\n\nThank you for your contribution, \nThe SciPost Team.')
-                emailmessage = EmailMessage('SciPost Thesis Link activated', email_text,
-                                            'SciPost Theses <theses@scipost.org>',
-                                            [thesislink.requested_by.user.email],
-                                            ['theses@scipost.org'],
-                                            reply_to=['theses@scipost.org'])
-                emailmessage.send(fail_silently=False)
-            elif form.cleaned_data['action_option'] == '0':
-                # re-edit the form starting from the data provided
-                form2 = RequestThesisLinkForm(initial={'title': thesislink.pub_title,
-                                                       'pub_ink': thesislink.pub_link,
-                                                       'author': thesislink.author,
-                                                       'institution': thesislink.institution,
-                                                       'defense_date': thesislink.defense_date,
-                                                       'abstract': thesislink.abstract})
-                thesislink.delete()
-                email_text = ('Dear ' + title_dict[thesislink.requested_by.title] + ' '
-                              + thesislink.requested_by.user.last_name
-                              + ', \n\nThe Thesis Link you have requested, concerning thesis with title '
-                              + thesislink.title + ' by ' + thesislink.author_list
-                              + ', has been activated '
-                              '(with slight modifications to your submitted details) at '
-                              'https://scipost.org/thesis/' + str(thesislink.id) + '.'
-                              '\n\nThank you for your contribution, \nThe SciPost Team.')
-                emailmessage = EmailMessage('SciPost Thesis Link activated', email_text,
-                                            'SciPost Theses <theses@scipost.org>',
-                                            [thesislink.requested_by.user.email],
-                                            ['theses@scipost.org'],
-                                            reply_to=['theses@scipost.org'])
-                # Don't send email yet... only when option 1 has succeeded!
-                # emailmessage.send(fail_silently=False)
-                context = {'form': form2}
-                return render(request, 'theses/request_thesislink.html', context)
-            elif form.cleaned_data['action_option'] == '2':
-                email_text = ('Dear ' + title_dict[thesislink.requested_by.title] + ' '
-                              + thesislink.requested_by.user.last_name
-                              + ', \n\nThe Thesis Link you have requested, concerning thesis with title '
-                              + thesislink.title + ' by ' + thesislink.author
-                              + ', has not been activated for the following reason: '
-                              + form.cleaned_data['refusal_reason']
-                              + '.\n\nThank you for your interest, \nThe SciPost Team.')
-                if form.cleaned_data['justification']:
-                    email_text += '\n\nFurther explanations: ' + \
-                        form.cleaned_data['justification']
-                emailmessage = EmailMessage('SciPost Thesis Link', email_text,
-                                            'SciPost Theses <theses@scipost.org>',
-                                            [thesislink.requested_by.user.email],
-                                            ['theses@scipost.org'],
-                                            reply_to=['theses@scipost.org'])
-                emailmessage.send(fail_silently=False)
-                thesislink.delete()
-
-    context = {'ack_header': 'Thesis Link request vetted.',
-               'followup_message': 'Return to the ',
-               'followup_link': reverse('theses:vet_thesislink_requests'),
-               'followup_link_label': 'Thesis Link requests page'}
-    return render(request, 'scipost/acknowledgement.html', context)
+        # I totally override the form_valid method. I do not call super.
+        # This is because, by default, an UpdateView saves the object as instance,
+        # which it builds from the form data. So, the changes (by whom the thesis link was vetted, etc.)
+        # would be lost. Instead, we need the form to save with commit=False, then modify
+        # the vetting fields, and then save.
+
+        # Builds model that reflects changes made during update. Does not yet save.
+        self.object = form.save(commit=False)
+        # Process vetting actions (object already gets saved.)
+        form.vet_request(self.object, self.request.user)
+        # Save again.
+        self.object.save()
+
+        messages.add_message(
+            self.request, messages.SUCCESS,
+            strings.acknowledge_vet_thesis_link)
+        return HttpResponseRedirect(self.get_success_url())
 
 
 def theses(request):