diff --git a/commentaries/factories.py b/commentaries/factories.py
new file mode 100644
index 0000000000000000000000000000000000000000..f0bae1a0912e9a18ceb0c6f079918c595534948b
--- /dev/null
+++ b/commentaries/factories.py
@@ -0,0 +1,33 @@
+import factory
+
+from .models import Commentary, COMMENTARY_TYPES
+
+from scipost.constants import SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS
+from scipost.factories import ContributorFactory
+from journals.models import SCIPOST_JOURNALS_DOMAINS
+
+
+class CommentaryFactory(factory.django.DjangoModelFactory):
+    class Meta:
+        model = Commentary
+        abstract = True
+
+    requested_by = factory.SubFactory(ContributorFactory)
+    vetted_by = factory.SubFactory(ContributorFactory)
+    type = COMMENTARY_TYPES[0][0]
+    discipline = SCIPOST_DISCIPLINES[0][0]
+    domain = SCIPOST_JOURNALS_DOMAINS[0][0]
+    subject_area = SCIPOST_SUBJECT_AREAS[0][1][0][0]
+    pub_title = factory.Sequence(lambda n: "Commentary %d" % n)
+    pub_DOI = '10.1103/PhysRevB.92.214427'
+    arxiv_identifier = '1610.06911v1'
+    author_list = factory.Faker('name')
+    pub_abstract = factory.Faker('text')
+
+
+class EmptyCommentaryFactory(CommentaryFactory):
+    pub_DOI = None
+    arxiv_identifier = None
+
+class VettedCommentaryFactory(CommentaryFactory):
+    vetted = True
diff --git a/commentaries/forms.py b/commentaries/forms.py
index 14cd5852633ca67586f10bb5cf3e39f7fcefd1ea..f0dcdf3a30c8497a8d19650abc9fda2c291bda45 100644
--- a/commentaries/forms.py
+++ b/commentaries/forms.py
@@ -1,7 +1,9 @@
 from django import forms
+from django.shortcuts import get_object_or_404
 
 from .models import Commentary
 
+from scipost.models import Contributor
 
 COMMENTARY_ACTION_CHOICES = (
     (0, 'modify'),
@@ -17,10 +19,12 @@ COMMENTARY_REFUSAL_CHOICES = (
     )
 commentary_refusal_dict = dict(COMMENTARY_REFUSAL_CHOICES)
 
+
 class DOIToQueryForm(forms.Form):
     doi = forms.CharField(widget=forms.TextInput(
         {'label': 'DOI', 'placeholder': 'ex.: 10.21468/00.000.000000'}))
 
+
 class IdentifierToQueryForm(forms.Form):
     identifier = forms.CharField(widget=forms.TextInput(
         {'label': 'arXiv identifier',
@@ -28,6 +32,8 @@ class IdentifierToQueryForm(forms.Form):
 
 
 class RequestCommentaryForm(forms.ModelForm):
+    existing_commentary = None
+
     class Meta:
         model = Commentary
         fields = ['type', 'discipline', 'domain', 'subject_area',
@@ -38,6 +44,7 @@ class RequestCommentaryForm(forms.ModelForm):
                   'pub_DOI', 'pub_abstract']
 
     def __init__(self, *args, **kwargs):
+        self.user = kwargs.pop('user', None)
         super(RequestCommentaryForm, self).__init__(*args, **kwargs)
         self.fields['metadata'].widget = forms.HiddenInput()
         self.fields['pub_date'].widget.attrs.update({'placeholder': 'Format: YYYY-MM-DD'})
@@ -46,6 +53,44 @@ class RequestCommentaryForm(forms.ModelForm):
         self.fields['pub_DOI'].widget.attrs.update({'placeholder': 'ex.: 10.21468/00.000.000000'})
         self.fields['pub_abstract'].widget.attrs.update({'cols': 100})
 
+    def clean(self, *args, **kwargs):
+        cleaned_data = super(RequestCommentaryForm, self).clean(*args, **kwargs)
+
+        # Either Arxiv-ID or DOI is given
+        if not cleaned_data['arxiv_identifier'] and not cleaned_data['pub_DOI']:
+            msg = ('You must provide either a DOI (for a published paper) '
+                'or an arXiv identifier (for a preprint).')
+            self.add_error('arxiv_identifier', msg)
+            self.add_error('pub_DOI', msg)
+        elif (cleaned_data['arxiv_identifier'] and
+              (Commentary.objects
+               .filter(arxiv_identifier=cleaned_data['arxiv_identifier']).exists())):
+            msg = 'There already exists a Commentary Page on this preprint, see'
+            self.existing_commentary = get_object_or_404(
+                Commentary,
+                arxiv_identifier=cleaned_data['arxiv_identifier'])
+            self.add_error('arxiv_identifier', msg)
+        elif (cleaned_data['pub_DOI'] and
+              Commentary.objects.filter(pub_DOI=cleaned_data['pub_DOI']).exists()):
+            msg = 'There already exists a Commentary Page on this publication, see'
+            self.existing_commentary = get_object_or_404(Commentary, pub_DOI=cleaned_data['pub_DOI'])
+            self.add_error('pub_DOI', msg)
+
+        # Current user is not known
+        if not self.user or not Contributor.objects.filter(user=self.user).exists():
+            self.add_error(None, 'Sorry, current user is not known to SciPost.')
+
+
+    def save(self, *args, **kwargs):
+        """Prefill instance before save"""
+        self.requested_by = Contributor.objects.get(user=self.user)
+        return super(RequestCommentaryForm, self).save(*args, **kwargs)
+
+    def get_existing_commentary(self):
+        """Get Commentary if found after validation"""
+        return self.existing_commentary
+
+
 class VetCommentaryForm(forms.Form):
     action_option = forms.ChoiceField(widget=forms.RadioSelect,
                                       choices=COMMENTARY_ACTION_CHOICES,
@@ -54,7 +99,16 @@ class VetCommentaryForm(forms.Form):
     email_response_field = forms.CharField(widget=forms.Textarea(
         attrs={'rows': 5, 'cols': 40}), label='Justification (optional)', required=False)
 
+
 class CommentarySearchForm(forms.Form):
+    """Search for Commentary specified by user"""
     pub_author = forms.CharField(max_length=100, required=False, label="Author(s)")
-    pub_title_keyword = forms.CharField(max_length=100, label="Title", required=False)
+    pub_title_keyword = forms.CharField(max_length=100, required=False, label="Title")
     pub_abstract_keyword = forms.CharField(max_length=1000, required=False, label="Abstract")
+
+    def search_results(self):
+        """Return all Commentary objects according to search"""
+        return Commentary.objects.vetted(
+            pub_title__icontains=self.cleaned_data['pub_title_keyword'],
+            pub_abstract__icontains=self.cleaned_data['pub_abstract_keyword'],
+            author_list__icontains=self.cleaned_data['pub_author']).order_by('-pub_date')
diff --git a/commentaries/migrations/0013_auto_20161213_2328.py b/commentaries/migrations/0013_auto_20161213_2328.py
new file mode 100644
index 0000000000000000000000000000000000000000..ec27da0367e4f87026cfc64b8131232f62bfb3cc
--- /dev/null
+++ b/commentaries/migrations/0013_auto_20161213_2328.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2016-12-13 22:28
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('commentaries', '0012_remove_commentary_specialization'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='commentary',
+            name='created',
+            field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
+            preserve_default=False,
+        ),
+        migrations.AlterField(
+            model_name='commentary',
+            name='latest_activity',
+            field=models.DateTimeField(auto_now=True),
+        ),
+    ]
diff --git a/commentaries/models.py b/commentaries/models.py
index c216ce3599e806f4ec35c2925dcb8d49c7b3caf2..a42005b4dae74ac4621960f27e9bfc11dcd4bbb0 100644
--- a/commentaries/models.py
+++ b/commentaries/models.py
@@ -1,70 +1,86 @@
 from django.utils import timezone
 from django.db import models
-from django.contrib.auth.models import User
 from django.contrib.postgres.fields import JSONField
 from django.template import Template, Context
 
 from journals.models import SCIPOST_JOURNALS_DOMAINS, SCIPOST_JOURNALS_SPECIALIZATIONS
-from scipost.models import Contributor
-from scipost.models import SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS
-
-
+from scipost.models import TimeStampedModel, Contributor
+from scipost.constants import SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS
 
 COMMENTARY_TYPES = (
     ('published', 'published paper'),
     ('preprint', 'arXiv preprint'),
     )
 
-class Commentary(models.Model):
+
+class CommentaryManager(models.Manager):
+    def vetted(self, **kwargs):
+        return self.filter(vetted=True, **kwargs)
+
+    def awaiting_vetting(self, **kwargs):
+        return self.filter(vetted=False, **kwargs)
+
+
+class Commentary(TimeStampedModel):
     """
     A Commentary contains all the contents of a SciPost Commentary page for a given publication.
     """
-    requested_by = models.ForeignKey (Contributor, blank=True, null=True,
-                                      on_delete=models.CASCADE, related_name='requested_by')
+    requested_by = models.ForeignKey(
+        Contributor, blank=True, null=True,
+        on_delete=models.CASCADE, related_name='requested_by')
     vetted = models.BooleanField(default=False)
-    vetted_by = models.ForeignKey (Contributor, blank=True, null=True, on_delete=models.CASCADE)
-    type = models.CharField(max_length=9, choices=COMMENTARY_TYPES) # published paper or arxiv preprint
+    vetted_by = models.ForeignKey(Contributor, blank=True, null=True, on_delete=models.CASCADE)
+    type = models.CharField(max_length=9, choices=COMMENTARY_TYPES)
     discipline = models.CharField(max_length=20, choices=SCIPOST_DISCIPLINES, default='physics')
     domain = models.CharField(max_length=3, choices=SCIPOST_JOURNALS_DOMAINS)
-#    specialization = models.CharField(max_length=1, choices=SCIPOST_JOURNALS_SPECIALIZATIONS)
-    subject_area = models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS, default='Phys:QP')
+    subject_area = models.CharField(
+        max_length=10, choices=SCIPOST_SUBJECT_AREAS,
+        default='Phys:QP')
     open_for_commenting = models.BooleanField(default=True)
     pub_title = models.CharField(max_length=300, verbose_name='title')
-    arxiv_identifier = models.CharField(max_length=100,
-                                        verbose_name="arXiv identifier (including version nr)",
-                                        blank=True, null=True)
+    arxiv_identifier = models.CharField(
+        max_length=100, verbose_name="arXiv identifier (including version nr)",
+        blank=True, null=True)
     arxiv_link = models.URLField(verbose_name='arXiv link (including version nr)', blank=True)
-    pub_DOI = models.CharField(max_length=200, verbose_name='DOI of the original publication',
-                               blank=True, null=True)
-    pub_DOI_link = models.URLField(verbose_name='DOI link to the original publication', blank=True)
+    pub_DOI = models.CharField(
+        max_length=200, verbose_name='DOI of the original publication',
+        blank=True, null=True)
+    pub_DOI_link = models.URLField(
+        verbose_name='DOI link to the original publication',
+        blank=True)
     metadata = JSONField(default={}, blank=True, null=True)
     arxiv_or_DOI_string = models.CharField(
         max_length=100,
         verbose_name='string form of arxiv nr or DOI for commentary url',
         default='')
     author_list = models.CharField(max_length=1000)
+
     # Authors which have been mapped to contributors:
-    authors = models.ManyToManyField (Contributor, blank=True,
-                                      related_name='authors_com')
-    authors_claims = models.ManyToManyField (Contributor, blank=True,
-                                             related_name='authors_com_claims')
-    authors_false_claims = models.ManyToManyField (Contributor, blank=True,
-                                                   related_name='authors_com_false_claims')
+    authors = models.ManyToManyField(
+        Contributor, blank=True,
+        related_name='authors_com')
+    authors_claims = models.ManyToManyField(
+        Contributor, blank=True,
+        related_name='authors_com_claims')
+    authors_false_claims = models.ManyToManyField(
+        Contributor, blank=True,
+        related_name='authors_com_false_claims')
     journal = models.CharField(max_length=300, blank=True, null=True)
     volume = models.CharField(max_length=50, blank=True, null=True)
     pages = models.CharField(max_length=50, blank=True, null=True)
-    pub_date = models.DateField(verbose_name='date of original publication', blank=True, null=True)
+    pub_date = models.DateField(
+        verbose_name='date of original publication',
+        blank=True, null=True)
     pub_abstract = models.TextField(verbose_name='abstract')
-    latest_activity = models.DateTimeField(default=timezone.now)
+
+    objects = CommentaryManager()
 
     class Meta:
         verbose_name_plural = 'Commentaries'
 
-
     def __str__(self):
         return self.pub_title
 
-
     def header_as_table(self):
         # for display in Commentary page itself
         header = ('<table>'
@@ -93,8 +109,8 @@ class Commentary(models.Model):
         header += '</table>'
         template = Template(header)
         context = Context({
-                'pub_title': self.pub_title, 'author_list': self.author_list,
-                })
+            'pub_title': self.pub_title, 'author_list': self.author_list,
+        })
         if self.type == 'published':
             context['journal'] = self.journal
             context['volume'] = self.volume
@@ -105,7 +121,6 @@ class Commentary(models.Model):
             context['arxiv_link'] = self.arxiv_link
         return template.render(context)
 
-
     def header_as_li(self):
         # for display in search lists
         context = Context({'scipost_url': self.scipost_url(), 'pub_title': self.pub_title,
@@ -136,7 +151,6 @@ class Commentary(models.Model):
 
         return template.render(context)
 
-
     def simple_header_as_li(self):
         # for display in Lists
         context = Context({'scipost_url': self.scipost_url(), 'pub_title': self.pub_title,
@@ -158,7 +172,6 @@ class Commentary(models.Model):
         template = Template(header)
         return template.render(context)
 
-
     def parse_links_into_urls(self):
         """ Takes the arXiv nr or DOI and turns it into the urls """
         if self.pub_DOI:
diff --git a/commentaries/test_forms.py b/commentaries/test_forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..694ce19ec56c93503b879a51038f068b06947f7c
--- /dev/null
+++ b/commentaries/test_forms.py
@@ -0,0 +1,42 @@
+import factory
+
+from django.test import TestCase
+
+from scipost.factories import UserFactory
+
+from .factories import VettedCommentaryFactory
+from .forms import RequestCommentaryForm
+from common.helpers import model_form_data
+
+
+class TestRequestCommentaryForm(TestCase):
+    fixtures = ['permissions', 'groups']
+
+    def setUp(self):
+        factory_instance = VettedCommentaryFactory.build()
+        self.user = UserFactory()
+        self.valid_form_data = model_form_data(factory_instance, RequestCommentaryForm)
+
+    def test_valid_data_is_valid_for_arxiv(self):
+        """Test valid form for Arxiv identifier"""
+        form_data = self.valid_form_data
+        form_data['pub_DOI'] = ''
+        form = RequestCommentaryForm(form_data, user=self.user)
+        self.assertTrue(form.is_valid())
+
+    def test_valid_data_is_valid_for_DOI(self):
+        """Test valid form for DOI"""
+        form_data = self.valid_form_data
+        form_data['arxiv_identifier'] = ''
+        form = RequestCommentaryForm(form_data, user=self.user)
+        self.assertTrue(form.is_valid())
+
+    # def test_form_has_no_identifiers(self):
+    #     """Test invalid form has no DOI nor Arxiv ID"""
+    #     form_data = self.valid_form_data
+    #     form_data['pub_DOI'] = ''
+    #     form_data['arxiv_identifier'] = ''
+    #     form = RequestCommentaryForm(form_data, user=self.user)
+    #     form_response = form.is_valid()
+    #     print(form_response)
+    #     self.assertFormError(form_response, form, 'arxiv_identifier', None)
diff --git a/commentaries/test_models.py b/commentaries/test_models.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e9cb5f6ba351402af656aec1be5d9ac257bc5c0
--- /dev/null
+++ b/commentaries/test_models.py
@@ -0,0 +1 @@
+from django.test import TestCase
diff --git a/commentaries/test_views.py b/commentaries/test_views.py
new file mode 100644
index 0000000000000000000000000000000000000000..e6cc591f82f92345fe466d9dad9a23fe0d24063f
--- /dev/null
+++ b/commentaries/test_views.py
@@ -0,0 +1,29 @@
+from django.contrib.auth.models import Group
+from django.core.urlresolvers import reverse
+from django.test import TestCase
+
+class RequestCommentaryTest(TestCase):
+    """Test cases for `request_commentary` view method"""
+    fixtures = ['permissions', 'groups', 'contributors']
+
+    def setUp(self):
+        self.view_url = reverse('commentaries:request_commentary')
+        self.login_url = reverse('scipost:login')
+        self.redirected_login_url = '%s?next=%s' % (self.login_url, self.view_url)
+
+    def test_get_requests(self):
+        """Test different GET requests on view"""
+        # Anoymous user should redirect to login page
+        request = self.client.get(self.view_url)
+        self.assertRedirects(request, self.redirected_login_url)
+
+        # Registered Contributor should get 200
+        self.client.login(username="Test", password="testpw")
+        request = self.client.get(self.view_url)
+        self.assertEquals(request.status_code, 200)
+
+    def test_post_invalid_forms(self):
+        """Test different kind of invalid RequestCommentaryForm submits"""
+        self.client.login(username="Test", password="testpw")
+        request = self.client.post(self.view_url)
+        self.assertEquals(request.status_code, 200)
diff --git a/commentaries/views.py b/commentaries/views.py
index 88f4d7c69b97e0c58870415078bd703ea2afa578..506fd838fc126d015f7c7144b29e4f73a4340b0f 100644
--- a/commentaries/views.py
+++ b/commentaries/views.py
@@ -6,15 +6,12 @@ import requests
 from django.db.models import Q
 from django.utils import timezone
 from django.shortcuts import get_object_or_404, render
-from django.contrib.auth import authenticate, login, logout
+from django.contrib.auth import login, logout
 from django.contrib.auth.decorators import login_required, permission_required
-from django.contrib.auth.models import User
 from django.core.mail import EmailMessage
 from django.core.urlresolvers import reverse
-from django.http import HttpResponse, HttpResponseRedirect
+from django.http import HttpResponse
 from django.shortcuts import redirect
-from django.views.decorators.csrf import csrf_protect
-from django.db.models import Avg
 
 from .models import Commentary
 from .forms import RequestCommentaryForm, DOIToQueryForm, IdentifierToQueryForm
@@ -35,64 +32,29 @@ from scipost.forms import AuthenticationForm
 @login_required
 @permission_required('scipost.can_request_commentary_pages', raise_exception=True)
 def request_commentary(request):
+    form = RequestCommentaryForm(request.POST or None, user=request.user)
     if request.method == 'POST':
-        form = RequestCommentaryForm(request.POST)
         if form.is_valid():
-            errormessage = ''
-            existing_commentary = None
-            if not form.cleaned_data['arxiv_identifier'] and not form.cleaned_data['pub_DOI']:
-                errormessage = ('You must provide either a DOI (for a published paper) '
-                                'or an arXiv identifier (for a preprint).')
-            elif (form.cleaned_data['arxiv_identifier'] and
-                  (Commentary.objects
-                   .filter(arxiv_identifier=form.cleaned_data['arxiv_identifier']).exists())):
-                errormessage = 'There already exists a Commentary Page on this preprint, see'
-                existing_commentary = get_object_or_404(
-                    Commentary,
-                    arxiv_identifier=form.cleaned_data['arxiv_identifier'])
-            elif (form.cleaned_data['pub_DOI'] and
-                  Commentary.objects.filter(pub_DOI=form.cleaned_data['pub_DOI']).exists()):
-                errormessage = 'There already exists a Commentary Page on this publication, see'
-                existing_commentary = get_object_or_404(Commentary, pub_DOI=form.cleaned_data['pub_DOI'])
-            if errormessage:
-                doiform = DOIToQueryForm()
-                identifierform = IdentifierToQueryForm()
-                context = {'form': form, 'doiform': doiform, 'identifierform': identifierform,
-                           'errormessage': errormessage,
-                           'existing_commentary': existing_commentary}
-                return render(request, 'commentaries/request_commentary.html', context)
-            
-            # Otherwise we can create the Commentary
-            contributor = Contributor.objects.get(user=request.user)
-            commentary = Commentary (
-                requested_by = contributor,
-                type = form.cleaned_data['type'],
-                discipline = form.cleaned_data['discipline'],
-                domain = form.cleaned_data['domain'],
-                subject_area = form.cleaned_data['subject_area'],
-                pub_title = form.cleaned_data['pub_title'],
-                arxiv_identifier = form.cleaned_data['arxiv_identifier'],
-                pub_DOI = form.cleaned_data['pub_DOI'],
-                metadata = form.cleaned_data['metadata'],
-                author_list = form.cleaned_data['author_list'],
-                journal = form.cleaned_data['journal'],
-                volume = form.cleaned_data['volume'],
-                pages = form.cleaned_data['pages'],
-                pub_date = form.cleaned_data['pub_date'],
-                pub_abstract = form.cleaned_data['pub_abstract'],
-                latest_activity = timezone.now(),
-                )
+            commentary = form.save(commit=False)
             commentary.parse_links_into_urls()
             commentary.save()
-            
+
             context = {'ack_header': 'Thank you for your request for a Commentary Page',
                        'ack_message': 'Your request will soon be handled by an Editor. ',
                        'followup_message': 'Return to your ',
                        'followup_link': reverse('scipost:personal_page'),
                        'followup_link_label': 'personal page'}
             return render(request, 'scipost/acknowledgement.html', context)
-    else:
-        form = RequestCommentaryForm()
+
+        else:
+            doiform = DOIToQueryForm()
+            existing_commentary = form.get_existing_commentary()
+            identifierform = IdentifierToQueryForm()
+            context = {'form': form, 'doiform': doiform, 'identifierform': identifierform,
+                       'errormessage': form.errors,
+                       'existing_commentary': existing_commentary}
+            return render(request, 'commentaries/request_commentary.html', context)
+
     doiform = DOIToQueryForm()
     identifierform = IdentifierToQueryForm()
     context = {'form': form, 'doiform': doiform, 'identifierform': identifierform}
@@ -120,7 +82,7 @@ def prefill_using_DOI(request):
                            'errormessage': errormessage,
                            'existing_commentary': existing_commentary}
                 return render(request, 'commentaries/request_commentary.html', context)
-            
+
             # Otherwise we query Crossref for the information:
             try:
                 queryurl = 'http://api.crossref.org/works/%s' % doiform.cleaned_data['doi']
@@ -133,12 +95,12 @@ def prefill_using_DOI(request):
                 for author in doiqueryJSON['message']['author'][1:]:
                     authorlist += ', ' + author['given'] + ' ' + author['family']
                 journal = doiqueryJSON['message']['container-title'][0]
-                
+
                 try:
                     volume = doiqueryJSON['message']['volume']
                 except KeyError:
                     volume = ''
-                
+
                 pages = ''
                 try:
                     pages = doiqueryJSON['message']['article-number'] # for Phys Rev
@@ -148,7 +110,7 @@ def prefill_using_DOI(request):
                     pages = doiqueryJSON['message']['page']
                 except KeyError:
                     pass
-                
+
                 pub_date = ''
                 try:
                     pub_date = (str(doiqueryJSON['message']['issued']['date-parts'][0][0]) + '-' +
@@ -209,7 +171,7 @@ def prefill_using_identifier(request):
                 queryurl = ('http://export.arxiv.org/api/query?id_list=%s'
                             % identifierform.cleaned_data['identifier'])
                 arxivquery = feedparser.parse(queryurl)
-                
+
                 # If paper has been published, should comment on published version
                 try:
                     arxiv_journal_ref = arxivquery['entries'][0]['arxiv_journal_ref']
@@ -223,7 +185,7 @@ def prefill_using_identifier(request):
                                     + '. Please comment on the published version.')
                 except (IndexError, KeyError):
                     pass
-                
+
                 if errormessage:
                     form = RequestCommentaryForm()
                     doiform = DOIToQueryForm()
@@ -231,7 +193,7 @@ def prefill_using_identifier(request):
                                'errormessage': errormessage,
                                'existing_commentary': existing_commentary}
                     return render(request, 'commentaries/request_commentary.html', context)
-                
+
                 # otherwise prefill the form:
                 metadata = arxivquery
                 pub_title = arxivquery['entries'][0]['title']
@@ -265,7 +227,7 @@ def prefill_using_identifier(request):
 @permission_required('scipost.can_vet_commentary_requests', raise_exception=True)
 def vet_commentary_requests(request):
     contributor = Contributor.objects.get(user=request.user)
-    commentary_to_vet = Commentary.objects.filter(vetted=False).first() # only handle one at a time
+    commentary_to_vet = Commentary.objects.awaiting_vetting().first() # only handle one at a time
     form = VetCommentaryForm()
     context = {'contributor': contributor, 'commentary_to_vet': commentary_to_vet, 'form': form }
     return render(request, 'commentaries/vet_commentary_requests.html', context)
@@ -348,62 +310,33 @@ def vet_commentary_request_ack(request, commentary_id):
                'followup_link_label': 'Commentary requests page'}
     return render(request, 'scipost/acknowledgement.html', context)
 
-
 def commentaries(request):
-    if request.method == 'POST':
-        form = CommentarySearchForm(request.POST)
-        if form.is_valid() and form.has_changed():
-            commentary_search_list = Commentary.objects.filter(
-                pub_title__icontains=form.cleaned_data['pub_title_keyword'],
-                author_list__icontains=form.cleaned_data['pub_author'],
-                pub_abstract__icontains=form.cleaned_data['pub_abstract_keyword'],
-                vetted=True,
-                )
-            commentary_search_list.order_by('-pub_date')
-        else:
-            commentary_search_list = []
-
+    """List and search all commentaries"""
+    form = CommentarySearchForm(request.POST or None)
+    if form.is_valid() and form.has_changed():
+        commentary_search_list = form.search_results()
     else:
-        form = CommentarySearchForm()
         commentary_search_list = []
 
-    comment_recent_list = (Comment.objects.filter(status='1')
-                           .order_by('-date_submitted')[:10])
-
-    commentary_recent_list = (Commentary.objects.filter(vetted=True)
-                              .order_by('-latest_activity')[:10])
-    context = {'form': form, 'commentary_search_list': commentary_search_list,
-               'comment_recent_list': comment_recent_list,
-               'commentary_recent_list': commentary_recent_list }
+    comment_recent_list = Comment.objects.filter(status='1').order_by('-date_submitted')[:10]
+    commentary_recent_list = Commentary.objects.vetted().order_by('-latest_activity')[:10]
+    context = {
+        'form': form, 'commentary_search_list': commentary_search_list,
+        'comment_recent_list': comment_recent_list,
+        'commentary_recent_list': commentary_recent_list}
     return render(request, 'commentaries/commentaries.html', context)
 
-
 def browse(request, discipline, nrweeksback):
-    if request.method == 'POST':
-        form = CommentarySearchForm(request.POST)
-        if form.is_valid() and form.has_changed():
-            commentary_search_list = Commentary.objects.filter(
-                pub_title__icontains=form.cleaned_data['pub_title_keyword'],
-                author_list__icontains=form.cleaned_data['pub_author'],
-                pub_abstract__icontains=form.cleaned_data['pub_abstract_keyword'],
-                vetted=True,
-                )
-            commentary_search_list.order_by('-pub_date')
-        else:
-            commentary_search_list = []
-        context = {'form': form, 'commentary_search_list': commentary_search_list}
-        return HttpResponseRedirect(request, 'commentaries/commentaries.html', context)
-    else:
-        form = CommentarySearchForm()
-    commentary_browse_list = Commentary.objects.filter(
-        vetted=True, discipline=discipline,
-        latest_activity__gte=timezone.now() + datetime.timedelta(weeks=-int(nrweeksback))
-        )
-    context = {'form': form, 'discipline': discipline, 'nrweeksback': nrweeksback,
-               'commentary_browse_list': commentary_browse_list }
+    """List all commentaries for discipline and period"""
+    commentary_browse_list = Commentary.objects.vetted(
+        discipline=discipline,
+        latest_activity__gte=timezone.now() + datetime.timedelta(weeks=-int(nrweeksback)))
+    context = {
+        'form': CommentarySearchForm(),
+        'discipline': discipline, 'nrweeksback': nrweeksback,
+        'commentary_browse_list': commentary_browse_list}
     return render(request, 'commentaries/commentaries.html', context)
 
-
 def commentary_detail(request, arxiv_or_DOI_string):
     commentary = get_object_or_404(Commentary, arxiv_or_DOI_string=arxiv_or_DOI_string)
     comments = commentary.comment_set.all()
diff --git a/journals/models.py b/journals/models.py
index 7afb01756af51cc4ac083b00d0fbff342e9d2b27..80464164d6915e51439452e8d41f5739657fa91a 100644
--- a/journals/models.py
+++ b/journals/models.py
@@ -3,8 +3,8 @@ from django.db import models
 from django.template import Template, Context
 from django.utils import timezone
 
-from scipost.models import SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS, subject_areas_dict, TITLE_CHOICES
-from scipost.models import ChoiceArrayField, Contributor
+from scipost.constants import SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS, subject_areas_dict
+from scipost.models import ChoiceArrayField, Contributor, TITLE_CHOICES
 
 
 class UnregisteredAuthor(models.Model):
diff --git a/scipost/constants.py b/scipost/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c7933e18e787769f830f999423730a6af836d82
--- /dev/null
+++ b/scipost/constants.py
@@ -0,0 +1,120 @@
+SCIPOST_DISCIPLINES = (
+    ('physics', 'Physics'),
+    ('astrophysics', 'Astrophysics'),
+    ('mathematics', 'Mathematics'),
+    ('computerscience', 'Computer Science'),
+    )
+disciplines_dict = dict(SCIPOST_DISCIPLINES)
+
+SCIPOST_SUBJECT_AREAS = (
+    ('Physics', (
+        ('Phys:AE', 'Atomic, Molecular and Optical Physics - Experiment'),
+        ('Phys:AT', 'Atomic, Molecular and Optical Physics - Theory'),
+        ('Phys:BI', 'Biophysics'),
+        ('Phys:CE', 'Condensed Matter Physics - Experiment'),
+        ('Phys:CT', 'Condensed Matter Physics - Theory'),
+        ('Phys:FD', 'Fluid Dynamics'),
+        ('Phys:GR', 'Gravitation, Cosmology and Astroparticle Physics'),
+        ('Phys:HE', 'High-Energy Physics - Experiment'),
+        ('Phys:HT', 'High-Energy Physics- Theory'),
+        ('Phys:HP', 'High-Energy Physics - Phenomenology'),
+        ('Phys:MP', 'Mathematical Physics'),
+        ('Phys:NE', 'Nuclear Physics - Experiment'),
+        ('Phys:NT', 'Nuclear Physics - Theory'),
+        ('Phys:QP', 'Quantum Physics'),
+        ('Phys:SM', 'Statistical and Soft Matter Physics'),
+        )
+     ),
+    ('Astrophysics', (
+        ('Astro:GA', 'Astrophysics of Galaxies'),
+        ('Astro:CO', 'Cosmology and Nongalactic Astrophysics'),
+        ('Astro:EP', 'Earth and Planetary Astrophysics'),
+        ('Astro:HE', 'High Energy Astrophysical Phenomena'),
+        ('Astro:IM', 'Instrumentation and Methods for Astrophysics'),
+        ('Astro:SR', 'Solar and Stellar Astrophysics'),
+        )
+     ),
+    ('Mathematics', (
+        ('Math:AG', 'Algebraic Geometry'),
+        ('Math:AT', 'Algebraic Topology'),
+        ('Math:AP', 'Analysis of PDEs'),
+        ('Math:CT', 'Category Theory'),
+        ('Math:CA', 'Classical Analysis and ODEs'),
+        ('Math:CO', 'Combinatorics'),
+        ('Math:AC', 'Commutative Algebra'),
+        ('Math:CV', 'Complex Variables'),
+        ('Math:DG', 'Differential Geometry'),
+        ('Math:DS', 'Dynamical Systems'),
+        ('Math:FA', 'Functional Analysis'),
+        ('Math:GM', 'General Mathematics'),
+        ('Math:GN', 'General Topology'),
+        ('Math:GT', 'Geometric Topology'),
+        ('Math:GR', 'Group Theory'),
+        ('Math:HO', 'History and Overview'),
+        ('Math:IT', 'Information Theory'),
+        ('Math:KT', 'K-Theory and Homology'),
+        ('Math:LO', 'Logic'),
+        ('Math:MP', 'Mathematical Physics'),
+        ('Math:MG', 'Metric Geometry'),
+        ('Math:NT', 'Number Theory'),
+        ('Math:NA', 'Numerical Analysis'),
+        ('Math:OA', 'Operator Algebras'),
+        ('Math:OC', 'Optimization and Control'),
+        ('Math:PR', 'Probability'),
+        ('Math:QA', 'Quantum Algebra'),
+        ('Math:RT', 'Representation Theory'),
+        ('Math:RA', 'Rings and Algebras'),
+        ('Math:SP', 'Spectral Theory'),
+        ('Math:ST', 'Statistics Theory'),
+        ('Math:SG', 'Symplectic Geometry'),
+        )
+     ),
+    ('Computer Science', (
+        ('Comp:AI', 'Artificial Intelligence'),
+        ('Comp:CC', 'Computational Complexity'),
+        ('Comp:CE', 'Computational Engineering, Finance, and Science'),
+        ('Comp:CG', 'Computational Geometry'),
+        ('Comp:GT', 'Computer Science and Game Theory'),
+        ('Comp:CV', 'Computer Vision and Pattern Recognition'),
+        ('Comp:CY', 'Computers and Society'),
+        ('Comp:CR', 'Cryptography and Security'),
+        ('Comp:DS', 'Data Structures and Algorithms'),
+        ('Comp:DB', 'Databases'),
+        ('Comp:DL', 'Digital Libraries'),
+        ('Comp:DM', 'Discrete Mathematics'),
+        ('Comp:DC', 'Distributed, Parallel, and Cluster Computing'),
+        ('Comp:ET', 'Emerging Technologies'),
+        ('Comp:FL', 'Formal Languages and Automata Theory'),
+        ('Comp:GL', 'General Literature'),
+        ('Comp:GR', 'Graphics'),
+        ('Comp:AR', 'Hardware Architecture'),
+        ('Comp:HC', 'Human-Computer Interaction'),
+        ('Comp:IR', 'Information Retrieval'),
+        ('Comp:IT', 'Information Theory'),
+        ('Comp:LG', 'Learning'),
+        ('Comp:LO', 'Logic in Computer Science'),
+        ('Comp:MS', 'Mathematical Software'),
+        ('Comp:MA', 'Multiagent Systems'),
+        ('Comp:MM', 'Multimedia'),
+        ('Comp:NI', 'Networking and Internet Architecture'),
+        ('Comp:NE', 'Neural and Evolutionary Computing'),
+        ('Comp:NA', 'Numerical Analysis'),
+        ('Comp:OS', 'Operating Systems'),
+        ('Comp:OH', 'Other Computer Science'),
+        ('Comp:PF', 'Performance'),
+        ('Comp:PL', 'Programming Languages'),
+        ('Comp:RO', 'Robotics'),
+        ('Comp:SI', 'Social and Information Networks'),
+        ('Comp:SE', 'Software Engineering'),
+        ('Comp:SD', 'Sound'),
+        ('Comp:SC', 'Symbolic Computation'),
+        ('Comp:SY', 'Systems and Control'),
+        )
+     ),
+)
+subject_areas_raw_dict = dict(SCIPOST_SUBJECT_AREAS)
+
+# Make dict of the form {'Phys:AT': 'Atomic...', ...}
+subject_areas_dict = {}
+for k in subject_areas_raw_dict.keys():
+    subject_areas_dict.update(dict(subject_areas_raw_dict[k]))
diff --git a/scipost/forms.py b/scipost/forms.py
index 3716ad8d83f4800b3c1e0eed0756279746f16bda..f8d0629bd7684d0ae9d2ee559d420167aadaf496 100644
--- a/scipost/forms.py
+++ b/scipost/forms.py
@@ -11,6 +11,7 @@ from crispy_forms.helper import FormHelper
 from crispy_forms.layout import Layout, Div, Field, Fieldset, HTML, Submit
 
 from .models import *
+from .constants import SCIPOST_DISCIPLINES
 
 from journals.models import Publication
 from submissions.models import SUBMISSION_STATUS_PUBLICLY_UNLISTED
diff --git a/scipost/models.py b/scipost/models.py
index 31e27a84a3986d012b3c6836443ac290619f855b..033c08ec50202e4cc88e200a4db4f8ee523a5be2 100644
--- a/scipost/models.py
+++ b/scipost/models.py
@@ -10,129 +10,10 @@ from django.utils.safestring import mark_safe
 
 from django_countries.fields import CountryField
 
-from scipost.models import *
-
-
-SCIPOST_DISCIPLINES = (
-    ('physics', 'Physics'),
-    ('astrophysics', 'Astrophysics'),
-    ('mathematics', 'Mathematics'),
-    ('computerscience', 'Computer Science'),
-    )
-disciplines_dict = dict(SCIPOST_DISCIPLINES)
-
-SCIPOST_SUBJECT_AREAS = (
-    ('Physics', (
-        ('Phys:AE', 'Atomic, Molecular and Optical Physics - Experiment'),
-        ('Phys:AT', 'Atomic, Molecular and Optical Physics - Theory'),
-        ('Phys:BI', 'Biophysics'),
-        ('Phys:CE', 'Condensed Matter Physics - Experiment'),
-        ('Phys:CT', 'Condensed Matter Physics - Theory'),
-        ('Phys:FD', 'Fluid Dynamics'),
-        ('Phys:GR', 'Gravitation, Cosmology and Astroparticle Physics'),
-        ('Phys:HE', 'High-Energy Physics - Experiment'),
-        ('Phys:HT', 'High-Energy Physics- Theory'),
-        ('Phys:HP', 'High-Energy Physics - Phenomenology'),
-        ('Phys:MP', 'Mathematical Physics'),
-        ('Phys:NE', 'Nuclear Physics - Experiment'),
-        ('Phys:NT', 'Nuclear Physics - Theory'),
-        ('Phys:QP', 'Quantum Physics'),
-        ('Phys:SM', 'Statistical and Soft Matter Physics'),
-        )
-     ),
-    ('Astrophysics', (
-        ('Astro:GA', 'Astrophysics of Galaxies'),
-        ('Astro:CO', 'Cosmology and Nongalactic Astrophysics'),
-        ('Astro:EP', 'Earth and Planetary Astrophysics'),
-        ('Astro:HE', 'High Energy Astrophysical Phenomena'),
-        ('Astro:IM', 'Instrumentation and Methods for Astrophysics'),
-        ('Astro:SR', 'Solar and Stellar Astrophysics'),
-        )
-     ),
-    ('Mathematics', (
-        ('Math:AG', 'Algebraic Geometry'),
-        ('Math:AT', 'Algebraic Topology'),
-        ('Math:AP', 'Analysis of PDEs'),
-        ('Math:CT', 'Category Theory'),
-        ('Math:CA', 'Classical Analysis and ODEs'),
-        ('Math:CO', 'Combinatorics'),
-        ('Math:AC', 'Commutative Algebra'),
-        ('Math:CV', 'Complex Variables'),
-        ('Math:DG', 'Differential Geometry'),
-        ('Math:DS', 'Dynamical Systems'),
-        ('Math:FA', 'Functional Analysis'),
-        ('Math:GM', 'General Mathematics'),
-        ('Math:GN', 'General Topology'),
-        ('Math:GT', 'Geometric Topology'),
-        ('Math:GR', 'Group Theory'),
-        ('Math:HO', 'History and Overview'),
-        ('Math:IT', 'Information Theory'),
-        ('Math:KT', 'K-Theory and Homology'),
-        ('Math:LO', 'Logic'),
-        ('Math:MP', 'Mathematical Physics'),
-        ('Math:MG', 'Metric Geometry'),
-        ('Math:NT', 'Number Theory'),
-        ('Math:NA', 'Numerical Analysis'),
-        ('Math:OA', 'Operator Algebras'),
-        ('Math:OC', 'Optimization and Control'),
-        ('Math:PR', 'Probability'),
-        ('Math:QA', 'Quantum Algebra'),
-        ('Math:RT', 'Representation Theory'),
-        ('Math:RA', 'Rings and Algebras'),
-        ('Math:SP', 'Spectral Theory'),
-        ('Math:ST', 'Statistics Theory'),
-        ('Math:SG', 'Symplectic Geometry'),
-        )
-     ),
-    ('Computer Science', (
-        ('Comp:AI', 'Artificial Intelligence'),
-        ('Comp:CC', 'Computational Complexity'),
-        ('Comp:CE', 'Computational Engineering, Finance, and Science'),
-        ('Comp:CG', 'Computational Geometry'),
-        ('Comp:GT', 'Computer Science and Game Theory'),
-        ('Comp:CV', 'Computer Vision and Pattern Recognition'),
-        ('Comp:CY', 'Computers and Society'),
-        ('Comp:CR', 'Cryptography and Security'),
-        ('Comp:DS', 'Data Structures and Algorithms'),
-        ('Comp:DB', 'Databases'),
-        ('Comp:DL', 'Digital Libraries'),
-        ('Comp:DM', 'Discrete Mathematics'),
-        ('Comp:DC', 'Distributed, Parallel, and Cluster Computing'),
-        ('Comp:ET', 'Emerging Technologies'),
-        ('Comp:FL', 'Formal Languages and Automata Theory'),
-        ('Comp:GL', 'General Literature'),
-        ('Comp:GR', 'Graphics'),
-        ('Comp:AR', 'Hardware Architecture'),
-        ('Comp:HC', 'Human-Computer Interaction'),
-        ('Comp:IR', 'Information Retrieval'),
-        ('Comp:IT', 'Information Theory'),
-        ('Comp:LG', 'Learning'),
-        ('Comp:LO', 'Logic in Computer Science'),
-        ('Comp:MS', 'Mathematical Software'),
-        ('Comp:MA', 'Multiagent Systems'),
-        ('Comp:MM', 'Multimedia'),
-        ('Comp:NI', 'Networking and Internet Architecture'),
-        ('Comp:NE', 'Neural and Evolutionary Computing'),
-        ('Comp:NA', 'Numerical Analysis'),
-        ('Comp:OS', 'Operating Systems'),
-        ('Comp:OH', 'Other Computer Science'),
-        ('Comp:PF', 'Performance'),
-        ('Comp:PL', 'Programming Languages'),
-        ('Comp:RO', 'Robotics'),
-        ('Comp:SI', 'Social and Information Networks'),
-        ('Comp:SE', 'Software Engineering'),
-        ('Comp:SD', 'Sound'),
-        ('Comp:SC', 'Symbolic Computation'),
-        ('Comp:SY', 'Systems and Control'),
-        )
-     ),
-)
-subject_areas_raw_dict = dict(SCIPOST_SUBJECT_AREAS)
+from .constants import SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS,\
+    disciplines_dict, subject_areas_dict
 
-# Make dict of the form {'Phys:AT': 'Atomic...', ...}
-subject_areas_dict = {}
-for k in subject_areas_raw_dict.keys():
-    subject_areas_dict.update(dict(subject_areas_raw_dict[k]))
+from scipost.models import *
 
 
 class ChoiceArrayField(ArrayField):
@@ -179,6 +60,19 @@ TITLE_CHOICES = (
 title_dict = dict(TITLE_CHOICES)
 
 
+class TimeStampedModel(models.Model):
+    """
+    All objects should inherit from this abstract model.
+    This will ensure the creation of created and modified
+    timestamps in the objects.
+    """
+    created = models.DateTimeField(auto_now_add=True)
+    latest_activity = models.DateTimeField(auto_now=True)
+
+    class Meta:
+        abstract = True
+
+
 class Contributor(models.Model):
     """
     All users of SciPost are Contributors.
diff --git a/submissions/models.py b/submissions/models.py
index 68a3d1e867b2720f97fd109dee8cd1d8a18d681a..2ea5a6c80cecdb9cdbc796f30b55e1ec87b17d8a 100644
--- a/submissions/models.py
+++ b/submissions/models.py
@@ -8,8 +8,8 @@ from django.template import Template, Context
 from .models import *
 
 from scipost.models import ChoiceArrayField, Contributor, title_dict, Remark
-from scipost.models import SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS
-from scipost.models import subject_areas_dict, TITLE_CHOICES
+from scipost.constants import SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS, subject_areas_dict
+from scipost.models import TITLE_CHOICES
 from journals.models import SCIPOST_JOURNALS_SUBMIT, SCIPOST_JOURNALS_DOMAINS
 from journals.models import SCIPOST_JOURNALS_SPECIALIZATIONS
 from journals.models import journals_submit_dict, journals_domains_dict, journals_spec_dict
diff --git a/theses/migrations/0006_auto_20161219_2012.py b/theses/migrations/0006_auto_20161219_2012.py
new file mode 100644
index 0000000000000000000000000000000000000000..935f906aacc44850699e1d60558ba9c3fd845f31
--- /dev/null
+++ b/theses/migrations/0006_auto_20161219_2012.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2016-12-19 19:12
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('theses', '0005_remove_thesislink_specialization'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='thesislink',
+            name='domain',
+            field=models.CharField(choices=[('E', 'Experimental'), ('T', 'Theoretical'), ('C', 'Computational'), ('ET', 'Exp. & Theor.'), ('EC', 'Exp. & Comp.'), ('TC', 'Theor. & Comp.'), ('ETC', 'Exp., Theor. & Comp.')], max_length=3),
+        ),
+    ]
diff --git a/theses/models.py b/theses/models.py
index 139948b9175deb6bc62fdc098bef6d3fc4a4ef86..50ec6b1f47548e9f43d6f53d31d091883f4ed091 100644
--- a/theses/models.py
+++ b/theses/models.py
@@ -6,6 +6,7 @@ from django.template import Template, Context
 from .models import *
 
 from journals.models import *
+from scipost.constants import SCIPOST_DISCIPLINES, subject_areas_dict, disciplines_dict
 from scipost.models import *