diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py
index 669f3bc7ed10183e7bcea091ff22551cd217818e..3a236b58512d1010200fe03a6e643579614ad7cc 100644
--- a/SciPost_v1/settings/base.py
+++ b/SciPost_v1/settings/base.py
@@ -93,6 +93,8 @@ INSTALLED_APPS = (
     'submissions',
     'theses',
     'virtualmeetings',
+    'production',
+    'partners',
     'webpack_loader',
 )
 
@@ -121,6 +123,7 @@ SHELL_PLUS_POST_IMPORTS = (
          'VettedCommentaryFactory',
          'UnvettedCommentaryFactory',
          'UnpublishedVettedCommentaryFactory',)),
+    ('scipost.factories', ('ContributorFactory')),
 )
 
 MATHJAX_ENABLED = True
diff --git a/SciPost_v1/urls.py b/SciPost_v1/urls.py
index f930e4a9312d8e0a5326bac188cb63824c27a412..7b29434e2aae982aa946d1363cfefcf35d4f4d79 100644
--- a/SciPost_v1/urls.py
+++ b/SciPost_v1/urls.py
@@ -42,6 +42,9 @@ urlpatterns = [
     url(r'^thesis/', include('theses.urls', namespace="theses")),
     url(r'^meetings/', include('virtualmeetings.urls', namespace="virtualmeetings")),
     url(r'^news/', include('news.urls', namespace="news")),
+    url(r'^production/', include('production.urls', namespace="production")),
+    url(r'^partners/', include('partners.urls', namespace="partners")),
+    url(r'^supporting_partners/', include('partners.urls', namespace="partners")), # Keep temporarily for historical reasons
 ]
 
 if settings.DEBUG:
diff --git a/commentaries/factories.py b/commentaries/factories.py
index 85961e640df59ae58a23bf099b0c2a411c0481db..9cac495d97050431bf7ccbacfdd2475cf7c55dcd 100644
--- a/commentaries/factories.py
+++ b/commentaries/factories.py
@@ -23,6 +23,9 @@ class CommentaryFactory(factory.django.DjangoModelFactory):
     pub_title = factory.Faker('text')
     pub_DOI = factory.Sequence(lambda n: random_external_doi())
     arxiv_identifier = factory.Sequence(lambda n: random_arxiv_identifier_with_version_number())
+    author_list = factory.Faker('name')
+    pub_abstract = factory.Faker('text')
+    pub_date = factory.Faker('date')
     arxiv_link = factory.Faker('uri')
     pub_abstract = factory.lazy_attribute(lambda x: Faker().paragraph())
 
@@ -56,3 +59,7 @@ class UnpublishedVettedCommentaryFactory(VettedCommentaryFactory):
 
 class UnvettedCommentaryFactory(CommentaryFactory):
     vetted = False
+
+class UnvettedArxivPreprintCommentaryFactory(CommentaryFactory):
+    vetted = False
+    pub_DOI = None
diff --git a/commentaries/forms.py b/commentaries/forms.py
index 8d95c58d896ace0b3f84b7b3da68b4b2c6ced1d9..4e77b521a054eae82330c8b8e2f61a2099e01475 100644
--- a/commentaries/forms.py
+++ b/commentaries/forms.py
@@ -2,107 +2,170 @@ import re
 
 from django import forms
 from django.shortcuts import get_object_or_404
+from django.urls import reverse
+from django.utils.safestring import mark_safe
+from django.template.loader import get_template
+from django.template import Context
 
 from .models import Commentary
+from .constants import COMMENTARY_PUBLISHED, COMMENTARY_PREPRINT
 
+from scipost.services import DOICaller, ArxivCaller
 from scipost.models import Contributor
 
+import strings
+
 
 class DOIToQueryForm(forms.Form):
-    doi = forms.CharField(widget=forms.TextInput(
-        {'label': 'DOI', 'placeholder': 'ex.: 10.21468/00.000.000000'}))
+    VALID_DOI_REGEXP = r'^(?i)10.\d{4,9}/[-._;()/:A-Z0-9]+$'
+    doi = forms.RegexField(regex=VALID_DOI_REGEXP, strip=True,
+                           help_text=strings.doi_query_help_text,
+                           error_messages={'invalid': strings.doi_query_invalid},
+                           widget=forms.TextInput({
+                                'label': 'DOI',
+                                'placeholder': strings.doi_query_placeholder
+                                }))
+
+    def clean_doi(self):
+        input_doi = self.cleaned_data['doi']
+
+        commentary = Commentary.objects.filter(pub_DOI=input_doi)
+        if commentary.exists():
+            error_message = get_template('commentaries/_doi_query_commentary_exists.html').render(
+                Context({'arxiv_or_DOI_string': commentary[0].arxiv_or_DOI_string})
+            )
+            raise forms.ValidationError(mark_safe(error_message))
+
+        caller = DOICaller(input_doi)
+        if caller.is_valid:
+            self.crossref_data = DOICaller(input_doi).data
+        else:
+            error_message = 'Could not find a resource for that DOI.'
+            raise forms.ValidationError(error_message)
 
+        return input_doi
 
-class IdentifierToQueryForm(forms.Form):
-    identifier = forms.CharField(widget=forms.TextInput(
-        {'label': 'arXiv identifier',
-         'placeholder': 'new style ####.####(#)v# or old-style e.g. cond-mat/#######'}))
+    def request_published_article_form_prefill_data(self):
+        additional_form_data = {'pub_DOI': self.cleaned_data['doi']}
+        return {**self.crossref_data, **additional_form_data}
 
-    def clean(self, *args, **kwargs):
-        cleaned_data = super(IdentifierToQueryForm, self).clean(*args, **kwargs)
-
-        identifierpattern_new = re.compile("^[0-9]{4,}.[0-9]{4,5}v[0-9]{1,2}$")
-        identifierpattern_old = re.compile("^[-.a-z]+/[0-9]{7,}v[0-9]{1,2}$")
-
-        if not (identifierpattern_new.match(cleaned_data['identifier']) or
-                identifierpattern_old.match(cleaned_data['identifier'])):
-                msg = ('The identifier you entered is improperly formatted '
-                       '(did you forget the version number?)')
-                self.add_error('identifier', msg)
-
-        try:
-            commentary = Commentary.objects.get(arxiv_identifier=cleaned_data['identifier'])
-        except (Commentary.DoesNotExist, KeyError):
-            # Commentary either does not exists or form is invalid
-            commentary = None
-
-        if commentary:
-            msg = 'There already exists a Commentary Page on this preprint, see %s' % (
-                    commentary.title_label())
-            self.add_error('identifier', msg)
-        return cleaned_data
 
+class ArxivQueryForm(forms.Form):
+    IDENTIFIER_PATTERN_NEW = r'^[0-9]{4,}.[0-9]{4,5}v[0-9]{1,2}$'
+    IDENTIFIER_PATTERN_OLD = r'^[-.a-z]+/[0-9]{7,}v[0-9]{1,2}$'
+    VALID_ARXIV_IDENTIFIER_REGEX = "(?:{})|(?:{})".format(IDENTIFIER_PATTERN_NEW, IDENTIFIER_PATTERN_OLD)
 
-class RequestCommentaryForm(forms.ModelForm):
-    """Create new valid Commetary by user request"""
-    existing_commentary = None
+    identifier = forms.RegexField(regex=VALID_ARXIV_IDENTIFIER_REGEX,
+                                  strip=True,
+                                  help_text=strings.arxiv_query_help_text,
+                                  error_messages={'invalid': strings.arxiv_query_invalid},
+                                  widget=forms.TextInput({
+                                        'placeholder': strings.arxiv_query_placeholder}))
+
+    def clean_identifier(self):
+        identifier = self.cleaned_data['identifier']
+
+        commentary = Commentary.objects.filter(arxiv_identifier=identifier)
+        if commentary.exists():
+            error_message = get_template('commentaries/_doi_query_commentary_exists.html').render(
+                Context({'arxiv_or_DOI_string': commentary[0].arxiv_or_DOI_string})
+            )
+            raise forms.ValidationError(mark_safe(error_message))
+
+        caller = ArxivCaller(identifier)
+        if caller.is_valid:
+            self.arxiv_data = ArxivCaller(identifier).data
+        else:
+            error_message = 'Could not find a resource for that arXiv identifier.'
+            raise forms.ValidationError(error_message)
+
+        return identifier
 
+    def request_arxiv_preprint_form_prefill_data(self):
+        additional_form_data = {'arxiv_identifier': self.cleaned_data['identifier']}
+        return {**self.arxiv_data, **additional_form_data}
+
+
+class RequestCommentaryForm(forms.ModelForm):
     class Meta:
         model = Commentary
-        fields = ['type', 'discipline', 'domain', 'subject_area',
-                  'pub_title', 'author_list',
-                  'metadata',
-                  'journal', 'volume', 'pages', 'pub_date',
-                  'arxiv_identifier',
-                  'pub_DOI', 'pub_abstract']
+        fields = [
+            'discipline', 'domain', 'subject_area', 'pub_title',
+            'author_list', 'pub_date', 'pub_abstract'
+        ]
+        placeholders = {
+            'pub_date': 'Format: YYYY-MM-DD'
+        }
 
     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'})
-        self.fields['arxiv_identifier'].widget.attrs.update(
-            {'placeholder': 'ex.:  1234.56789v1 or cond-mat/1234567v1'})
-        self.fields['pub_DOI'].widget.attrs.update({'placeholder': 'ex.: 10.21468/00.000.000000'})
-        self.fields['pub_abstract'].widget.attrs.update({'cols': 100})
+        self.requested_by = kwargs.pop('requested_by', None)
+        super().__init__(*args, **kwargs)
 
-    def clean(self, *args, **kwargs):
-        """Check if form is valid and contains an unique identifier"""
-        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):
+        self.instance.parse_links_into_urls()
+        if self.requested_by:
+            self.instance.requested_by = self.requested_by
+        return super().save(*args, **kwargs)
+
+
+class RequestArxivPreprintForm(RequestCommentaryForm):
+    class Meta(RequestCommentaryForm.Meta):
+        model = Commentary
+        fields = RequestCommentaryForm.Meta.fields + ['arxiv_identifier']
+
+    def __init__(self, *args, **kwargs):
+        super(RequestArxivPreprintForm, self).__init__(*args, **kwargs)
+        # We want arxiv_identifier to be a required field.
+        # Since it can be blank on the model, we have to override this property here.
+        self.fields['arxiv_identifier'].required = True
+
+    # TODO: add regex here?
+    def clean_arxiv_identifier(self):
+        arxiv_identifier = self.cleaned_data['arxiv_identifier']
+
+        commentary = Commentary.objects.filter(arxiv_identifier=arxiv_identifier)
+        if commentary.exists():
+            error_message = get_template('commentaries/_doi_query_commentary_exists.html').render(
+                Context({'arxiv_or_DOI_string': commentary[0].arxiv_or_DOI_string})
+            )
+            raise forms.ValidationError(mark_safe(error_message))
+
+        return arxiv_identifier
 
     def save(self, *args, **kwargs):
-        """Prefill instance before save"""
-        self.instance.requested_by = Contributor.objects.get(user=self.user)
-        return super(RequestCommentaryForm, self).save(*args, **kwargs)
+        self.instance.type = COMMENTARY_PREPRINT
+        return super().save(*args, **kwargs)
+
+
+class RequestPublishedArticleForm(RequestCommentaryForm):
+    class Meta(RequestCommentaryForm.Meta):
+        fields = RequestCommentaryForm.Meta.fields + ['journal', 'volume', 'pages', 'pub_DOI']
+        placeholders = {
+            **RequestCommentaryForm.Meta.placeholders,
+            **{'pub_DOI': 'ex.: 10.21468/00.000.000000'}
+        }
+
+    def __init__(self, *args, **kwargs):
+        super(RequestPublishedArticleForm, self).__init__(*args, **kwargs)
+        # We want pub_DOI to be a required field.
+        # Since it can be blank on the model, we have to override this property here.
+        self.fields['pub_DOI'].required = True
 
-    def get_existing_commentary(self):
-        """Get Commentary if found after validation"""
-        return self.existing_commentary
+    def clean_pub_DOI(self):
+        input_doi = self.cleaned_data['pub_DOI']
+
+        commentary = Commentary.objects.filter(pub_DOI=input_doi)
+        if commentary.exists():
+            error_message = get_template('commentaries/_doi_query_commentary_exists.html').render(
+                Context({'arxiv_or_DOI_string': commentary[0].arxiv_or_DOI_string})
+            )
+            raise forms.ValidationError(mark_safe(error_message))
+
+        return input_doi
+
+    def save(self, *args, **kwargs):
+        self.instance.type = COMMENTARY_PUBLISHED
+        return super().save(*args, **kwargs)
 
 
 class VetCommentaryForm(forms.Form):
@@ -178,6 +241,13 @@ class VetCommentaryForm(forms.Form):
             raise ValueError(('VetCommentaryForm could not be processed '
                               'because the data didn\'t validate'))
 
+    def clean_refusal_reason(self):
+        """`refusal_reason` field is required if action==refuse."""
+        if self.commentary_is_refused():
+            if int(self.cleaned_data['refusal_reason']) == self.REFUSAL_EMPTY:
+                self.add_error('refusal_reason', 'Please, choose a reason for rejection.')
+        return self.cleaned_data['refusal_reason']
+
     def get_commentary(self):
         """Return Commentary if available"""
         self._form_is_cleaned()
@@ -189,25 +259,23 @@ class VetCommentaryForm(forms.Form):
             return self.COMMENTARY_REFUSAL_DICT[int(self.cleaned_data['refusal_reason'])]
 
     def commentary_is_accepted(self):
-        self._form_is_cleaned()
         return int(self.cleaned_data['action_option']) == self.ACTION_ACCEPT
 
     def commentary_is_modified(self):
-        self._form_is_cleaned()
         return int(self.cleaned_data['action_option']) == self.ACTION_MODIFY
 
     def commentary_is_refused(self):
-        self._form_is_cleaned()
         return int(self.cleaned_data['action_option']) == self.ACTION_REFUSE
 
     def process_commentary(self):
         """Vet the commentary or delete it from the database"""
+        # Modified actions are not doing anything. Users are redirected to an edit page instead.
         if self.commentary_is_accepted():
             self.commentary.vetted = True
             self.commentary.vetted_by = Contributor.objects.get(user=self.user)
             self.commentary.save()
             return self.commentary
-        elif self.commentary_is_modified() or self.commentary_is_refused():
+        elif self.commentary_is_refused():
             self.commentary.delete()
             return None
 
diff --git a/commentaries/models.py b/commentaries/models.py
index 51490c7d8fc3c344e9ed2f3104db5a3cb2b80823..da8321b411630469c64122437bb5de53f452a870 100644
--- a/commentaries/models.py
+++ b/commentaries/models.py
@@ -16,11 +16,11 @@ class Commentary(ArxivCallable, 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('scipost.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)
+    vetted_by = models.ForeignKey('scipost.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=DISCIPLINE_PHYSICS)
diff --git a/commentaries/templates/commentaries/_commentary_summary.html b/commentaries/templates/commentaries/_commentary_summary.html
index 681641bf617197f425237dc949a917d76168e69f..be9db8316e26049b3d925a5d52d3886e564b0ccc 100644
--- a/commentaries/templates/commentaries/_commentary_summary.html
+++ b/commentaries/templates/commentaries/_commentary_summary.html
@@ -11,7 +11,7 @@
         <td>As Contributors:</td>
         <td>
             {% for author in commentary.authors.all %}
-                <a href="{% url 'scipost:contributor_info' author.id %}">{{author.user.first_name}} {{author.user.last_name}}</a>
+                {% if not forloop.first %} &middot; {% endif %}<a href="{% url 'scipost:contributor_info' author.id %}">{{author.user.first_name}} {{author.user.last_name}}</a>
             {% empty %}
                 (none claimed)
             {% endfor %}
diff --git a/commentaries/templates/commentaries/_doi_query_commentary_exists.html b/commentaries/templates/commentaries/_doi_query_commentary_exists.html
new file mode 100644
index 0000000000000000000000000000000000000000..2cc621178767dbf6e36cca6df4d874c0d9703ed1
--- /dev/null
+++ b/commentaries/templates/commentaries/_doi_query_commentary_exists.html
@@ -0,0 +1 @@
+There already exists a <a href="{% url 'commentaries:commentary' arxiv_or_DOI_string=arxiv_or_DOI_string %}">Commentary Page</a> on this publication.
diff --git a/commentaries/templates/commentaries/commentary_list.html b/commentaries/templates/commentaries/commentary_list.html
index a8c92a52df708b0f81a43c36bd0b77d6817d63ef..4c1b1269164c93330a273c7137de77a89abc017a 100644
--- a/commentaries/templates/commentaries/commentary_list.html
+++ b/commentaries/templates/commentaries/commentary_list.html
@@ -71,7 +71,7 @@
         {% else %}
           <h2>Search results:</h3>
         {% endif %}
-        {% if object_list %}
+        {% if commentary_list %}
             {% if is_paginated %}
               <p>
               {% if page_obj.has_previous %}
@@ -88,7 +88,7 @@
             <div class="col-12">
 
             <ul class="list-group list-group-flush">
-                {% for object in object_list %}
+                {% for object in commentary_list %}
                     <li class="list-group-item">
                         {% include 'commentaries/_commentary_card_content.html' with commentary=object %}
                     </li>
diff --git a/commentaries/templates/commentaries/modify_commentary_request.html b/commentaries/templates/commentaries/modify_commentary_request.html
new file mode 100644
index 0000000000000000000000000000000000000000..c443012174802059f7e2b35f492ad33aa93dc22c
--- /dev/null
+++ b/commentaries/templates/commentaries/modify_commentary_request.html
@@ -0,0 +1,44 @@
+{% extends 'scipost/_personal_page_base.html' %}
+
+{% load bootstrap %}
+
+{% block pagetitle %}: vet Commentary requests{% endblock pagetitle %}
+
+{% block breadcrumb_items %}
+    {{block.super}}
+    <span class="breadcrumb-item">Vet Commentary Page requests</span>
+{% endblock %}
+
+{% block content %}
+
+<div class="row">
+    <div class="col-12">
+        <h1>SciPost Commentary Page request to modify and accept:</h1>
+    </div>
+</div>
+
+<hr>
+<div class="row">
+    <div class="col-12">
+        {% include 'commentaries/_commentary_summary.html' with commentary=commentary %}
+    </div>
+</div>
+
+<div class="row">
+    <div class="col-12">
+        <h3 class="mt-4">Abstract:</h3>
+        <p>{{ commentary.pub_abstract }}</p>
+    </div>
+</div>
+
+<div class="row">
+    <div class="col-12">
+        <form action="{% url 'commentaries:modify_commentary_request' commentary_id=commentary.id %}" method="post">
+            {% csrf_token %}
+            {{ form|bootstrap }}
+            <input type="submit" class="btn btn-secondary" value="Submit and accept" />
+        </form>
+    </div>
+</div>
+
+{% endblock  %}
diff --git a/commentaries/templates/commentaries/request_arxiv_preprint.html b/commentaries/templates/commentaries/request_arxiv_preprint.html
new file mode 100644
index 0000000000000000000000000000000000000000..2c50e631d5561e8f6094c74fa6edecd4fd23144e
--- /dev/null
+++ b/commentaries/templates/commentaries/request_arxiv_preprint.html
@@ -0,0 +1,41 @@
+{% extends 'scipost/base.html' %}
+{% load bootstrap %}
+{% load scipost_extras %}
+
+{% block pagetitle %}: request Commentary{% endblock pagetitle %}
+
+{% block content %}
+
+<div class="row">
+    <div class="col-12">
+        <div class="card card-grey">
+            <div class="card-block">
+              <h1 class="card-title">Request Activation of a Commentary Page</h1>
+              <a href="{% url 'commentaries:request_published_article' %}">Click here to request a Commentary Page on a published article</a>
+          </div>
+        </div>
+    </div>
+</div>
+
+<div class="row">
+    <div class="col-md-8 offset-md-2">
+        <form action="{% url 'commentaries:prefill_using_arxiv_identifier' %}" method="post">
+          {% csrf_token %}
+          {{ query_form|bootstrap }}
+          <input class="btn btn-secondary" type="submit" value="Query arXiv"/>
+        </form>
+    </div>
+</div>
+
+{# <hr>#}
+<div class="row">
+    <div class="col-md-8 offset-md-2">
+        <form id="requestForm" action="{% url 'commentaries:request_arxiv_preprint' %}" method="post">
+            {% csrf_token %}
+            {{ form|bootstrap }}
+            <input class="btn btn-primary" type="submit" value="Submit"/>
+        </form>
+    </div>
+</div>
+
+{% endblock content%}
diff --git a/commentaries/templates/commentaries/request_commentary.html b/commentaries/templates/commentaries/request_commentary.html
index dec478082ee981283b6a946ff54db43c78383faa..bc71f5b15fc2a196767816fde0e00ee49911a738 100644
--- a/commentaries/templates/commentaries/request_commentary.html
+++ b/commentaries/templates/commentaries/request_commentary.html
@@ -8,112 +8,19 @@
 
 {% block content %}
 
-  <script>
-  $(document).ready(function(){
-
-    var allToggableRows = $('#requestForm .form-group').slice(1)
-    var type_selector = $('select#id_type')
-
-    var preprint = [5,6,7,8,10]
-    var published = [9]
-
-    function show(indices){
-      allToggableRows.each(function(index){
-        if($.inArray( index, indices) != -1){
-          $(this).hide()
-        }else{
-          $(this).show()
-        }
-
-      })
-    }
-
-    switch (type_selector.val()) {
-     case "":
-       allToggableRows.hide()
-       $("#DOIprefill").hide();
-       $("#arXivprefill").hide();
-       break;
-     case "published":
-       show(published)
-       $("#DOIprefill").show()
-       $("#arXivprefill").hide();
-       break;
-     case "preprint":
-       show(preprint)
-       $("#DOIprefill").hide()
-       $("#arXivprefill").show();
-       break;
-     default:
-       allToggableRows.hide()
-       $("#DOIprefill").hide();
-       $("#arXivprefill").hide();
-    }
-
-    type_selector.on('change', function() {
-      var selection = $(this).val();
-      switch(selection){
-        case "published":
-          show(published)
-          $("#DOIprefill").show()
-          $("#arXivprefill").hide();
-          break;
-        case "preprint":
-          show(preprint)
-          $("#DOIprefill").hide()
-          $("#arXivprefill").show();
-          break;
-        default:
-          $("#DOIprefill").hide()
-          $("#arXivprefill").hide();
-          allToggableRows.hide()
-      }
-    });
-  });
-</script>
-
 <div class="row">
     <div class="col-12">
-        <div class="panel">
-            <h1>Request Activation of a Commentary Page:</h1>
-        </div>
+        <h1 class="highlight">Request Activation of a Commentary Page:</h1>
     </div>
-</div>
-<div class="row">
-    <div class="col-md-6 offset-md-3">
-    {% if errormessage %}
-        <h3 style="color: red;">Error: {{ errormessage }}</h3>
-        {% if existing_commentary %}
-            <ul>{% include 'commentaries/_commentary_card_content.html' with object=existing_commentary %}</ul>
-        {% endif %}
-        <br/>
-    {% endif %}
-
-  <div id="DOIprefill">
-    <h3><em>For published papers, you can prefill the form (except for domain, subject area and abstract) using the DOI:</em></h3>
-    <p><em>(give the DOI as 10.[4 to 9 digits]/[string], without prefix, as per the placeholder)</em></p>
-    <form action="{% url 'commentaries:prefill_using_DOI' %}" method="post">
-      {% csrf_token %}
-      {{ doiform|bootstrap }}
-      <input class="btn btn-secondary" type="submit" value="Query DOI"/>
-    </form>
-  </div>
-  <div id="arXivprefill">
-    <h3><em>For preprints, you can prefill the form using the arXiv identifier:</em></h3>
-    <p><em>(give the identifier without prefix, as per the placeholder)</em></p>
-    <form action="{% url 'commentaries:prefill_using_identifier' %}" method="post">
-      {% csrf_token %}
-      {{ identifierform|bootstrap }}
-      <input class="btn btn-secondary" type="submit" value="Query arXiv"/>
-    </form>
-  </div>
-  <br/>
-  <form id="requestForm" action="{% url 'commentaries:request_commentary' %}" method="post">
-    {% csrf_token %}
-      {{ request_commentary_form|bootstrap }}
-    <input class="btn btn-primary" type="submit" value="Submit"/>
-  </form>
-
+    <div class="col-12">
+        <ul>
+            <li>
+                <a href="{% url 'commentaries:request_published_article' %}">Click here to request a Commentary Page on a published article</a>
+            </li>
+            <li>
+                <a href="{% url 'commentaries:request_arxiv_preprint' %}">Click here to request a Commentary Page on an arXiv preprint</a>
+            </li>
+        </ul>
     </div>
 </div>
 
diff --git a/commentaries/templates/commentaries/request_commentary_old.html b/commentaries/templates/commentaries/request_commentary_old.html
new file mode 100644
index 0000000000000000000000000000000000000000..e5c38d7f6b93bf7a5ce8a41d050776222d0a2d35
--- /dev/null
+++ b/commentaries/templates/commentaries/request_commentary_old.html
@@ -0,0 +1,56 @@
+{% extends 'scipost/base.html' %}
+
+{% load bootstrap %}
+
+{% load scipost_extras %}
+
+{% block pagetitle %}: request Commentary{% endblock pagetitle %}
+
+{% block content %}
+
+<div class="row">
+    <div class="col-12">
+        <div class="panel">
+            <h1>Request Activation of a Commentary Page:</h1>
+        </div>
+    </div>
+</div>
+<div class="row">
+    <div class="col-md-6 offset-md-3">
+    {% if errormessage %}
+        <h3 style="color: red;">Error: {{ errormessage }}</h3>
+        {% if existing_commentary %}
+            <ul>{% include 'commentaries/_commentary_card_content.html' with object=existing_commentary %}</ul>
+        {% endif %}
+        <br/>
+    {% endif %}
+
+  <div id="DOIprefill">
+    <h3><em>For published papers, you can prefill the form (except for domain, subject area and abstract) using the DOI:</em></h3>
+    <p><em>(give the DOI as 10.[4 to 9 digits]/[string], without prefix, as per the placeholder)</em></p>
+    <form action="{% url 'commentaries:prefill_using_DOI' %}" method="post">
+      {% csrf_token %}
+      {{ doiform|bootstrap }}
+      <input class="btn btn-secondary" type="submit" value="Query DOI"/>
+    </form>
+  </div>
+  <div id="arXivprefill">
+    <h3><em>For preprints, you can prefill the form using the arXiv identifier:</em></h3>
+    <p><em>(give the identifier without prefix, as per the placeholder)</em></p>
+    <form action="{% url 'commentaries:prefill_using_identifier' %}" method="post">
+      {% csrf_token %}
+      {{ identifierform|bootstrap }}
+      <input class="btn btn-secondary" type="submit" value="Query arXiv"/>
+    </form>
+  </div>
+  <br/>
+  <form id="requestForm" action="{% url 'commentaries:request_commentary' %}" method="post">
+    {% csrf_token %}
+      {{ request_commentary_form|bootstrap }}
+    <input class="btn btn-primary" type="submit" value="Submit"/>
+  </form>
+
+    </div>
+</div>
+
+{% endblock content %}
diff --git a/commentaries/templates/commentaries/request_published_article.html b/commentaries/templates/commentaries/request_published_article.html
new file mode 100644
index 0000000000000000000000000000000000000000..21511e9e5ee37ce74767ee2e92b1b9015dc6885e
--- /dev/null
+++ b/commentaries/templates/commentaries/request_published_article.html
@@ -0,0 +1,41 @@
+{% extends 'scipost/base.html' %}
+{% load bootstrap %}
+{% load scipost_extras %}
+
+{% block pagetitle %}: request Commentary{% endblock pagetitle %}
+
+{% block content %}
+
+
+<div class="row">
+    <div class="col-12">
+        <div class="card card-grey">
+            <div class="card-block">
+              <h1 class="card-title">Request Activation of a Commentary Page</h1>
+              <a href="{% url 'commentaries:request_arxiv_preprint' %}">Click here to request a Commentary Page on an arXiv preprint</a>
+          </div>
+        </div>
+    </div>
+</div>
+
+<div class="row">
+    <div class='col-md-8 offset-md-2'>
+        <form action="{% url 'commentaries:prefill_using_DOI' %}" method="post">
+          {% csrf_token %}
+          {{ query_form|bootstrap }}
+          <input class="btn btn-secondary" type="submit" value="Query DOI"/>
+        </form>
+    </div>
+</div>
+
+<div class="row">
+    <div class='col-md-8 offset-md-2'>
+        <form id="requestForm" action="{% url 'commentaries:request_published_article' %}" method="post">
+            {% csrf_token %}
+            {{ form|bootstrap }}
+            <input class="btn btn-primary" type="submit" value="Submit"/>
+        </form>
+    </div>
+</div>
+
+{% endblock content %}
diff --git a/commentaries/templates/commentaries/vet_commentary_requests.html b/commentaries/templates/commentaries/vet_commentary_requests.html
index 70edddb6e8eaca847e8337f0d5b1d003e759daae..e6c77048113d89bf60598f70b270c15e25022a67 100644
--- a/commentaries/templates/commentaries/vet_commentary_requests.html
+++ b/commentaries/templates/commentaries/vet_commentary_requests.html
@@ -1,35 +1,41 @@
-{% extends 'scipost/base.html' %}
+{% extends 'scipost/_personal_page_base.html' %}
 
-{% block pagetitle %}: vet Commentary requests{% endblock pagetitle %}
-
-{% block bodysup %}
-
-<section>
-  {% if not commentary_to_vet %}
-  <h1>There are no Commentary Page requests for you to vet.</h1>
-
-  {% else %}
+{% load bootstrap %}
 
-  <h1>SciPost Commentary Page request to vet:</h1>
-
-  <br>
-  <hr>
-  <div class="row">
-    <div class="col-8">
-      {% include 'commentaries/_commentary_summary.html' with commentary=commentary_to_vet %}
-      <h3>Abstract:</h3>
-      <p>{{ commentary_to_vet.pub_abstract }}</p>
+{% block pagetitle %}: vet Commentary requests{% endblock pagetitle %}
 
+{% block breadcrumb_items %}
+    {{block.super}}
+    <span class="breadcrumb-item">Vet Commentary Page requests</span>
+{% endblock %}
+
+{% block content %}
+
+<div class="row">
+    <div class="col-12">
+      {% if not commentary_to_vet %}
+          <h1>There are no Commentary Page requests for you to vet.</h1>
+          <h3><a href="{% url 'scipost:personal_page' %}">Return to personal page</a></h3>
+      {% else %}
+          <h1>SciPost Commentary Page request to vet:</h1>
+
+          <hr>
+          <div class="row">
+            <div class="col-md-7">
+              {% include 'commentaries/_commentary_summary.html' with commentary=commentary_to_vet %}
+              <h3 class="mt-4">Abstract:</h3>
+              <p>{{ commentary_to_vet.pub_abstract }}</p>
+
+            </div>
+            <div class="col-md-5">
+              <form action="{% url 'commentaries:vet_commentary_requests_submit' commentary_id=commentary_to_vet.id %}" method="post">
+                {% csrf_token %}
+                {{ form|bootstrap }}
+                <input type="submit" class="btn btn-secondary" value="Submit" />
+            </div>
+          </div>
+        {% endif %}
     </div>
-    <div class="col-4">
-      <form action="{% url 'commentaries:vet_commentary_request_ack' commentary_id=commentary_to_vet.id %}" method="post">
-        {% csrf_token %}
-        {{ form.as_ul }}
-        <input type="submit" value="Submit" />
-    </div>
-  </div>
-
-  {% endif %}
-</section>
+</div>
 
-{% endblock bodysup %}
+{% endblock  %}
diff --git a/commentaries/test_forms.py b/commentaries/test_forms.py
index 8733064ae2267d07111ed7c9fe0c1662cc85fb3f..64d6fda33f5bb8bcb968d89dd17567becdbf6bef 100644
--- a/commentaries/test_forms.py
+++ b/commentaries/test_forms.py
@@ -1,17 +1,105 @@
+import re
+
 from django.test import TestCase
 
 from common.helpers import model_form_data
-from scipost.factories import UserFactory
+from scipost.factories import UserFactory, ContributorFactory
 
-from .factories import VettedCommentaryFactory, UnvettedCommentaryFactory
-from .forms import RequestCommentaryForm, VetCommentaryForm
+from .factories import VettedCommentaryFactory, UnvettedCommentaryFactory,\
+                       UnvettedArxivPreprintCommentaryFactory
+from .forms import RequestPublishedArticleForm, VetCommentaryForm, DOIToQueryForm,\
+                   ArxivQueryForm, RequestArxivPreprintForm
 from .models import Commentary
 from common.helpers.test import add_groups_and_permissions
 
 
+class TestArxivQueryForm(TestCase):
+    def setUp(self):
+        add_groups_and_permissions()
+        ContributorFactory.create_batch(5)
+
+    def test_new_arxiv_identifier_is_valid(self):
+        new_identifier_data = {'identifier': '1612.07611v1'}
+        form = ArxivQueryForm(new_identifier_data)
+        self.assertTrue(form.is_valid())
+
+    def test_old_arxiv_identifier_is_valid(self):
+        old_identifier_data = {'identifier': 'cond-mat/0612480v1'}
+        form = ArxivQueryForm(old_identifier_data)
+        self.assertTrue(form.is_valid())
+
+    def test_invalid_arxiv_identifier(self):
+        invalid_data = {'identifier': 'i am not valid'}
+        form = ArxivQueryForm(invalid_data)
+        self.assertFalse(form.is_valid())
+
+    def test_new_arxiv_identifier_without_version_number_is_invalid(self):
+        data = {'identifier': '1612.07611'}
+        form = ArxivQueryForm(data)
+        self.assertFalse(form.is_valid())
+
+    def test_old_arxiv_identifier_without_version_number_is_invalid(self):
+        data = {'identifier': 'cond-mat/0612480'}
+        form = ArxivQueryForm(data)
+        self.assertFalse(form.is_valid())
+
+    def test_arxiv_identifier_that_already_has_commentary_page_is_invalid(self):
+        unvetted_commentary = UnvettedCommentaryFactory()
+        invalid_data = {'identifier': unvetted_commentary.arxiv_identifier}
+        form = ArxivQueryForm(invalid_data)
+        self.assertFalse(form.is_valid())
+        error_message = form.errors['identifier'][0]
+        self.assertRegexpMatches(error_message, re.compile('already exist'))
+
+    def test_valid_but_non_existent_identifier_is_invalid(self):
+        invalid_data = {'identifier': '1613.07611v1'}
+        form = ArxivQueryForm(invalid_data)
+        self.assertFalse(form.is_valid())
+
+
+class TestDOIToQueryForm(TestCase):
+    def setUp(self):
+        add_groups_and_permissions()
+        ContributorFactory.create_batch(5)
+
+    def test_invalid_doi_is_invalid(self):
+        invalid_data = {'doi': 'blablab'}
+        form = DOIToQueryForm(invalid_data)
+        self.assertFalse(form.is_valid())
+
+    def test_doi_that_already_has_commentary_page_is_invalid(self):
+        unvetted_commentary = UnvettedCommentaryFactory()
+        invalid_data = {'doi': unvetted_commentary.pub_DOI}
+        form = DOIToQueryForm(invalid_data)
+        self.assertFalse(form.is_valid())
+        error_message = form.errors['doi'][0]
+        self.assertRegexpMatches(error_message, re.compile('already exist'))
+
+    def test_physrev_doi_is_valid(self):
+        physrev_doi = "10.21468/SciPostPhys.2.2.010"
+        form = DOIToQueryForm({'doi': physrev_doi})
+        self.assertTrue(form.is_valid())
+
+    def test_scipost_doi_is_valid(self):
+        scipost_doi = "10.21468/SciPostPhys.2.2.010"
+        form = DOIToQueryForm({'doi': scipost_doi})
+        self.assertTrue(form.is_valid())
+
+    def test_old_doi_is_valid(self):
+        old_doi = "10.1088/0022-3719/7/6/005"
+        form = DOIToQueryForm({'doi': old_doi})
+        self.assertTrue(form.is_valid())
+
+    def test_valid_but_nonexistent_doi_is_invalid(self):
+        doi = "10.21468/NonExistentJournal.2.2.010"
+        form = DOIToQueryForm({'doi': doi})
+        self.assertEqual(form.is_valid(), False)
+
+
 class TestVetCommentaryForm(TestCase):
     def setUp(self):
         add_groups_and_permissions()
+        ContributorFactory.create_batch(5)
         self.commentary = UnvettedCommentaryFactory.create()
         self.user = UserFactory()
         self.form_data = {
@@ -20,6 +108,7 @@ class TestVetCommentaryForm(TestCase):
             'email_response_field': 'Lorem Ipsum'
         }
 
+
     def test_valid_accepted_form(self):
         """Test valid form data and return Commentary"""
         form = VetCommentaryForm(self.form_data, commentary_id=self.commentary.id, user=self.user)
@@ -70,71 +159,55 @@ class TestVetCommentaryForm(TestCase):
         self.assertRaises(ValueError, form.process_commentary)
 
 
-class TestRequestCommentaryForm(TestCase):
+class TestRequestPublishedArticleForm(TestCase):
     def setUp(self):
         add_groups_and_permissions()
-        factory_instance = VettedCommentaryFactory.build()
+        ContributorFactory.create_batch(5)
+        factory_instance = UnvettedCommentaryFactory.build()
         self.user = UserFactory()
-        self.valid_form_data = model_form_data(factory_instance, RequestCommentaryForm)
-
-    def empty_and_return_form_data(self, key):
-        """Empty specific valid_form_data field and return"""
-        self.valid_form_data[key] = None
-        return self.valid_form_data
-
-    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())
-
-        # Check if the user is properly saved to the new Commentary as `requested_by`
-        commentary = form.save()
-        self.assertTrue(commentary.requested_by)
+        self.valid_form_data = model_form_data(factory_instance, RequestPublishedArticleForm)
 
-    def test_valid_data_is_valid_for_DOI(self):
+    def test_valid_data_is_valid(self):
         """Test valid form for DOI"""
-        form_data = self.valid_form_data
-        form_data['arxiv_identifier'] = ''
-        form = RequestCommentaryForm(form_data, user=self.user)
+        form = RequestPublishedArticleForm(self.valid_form_data)
         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)
-        self.assertFalse(form.is_valid())
-        self.assertTrue('arxiv_identifier' in form.errors)
-        self.assertTrue('pub_DOI' in form.errors)
-
-    def test_form_with_duplicate_DOI(self):
-        """Test form response with already existing DOI"""
-        # Create a factory instance containing Arxiv ID and DOI
-        VettedCommentaryFactory.create()
-
-        # Test duplicate DOI entry
-        form_data = self.empty_and_return_form_data('arxiv_identifier')
-        form = RequestCommentaryForm(form_data, user=self.user)
-        self.assertTrue('pub_DOI' in form.errors)
-        self.assertFalse(form.is_valid())
+    def test_doi_that_already_has_commentary_page_is_invalid(self):
+        unvetted_commentary = UnvettedCommentaryFactory()
+        invalid_data = {**self.valid_form_data, **{'pub_DOI': unvetted_commentary.pub_DOI}}
+        form = RequestPublishedArticleForm(invalid_data)
+        self.assertEqual(form.is_valid(), False)
+        error_message = form.errors['pub_DOI'][0]
+        self.assertRegexpMatches(error_message, re.compile('already exist'))
 
-        # Check is existing commentary is valid
-        existing_commentary = form.get_existing_commentary()
-        self.assertEqual(existing_commentary.pub_DOI, form_data['pub_DOI'])
+    def test_commentary_without_pub_DOI_is_invalid(self):
+        invalid_data = {**self.valid_form_data, **{'pub_DOI': ''}}
+        form = RequestPublishedArticleForm(invalid_data)
+        self.assertEqual(form.is_valid(), False)
 
-    def test_form_with_duplicate_arxiv_id(self):
-        """Test form response with already existing Arxiv ID"""
-        VettedCommentaryFactory.create()
 
-        # Test duplicate Arxiv entry
-        form_data = self.empty_and_return_form_data('pub_DOI')
-        form = RequestCommentaryForm(form_data, user=self.user)
-        self.assertTrue('arxiv_identifier' in form.errors)
-        self.assertFalse(form.is_valid())
+class TestRequestArxivPreprintForm(TestCase):
+    def setUp(self):
+        add_groups_and_permissions()
+        ContributorFactory.create_batch(5)
+        factory_instance = UnvettedArxivPreprintCommentaryFactory.build()
+        self.user = UserFactory()
+        self.valid_form_data = model_form_data(factory_instance, RequestPublishedArticleForm)
+        self.valid_form_data['arxiv_identifier'] = factory_instance.arxiv_identifier
+
+    def test_valid_data_is_valid(self):
+        form = RequestArxivPreprintForm(self.valid_form_data)
+        self.assertTrue(form.is_valid())
 
-        # Check is existing commentary is valid
-        existing_commentary = form.get_existing_commentary()
-        self.assertEqual(existing_commentary.arxiv_identifier, form_data['arxiv_identifier'])
+    def test_identifier_that_already_has_commentary_page_is_invalid(self):
+        commentary = UnvettedArxivPreprintCommentaryFactory()
+        invalid_data = {**self.valid_form_data, **{'arxiv_identifier': commentary.arxiv_identifier}}
+        form = RequestArxivPreprintForm(invalid_data)
+        self.assertEqual(form.is_valid(), False)
+        error_message = form.errors['arxiv_identifier'][0]
+        self.assertRegexpMatches(error_message, re.compile('already exist'))
+
+    def test_commentary_without_arxiv_identifier_is_invalid(self):
+        invalid_data = {**self.valid_form_data, **{'arxiv_identifier': ''}}
+        form = RequestArxivPreprintForm(invalid_data)
+        self.assertEqual(form.is_valid(), False)
diff --git a/commentaries/test_models.py b/commentaries/test_models.py
index e4defabc8afbec48d89189ef98a5e9697d1657d0..5fb96f3ef5ac04ae31bf79dd6b63da465137836e 100644
--- a/commentaries/test_models.py
+++ b/commentaries/test_models.py
@@ -1 +1,11 @@
-# from django.test import TestCase
+from django.test import TestCase
+
+from common.helpers.test import add_groups_and_permissions
+
+from scipost.factories import ContributorFactory
+from .factories import UnvettedCommentaryFactory
+
+
+class TestCommentary(TestCase):
+    def setUp(self):
+        add_groups_and_permissions()
diff --git a/commentaries/test_views.py b/commentaries/test_views.py
index 17ea43b4c1d38d48a40b41d0e9edddd86930d61a..24f46518623509e53016c7939f564ceee26d53cd 100644
--- a/commentaries/test_views.py
+++ b/commentaries/test_views.py
@@ -2,38 +2,79 @@ from django.core.urlresolvers import reverse
 from django.contrib.auth.models import Group
 from django.test import TestCase, Client, RequestFactory
 
+from scipost.models import Contributor
 from scipost.factories import ContributorFactory, UserFactory
 
-from .factories import UnvettedCommentaryFactory, VettedCommentaryFactory, UnpublishedVettedCommentaryFactory
-from .forms import CommentarySearchForm
+from .factories import UnvettedCommentaryFactory, VettedCommentaryFactory, UnpublishedVettedCommentaryFactory, \
+    UnvettedArxivPreprintCommentaryFactory
+from .forms import CommentarySearchForm, RequestPublishedArticleForm
 from .models import Commentary
-from .views import RequestCommentary
+from .views import RequestPublishedArticle, prefill_using_DOI, RequestArxivPreprint
 from common.helpers.test import add_groups_and_permissions
+from common.helpers import model_form_data
 
 
-class RequestCommentaryTest(TestCase):
-    """Test cases for `request_commentary` view method"""
+class PrefillUsingDOITest(TestCase):
     def setUp(self):
         add_groups_and_permissions()
-        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_redirects_if_not_logged_in(self):
-        request = self.client.get(self.view_url)
-        self.assertRedirects(request, self.redirected_login_url)
+        self.target = reverse('commentaries:prefill_using_DOI')
+        self.physrev_doi = '10.1103/PhysRevB.92.214427'
 
-    def test_valid_response_if_logged_in(self):
-        """Test different GET requests on view"""
-        request = RequestFactory().get(self.view_url)
+    def test_submit_valid_physrev_doi(self):
+        post_data = {'doi': self.physrev_doi}
+        request = RequestFactory().post(self.target, post_data)
         request.user = UserFactory()
-        response = RequestCommentary.as_view()(request)
+
+        response = prefill_using_DOI(request)
         self.assertEqual(response.status_code, 200)
 
-    def test_post_invalid_forms(self):
-        """Test different kind of invalid RequestCommentaryForm submits"""
-        raise NotImplementedError
 
+class RequestPublishedArticleTest(TestCase):
+    def setUp(self):
+        add_groups_and_permissions()
+        self.target = reverse('commentaries:request_published_article')
+        self.contributor = ContributorFactory()
+        self.commentary_instance = UnvettedCommentaryFactory.build(requested_by=self.contributor)
+        self.valid_form_data = model_form_data(self.commentary_instance, RequestPublishedArticleForm)
+
+    def test_commentary_gets_created_with_correct_type_and_link(self):
+        request = RequestFactory().post(self.target, self.valid_form_data)
+        request.user = self.contributor.user
+
+        self.assertEqual(Commentary.objects.count(), 0)
+        response = RequestPublishedArticle.as_view()(request)
+        self.assertEqual(Commentary.objects.count(), 1)
+
+        commentary = Commentary.objects.first()
+        self.assertEqual(commentary.pub_DOI, self.valid_form_data['pub_DOI'])
+        self.assertEqual(commentary.type, 'published')
+        self.assertEqual(commentary.arxiv_or_DOI_string, commentary.pub_DOI)
+        self.assertEqual(commentary.requested_by, self.contributor)
+
+
+class RequestArxivPreprintTest(TestCase):
+    def setUp(self):
+        add_groups_and_permissions()
+        self.target = reverse('commentaries:request_arxiv_preprint')
+        self.contributor = ContributorFactory()
+        self.commentary_instance = UnvettedArxivPreprintCommentaryFactory.build(requested_by=self.contributor)
+        self.valid_form_data = model_form_data(self.commentary_instance, RequestPublishedArticleForm)
+        # The form field is called 'identifier', while the model field is called 'arxiv_identifier',
+        # so model_form_data doesn't include it.
+        self.valid_form_data['arxiv_identifier'] = self.commentary_instance.arxiv_identifier
+
+    def test_commentary_gets_created_with_correct_type_and_link_and_requested_by(self):
+        request = RequestFactory().post(self.target, self.valid_form_data)
+        request.user = self.contributor.user
+
+        self.assertEqual(Commentary.objects.count(), 0)
+        response = RequestArxivPreprint.as_view()(request)
+        self.assertEqual(Commentary.objects.count(), 1)
+        commentary = Commentary.objects.first()
+        self.assertEqual(commentary.arxiv_identifier, self.valid_form_data['arxiv_identifier'])
+        self.assertEqual(commentary.type, 'preprint')
+        self.assertEqual(commentary.arxiv_or_DOI_string, "arXiv:" + self.commentary_instance.arxiv_identifier)
+        self.assertEqual(commentary.requested_by, self.contributor)
 
 class VetCommentaryRequestsTest(TestCase):
     """Test cases for `vet_commentary_requests` view method"""
@@ -77,12 +118,13 @@ class VetCommentaryRequestsTest(TestCase):
         self.assertEquals(response.context['commentary_to_vet'], None)
 
         # Only vetted Commentaries exist!
-        VettedCommentaryFactory()
+        # ContributorFactory.create_batch(5)
+        VettedCommentaryFactory(requested_by=ContributorFactory(), vetted_by=ContributorFactory())
         response = self.client.get(self.view_url)
         self.assertEquals(response.context['commentary_to_vet'], None)
 
         # Unvetted Commentaries do exist!
-        UnvettedCommentaryFactory()
+        UnvettedCommentaryFactory(requested_by=ContributorFactory())
         response = self.client.get(self.view_url)
         self.assertTrue(type(response.context['commentary_to_vet']) is Commentary)
 
@@ -92,7 +134,7 @@ class BrowseCommentariesTest(TestCase):
 
     def setUp(self):
         add_groups_and_permissions()
-        VettedCommentaryFactory(discipline='physics')
+        VettedCommentaryFactory(discipline='physics', requested_by=ContributorFactory())
         self.view_url = reverse('commentaries:browse', kwargs={
             'discipline': 'physics',
             'nrweeksback': '1'
@@ -104,7 +146,7 @@ class BrowseCommentariesTest(TestCase):
         self.assertEquals(response.status_code, 200)
 
         # The created vetted Commentary is found!
-        self.assertTrue(response.context['commentary_browse_list'].count() >= 1)
+        self.assertTrue(response.context['commentary_list'].count() >= 1)
         # The search form is passed trough the view...
         self.assertTrue(type(response.context['form']) is CommentarySearchForm)
 
@@ -113,7 +155,8 @@ class CommentaryDetailTest(TestCase):
     def setUp(self):
         add_groups_and_permissions()
         self.client = Client()
-        self.commentary = UnpublishedVettedCommentaryFactory()
+        self.commentary = UnpublishedVettedCommentaryFactory(
+            requested_by=ContributorFactory(), vetted_by=ContributorFactory())
         self.target = reverse(
             'commentaries:commentary',
             kwargs={'arxiv_or_DOI_string': self.commentary.arxiv_or_DOI_string}
@@ -122,3 +165,9 @@ class CommentaryDetailTest(TestCase):
     def test_status_code_200(self):
         response = self.client.get(self.target)
         self.assertEqual(response.status_code, 200)
+
+    def test_unvetted_commentary(self):
+        commentary = UnvettedCommentaryFactory(requested_by=ContributorFactory())
+        target = reverse('commentaries:commentary', kwargs={'arxiv_or_DOI_string': commentary.arxiv_or_DOI_string})
+        response = self.client.get(target)
+        self.assertEqual(response.status_code, 404)
diff --git a/commentaries/urls.py b/commentaries/urls.py
index a1b5ba5ec119853ae337ab52c7213cb4feadea8f..7d3280cb2d93dd748b51713f6c7f20cd390a8484 100644
--- a/commentaries/urls.py
+++ b/commentaries/urls.py
@@ -21,12 +21,18 @@ urlpatterns = [
     url(r'^(?P<arxiv_or_DOI_string>arXiv:[a-z-]+/[0-9]{7,}(v[0-9]+)?)/$',
         views.commentary_detail, name='commentary'),
 
-    url(r'^request_commentary$', views.RequestCommentary.as_view(), name='request_commentary'),
+    url(r'^request_commentary$', views.request_commentary, name='request_commentary'),
+    url(r'^request_commentary/published_article$', views.RequestPublishedArticle.as_view(),
+        name='request_published_article'),
+    url(r'^request_commentary/arxiv_preprint$', views.RequestArxivPreprint.as_view(),
+        name='request_arxiv_preprint'),
     url(r'^prefill_using_DOI$', views.prefill_using_DOI, name='prefill_using_DOI'),
-    url(r'^prefill_using_identifier$', views.PrefillUsingIdentifierView.as_view(),
-        name='prefill_using_identifier'),
+    url(r'^prefill_using_arxiv_identifier$', views.prefill_using_arxiv_identifier,
+        name='prefill_using_arxiv_identifier'),
     url(r'^vet_commentary_requests$', views.vet_commentary_requests,
         name='vet_commentary_requests'),
-    url(r'^vet_commentary_request_ack/(?P<commentary_id>[0-9]+)$',
-        views.vet_commentary_request_ack, name='vet_commentary_request_ack'),
+    url(r'^vet_commentary_requests/(?P<commentary_id>[0-9]+)$', views.vet_commentary_requests,
+        name='vet_commentary_requests_submit'),
+    url(r'^vet_commentary_requests/(?P<commentary_id>[0-9]+)/modify$',
+        views.modify_commentary_request, name='modify_commentary_request'),
 ]
diff --git a/commentaries/views.py b/commentaries/views.py
index d8554ec92e92690cf12c7c6e27802183d3f2154a..3dc87784707e61820a4f7afc2749d5ddebccb52c 100644
--- a/commentaries/views.py
+++ b/commentaries/views.py
@@ -1,275 +1,200 @@
-import re
-import requests
-
 from django.shortcuts import get_object_or_404, render
 from django.contrib import messages
 from django.contrib.auth.decorators import permission_required
-from django.contrib.auth.mixins import LoginRequiredMixin
 from django.core.mail import EmailMessage
 from django.core.urlresolvers import reverse, reverse_lazy
 from django.db.models import Q
 from django.shortcuts import redirect
 from django.template.loader import render_to_string
-from django.views.generic.edit import CreateView, FormView
+from django.views.generic.edit import CreateView
 from django.views.generic.list import ListView
 from django.utils.decorators import method_decorator
+from django.http import Http404
 
 from .models import Commentary
-from .forms import RequestCommentaryForm, DOIToQueryForm, IdentifierToQueryForm
-from .forms import VetCommentaryForm, CommentarySearchForm
+from .forms import DOIToQueryForm, ArxivQueryForm, VetCommentaryForm, RequestCommentaryForm,\
+                   CommentarySearchForm, RequestPublishedArticleForm, RequestArxivPreprintForm
 
 from comments.models import Comment
 from comments.forms import CommentForm
 from scipost.models import Contributor
-from scipost.services import ArxivCaller
 
 import strings
 
 
-################
-# Commentaries
-################
-
-class RequestCommentaryMixin(object):
-    def get_context_data(self, **kwargs):
-        '''Pass the DOI and identifier forms to the context.'''
-        if 'request_commentary_form' not in kwargs:
-            # Only intercept if not prefilled
-            kwargs['request_commentary_form'] = RequestCommentaryForm()
-        context = super(RequestCommentaryMixin, self).get_context_data(**kwargs)
-
-        context['existing_commentary'] = None
-        context['doiform'] = DOIToQueryForm()
-        context['identifierform'] = IdentifierToQueryForm()
-        return context
+@permission_required('scipost.can_request_commentary_pages', raise_exception=True)
+def request_commentary(request):
+    return render(request, 'commentaries/request_commentary.html')
 
 
 @method_decorator(permission_required(
     'scipost.can_request_commentary_pages', raise_exception=True), name='dispatch')
-class RequestCommentary(LoginRequiredMixin, RequestCommentaryMixin, CreateView):
-    form_class = RequestCommentaryForm
-    template_name = 'commentaries/request_commentary.html'
+class RequestCommentary(CreateView):
     success_url = reverse_lazy('scipost:personal_page')
 
-    def get_form_kwargs(self, *args, **kwargs):
-        '''User should be included in the arguments to have a valid form.'''
-        form_kwargs = super(RequestCommentary, self).get_form_kwargs(*args, **kwargs)
-        form_kwargs['user'] = self.request.user
-        return form_kwargs
+    def get_form_kwargs(self):
+        kwargs = super().get_form_kwargs()
+        kwargs['requested_by'] = self.request.user.contributor
+        return kwargs
 
     def form_valid(self, form):
-        form.instance.parse_links_into_urls()
-        messages.success(self.request, strings.acknowledge_request_commentary)
-        return super(RequestCommentary, self).form_valid(form)
+        messages.success(self.request, strings.acknowledge_request_commentary, fail_silently=True)
+        return super().form_valid(form)
+
+
+class RequestPublishedArticle(RequestCommentary):
+    form_class = RequestPublishedArticleForm
+    template_name = 'commentaries/request_published_article.html'
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context['query_form'] = DOIToQueryForm()
+        return context
+
+
+class RequestArxivPreprint(RequestCommentary):
+    form_class = RequestArxivPreprintForm
+    template_name = 'commentaries/request_arxiv_preprint.html'
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context['query_form'] = ArxivQueryForm()
+        return context
 
 
 @permission_required('scipost.can_request_commentary_pages', raise_exception=True)
 def prefill_using_DOI(request):
-    """ Probes CrossRef API with the DOI, to pre-fill the form. """
     if request.method == "POST":
-        doiform = DOIToQueryForm(request.POST)
-        if doiform.is_valid():
-            # Check if given doi is of expected form:
-            doipattern = re.compile("^10.[0-9]{4,9}/[-._;()/:a-zA-Z0-9]+")
-            errormessage = ''
-            existing_commentary = None
-            if not doipattern.match(doiform.cleaned_data['doi']):
-                errormessage = 'The DOI you entered is improperly formatted.'
-            elif Commentary.objects.filter(pub_DOI=doiform.cleaned_data['doi']).exists():
-                errormessage = 'There already exists a Commentary Page on this publication, see'
-                existing_commentary = get_object_or_404(Commentary,
-                                                        pub_DOI=doiform.cleaned_data['doi'])
-            if errormessage:
-                form = RequestCommentaryForm()
-                identifierform = IdentifierToQueryForm()
-                context = {
-                    'request_commentary_form': form,
-                    'doiform': doiform,
-                    'identifierform': identifierform,
-                    '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']
-                doiquery = requests.get(queryurl)
-                doiqueryJSON = doiquery.json()
-                metadata = doiqueryJSON
-                pub_title = doiqueryJSON['message']['title'][0]
-                authorlist = (doiqueryJSON['message']['author'][0]['given'] + ' ' +
-                              doiqueryJSON['message']['author'][0]['family'])
-                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
-                except KeyError:
-                    pass
-                try:
-                    pages = doiqueryJSON['message']['page']
-                except KeyError:
-                    pass
-
-                pub_date = ''
-                try:
-                    pub_date = (str(doiqueryJSON['message']['issued']['date-parts'][0][0]) + '-' +
-                                str(doiqueryJSON['message']['issued']['date-parts'][0][1]))
-                    try:
-                        pub_date += '-' + str(
-                            doiqueryJSON['message']['issued']['date-parts'][0][2])
-                    except (IndexError, KeyError):
-                        pass
-                except (IndexError, KeyError):
-                    pass
-                pub_DOI = doiform.cleaned_data['doi']
-                form = RequestCommentaryForm(
-                    initial={'type': 'published', 'metadata': metadata,
-                             'pub_title': pub_title, 'author_list': authorlist,
-                             'journal': journal, 'volume': volume,
-                             'pages': pages, 'pub_date': pub_date,
-                             'pub_DOI': pub_DOI})
-                identifierform = IdentifierToQueryForm()
-                context = {
-                    'request_commentary_form': form,
-                    'doiform': doiform,
-                    'identifierform': identifierform
-                }
-                context['title'] = pub_title
-                return render(request, 'commentaries/request_commentary.html', context)
-            except (IndexError, KeyError, ValueError):
-                pass
+        query_form = DOIToQueryForm(request.POST)
+        # The form checks if doi is valid and commentary doesn't already exist.
+        if query_form.is_valid():
+            prefill_data = query_form.request_published_article_form_prefill_data()
+            form = RequestPublishedArticleForm(initial=prefill_data)
+            messages.success(request, strings.acknowledge_doi_query, fail_silently=True)
         else:
-            pass
-    return redirect(reverse('commentaries:request_commentary'))
+            form = RequestPublishedArticleForm()
 
+        context = {
+            'form': form,
+            'query_form': query_form,
+        }
+        return render(request, 'commentaries/request_published_article.html', context)
+    else:
+        raise Http404
 
-@method_decorator(permission_required(
-    'scipost.can_request_commentary_pages', raise_exception=True), name='dispatch')
-class PrefillUsingIdentifierView(RequestCommentaryMixin, FormView):
-    form_class = IdentifierToQueryForm
-    template_name = 'commentaries/request_commentary.html'
-
-    def form_invalid(self, identifierform):
-        for field, errors in identifierform.errors.items():
-            for error in errors:
-                messages.warning(self.request, error)
-        return render(self.request, 'commentaries/request_commentary.html',
-                      self.get_context_data(**{}))
-
-    def form_valid(self, identifierform):
-        '''Prefill using the ArxivCaller if the Identifier is valid'''
-        caller = ArxivCaller(Commentary, identifierform.cleaned_data['identifier'])
-        caller.process()
-
-        if caller.is_valid():
-            # Prefill the form
-            metadata = caller.metadata
-            pub_title = metadata['entries'][0]['title']
-            authorlist = metadata['entries'][0]['authors'][0]['name']
-            for author in metadata['entries'][0]['authors'][1:]:
-                authorlist += ', ' + author['name']
-            arxiv_link = metadata['entries'][0]['id']
-            abstract = metadata['entries'][0]['summary']
-
-            initialdata = {
-                'type': 'preprint',
-                'metadata': metadata,
-                'pub_title': pub_title,
-                'author_list': authorlist,
-                'arxiv_identifier': identifierform.cleaned_data['identifier'],
-                'arxiv_link': arxiv_link,
-                'pub_abstract': abstract
-            }
-            context = {
-                'title': pub_title,
-                'request_commentary_form': RequestCommentaryForm(initial=initialdata)
-            }
-            messages.success(self.request, 'Arxiv completed')
-            return render(self.request, 'commentaries/request_commentary.html',
-                          self.get_context_data(**context))
+
+@permission_required('scipost.can_request_commentary_pages', raise_exception=True)
+def prefill_using_arxiv_identifier(request):
+    if request.method == "POST":
+        query_form = ArxivQueryForm(request.POST)
+        if query_form.is_valid():
+            prefill_data = query_form.request_arxiv_preprint_form_prefill_data()
+            form = RequestArxivPreprintForm(initial=prefill_data)
+            messages.success(request, strings.acknowledge_arxiv_query, fail_silently=True)
         else:
-            msg = caller.get_error_message()
-            messages.error(self.request, msg)
-            return render(self.request, 'commentaries/request_commentary.html',
-                          self.get_context_data(**{}))
+            form = RequestArxivPreprintForm()
+
+        context = {
+            'form': form,
+            'query_form': query_form,
+        }
+        return render(request, 'commentaries/request_arxiv_preprint.html', context)
+    else:
+        raise Http404
 
 
 @permission_required('scipost.can_vet_commentary_requests', raise_exception=True)
-def vet_commentary_requests(request):
+def vet_commentary_requests(request, commentary_id=None):
     """Show the first commentary thats awaiting vetting"""
-    contributor = Contributor.objects.get(user=request.user)
-    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}
+    queryset = Commentary.objects.awaiting_vetting().exclude(requested_by=request.user.contributor)
+    if commentary_id:
+        # Security fix: Smart asses can vet their own commentary without this line.
+        commentary_to_vet = get_object_or_404(queryset, id=commentary_id)
+    else:
+        commentary_to_vet = queryset.first()
+
+    form = VetCommentaryForm(request.POST or None, user=request.user, commentary_id=commentary_id)
+    if form.is_valid():
+        # Get commentary
+        commentary = form.get_commentary()
+        email_context = {
+            'commentary': commentary
+        }
+
+        # Retrieve email_template for action
+        if form.commentary_is_accepted():
+            email_template = 'commentaries/vet_commentary_email_accepted.html'
+        elif form.commentary_is_refused():
+            email_template = 'commentaries/vet_commentary_email_rejected.html'
+            email_context['refusal_reason'] = form.get_refusal_reason()
+            email_context['further_explanation'] = form.cleaned_data['email_response_field']
+        elif form.commentary_is_modified():
+            # For a modified commentary, redirect to request_commentary_form
+            return redirect(reverse('commentaries:modify_commentary_request',
+                                    args=(commentary.id,)))
+
+        # Send email and process form
+        email_text = render_to_string(email_template, email_context)
+        email_args = (
+            'SciPost Commentary Page activated',
+            email_text,
+            commentary.requested_by.user.email,
+            ['commentaries@scipost.org']
+        )
+        emailmessage = EmailMessage(*email_args, reply_to=['commentaries@scipost.org'])
+        emailmessage.send(fail_silently=False)
+        commentary = form.process_commentary()
+
+        messages.success(request, 'SciPost Commentary request vetted.')
+        return redirect(reverse('commentaries:vet_commentary_requests'))
+
+    context = {
+        'commentary_to_vet': commentary_to_vet,
+        'form': form
+    }
     return render(request, 'commentaries/vet_commentary_requests.html', context)
 
 
 @permission_required('scipost.can_vet_commentary_requests', raise_exception=True)
-def vet_commentary_request_ack(request, commentary_id):
-    if request.method == 'POST':
-        form = VetCommentaryForm(request.POST, user=request.user, commentary_id=commentary_id)
-        if form.is_valid():
-            # Get commentary
-            commentary = form.get_commentary()
-            email_context = {
-                'commentary': commentary
-            }
-
-            # Retrieve email_template for action
-            if form.commentary_is_accepted():
-                email_template = 'commentaries/vet_commentary_email_accepted.html'
-            elif form.commentary_is_modified():
-                email_template = 'commentaries/vet_commentary_email_modified.html'
-
-                request_commentary_form = RequestCommentaryForm(initial={
-                    'pub_title': commentary.pub_title,
-                    'arxiv_link': commentary.arxiv_link,
-                    'pub_DOI_link': commentary.pub_DOI_link,
-                    'author_list': commentary.author_list,
-                    'pub_date': commentary.pub_date,
-                    'pub_abstract': commentary.pub_abstract
-                })
-            elif form.commentary_is_refused():
-                email_template = 'commentaries/vet_commentary_email_rejected.html'
-                email_context['refusal_reason'] = form.get_refusal_reason()
-                email_context['further_explanation'] = form.cleaned_data['email_response_field']
-
-            # Send email and process form
-            email_text = render_to_string(email_template, email_context)
-            email_args = (
-                'SciPost Commentary Page activated',
-                email_text,
-                commentary.requested_by.user.email,
-                ['commentaries@scipost.org']
-            )
-            emailmessage = EmailMessage(*email_args, reply_to=['commentaries@scipost.org'])
-            emailmessage.send(fail_silently=False)
-            commentary = form.process_commentary()
-
-            # For a modified commentary, redirect to request_commentary_form
-            if form.commentary_is_modified():
-                context = {'form': request_commentary_form}
-                return render(request, 'commentaries/request_commentary.html', context)
-
-    context = {'ack_header': 'SciPost Commentary request vetted.',
-               'followup_message': 'Return to the ',
-               'followup_link': reverse('commentaries:vet_commentary_requests'),
-               'followup_link_label': 'Commentary requests page'}
-    return render(request, 'scipost/acknowledgement.html', context)
+def modify_commentary_request(request, commentary_id):
+    """Modify a commentary request after vetting with status 'modified'."""
+    commentary = get_object_or_404((Commentary.objects.awaiting_vetting()
+                                    .exclude(requested_by=request.user.contributor)),
+                                    id=commentary_id)
+    form = RequestCommentaryForm(request.POST or None, instance=commentary)
+    if form.is_valid():
+        # Process commentary data
+        commentary = form.save(commit=False)
+        commentary.vetted = True
+        commentary.save()
+
+        # Send email and process form
+        email_template = 'commentaries/vet_commentary_email_modified.html'
+        email_text = render_to_string(email_template, {'commentary': commentary})
+        email_args = (
+            'SciPost Commentary Page activated',
+            email_text,
+            commentary.requested_by.user.email,
+            ['commentaries@scipost.org']
+        )
+        emailmessage = EmailMessage(*email_args, reply_to=['commentaries@scipost.org'])
+        emailmessage.send(fail_silently=False)
+
+        messages.success(request, 'SciPost Commentary request modified and vetted.')
+        return redirect(reverse('commentaries:vet_commentary_requests'))
+
+    context = {
+        'commentary': commentary,
+        'form': form
+    }
+    return render(request, 'commentaries/modify_commentary_request.html', context)
 
 
 class CommentaryListView(ListView):
     model = Commentary
     form = CommentarySearchForm
     paginate_by = 10
+    context_object_name = 'commentary_list'
 
     def get_queryset(self):
         '''Perform search form here already to get the right pagination numbers.'''
@@ -305,7 +230,9 @@ class CommentaryListView(ListView):
 
 
 def commentary_detail(request, arxiv_or_DOI_string):
-    commentary = get_object_or_404(Commentary, arxiv_or_DOI_string=arxiv_or_DOI_string)
+    commentary = get_object_or_404(Commentary.objects.vetted(),
+                                   arxiv_or_DOI_string=arxiv_or_DOI_string)
+
     comments = commentary.comment_set.all()
     form = CommentForm()
     try:
diff --git a/journals/admin.py b/journals/admin.py
index 79667f7bfc31d7d998b1348e5ddcfebd0645009d..48f9f4ee5a90149ca010e5fe22edf9d85e75f7c6 100644
--- a/journals/admin.py
+++ b/journals/admin.py
@@ -1,14 +1,8 @@
 from django.contrib import admin, messages
 
-from journals.models import ProductionStream, ProductionEvent
 from journals.models import Journal, Volume, Issue, Publication, Deposit
 
 
-admin.site.register(ProductionStream)
-
-
-admin.site.register(ProductionEvent)
-
 
 class JournalAdmin(admin.ModelAdmin):
     search_fields = ['name']
diff --git a/journals/constants.py b/journals/constants.py
index 3a3aa9b88e6191b3c6620a33a1d3d84fc5db0d86..6bd5de5cf9a4285d253a22a9bbb638efde5396f0 100644
--- a/journals/constants.py
+++ b/journals/constants.py
@@ -56,18 +56,3 @@ ISSUE_STATUSES = (
     (STATUS_DRAFT, 'Draft'),
     (STATUS_PUBLISHED, 'Published'),
 )
-
-PRODUCTION_STREAM_STATUS = (
-    ('ongoing', 'Ongoing'),
-    ('completed', 'Completed'),
-)
-
-PRODUCTION_EVENTS = (
-    ('assigned_to_supervisor', 'Assigned to Supervisor'),
-    ('officer_tasked_with_proof_production', 'Officer tasked with proofs production'),
-    ('proofs_produced', 'Proofs have been produced'),
-    ('proofs_sent_to_authors', 'Proofs sent to Authors'),
-    ('proofs_returned_by_authors', 'Proofs returned by Authors'),
-    ('corrections_implemented', 'Corrections implemented'),
-    ('authors_have_accepted_proofs', 'Authors have accepted proofs'),
-)
diff --git a/journals/forms.py b/journals/forms.py
index 910bbd5b4f375f2aa40dc63893b542de1dc68a8b..d173a344773c53a27ea61e613631c63cdf7b8edc 100644
--- a/journals/forms.py
+++ b/journals/forms.py
@@ -1,22 +1,11 @@
 from django import forms
 from django.utils import timezone
 
-from .models import ProductionEvent
 from .models import UnregisteredAuthor, Issue, Publication
 
 from submissions.models import Submission
 
 
-class ProductionEventForm(forms.ModelForm):
-    class Meta:
-        model = ProductionEvent
-        exclude = ['stream', 'noted_on', 'noted_by']
-
-    def __init__(self, *args, **kwargs):
-        super(ProductionEventForm, self).__init__(*args, **kwargs)
-        self.fields['duration'].widget.attrs.update(
-            {'placeholder': 'HH:MM:SS'})
-
 
 class InitiatePublicationForm(forms.Form):
     accepted_submission = forms.ModelChoiceField(
diff --git a/journals/migrations/0023_auto_20170517_1846.py b/journals/migrations/0023_auto_20170517_1846.py
new file mode 100644
index 0000000000000000000000000000000000000000..b3462dcdd0e7b5008c780b0446f1324405f07df4
--- /dev/null
+++ b/journals/migrations/0023_auto_20170517_1846.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2017-05-17 16:46
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('journals', '0022_auto_20170517_1608'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='productionevent',
+            name='noted_by',
+        ),
+        migrations.RemoveField(
+            model_name='productionevent',
+            name='stream',
+        ),
+        migrations.RemoveField(
+            model_name='productionstream',
+            name='submission',
+        ),
+        migrations.DeleteModel(
+            name='ProductionEvent',
+        ),
+        migrations.DeleteModel(
+            name='ProductionStream',
+        ),
+    ]
diff --git a/journals/models.py b/journals/models.py
index 7a413a808ed36963c70f227198e2ac79040fecf2..1d1862c0f3b71df99ca9d038738bedb9b45af0d6 100644
--- a/journals/models.py
+++ b/journals/models.py
@@ -7,8 +7,7 @@ from django.urls import reverse
 from .behaviors import doi_journal_validator, doi_volume_validator,\
                        doi_issue_validator, doi_publication_validator
 from .constants import SCIPOST_JOURNALS, SCIPOST_JOURNALS_DOMAINS,\
-                       STATUS_DRAFT, STATUS_PUBLISHED, ISSUE_STATUSES,\
-                       PRODUCTION_STREAM_STATUS, PRODUCTION_EVENTS
+                       STATUS_DRAFT, STATUS_PUBLISHED, ISSUE_STATUSES
 from .helpers import paper_nr_string, journal_name_abbrev_citation
 from .managers import IssueManager, PublicationManager, JournalManager
 
@@ -17,34 +16,6 @@ from scipost.fields import ChoiceArrayField
 from scipost.models import Contributor
 
 
-##############
-# Production #
-##############
-
-class ProductionStream(models.Model):
-    submission = models.OneToOneField('submissions.Submission', on_delete=models.CASCADE)
-    opened = models.DateTimeField()
-
-    def __str__(self):
-        return str(self.submission)
-
-    def total_duration(self):
-        totdur = self.productionevent_set.all().aggregate(models.Sum('duration'))
-        return totdur['duration__sum']
-
-
-class ProductionEvent(models.Model):
-    stream = models.ForeignKey(ProductionStream, on_delete=models.CASCADE)
-    event = models.CharField(max_length=64, choices=PRODUCTION_EVENTS)
-    comments = models.TextField(blank=True, null=True)
-    noted_on = models.DateTimeField(default=timezone.now)
-    noted_by = models.ForeignKey(Contributor, on_delete=models.CASCADE)
-    duration = models.DurationField(blank=True, null=True)
-
-    def __str__(self):
-        return '%s: %s' % (str(self.stream.submission), self.get_event_display())
-
-
 ################
 # Journals etc #
 ################
diff --git a/journals/templates/journals/publication_detail.html b/journals/templates/journals/publication_detail.html
index 38ddbd0126458949676a818100764228b9277907..e71cba60ff028d343366224a34d76021edc3b27f 100644
--- a/journals/templates/journals/publication_detail.html
+++ b/journals/templates/journals/publication_detail.html
@@ -24,7 +24,7 @@
     {% endfor %}
     <meta name="citation_doi" content="{{ publication.doi_string }}"/>
     <meta name="citation_publication_date" content="{{ publication.publication_date|date:'Y/m/d' }}"/>
-    <meta name="citation_journal_title" content="{{ journal.name }}"/>
+    <meta name="citation_journal_title" content="{{ journal }}"/>
     <meta name="citation_issn" content="{{ journal.issn }}"/>
     <meta name="citation_volume" content="{{ publication.in_issue.in_volume.number }}"/>
     <meta name="citation_issue" content="{{ publication.in_issue.number }}"/>
diff --git a/journals/urls/general.py b/journals/urls/general.py
index 13287579afdf1ea4e3e9361ca661578c3c82ed4c..25679dc22c730474636a0d9a468fd170ebc9973c 100644
--- a/journals/urls/general.py
+++ b/journals/urls/general.py
@@ -12,11 +12,6 @@ urlpatterns = [
         TemplateView.as_view(template_name='journals/journals_terms_and_conditions.html'),
         name='journals_terms_and_conditions'),
 
-    # Production
-    url(r'^production$', journals_views.production, name='production'),
-    url(r'^add_production_event/(?P<stream_id>[0-9]+)$',
-        journals_views.add_production_event, name='add_production_event'),
-
     # Editorial and Administrative Workflow
     url(r'^initiate_publication$',
         journals_views.initiate_publication,
diff --git a/journals/views.py b/journals/views.py
index 67301e77c8d03556df956ef7d09e1b581ed3232c..2f0f5726d1cd3f34fac17aeb46f71192dabf6fa3 100644
--- a/journals/views.py
+++ b/journals/views.py
@@ -15,9 +15,7 @@ from django.http import HttpResponse
 
 from .exceptions import PaperNumberingError
 from .helpers import paper_nr_string
-from .models import ProductionStream, ProductionEvent
 from .models import Journal, Issue, Publication, UnregisteredAuthor
-from .forms import ProductionEventForm
 from .forms import FundingInfoForm, InitiatePublicationForm, ValidatePublicationForm,\
                    UnregisteredAuthorForm, CreateMetadataXMLForm, CitationListBibitemsForm
 from .utils import JournalUtils
@@ -142,61 +140,6 @@ def issue_detail(request, doi_label):
     return render(request, 'journals/journal_issue_detail.html', context)
 
 
-######################
-# Production process #
-######################
-
-@permission_required('scipost.can_view_production', return_403=True)
-def production(request):
-    """
-    Overview page for the production process.
-    All papers with accepted but not yet published status are included here.
-    """
-    accepted_submissions = Submission.objects.filter(
-        status='accepted').order_by('latest_activity')
-    streams = ProductionStream.objects.all().order_by('opened')
-    prodevent_form = ProductionEventForm()
-    context = {
-        'accepted_submissions': accepted_submissions,
-        'streams': streams,
-        'prodevent_form': prodevent_form,
-    }
-    return render(request, 'journals/production.html', context)
-
-@permission_required('scipost.can_view_production', return_403=True)
-@transaction.atomic
-def add_production_event(request, stream_id):
-    stream = get_object_or_404(ProductionStream, pk=stream_id)
-    if request.method == 'POST':
-        prodevent_form = ProductionEventForm(request.POST)
-        if prodevent_form.is_valid():
-            prodevent = ProductionEvent(
-                stream=stream,
-                event=prodevent_form.cleaned_data['event'],
-                comments=prodevent_form.cleaned_data['comments'],
-                noted_on=timezone.now(),
-                noted_by=request.user.contributor,
-                duration=prodevent_form.cleaned_data['duration'],)
-            prodevent.save()
-            return redirect(reverse('journals:production'))
-        else:
-            errormessage = 'The form was invalidly filled.'
-            return render(request, 'scipost/error.html', {'errormessage': errormessage})
-    else:
-        errormessage = 'This view can only be posted to.'
-        return render(request, 'scipost/error.html', {'errormessage': errormessage})
-
-
-
-
-def upload_proofs(request):
-    """
-    TODO
-    Called by a member of the Production Team.
-    Upload the production version .pdf of a submission.
-    """
-    return render(request, 'journals/upload_proofs.html')
-
 
 #######################
 # Publication process #
diff --git a/partners/__init__.py b/partners/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/partners/admin.py b/partners/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..2b1ddd057b5450a624f52bfba070d5de1bc36f56
--- /dev/null
+++ b/partners/admin.py
@@ -0,0 +1,18 @@
+from django.contrib import admin
+
+from .models import ContactPerson, Partner, Consortium,\
+    ProspectivePartner, MembershipAgreement
+
+
+admin.site.register(ContactPerson)
+
+
+class PartnerAdmin(admin.ModelAdmin):
+    search_fields = ['institution', 'institution_acronym',
+                     'institution_address', 'contact_person']
+
+
+admin.site.register(Partner, PartnerAdmin)
+admin.site.register(Consortium)
+admin.site.register(ProspectivePartner)
+admin.site.register(MembershipAgreement)
diff --git a/partners/apps.py b/partners/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..22e6fe3bc79c57abf3a8a51ccf4f6dc9ca1ff251
--- /dev/null
+++ b/partners/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class PartnersConfig(AppConfig):
+    name = 'partners'
diff --git a/partners/constants.py b/partners/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..ec05c2315596b05013b825e5c27531e75aaca543
--- /dev/null
+++ b/partners/constants.py
@@ -0,0 +1,43 @@
+import datetime
+
+
+PARTNER_TYPES = (
+    ('Int. Fund. Agency', 'International Funding Agency'),
+    ('Nat. Fund. Agency', 'National Funding Agency'),
+    ('Nat. Library', 'National Library'),
+    ('Univ. Library', 'University Library'),
+    ('Res. Library', 'Research Library'),
+    ('Foundation', 'Foundation'),
+    ('Individual', 'Individual'),
+)
+
+PARTNER_STATUS = (
+    ('Prospective', 'Prospective'),
+    ('Negotiating', 'Negotiating'),
+    ('Active', 'Active'),
+    ('Inactive', 'Inactive'),
+)
+
+
+CONSORTIUM_STATUS = (
+    ('Prospective', 'Prospective'),
+    ('Active', 'Active'),
+    ('Inactive', 'Inactive'),
+)
+
+
+MEMBERSHIP_AGREEMENT_STATUS = (
+    ('Submitted', 'Request submitted by Partner'),
+    ('Pending', 'Sent to Partner, response pending'),
+    ('Signed', 'Signed by Partner'),
+    ('Honoured', 'Honoured: payment of Partner received'),
+    ('Completed', 'Completed: agreement has been fulfilled'),
+)
+
+MEMBERSHIP_DURATION = (
+    (datetime.timedelta(days=365), '1 year'),
+    (datetime.timedelta(days=730), '2 years'),
+    (datetime.timedelta(days=1095), '3 years'),
+    (datetime.timedelta(days=1460), '4 years'),
+    (datetime.timedelta(days=1825), '5 years'),
+)
diff --git a/partners/forms.py b/partners/forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..3aa5ed7fe1168577f6b676b20755236600e105d2
--- /dev/null
+++ b/partners/forms.py
@@ -0,0 +1,47 @@
+from django import forms
+
+from captcha.fields import ReCaptchaField
+from django_countries import countries
+from django_countries.widgets import CountrySelectWidget
+from django_countries.fields import LazyTypedChoiceField
+
+from .constants import PARTNER_TYPES
+from .models import ContactPerson, Partner, ProspectivePartner, MembershipAgreement
+
+from scipost.models import TITLE_CHOICES
+
+
+class PartnerForm(forms.ModelForm):
+    class Meta:
+        model = Partner
+        fields = '__all__'
+
+    def __init__(self, *args, **kwargs):
+        super(PartnerForm, self).__init__(*args, **kwargs)
+        self.fields['institution_address'].widget = forms.Textarea({'rows': 8, })
+
+
+class ProspectivePartnerForm(forms.ModelForm):
+    class Meta:
+        model = ProspectivePartner
+        exclude = ['date_received', 'date_processed', 'processed']
+
+
+class MembershipQueryForm(forms.Form):
+    """
+    This form is to be used by an agent of the prospective Partner,
+    in order to request more information about potentially joining the SPB.
+    """
+    title = forms.ChoiceField(choices=TITLE_CHOICES, label='* Your title')
+    first_name = forms.CharField(label='* Your first name', max_length=100)
+    last_name = forms.CharField(label='* Your last name', max_length=100)
+    email = forms.EmailField(label='* Your email address')
+    role = forms.CharField(label='* Your role in your organization')
+    partner_type = forms.ChoiceField(choices=PARTNER_TYPES, label='* Partner type')
+    institution_name = forms.CharField(label='* Name of your institution')
+    country = LazyTypedChoiceField(
+        choices=countries, label='* Country', initial='NL',
+        widget=CountrySelectWidget(layout=(
+            '{widget}<img class="country-select-flag" id="{flag_id}"'
+            ' style="margin: 6px 4px 0" src="{country.flag}">')))
+    captcha = ReCaptchaField(attrs={'theme': 'clean'}, label='*Please verify to continue:')
diff --git a/partners/migrations/0001_initial.py b/partners/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f7fdd7dcfe5882c5057e8d2d8991c996d956abc
--- /dev/null
+++ b/partners/migrations/0001_initial.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2017-05-19 08:59
+from __future__ import unicode_literals
+
+import datetime
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import django_countries.fields
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Consortium',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=128)),
+                ('status', models.CharField(choices=[('Prospective', 'Prospective'), ('Active', 'Active'), ('Inactive', 'Inactive')], max_length=16)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='ContactPerson',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('title', models.CharField(choices=[('PR', 'Prof.'), ('DR', 'Dr'), ('MR', 'Mr'), ('MRS', 'Mrs')], max_length=4)),
+                ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='MembershipAgreement',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('status', models.CharField(choices=[('Submitted', 'Request submitted by Partner'), ('Pending', 'Sent to Partner, response pending'), ('Signed', 'Signed by Partner'), ('Honoured', 'Honoured: payment of Partner received'), ('Completed', 'Completed: agreement has been fulfilled')], max_length=16)),
+                ('date_requested', models.DateField()),
+                ('start_date', models.DateField()),
+                ('duration', models.DurationField(choices=[(datetime.timedelta(365), '1 year'), (datetime.timedelta(730), '2 years'), (datetime.timedelta(1095), '3 years'), (datetime.timedelta(1460), '4 years'), (datetime.timedelta(1825), '5 years')])),
+                ('offered_yearly_contribution', models.SmallIntegerField(default=0)),
+                ('consortium', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='partners.Consortium')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Partner',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('partner_type', models.CharField(choices=[('Int. Fund. Agency', 'International Funding Agency'), ('Nat. Fund. Agency', 'National Funding Agency'), ('Nat. Library', 'National Library'), ('Univ. Library', 'University Library'), ('Res. Library', 'Research Library'), ('Foundation', 'Foundation'), ('Individual', 'Individual')], max_length=32)),
+                ('status', models.CharField(choices=[('Prospective', 'Prospective'), ('Negotiating', 'Negotiating'), ('Active', 'Active'), ('Inactive', 'Inactive')], max_length=16)),
+                ('institution_name', models.CharField(max_length=256)),
+                ('institution_acronym', models.CharField(max_length=10)),
+                ('institution_address', models.CharField(blank=True, max_length=1000, null=True)),
+                ('country', django_countries.fields.CountryField(max_length=2)),
+                ('financial_contact', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='partner_financial_contact', to='partners.ContactPerson')),
+                ('main_contact', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='partner_main_contact', to='partners.ContactPerson')),
+                ('technical_contact', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='partner_technical_contact', to='partners.ContactPerson')),
+            ],
+        ),
+        migrations.AddField(
+            model_name='membershipagreement',
+            name='partner',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='partners.Partner'),
+        ),
+        migrations.AddField(
+            model_name='consortium',
+            name='partners',
+            field=models.ManyToManyField(blank=True, to='partners.Partner'),
+        ),
+    ]
diff --git a/partners/migrations/0002_auto_20170519_1335.py b/partners/migrations/0002_auto_20170519_1335.py
new file mode 100644
index 0000000000000000000000000000000000000000..8bffe6c9bbe4a80f18c7533550ad3b1ebce65321
--- /dev/null
+++ b/partners/migrations/0002_auto_20170519_1335.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2017-05-19 11:35
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.utils.timezone
+import django_countries.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('partners', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='MembershipQuery',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('title', models.CharField(choices=[('PR', 'Prof.'), ('DR', 'Dr'), ('MR', 'Mr'), ('MRS', 'Mrs')], max_length=4)),
+                ('first_name', models.CharField(max_length=32)),
+                ('last_name', models.CharField(max_length=32)),
+                ('email', models.EmailField(max_length=254)),
+                ('partner_type', models.CharField(choices=[('Int. Fund. Agency', 'International Funding Agency'), ('Nat. Fund. Agency', 'National Funding Agency'), ('Nat. Library', 'National Library'), ('Univ. Library', 'University Library'), ('Res. Library', 'Research Library'), ('Foundation', 'Foundation'), ('Individual', 'Individual')], max_length=32)),
+                ('institution_name', models.CharField(max_length=256)),
+                ('country', django_countries.fields.CountryField(max_length=2)),
+                ('date_received', models.DateTimeField(default=django.utils.timezone.now)),
+                ('date_processed', models.DateTimeField()),
+                ('processed', models.BooleanField(default=False)),
+            ],
+            options={
+                'verbose_name_plural': 'membership queries',
+            },
+        ),
+        migrations.AlterModelOptions(
+            name='consortium',
+            options={'verbose_name_plural': 'consortia'},
+        ),
+    ]
diff --git a/partners/migrations/0003_auto_20170519_1424.py b/partners/migrations/0003_auto_20170519_1424.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5d9c35d8fcd8f7dc056c421fe2b0b32ceae6339
--- /dev/null
+++ b/partners/migrations/0003_auto_20170519_1424.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2017-05-19 12:24
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.utils.timezone
+import django_countries.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('partners', '0002_auto_20170519_1335'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ProspectivePartner',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('title', models.CharField(choices=[('PR', 'Prof.'), ('DR', 'Dr'), ('MR', 'Mr'), ('MRS', 'Mrs')], max_length=4)),
+                ('first_name', models.CharField(max_length=32)),
+                ('last_name', models.CharField(max_length=32)),
+                ('email', models.EmailField(max_length=254)),
+                ('role', models.CharField(max_length=128)),
+                ('partner_type', models.CharField(choices=[('Int. Fund. Agency', 'International Funding Agency'), ('Nat. Fund. Agency', 'National Funding Agency'), ('Nat. Library', 'National Library'), ('Univ. Library', 'University Library'), ('Res. Library', 'Research Library'), ('Foundation', 'Foundation'), ('Individual', 'Individual')], max_length=32)),
+                ('institution_name', models.CharField(max_length=256)),
+                ('country', django_countries.fields.CountryField(max_length=2)),
+                ('date_received', models.DateTimeField(default=django.utils.timezone.now)),
+                ('date_processed', models.DateTimeField()),
+                ('processed', models.BooleanField(default=False)),
+            ],
+        ),
+        migrations.DeleteModel(
+            name='MembershipQuery',
+        ),
+    ]
diff --git a/partners/migrations/0004_auto_20170519_1425.py b/partners/migrations/0004_auto_20170519_1425.py
new file mode 100644
index 0000000000000000000000000000000000000000..f44c307494246de26b93ad9a8dc869f5a9d3a8a2
--- /dev/null
+++ b/partners/migrations/0004_auto_20170519_1425.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2017-05-19 12:25
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('partners', '0003_auto_20170519_1424'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='prospectivepartner',
+            name='date_processed',
+            field=models.DateTimeField(blank=True, null=True),
+        ),
+    ]
diff --git a/partners/migrations/__init__.py b/partners/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/partners/models.py b/partners/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..82450ab9946f58536bb1892d64b2d97b3a296401
--- /dev/null
+++ b/partners/models.py
@@ -0,0 +1,105 @@
+from django.contrib.auth.models import User
+from django.db import models
+from django.utils import timezone
+
+from django_countries.fields import CountryField
+
+from .constants import PARTNER_TYPES, PARTNER_STATUS, CONSORTIUM_STATUS,\
+    MEMBERSHIP_AGREEMENT_STATUS, MEMBERSHIP_DURATION
+
+from scipost.constants import TITLE_CHOICES
+
+
+class ContactPerson(models.Model):
+    """
+    A ContactPerson is a simple form of User which is meant
+    to be associated to Partner objects
+    (main contact, financial/technical contact etc).
+    ContactPersons and Contributors have different rights.
+    """
+    user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True)
+    title = models.CharField(max_length=4, choices=TITLE_CHOICES)
+
+    def __str__(self):
+        return '%s %s, %s' % (self.get_title_display(), self.user.last_name, self.user.first_name)
+
+
+class Partner(models.Model):
+    """
+    Supporting Partners.
+    These are the official Partner objects created by SciPost Admin.
+    """
+    partner_type = models.CharField(max_length=32, choices=PARTNER_TYPES)
+    status = models.CharField(max_length=16, choices=PARTNER_STATUS)
+    institution_name = models.CharField(max_length=256)
+    institution_acronym = models.CharField(max_length=10)
+    institution_address = models.CharField(max_length=1000, blank=True, null=True)
+    country = CountryField()
+    main_contact = models.ForeignKey(ContactPerson, on_delete=models.CASCADE,
+                                     blank=True, null=True,
+                                     related_name='partner_main_contact')
+    financial_contact = models.ForeignKey(ContactPerson, on_delete=models.CASCADE,
+                                          blank=True, null=True,
+                                          related_name='partner_financial_contact')
+    technical_contact = models.ForeignKey(ContactPerson, on_delete=models.CASCADE,
+                                          blank=True, null=True,
+                                          related_name='partner_technical_contact')
+
+    def __str__(self):
+        return self.institution_acronym + ' (' + self.get_status_display() + ')'
+
+
+class Consortium(models.Model):
+    """
+    Collection of Partners.
+    """
+    name = models.CharField(max_length=128)
+    partners = models.ManyToManyField(Partner, blank=True)
+    status = models.CharField(max_length=16, choices=CONSORTIUM_STATUS)
+
+    class Meta:
+        verbose_name_plural = 'consortia'
+
+
+class ProspectivePartner(models.Model):
+    """
+    Created from the membership_request page, after submitting a query form.
+    """
+    title = models.CharField(max_length=4, choices=TITLE_CHOICES)
+    first_name = models.CharField(max_length=32)
+    last_name = models.CharField(max_length=32)
+    email = models.EmailField()
+    role = models.CharField(max_length=128)
+    partner_type = models.CharField(max_length=32, choices=PARTNER_TYPES)
+    institution_name = models.CharField(max_length=256)
+    country = CountryField()
+    date_received = models.DateTimeField(default=timezone.now)
+    date_processed = models.DateTimeField(blank=True, null=True)
+    processed = models.BooleanField(default=False)
+
+    def __str__(self):
+        resp = "processed"
+        if not self.processed:
+            resp = "unprocessed"
+        return '%s (received %s), %s' % (self.institution_name,
+                                         self.date_received.strftime("%Y-%m-%d"),
+                                         resp)
+
+
+class MembershipAgreement(models.Model):
+    """
+    Agreement for membership of the Supporting Partners Board.
+    A new instance is created each time an Agreement is made or renewed.
+    """
+    partner = models.ForeignKey(Partner, on_delete=models.CASCADE, blank=True, null=True)
+    consortium = models.ForeignKey(Consortium, on_delete=models.CASCADE, blank=True, null=True)
+    status = models.CharField(max_length=16, choices=MEMBERSHIP_AGREEMENT_STATUS)
+    date_requested = models.DateField()
+    start_date = models.DateField()
+    duration = models.DurationField(choices=MEMBERSHIP_DURATION)
+    offered_yearly_contribution = models.SmallIntegerField(default=0)
+
+    def __str__(self):
+        return (str(self.partner) +
+                ' [' + self.get_duration_display() +
+                ' from ' + self.start_date.strftime('%Y-%m-%d') + ']')
diff --git a/partners/templates/partners/_partner_card.html b/partners/templates/partners/_partner_card.html
new file mode 100644
index 0000000000000000000000000000000000000000..31175dccfad720d674489d66f3165abe4803c9af
--- /dev/null
+++ b/partners/templates/partners/_partner_card.html
@@ -0,0 +1,31 @@
+{% load bootstrap %}
+
+<div class="card-block">
+  <div class="row">
+    <div class="col-1">
+      <p>{{ partner.country }}</p>
+    </div>
+    <div class="col-4">
+      <h3>{{ partner.institution_name }}</h3>
+      <p>{{ partner.institution_acronym }}</p>
+      <p>({{ pp.get_partner_type_display }})</p>
+    </div>
+    <div class="col-4">
+      {% if partner.main_contact %}
+      <p>Main contact: {{ partner.main_contact..get_title_display }} {{ partner.main_contact.user.first_name }} {{ partner.main_contact.user.last_name }}</p>
+      <p>{{ partner.main_contact.user.email }}</p>
+      {% endif %}
+      {% if partner.financial_contact %}
+      <p>Financial contact: {{ partner.financial_contact..get_title_display }} {{ partner.financial_contact.user.first_name }} {{ partner.financial_contact.user.last_name }}</p>
+      <p>{{ partner.financial_contact.user.email }}</p>
+      {% endif %}
+      {% if partner.technical_contact %}
+      <p>Technical contact: {{ partner.technical_contact..get_title_display }} {{ partner.technical_contact.user.first_name }} {{ partner.technical_contact.user.last_name }}</p>
+      <p>{{ partner.technical_contact.user.email }}</p>
+      {% endif %}
+    </div>
+    <div class="col-3">
+      <p>Edit</p>
+    </div>
+  </div>
+</div>
diff --git a/partners/templates/partners/_prospective_partner_card.html b/partners/templates/partners/_prospective_partner_card.html
new file mode 100644
index 0000000000000000000000000000000000000000..fe2624c247b05a878622743b0c213a80467220cc
--- /dev/null
+++ b/partners/templates/partners/_prospective_partner_card.html
@@ -0,0 +1,22 @@
+{% load bootstrap %}
+
+<div class="card-block">
+  <div class="row">
+    <div class="col-1">
+      <p>{{ pp.country }}</p>
+    </div>
+    <div class="col-4">
+      <h3>{{ pp.institution_name }}</h3>
+      <p>({{ pp.get_partner_type_display }})</p>
+      <p>Received {{ pp.date_received }}</p>
+    </div>
+    <div class="col-4">
+      <p>Contact: {{ pp.get_title_display }} {{ pp.first_name }} {{ pp.last_name }}</p>
+      <p>(role: {{ pp.role }})</p>
+      <p>{{ pp.email }}</p>
+    </div>
+    <div class="col-3">
+      <p>Edit</p>
+    </div>
+  </div>
+</div>
diff --git a/partners/templates/partners/add_prospective_partner.html b/partners/templates/partners/add_prospective_partner.html
new file mode 100644
index 0000000000000000000000000000000000000000..737780443f5c8da12d23699d2845e666fbf1ce14
--- /dev/null
+++ b/partners/templates/partners/add_prospective_partner.html
@@ -0,0 +1,29 @@
+{% extends 'scipost/base.html' %}
+
+{% block pagetitle %}: Supporting Partners: add{% endblock pagetitle %}
+
+{% load bootstrap %}
+
+{% block content %}
+
+<section>
+  <div class="flex-container">
+    <div class="flex-greybox">
+      <h1>Add a Prospective Partner</h1>
+    </div>
+  </div>
+  <p>Please provide contact details of an appropriate representative, and details about the potential Partner.</p>
+
+    <form action="{% url 'partners:add_prospective_partner' %}" method="post">
+      {% csrf_token %}
+      {{ form|bootstrap }}
+      <input class="btn btn-primary" type="submit" value="Submit"/>
+    </form>
+
+    {% if errormessage %}
+    <p class="text-danger">{{ errormessage }}</p>
+    {% endif %}
+
+</section>
+
+{% endblock content %}
diff --git a/partners/templates/partners/manage_partners.html b/partners/templates/partners/manage_partners.html
new file mode 100644
index 0000000000000000000000000000000000000000..95fb172639561b9c03f3d03c7a97711b0ccdf18a
--- /dev/null
+++ b/partners/templates/partners/manage_partners.html
@@ -0,0 +1,55 @@
+{% extends 'scipost/base.html' %}
+
+{% block pagetitle %}: Supporting Partners: manage{% endblock pagetitle %}
+
+
+{% block content %}
+
+<div class="flex-container">
+  <div class="flex-greybox">
+    <h1>Partners Management Page</h1>
+  </div>
+</div>
+
+<section>
+  <div class="flex-container">
+    <div class="flex-greybox">
+      <h2>Partners</h2>
+    </div>
+  </div>
+  <ul class="list-group list-group-flush">
+    {% for partner in partners %}
+    <li class="list-group-item">{% include 'partners/_partner_card.html' with partner=partner %}</li>
+    {% endfor %}
+  </ul>
+</section>
+
+<section>
+  <div class="flex-container">
+    <div class="flex-greybox">
+      <h2>Prospective Partners (not yet processed)</h2>
+    </div>
+  </div>
+  <h3><a href="{% url 'partners:add_prospective_partner' %}">Add a prospective partner</a></h3>
+  <br/>
+  <ul class="list-group list-group-flush">
+    {% for partner in prospective_partners %}
+    <li class="list-group-item">{% include 'partners/_prospective_partner_card.html' with pp=partner %}</li>
+    {% endfor %}
+  </ul>
+</section>
+
+<section>
+  <div class="flex-container">
+    <div class="flex-greybox">
+      <h2>Agreements</h2>
+    </div>
+  </div>
+  <ul>
+    {% for agreement in agreements %}
+    <li>{{ agreement }}</li>
+    {% endfor %}
+  </ul>
+</section>
+
+{% endblock content %}
diff --git a/scipost/templates/scipost/SPB_membership_request.html b/partners/templates/partners/membership_request.html
similarity index 74%
rename from scipost/templates/scipost/SPB_membership_request.html
rename to partners/templates/partners/membership_request.html
index 2048761d33e9e9e89f7d71d9ba4181d756e24c01..13e312907df28f848d1eeb15705c45a45ed33cb9 100644
--- a/scipost/templates/scipost/SPB_membership_request.html
+++ b/partners/templates/partners/membership_request.html
@@ -37,27 +37,27 @@ $(document).ready(function(){
 
   <div class="flex-container">
     <div class="flex-whitebox">
-      <p>You can hereby initiate the process to become one of our Supporting Partners.</p>
+      <p>You can hereby request further details on the process to become one
+	of our Supporting Partners.</p>
       <p>Filling in this form does not yet constitute a binding agreement.</p>
       <p>It simply expresses your interest in considering joining our Supporting Partners Board.</p>
-      <p>After filling this form, SciPost Administration will contact you with a Partnership
-	Agreement offer.</p>
+      <p>After filling this form, SciPost Administration will contact you with more details on Partnership.</p>
       <p><em>Note: you will automatically be considered as the contact person for this Partner.</em></p>
 
       {% if errormessage %}
       <p style="color: red;">{{ errormessage }}</p>
       {% endif %}
 
-      <form action="{% url 'scipost:SPB_membership_request' %}" method="post">
+      <form action="{% url 'partners:membership_request' %}" method="post">
 	{% csrf_token %}
-	<h3>Partner details:</h3>
-	
-	{{ SP_form|bootstrap }}
-	<h3>Agreement terms:</h3>
-	{{ membership_form|bootstrap }}
-	<input class="btn btn-secondary" type="submit" value="Submit"/>
+	<h3>Please provide us the following relevant details:</h3>
+	{{ query_form|bootstrap }}
+	<input class="btn btn-primary" type="submit" value="Submit"/>
       </form>
 
+      {% if errormessage %}
+          <p class="text-danger">{{ errormessage }}</p>
+      {% endif %}
     </div>
   </div>
 
diff --git a/scipost/templates/scipost/supporting_partners.html b/partners/templates/partners/supporting_partners.html
similarity index 93%
rename from scipost/templates/scipost/supporting_partners.html
rename to partners/templates/partners/supporting_partners.html
index f5ced193e5cf7364cf6501cbbdf892a32e365e58..b1434fb8c75e7465993224ad3118362e8ac9b45b 100644
--- a/scipost/templates/scipost/supporting_partners.html
+++ b/partners/templates/partners/supporting_partners.html
@@ -8,7 +8,6 @@
 
 {% block bodysup %}
 
-
 <section>
   <div class="flex-container">
     <div class="flex-greybox">
@@ -16,6 +15,10 @@
     </div>
   </div>
 
+  {% if perms.scipost.can_manage_SPB %}
+  <a href="{% url 'partners:manage' %}">Manage Partners</a>
+  {% endif %}
+
   <div class="flex-container">
     <div class="flex-whitebox">
 
@@ -41,7 +44,7 @@
 
   <p>We hereby cordially invite interested parties who are supportive of SciPost's mission to join the SciPost Supporting Partners Board by signing a <a href="{% static 'scipost/SPB/SciPost_Supporting_Partner_Agreement.pdf' %}">Partner Agreement</a>.</p>
 
-  <p>Prospective partners can initiate the process leading to Membership by filling the <a href="{% url 'scipost:SPB_membership_request' %}">online request form</a>.</p>
+  <p>Prospective partners can query for more information about Membership by filling the <a href="{% url 'partners:membership_request' %}">online query form</a>.</p>
 
   <br/>
   <p>The <a href="{% static 'scipost/SPB/SciPost_Supporting_Partner_Agreement.pdf' %}">Partner Agreement</a> itself contains a detailed presentation of the Foundation, its activities and financial aspects. What follows is a summary of the most important points.</p>
@@ -107,7 +110,7 @@
   <h3>Activation procedure</h3>
   <p>In order to become a Supporting Partner, one must:
     <ul>
-      <li>Fill in the online <a href="{% url 'scipost:SPB_membership_request' %}">membership request form</a> (the form must be filled in by a registered Contributor, employed by or associated to the prospective Partner and acting as an authorized agent for the latter; personal contact details of this person will be treated confidentially).</li>
+      <li>Fill in the online <a href="{% url 'partners:membership_request' %}">membership request form</a> (the form must be filled in by an authorized agent employed by or associated to the prospective Partner; personal contact details of this person will be treated confidentially).</li>
       <li>Wait for the email response from the SciPost administration, containing a Partnership Agreement offer including detailed terms (start date, duration, financial contribution).</li>
       <li>Email a scan of the signed copy of the Partnership Agreement to SciPost.</li>
       <li>Proceed with the payment of the financial contribution, following invoicing from the SciPost Foundation.</li>
diff --git a/partners/tests.py b/partners/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/partners/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/partners/urls.py b/partners/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..5558aaaed1659597f7511d16fc50b80acc947868
--- /dev/null
+++ b/partners/urls.py
@@ -0,0 +1,14 @@
+from django.conf.urls import url
+
+from . import views
+
+urlpatterns = [
+
+    url(r'^$', views.supporting_partners,
+        name='partners'),
+    url(r'^membership_request$', views.membership_request,
+        name='membership_request'),
+    url(r'^manage$', views.manage, name='manage'),
+    url(r'^add_prospective_partner$', views.add_prospective_partner,
+        name='add_prospective_partner'),
+]
diff --git a/partners/views.py b/partners/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..7473a5352a699f7b2c940ad97dcffaab3554fd68
--- /dev/null
+++ b/partners/views.py
@@ -0,0 +1,64 @@
+from django.contrib import messages
+from django.shortcuts import render, reverse, redirect
+from django.utils import timezone
+
+from guardian.decorators import permission_required
+
+from .models import Partner, ProspectivePartner, MembershipAgreement
+from .forms import ProspectivePartnerForm, MembershipQueryForm
+
+
+def supporting_partners(request):
+    prospective_agreements = MembershipAgreement.objects.filter(
+        status='Submitted').order_by('date_requested')
+    context = {'prospective_partners': prospective_agreements, }
+    return render(request, 'partners/supporting_partners.html', context)
+
+
+def membership_request(request):
+    query_form = MembershipQueryForm(request.POST or None)
+    if query_form.is_valid():
+        query = ProspectivePartner(
+            title=query_form.cleaned_data['title'],
+            first_name=query_form.cleaned_data['first_name'],
+            last_name=query_form.cleaned_data['last_name'],
+            email=query_form.cleaned_data['email'],
+            partner_type=query_form.cleaned_data['partner_type'],
+            institution_name=query_form.cleaned_data['institution_hame'],
+            country=query_form.cleaned_data['country'],
+            date_received=timezone.now(),
+        )
+        query.save()
+        ack_message = ('Thank you for your SPB Membership query. '
+                       'We will get back to you in the very near future '
+                       'with further details.')
+        context = {'ack_message': ack_message, }
+        return render(request, 'scipost/acknowledgement.html', context)
+    context = {'query_form': query_form}
+    return render(request, 'partners/membership_request.html', context)
+
+
+@permission_required('scipost.can_manage_SPB', return_403=True)
+def manage(request):
+    """
+    Lists relevant info regarding management of Supporting Partners Board.
+    """
+    partners = Partner.objects.all().order_by('country', 'institution_name')
+    prospective_partners = ProspectivePartner.objects.filter(
+        processed=False).order_by('date_received')
+    agreements = MembershipAgreement.objects.all().order_by('date_requested')
+    context = {'partners': partners,
+               'prospective_partners': prospective_partners,
+               'agreements': agreements, }
+    return render(request, 'partners/manage_partners.html', context)
+
+
+@permission_required('scipost.can_manage_SPB', return_403=True)
+def add_prospective_partner(request):
+    form = ProspectivePartnerForm(request.POST or None)
+    if form.is_valid():
+        form.save()
+        messages.success(request, 'Prospective Partners successfully added')
+        return redirect(reverse('partners:manage'))
+    context = {'form': form}
+    return render(request, 'partners/add_prospective_partner.html', context)
diff --git a/production/__init__.py b/production/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/production/admin.py b/production/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..41ae12e06e49ec6a43fc034ed520b5708e3b2ff0
--- /dev/null
+++ b/production/admin.py
@@ -0,0 +1,7 @@
+from django.contrib import admin
+
+from .models import ProductionStream, ProductionEvent
+
+
+admin.site.register(ProductionStream)
+admin.site.register(ProductionEvent)
diff --git a/production/apps.py b/production/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..d8fba60df00793d7fbaff3cf230a0ba937c9576a
--- /dev/null
+++ b/production/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class ProductionConfig(AppConfig):
+    name = 'production'
diff --git a/production/constants.py b/production/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..63d9f65fef9e6df30ea6af26cb5767a5fa46bec8
--- /dev/null
+++ b/production/constants.py
@@ -0,0 +1,19 @@
+PRODUCTION_STREAM_STATUS = (
+    ('ongoing', 'Ongoing'),
+    ('completed', 'Completed'),
+)
+
+
+PRODUCTION_EVENTS = (
+    ('assigned_to_supervisor', 'Assigned by EdAdmin to Supervisor'),
+    ('message_edadmin_to_supervisor', 'Message from EdAdmin to Supervisor'),
+    ('message_supervisor_to_edadmin', 'Message from Supervisor to EdAdmin'),
+    ('officer_tasked_with_proof_production', 'Supervisor tasked officer with proofs production'),
+    ('message_supervisor_to_officer', 'Message from Supervisor to Officer'),
+    ('message_officer_to_supervisor', 'Message from Officer to Supervisor'),
+    ('proofs_produced', 'Proofs have been produced'),
+    ('proofs_sent_to_authors', 'Proofs sent to Authors'),
+    ('proofs_returned_by_authors', 'Proofs returned by Authors'),
+    ('corrections_implemented', 'Corrections implemented'),
+    ('authors_have_accepted_proofs', 'Authors have accepted proofs'),
+)
diff --git a/production/forms.py b/production/forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f47ebe6755bf04607cf9f15f4515f73d7d090f0
--- /dev/null
+++ b/production/forms.py
@@ -0,0 +1,13 @@
+from django import forms
+
+from .models import ProductionEvent
+
+
+class ProductionEventForm(forms.ModelForm):
+    class Meta:
+        model = ProductionEvent
+        exclude = ['stream', 'noted_on', 'noted_by']
+        widgets = {
+            'comments': forms.Textarea(attrs={'rows': 4}),
+            'duration': forms.TextInput(attrs={'placeholder': 'HH:MM:SS'})
+        }
diff --git a/production/migrations/0001_initial.py b/production/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..3e3ed1ac3f9aff3d6ab3a6ab1f0367aea832859d
--- /dev/null
+++ b/production/migrations/0001_initial.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2017-05-17 17:23
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('submissions', '0043_auto_20170512_0836'),
+        ('scipost', '0054_delete_newsitem'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ProductionEvent',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('event', models.CharField(choices=[('assigned_to_supervisor', 'Assigned to Supervisor'), ('officer_tasked_with_proof_production', 'Officer tasked with proofs production'), ('proofs_produced', 'Proofs have been produced'), ('proofs_sent_to_authors', 'Proofs sent to Authors'), ('proofs_returned_by_authors', 'Proofs returned by Authors'), ('corrections_implemented', 'Corrections implemented'), ('authors_have_accepted_proofs', 'Authors have accepted proofs')], max_length=64)),
+                ('comments', models.TextField(blank=True, null=True)),
+                ('noted_on', models.DateTimeField(default=django.utils.timezone.now)),
+                ('duration', models.DurationField(blank=True, null=True)),
+                ('noted_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='scipost.Contributor')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='ProductionStream',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('opened', models.DateTimeField()),
+                ('submission', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='submissions.Submission')),
+            ],
+        ),
+        migrations.AddField(
+            model_name='productionevent',
+            name='stream',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='production.ProductionStream'),
+        ),
+    ]
diff --git a/production/migrations/0002_auto_20170517_1942.py b/production/migrations/0002_auto_20170517_1942.py
new file mode 100644
index 0000000000000000000000000000000000000000..53aa4d79751c03ad4491b5da6731abc11d705509
--- /dev/null
+++ b/production/migrations/0002_auto_20170517_1942.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2017-05-17 17:42
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('production', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='productionevent',
+            name='event',
+            field=models.CharField(choices=[('assigned_to_supervisor', 'Assigned by EdAdmin to Supervisor'), ('message_edadmin_to_supervisor', 'Message from EdAdmin to Supervisor'), ('message_supervisor_to_edadmin', 'Message from Supervisor to EdAdmin'), ('officer_tasked_with_proof_production', 'Supervisor tasked officer with proofs production'), ('message_supervisor_to_officer', 'Message from Supervisor to Officer'), ('message_officer_to_supervisor', 'Message from Officer to Supervisor'), ('proofs_produced', 'Proofs have been produced'), ('proofs_sent_to_authors', 'Proofs sent to Authors'), ('proofs_returned_by_authors', 'Proofs returned by Authors'), ('corrections_implemented', 'Corrections implemented'), ('authors_have_accepted_proofs', 'Authors have accepted proofs')], max_length=64),
+        ),
+    ]
diff --git a/production/migrations/__init__.py b/production/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/production/models.py b/production/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2cb30209aa2153f2c6b3e2365ce3e1dd6fd2b95
--- /dev/null
+++ b/production/models.py
@@ -0,0 +1,34 @@
+from django.db import models
+from django.utils import timezone
+
+from .constants import PRODUCTION_EVENTS
+
+from scipost.models import Contributor
+
+
+##############
+# Production #
+##############
+
+class ProductionStream(models.Model):
+    submission = models.OneToOneField('submissions.Submission', on_delete=models.CASCADE)
+    opened = models.DateTimeField()
+
+    def __str__(self):
+        return str(self.submission)
+
+    def total_duration(self):
+        totdur = self.productionevent_set.all().aggregate(models.Sum('duration'))
+        return totdur['duration__sum']
+
+
+class ProductionEvent(models.Model):
+    stream = models.ForeignKey(ProductionStream, on_delete=models.CASCADE)
+    event = models.CharField(max_length=64, choices=PRODUCTION_EVENTS)
+    comments = models.TextField(blank=True, null=True)
+    noted_on = models.DateTimeField(default=timezone.now)
+    noted_by = models.ForeignKey(Contributor, on_delete=models.CASCADE)
+    duration = models.DurationField(blank=True, null=True)
+
+    def __str__(self):
+        return '%s: %s' % (str(self.stream.submission), self.get_event_display())
diff --git a/journals/templates/journals/_production_event_li.html b/production/templates/production/_production_event_li.html
similarity index 100%
rename from journals/templates/journals/_production_event_li.html
rename to production/templates/production/_production_event_li.html
diff --git a/journals/templates/journals/_production_stream_card.html b/production/templates/production/_production_stream_card.html
similarity index 81%
rename from journals/templates/journals/_production_stream_card.html
rename to production/templates/production/_production_stream_card.html
index f2a78b13fd8770a13b8064ac0bbbbe7557add4b8..fda086794d4c0faa5efcdf4bbc80d314b7dc2a26 100644
--- a/journals/templates/journals/_production_stream_card.html
+++ b/production/templates/production/_production_stream_card.html
@@ -8,7 +8,7 @@
       <h3>Events</h3>
       <ul>
 	{% for event in stream.productionevent_set.all %}
-	{% include 'journals/_production_event_li.html' with event=event %}
+	{% include 'production/_production_event_li.html' with event=event %}
 	{% empty %}
 	<li>No events were found.</li>
 	{% endfor %}
@@ -20,7 +20,7 @@
     </div>
     <div class="col-5">
       <h3>Add an event to this production stream:</h3>
-      <form action="{% url 'journals:add_production_event' stream_id=stream.id %}" method="post">
+      <form action="{% url 'production:add_event' stream_id=stream.id %}" method="post">
 	{% csrf_token %}
 	{{ form|bootstrap }}
 	<input type="submit" name="submit" value="Submit">
diff --git a/journals/templates/journals/production.html b/production/templates/production/production.html
similarity index 79%
rename from journals/templates/journals/production.html
rename to production/templates/production/production.html
index 2f1b67f3a957bf520a09d7a461936822d45a3161..36b7dcf39eccbc0c9982f5ebe5703962d4955682 100644
--- a/journals/templates/journals/production.html
+++ b/production/templates/production/production.html
@@ -10,7 +10,7 @@
     <ul class="list-group list-group-flush">
       {% for stream in streams %}
       <li class="list-group-item">
-	{% include 'journals/_production_stream_card.html' with stream=stream form=prodevent_form %}
+	{% include 'production/_production_stream_card.html' with stream=stream form=prodevent_form %}
       </li>
       <hr/>
       {% endfor %}
diff --git a/production/tests.py b/production/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/production/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/production/urls.py b/production/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..11007074f3a47e420c14b81f62009486dab8f23c
--- /dev/null
+++ b/production/urls.py
@@ -0,0 +1,9 @@
+from django.conf.urls import url
+
+from production import views as production_views
+
+urlpatterns = [
+    url(r'^$', production_views.production, name='production'),
+    url(r'^add_event/(?P<stream_id>[0-9]+)$',
+        production_views.add_event, name='add_event'),
+]
diff --git a/production/views.py b/production/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..4093d7751d30398ce87f78f36b3482b1b5dae76f
--- /dev/null
+++ b/production/views.py
@@ -0,0 +1,66 @@
+from django.core.urlresolvers import reverse
+from django.db import transaction
+from django.shortcuts import get_object_or_404, render, redirect
+from django.utils import timezone
+
+from guardian.decorators import permission_required
+
+from .models import ProductionStream, ProductionEvent
+from .forms import ProductionEventForm
+
+from submissions.models import Submission
+
+
+######################
+# Production process #
+######################
+
+@permission_required('scipost.can_view_production', return_403=True)
+def production(request):
+    """
+    Overview page for the production process.
+    All papers with accepted but not yet published status are included here.
+    """
+    accepted_submissions = Submission.objects.filter(
+        status='accepted').order_by('latest_activity')
+    streams = ProductionStream.objects.all().order_by('opened')
+    prodevent_form = ProductionEventForm()
+    context = {
+        'accepted_submissions': accepted_submissions,
+        'streams': streams,
+        'prodevent_form': prodevent_form,
+    }
+    return render(request, 'production/production.html', context)
+
+
+@permission_required('scipost.can_view_production', return_403=True)
+@transaction.atomic
+def add_event(request, stream_id):
+    stream = get_object_or_404(ProductionStream, pk=stream_id)
+    if request.method == 'POST':
+        prodevent_form = ProductionEventForm(request.POST)
+        if prodevent_form.is_valid():
+            prodevent = ProductionEvent(
+                stream=stream,
+                event=prodevent_form.cleaned_data['event'],
+                comments=prodevent_form.cleaned_data['comments'],
+                noted_on=timezone.now(),
+                noted_by=request.user.contributor,
+                duration=prodevent_form.cleaned_data['duration'],)
+            prodevent.save()
+            return redirect(reverse('production:production'))
+        else:
+            errormessage = 'The form was invalidly filled.'
+            return render(request, 'scipost/error.html', {'errormessage': errormessage})
+    else:
+        errormessage = 'This view can only be posted to.'
+        return render(request, 'scipost/error.html', {'errormessage': errormessage})
+
+
+def upload_proofs(request):
+    """
+    TODO
+    Called by a member of the Production Team.
+    Upload the production version .pdf of a submission.
+    """
+    return render(request, 'production/upload_proofs.html')
diff --git a/scipost/admin.py b/scipost/admin.py
index af7ea2f7840bc90c5f207a131569457c6694055d..2f137a42295438e82e056a70de4a15d600104ca4 100644
--- a/scipost/admin.py
+++ b/scipost/admin.py
@@ -8,7 +8,7 @@ from django.contrib.auth.models import User, Permission
 from scipost.models import Contributor, Remark,\
                            DraftInvitation,\
                            AffiliationObject,\
-                           SupportingPartner, SPBMembershipAgreement, RegistrationInvitation,\
+                           RegistrationInvitation,\
                            AuthorshipClaim, PrecookedEmail,\
                            EditorialCollege, EditorialCollegeFellowship
 
@@ -126,20 +126,6 @@ class AffiliationObjectAdmin(admin.ModelAdmin):
 admin.site.register(AffiliationObject, AffiliationObjectAdmin)
 
 
-class SPBMembershipAgreementInline(admin.StackedInline):
-    model = SPBMembershipAgreement
-
-
-class SupportingPartnerAdmin(admin.ModelAdmin):
-    search_fields = ['institution', 'institution_acronym',
-                     'institution_address', 'contact_person']
-    inlines = [
-        SPBMembershipAgreementInline,
-    ]
-
-
-admin.site.register(SupportingPartner, SupportingPartnerAdmin)
-
 
 class EditorialCollegeAdmin(admin.ModelAdmin):
     search_fields = ['discipline', 'member']
diff --git a/scipost/constants.py b/scipost/constants.py
index 790f06d6669469d03a4d664a8a4d0a865a6d0095..aa19e9b476d151d4400e2a14037b60c24b4a17b9 100644
--- a/scipost/constants.py
+++ b/scipost/constants.py
@@ -1,4 +1,3 @@
-import datetime
 
 
 DISCIPLINE_PHYSICS = 'physics'
@@ -186,39 +185,3 @@ SCIPOST_FROM_ADDRESSES = (
     ('J. van Wezel', 'J. van Wezel <vanwezel@scipost.org>'),
 )
 SciPost_from_addresses_dict = dict(SCIPOST_FROM_ADDRESSES)
-
-#
-# Supporting partner models
-#
-PARTNER_TYPES = (
-    ('Int. Fund. Agency', 'International Funding Agency'),
-    ('Nat. Fund. Agency', 'National Funding Agency'),
-    ('Nat. Library', 'National Library'),
-    ('Univ. Library', 'University Library'),
-    ('Res. Library', 'Research Library'),
-    ('Consortium', 'Consortium'),
-    ('Foundation', 'Foundation'),
-    ('Individual', 'Individual'),
-)
-
-PARTNER_STATUS = (
-    ('Prospective', 'Prospective'),
-    ('Active', 'Active'),
-    ('Inactive', 'Inactive'),
-)
-
-
-SPB_MEMBERSHIP_AGREEMENT_STATUS = (
-    ('Submitted', 'Request submitted by Partner'),
-    ('Pending', 'Sent to Partner, response pending'),
-    ('Signed', 'Signed by Partner'),
-    ('Honoured', 'Honoured: payment of Partner received'),
-)
-
-SPB_MEMBERSHIP_DURATION = (
-    (datetime.timedelta(days=365), '1 year'),
-    (datetime.timedelta(days=730), '2 years'),
-    (datetime.timedelta(days=1095), '3 years'),
-    (datetime.timedelta(days=1460), '4 years'),
-    (datetime.timedelta(days=1825), '5 years'),
-)
diff --git a/scipost/factories.py b/scipost/factories.py
index ce122a6556cc9fb8ffdc1666608f2197382a2b7a..67cb4e2df31821db68e780bbf44fef72d83636c2 100644
--- a/scipost/factories.py
+++ b/scipost/factories.py
@@ -22,6 +22,8 @@ class ContributorFactory(factory.django.DjangoModelFactory):
     country_of_employment = factory.Iterator(list(COUNTRIES))
     affiliation = factory.Faker('company')
     expertises = factory.Iterator(SCIPOST_SUBJECT_AREAS[0][1], getter=lambda c: [c[0]])
+    personalwebpage = factory.Faker('domain_name')
+    address = factory.Faker('address')
 
     class Meta:
         model = Contributor
diff --git a/scipost/forms.py b/scipost/forms.py
index e1508ded5f231432cd2cb86afe3ff354c3b84cd1..7d830980a558b50c03ae13a09758c4875e4a4852 100644
--- a/scipost/forms.py
+++ b/scipost/forms.py
@@ -16,7 +16,6 @@ from crispy_forms.layout import Layout, Div, Field, HTML
 
 from .constants import SCIPOST_DISCIPLINES, TITLE_CHOICES, SCIPOST_FROM_ADDRESSES
 from .models import Contributor, DraftInvitation, RegistrationInvitation,\
-                    SupportingPartner, SPBMembershipAgreement,\
                     UnavailabilityPeriod, PrecookedEmail
 
 from journals.models import Publication
@@ -311,62 +310,3 @@ class SendPrecookedEmailForm(forms.Form):
         required=False, initial=False,
         label='Include SciPost summary at end of message')
     from_address = forms.ChoiceField(choices=SCIPOST_FROM_ADDRESSES)
-
-
-#############################
-# Supporting Partners Board #
-#############################
-
-class SupportingPartnerForm(forms.ModelForm):
-    class Meta:
-        model = SupportingPartner
-        fields = ['partner_type', 'institution',
-                  'institution_acronym', 'institution_address',
-                  'consortium_members'
-                  ]
-
-    def __init__(self, *args, **kwargs):
-        super(SupportingPartnerForm, self).__init__(*args, **kwargs)
-        self.fields['institution_address'].widget = forms.Textarea({'rows': 8, })
-        self.fields['consortium_members'].widget.attrs.update(
-            {'placeholder': 'Please list the names of the institutions within the consortium', })
-        self.helper = FormHelper()
-        self.helper.layout = Layout(
-            Div(
-                Div(
-                    Field('institution'),
-                    Field('institution_acronym'),
-                    Field('institution_address'),
-                    css_class='col-6'),
-                Div(
-                    Field('partner_type'),
-                    Field('consortium_members'),
-                    css_class='col-6'),
-                css_class='row')
-        )
-
-
-class SPBMembershipForm(forms.ModelForm):
-    class Meta:
-        model = SPBMembershipAgreement
-        fields = ['start_date', 'duration', 'offered_yearly_contribution']
-
-    def __init__(self, *args, **kwargs):
-        super(SPBMembershipForm, self).__init__(*args, **kwargs)
-        self.fields['start_date'].widget.attrs.update({'placeholder': 'YYYY-MM-DD'})
-        self.fields['offered_yearly_contribution'].initial = 1000
-        self.helper = FormHelper()
-        self.helper.layout = Layout(
-            Div(
-                Div(
-                    Field('start_date'),
-                    css_class="col-4"),
-                Div(
-                    Field('duration'),
-                    css_class="col-2"),
-                Div(
-                    Field('offered_yearly_contribution'),
-                    HTML('(euros)'),
-                    css_class="col-4"),
-                css_class="row"),
-        )
diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py
index f353c60f855feef314a1e4037d91d97f15432629..5a40c6b13b941194b8c15f819a3fe884b16150b2 100644
--- a/scipost/management/commands/add_groups_and_permissions.py
+++ b/scipost/management/commands/add_groups_and_permissions.py
@@ -14,6 +14,7 @@ class Command(BaseCommand):
 
         # Create Groups
         SciPostAdmin, created = Group.objects.get_or_create(name='SciPost Administrators')
+        FinancialAdmin, created = Group.objects.get_or_create(name='Financial Administrators')
         AdvisoryBoard, created = Group.objects.get_or_create(name='Advisory Board')
         EditorialAdmin, created = Group.objects.get_or_create(name='Editorial Administrators')
         EditorialCollege, created = Group.objects.get_or_create(name='Editorial College')
@@ -29,6 +30,12 @@ class Command(BaseCommand):
         # Create Permissions
         content_type = ContentType.objects.get_for_model(Contributor)
 
+        # Supporting Partners
+        can_manage_SPB, created = Permission.objects.get_or_create(
+            codename='can_manage_SPB',
+            name='Can manage Supporting Partners Board',
+            content_type=content_type)
+
         # Registration and invitations
         can_vet_registration_requests, created = Permission.objects.get_or_create(
             codename='can_vet_registration_requests',
diff --git a/scipost/migrations/0055_auto_20170519_0937.py b/scipost/migrations/0055_auto_20170519_0937.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3818779c198cd915fc642c64d90918024b12a9a
--- /dev/null
+++ b/scipost/migrations/0055_auto_20170519_0937.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2017-05-19 07:37
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('scipost', '0054_delete_newsitem'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='spbmembershipagreement',
+            name='partner',
+        ),
+        migrations.RemoveField(
+            model_name='supportingpartner',
+            name='contact_person',
+        ),
+        migrations.DeleteModel(
+            name='SPBMembershipAgreement',
+        ),
+        migrations.DeleteModel(
+            name='SupportingPartner',
+        ),
+    ]
diff --git a/scipost/models.py b/scipost/models.py
index 6cd9f4bca121d80600f13367d54f7edf41ec8118..1b2fa091fee75a4422c4f6186913904e4c3a48a8 100644
--- a/scipost/models.py
+++ b/scipost/models.py
@@ -17,9 +17,7 @@ from .constants import SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS,\
                        subject_areas_dict, CONTRIBUTOR_STATUS, TITLE_CHOICES,\
                        INVITATION_STYLE, INVITATION_TYPE,\
                        INVITATION_CONTRIBUTOR, INVITATION_FORMAL,\
-                       AUTHORSHIP_CLAIM_PENDING, AUTHORSHIP_CLAIM_STATUS,\
-                       PARTNER_TYPES, PARTNER_STATUS,\
-                       SPB_MEMBERSHIP_AGREEMENT_STATUS, SPB_MEMBERSHIP_DURATION
+                       AUTHORSHIP_CLAIM_PENDING, AUTHORSHIP_CLAIM_STATUS
 from .fields import ChoiceArrayField
 from .managers import FellowManager, ContributorManager
 
@@ -108,76 +106,10 @@ class Contributor(models.Model):
         self.key_expires = datetime.datetime.now() + datetime.timedelta(days=2)
         self.save()
 
-    def private_info_as_table(self):
-        template = Template('''
-            <table>
-            <tr><td>Title: </td><td>&nbsp;</td><td>{{ title }}</td></tr>
-            <tr><td>First name: </td><td>&nbsp;</td><td>{{ first_name }}</td></tr>
-            <tr><td>Last name: </td><td>&nbsp;</td><td>{{ last_name }}</td></tr>
-            <tr><td>Email: </td><td>&nbsp;</td><td>{{ email }}</td></tr>
-            <tr><td>ORCID id: </td><td>&nbsp;</td><td>{{ orcid_id }}</td></tr>
-            <tr><td>Country of employment: </td><td>&nbsp;</td>
-            <td>{{ country_of_employment }}</td></tr>
-            <tr><td>Affiliation: </td><td>&nbsp;</td><td>{{ affiliation }}</td></tr>
-            <tr><td>Address: </td><td>&nbsp;</td><td>{{ address }}</td></tr>
-            <tr><td>Personal web page: </td><td>&nbsp;</td><td>{{ personalwebpage }}</td></tr>
-            <tr><td>Accept SciPost emails: </td><td>&nbsp;</td><td>{{ accepts_SciPost_emails }}</td></tr>
-            </table>
-        ''')
-        context = Context({
-            'title': self.get_title_display(),
-            'first_name': self.user.first_name,
-            'last_name': self.user.last_name,
-            'email': self.user.email,
-            'orcid_id': self.orcid_id,
-            'country_of_employment': str(self.country_of_employment.name),
-            'affiliation': self.affiliation,
-            'address': self.address,
-            'personalwebpage': self.personalwebpage,
-            'accepts_SciPost_emails': self.accepts_SciPost_emails,
-        })
-        return template.render(context)
-
-    def public_info_as_table(self):
-        """Prints out all publicly-accessible info as a table."""
-
-        template = Template('''
-            <table>
-            <tr><td>Title: </td><td>&nbsp;</td><td>{{ title }}</td></tr>
-            <tr><td>First name: </td><td>&nbsp;</td><td>{{ first_name }}</td></tr>
-            <tr><td>Last name: </td><td>&nbsp;</td><td>{{ last_name }}</td></tr>
-            <tr><td>ORCID id: </td><td>&nbsp;</td><td>{{ orcid_id }}</td></tr>
-            <tr><td>Country of employment: </td><td>&nbsp;</td>
-            <td>{{ country_of_employment }}</td></tr>
-            <tr><td>Affiliation: </td><td>&nbsp;</td><td>{{ affiliation }}</td></tr>
-            <tr><td>Personal web page: </td><td>&nbsp;</td><td>{{ personalwebpage }}</td></tr>
-            </table>
-        ''')
-        context = Context({
-                'title': self.get_title_display(),
-                'first_name': self.user.first_name,
-                'last_name': self.user.last_name,
-                'email': self.user.email,
-                'orcid_id': self.orcid_id,
-                'country_of_employment': str(self.country_of_employment.name),
-                'affiliation': self.affiliation,
-                'address': self.address,
-                'personalwebpage': self.personalwebpage
-                })
-        return template.render(context)
-
     def discipline_as_string(self):
         # Redundant, to be removed in future
         return self.get_discipline_display()
 
-    def expertises_as_ul(self):
-        output = '<ul>'
-        if self.expertises:
-            for exp in self.expertises:
-                output += '<li>%s</li>' % subject_areas_dict[exp]
-        output += '</ul>'
-        return mark_safe(output)
-
     def expertises_as_string(self):
         if self.expertises:
             return ', '.join([subject_areas_dict[exp].lower() for exp in self.expertises])
@@ -400,43 +332,6 @@ class AffiliationObject(models.Model):
     subunit = models.CharField(max_length=128)
 
 
-#############################
-# Supporting Partners Board #
-#############################
-
-class SupportingPartner(models.Model):
-    """
-    Supporting Partners.
-    """
-    partner_type = models.CharField(max_length=32, choices=PARTNER_TYPES)
-    status = models.CharField(max_length=16, choices=PARTNER_STATUS)
-    institution = models.CharField(max_length=256)
-    institution_acronym = models.CharField(max_length=10)
-    institution_address = models.CharField(max_length=1000)
-    consortium_members = models.TextField(blank=True, null=True)
-    contact_person = models.ForeignKey(Contributor, on_delete=models.CASCADE)
-
-    def __str__(self):
-        return self.institution_acronym + ' (' + self.get_status_display() + ')'
-
-
-class SPBMembershipAgreement(models.Model):
-    """
-    Agreement for membership of the Supporting Partners Board.
-    A new instance is created each time an Agreement is made or renewed.
-    """
-    partner = models.ForeignKey(SupportingPartner, on_delete=models.CASCADE)
-    status = models.CharField(max_length=16, choices=SPB_MEMBERSHIP_AGREEMENT_STATUS)
-    date_requested = models.DateField()
-    start_date = models.DateField()
-    duration = models.DurationField(choices=SPB_MEMBERSHIP_DURATION)
-    offered_yearly_contribution = models.SmallIntegerField(default=0)
-
-    def __str__(self):
-        return (str(self.partner) +
-                ' [' + self.get_duration_display() +
-                ' from ' + self.start_date.strftime('%Y-%m-%d') + ']')
-
 
 ######################
 # Static info models #
diff --git a/scipost/services.py b/scipost/services.py
index 395e61e7ea5859a147a2170994c84e43ab6c16c3..d9a03b5295223b71908cc8d7e97d0753f89ddc1a 100644
--- a/scipost/services.py
+++ b/scipost/services.py
@@ -2,6 +2,8 @@
 import feedparser
 import requests
 import re
+import datetime
+import dateutil.parser
 
 from django.template import Template, Context
 from .behaviors import ArxivCallable
@@ -9,235 +11,103 @@ from .behaviors import ArxivCallable
 from strings import arxiv_caller_errormessages
 
 
-class BaseCaller(object):
-    '''Base mixin for caller (Arxiv, DOI).
-    The basic workflow is to initiate the caller, call process() to make the actual call
-    followed by is_valid() to validate the response of the call.
-
-    An actual caller should inherit at least the following:
-    > Properties:
-      - query_base_url
-      - caller_regex
-    > Methods:
-      - process()
-    '''
-    # State of the caller
-    _is_processed = False
-    caller_regex = None
-    errorcode = None
-    errorvariables = {}
-    errormessages = {}
-    identifier_without_vn_nr = ''
-    identifier_with_vn_nr = ''
-    metadata = {}
-    query_base_url = None
-    target_object = None
-    version_nr = None
-
-    def __init__(self, target_object, identifier, *args, **kwargs):
-        '''Initiate the Caller by assigning which object is used
-        the Arxiv identifier to be called.
-
-        After initiating call in specific order:
-        - process()
-        - is_valid()
-
-        Keyword arguments:
-        target_object -- The model calling the Caller (object)
-        identifier    -- The identifier used for the call (string)
-        '''
-        try:
-            self._check_valid_caller()
-        except NotImplementedError as e:
-            print('Caller invalid: %s' % e)
-            return
-
-        # Set given arguments
-        self.target_object = target_object
-        self.identifier = identifier
-        self._precheck_if_valid()
-        super(BaseCaller, self).__init__(*args, **kwargs)
-
-    def _check_identifier(self):
-        '''Split the given identifier in an article identifier and version number.'''
-        if not self.caller_regex:
-            raise NotImplementedError('No regex is set for this caller')
-
-        if re.match(self.caller_regex, self.identifier):
-            self.identifier_without_vn_nr = self.identifier.rpartition('v')[0]
-            self.identifier_with_vn_nr = self.identifier
-            self.version_nr = int(self.identifier.rpartition('v')[2])
+class DOICaller:
+    def __init__(self, doi_string):
+        self.doi_string = doi_string
+        self._call_crosslink()
+        if self.is_valid:
+            self._format_data()
+
+    def _call_crosslink(self):
+        url = 'http://api.crossref.org/works/%s' % self.doi_string
+        request = requests.get(url)
+        if request.ok:
+            self.is_valid = True
+            self._crossref_data = request.json()['message']
+        else:
+            self.is_valid = False
+
+    def _format_data(self):
+        data = self._crossref_data
+        pub_title = data['title'][0]
+        author_list = ['{} {}'.format(author['given'], author['family']) for author in data['author']]
+        # author_list is given as a comma separated list of names on the relevant models (Commentary, Submission)
+        author_list = ", ".join(author_list)
+        journal = data['container-title'][0]
+        volume = data.get('volume', '')
+        pages = self._get_pages(data)
+        pub_date = self._get_pub_date(data)
+
+        self.data = {
+            'pub_title': pub_title,
+            'author_list': author_list,
+            'journal': journal,
+            'volume': volume,
+            'pages': pages,
+            'pub_date': pub_date,
+        }
+
+    def _get_pages(self, data):
+        # For Physical Review
+        pages = data.get('article-number', '')
+        # For other journals?
+        pages = data.get('page', '')
+        return pages
+
+    def _get_pub_date(self, data):
+        date_parts = data.get('issued', {}).get('date-parts', {})
+        if date_parts:
+            date_parts = date_parts[0]
+            year = date_parts[0]
+            month = date_parts[1]
+            day = date_parts[2]
+            pub_date = datetime.date(year, month, day).isoformat()
         else:
-            self.errorvariables['identifier_with_vn_nr'] = self.identifier
-            raise ValueError('bad_identifier')
-
-    def _check_valid_caller(self):
-        '''Check if all methods and variables are set appropriately'''
-        if not self.query_base_url:
-            raise NotImplementedError('No `query_base_url` set')
-
-    def _precheck_duplicate(self):
-        '''Check if identifier for object already exists.'''
-        if self.target_object.same_version_exists(self.identifier_with_vn_nr):
-            raise ValueError('preprint_already_submitted')
-
-    def _precheck_previous_submissions_are_valid(self):
-        '''Check if previous submitted versions have the appropriate status.'''
-        try:
-            self.previous_submissions = self.target_object.different_versions(
-                                        self.identifier_without_vn_nr)
-        except AttributeError:
-            # Commentaries do not have previous version numbers?
-            pass
-
-        if self.previous_submissions:
-            for submission in [self.previous_submissions[0]]:
-                if submission.status == 'revision_requested':
-                    self.resubmission = True
-                elif submission.status in ['rejected', 'rejected_visible']:
-                    raise ValueError('previous_submissions_rejected')
-                else:
-                    raise ValueError('previous_submission_undergoing_refereeing')
-
-    def _precheck_if_valid(self):
-        '''The master method to perform all checks required during initializing Caller.'''
-        try:
-            self._check_identifier()
-            self._precheck_duplicate()
-            self._precheck_previous_submissions_are_valid()
-            # More tests should be called right here...!
-        except ValueError as e:
-            self.errorcode = str(e)
-
-        return not self.errorcode
-
-    def _post_process_checks(self):
-        '''Perform checks after process, to check received data.
-
-        Return:
-        None -- Raise ValueError with error code for an invalid check.
-        '''
-        pass
-
-    def is_valid(self):
-        '''Check if the process() call received valid data.
-
-        If `is_valid()` is overwritten in the actual caller, be
-        sure to call this parent method in the last line!
-
-        Return:
-        boolean -- True for valid data received. False otherwise.
-        '''
-        if self.errorcode:
-            return False
-        if not self._is_processed:
-            raise ValueError('`process()` should be called first!')
-        return True
-
-    def process(self):
-        '''Call to receive data.
-
-        The `process()` should be implemented in the actual
-        caller be! Be sure to call this parent method in the last line!
-        '''
-        try:
-            self._post_process_checks()
-        except ValueError as e:
-            self.errorcode = str(e)
-
-        self._is_processed = True
-
-    def get_error_message(self, errormessages={}):
-        '''Return the errormessages for a specific error code, with the possibility to
-        overrule the default errormessage dictionary for the specific Caller.
-        '''
-        try:
-            t = Template(errormessages[self.errorcode])
-        except KeyError:
-            t = Template(self.errormessages[self.errorcode])
-        return t.render(Context(self.errorvariables))
-
-
-class DOICaller(BaseCaller):
-    """Perform a DOI lookup for a given identifier."""
-    pass
-
-
-class ArxivCaller(BaseCaller):
-    """ Performs an Arxiv article lookup for given identifier """
-
-    # State of the caller
-    resubmission = False
-    previous_submissions = []
-    errormessages = arxiv_caller_errormessages
-    errorvariables = {
-        'arxiv_journal_ref': '',
-        'arxiv_doi': '',
-        'identifier_with_vn_nr': ''
-    }
-    arxiv_journal_ref = ''
-    arxiv_doi = ''
-    metadata = {}
+            pub_date = ''
+
+        return pub_date
+
+
+class ArxivCaller:
     query_base_url = 'http://export.arxiv.org/api/query?id_list=%s'
-    caller_regex = "^[0-9]{4,}.[0-9]{4,5}v[0-9]{1,2}$"
-
-    def __init__(self, target_object, identifier):
-        if not issubclass(target_object, ArxivCallable):
-            raise TypeError('Given target_object is not an ArxivCallable object.')
-        super(ArxivCaller, self).__init__(target_object, identifier)
-
-    def process(self):
-        '''Do the actual call the receive Arxiv information.'''
-        if self.errorcode:
-            return
-
-        queryurl = (self.query_base_url % self.identifier_with_vn_nr)
-
-        try:
-            self._response = requests.get(queryurl, timeout=4.0)
-        except requests.ReadTimeout:
-            self.errorcode = 'arxiv_timeout'
-            return
-        except requests.ConnectionError:
-            self.errorcode = 'arxiv_timeout'
-            return
-
-        self._response_content = feedparser.parse(self._response.content)
-
-        super(ArxivCaller, self).process()
-
-    def _post_process_checks(self):
-        # Check if response has at least one entry
-        if self._response.status_code == 400 or 'entries' not in self._response_content:
-            raise ValueError('arxiv_bad_request')
-
-        # Check if preprint exists
-        if not self.preprint_exists():
-            raise ValueError('preprint_does_not_exist')
-
-        # Check via journal ref if already published
-        self.arxiv_journal_ref = self.published_journal_ref()
-        self.errorvariables['arxiv_journal_ref'] = self.arxiv_journal_ref
-        if self.arxiv_journal_ref:
-            raise ValueError('paper_published_journal_ref')
-
-        # Check via DOI if already published
-        self.arxiv_doi = self.published_doi()
-        self.errorvariables['arxiv_doi'] = self.arxiv_doi
-        if self.arxiv_doi:
-            raise ValueError('paper_published_doi')
-
-        self.metadata = self._response_content
-
-    def preprint_exists(self):
-        return 'title' in self._response_content['entries'][0]
-
-    def published_journal_ref(self):
-        if 'arxiv_journal_ref' in self._response_content['entries'][0]:
-            return self._response_content['entries'][0]['arxiv_journal_ref']
-        return None
-
-    def published_doi(self):
-        if 'arxiv_doi' in self._response_content['entries'][0]:
-            return self._response_content['entries'][0]['arxiv_doi']
-        return None
+
+    def __init__(self, identifier):
+        self.identifier = identifier
+        self._call_arxiv()
+        if self.is_valid:
+            self._format_data()
+
+    def _call_arxiv(self):
+        url = self.query_base_url % self.identifier
+        request = requests.get(url)
+        response_content = feedparser.parse(request.content)
+        arxiv_data = response_content['entries'][0]
+        if self._search_result_present(arxiv_data):
+            self.is_valid = True
+            self._arxiv_data = arxiv_data
+            self.metadata = response_content
+        else:
+            self.is_valid = False
+
+    def _format_data(self):
+        data = self._arxiv_data
+        pub_title = data['title']
+        author_list = [author['name'] for author in data['authors']]
+        # author_list is given as a comma separated list of names on the relevant models (Commentary, Submission)
+        author_list = ", ".join(author_list)
+        arxiv_link = data['id']
+        abstract = data['summary']
+        pub_date = dateutil.parser.parse(data['published']).date()
+
+        self.data = {
+            'pub_title': pub_title,
+            'title': pub_title,  # Duplicate for Commentary/Submission cross-compatibility
+            'author_list': author_list,
+            'arxiv_link': arxiv_link,
+            'pub_abstract': abstract,
+            'abstract': abstract,  # Duplicate for Commentary/Submission cross-compatibility
+            'pub_date': pub_date,
+        }
+
+    def _search_result_present(self, data):
+        return 'title' in data
diff --git a/scipost/templates/scipost/_assignments_summary_as_td.html b/scipost/templates/scipost/_assignments_summary_as_td.html
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/scipost/templates/scipost/_expertises_as_ul.html b/scipost/templates/scipost/_expertises_as_ul.html
new file mode 100644
index 0000000000000000000000000000000000000000..16150eeff9de2f19d6ce3483837ec9dd6ee6c861
--- /dev/null
+++ b/scipost/templates/scipost/_expertises_as_ul.html
@@ -0,0 +1,9 @@
+{% load scipost_extras %}
+
+<ul>
+    {% for expertise in contributor.expertises %}
+        <li>
+            {{ expertise|get_specialization_display }}
+        </li>
+    {% endfor %}
+</ul>
diff --git a/scipost/templates/scipost/_private_info_as_table.html b/scipost/templates/scipost/_private_info_as_table.html
new file mode 100644
index 0000000000000000000000000000000000000000..04d525681719b0e696584c1584c9baeaaa12d04b
--- /dev/null
+++ b/scipost/templates/scipost/_private_info_as_table.html
@@ -0,0 +1,13 @@
+<table>
+    <tr><td>Title: </td><td>&nbsp;</td><td>{{ contributor.get_title_display }}</td></tr>
+    <tr><td>First name: </td><td>&nbsp;</td><td>{{ contributor.user.first_name }}</td></tr>
+    <tr><td>Last name: </td><td>&nbsp;</td><td>{{ contributor.user.last_name }}</td></tr>
+    <tr><td>Email: </td><td>&nbsp;</td><td>{{ contributor.user.email }}</td></tr>
+    <tr><td>ORCID id: </td><td>&nbsp;</td><td>{{ contributor.orcid_id }}</td></tr>
+    <tr><td>Country of employment: </td><td>&nbsp;</td>
+    <td>{{ contributor.country_of_employment.name }}</td></tr>
+    <tr><td>Affiliation: </td><td>&nbsp;</td><td>{{ contributor.affiliation }}</td></tr>
+    <tr><td>Address: </td><td>&nbsp;</td><td>{{ contributor.address }}</td></tr>
+    <tr><td>Personal web page: </td><td>&nbsp;</td><td>{{ contributor.personalwebpage }}</td></tr>
+    <tr><td>Accept SciPost emails: </td><td>&nbsp;</td><td>{{ contributor.accepts_SciPost_emails }}</td></tr>
+</table>
diff --git a/scipost/templates/scipost/_public_info_as_table.html b/scipost/templates/scipost/_public_info_as_table.html
new file mode 100644
index 0000000000000000000000000000000000000000..964e8098875162e47bc4b1fa7a16f1687b1b29cf
--- /dev/null
+++ b/scipost/templates/scipost/_public_info_as_table.html
@@ -0,0 +1,10 @@
+<table>
+    <tr><td>Title: </td><td>&nbsp;</td><td>{{ contributor.get_title_display }}</td></tr>
+    <tr><td>First name: </td><td>&nbsp;</td><td>{{ contributor.user.first_name }}</td></tr>
+    <tr><td>Last name: </td><td>&nbsp;</td><td>{{ contributor.user.last_name }}</td></tr>
+    <tr><td>ORCID id: </td><td>&nbsp;</td><td>{{ contributor.orcid_id }}</td></tr>
+    <tr><td>Country of employment: </td><td>&nbsp;</td>
+    <td>{{ contributor.country_of_employment.name }}</td></tr>
+    <tr><td>Affiliation: </td><td>&nbsp;</td><td>{{ contributor.affiliation }}</td></tr>
+    <tr><td>Personal web page: </td><td>&nbsp;</td><td>{{ contributor.personalwebpage }}</td></tr>
+</table>
diff --git a/scipost/templates/scipost/base.html b/scipost/templates/scipost/base.html
index 1b34a3cbd5aeeb8e2ecff7a934714a18df91fbc4..827cdc5f951e64562772aaa598f4ef12b72bb1fa 100644
--- a/scipost/templates/scipost/base.html
+++ b/scipost/templates/scipost/base.html
@@ -56,7 +56,7 @@
       processEscapes: true
       }});
     </script>
-    <script type="text/javascript" async src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-MML-AM_CHTML"></script>
+    <script type="text/javascript" async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML"></script>
 
     {% render_bundle 'main' 'js' %}
     {% render_bundle 'bootstrap' 'js' %}
diff --git a/scipost/templates/scipost/contributor_info.html b/scipost/templates/scipost/contributor_info.html
index 7f687bb6bb53ed9fa0fd0d9470435a9e42eee98b..de3c1c059991c66714ce4bd67a331014f77190c1 100644
--- a/scipost/templates/scipost/contributor_info.html
+++ b/scipost/templates/scipost/contributor_info.html
@@ -15,7 +15,8 @@
     </div>
 </div>
 
-{{ contributor.public_info_as_table }}
+{% include "scipost/_public_info_as_table.html" with contributor=contributor %}
+
 <br>
 {% if contributor_publications %}
     {# <hr>#}
diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html
index 0fdefd67db08f4abf334fe25cb86c9b37b9239c1..6f3c572d5f36abdf5f2296792252f41fb2af432a 100644
--- a/scipost/templates/scipost/personal_page.html
+++ b/scipost/templates/scipost/personal_page.html
@@ -81,14 +81,14 @@
             <div class="row">
                 <div class="col-md-6">
                     <h3>Your personal details:</h3>
-                    {{ contributor.private_info_as_table }}
+                    {% include "scipost/_private_info_as_table.html" with contributor=contributor %}
 
                     <h3 class="mt-3">Your main discipline:</h3>
                     <ul><li>{{ contributor.discipline_as_string }}</li></ul>
 
                     <h3 class="mt-3">Your expertises:</h3>
                     {% if contributor.expertises %}
-                        {{ contributor.expertises_as_ul }}
+                        {% include "scipost/_expertises_as_ul.html" with contributor=contributor %}
                     {% else %}
                         <p>You haven't listed your expertise(s).<br/>
                         Do so by <a href="{% url 'scipost:update_personal_data' %}">updating your personal data</a>
diff --git a/scipost/templates/scipost/vet_registration_requests.html b/scipost/templates/scipost/vet_registration_requests.html
index 8b5630bff03f7b936b70fb4b25a051229a162d7e..532269903f507b3fe4361240ff28528ff6e37d98 100644
--- a/scipost/templates/scipost/vet_registration_requests.html
+++ b/scipost/templates/scipost/vet_registration_requests.html
@@ -41,7 +41,7 @@ $(function() {
     {% if not forloop.first %}<hr class="small">{% endif %}
     <div class="row">
         <div class="col-md-4">
-            {{ contributor_to_vet.private_info_as_table }}
+            {% include "scipost/_private_info_as_table.html" with contributor=contributor %}
         </div>
         <div class="col-md-8">
             <form action="{% url 'scipost:vet_registration_request_ack' contributor_id=contributor_to_vet.id %}" method="post">
diff --git a/scipost/test_services.py b/scipost/test_services.py
index e724b7856b0d5ec1a280239d440a66c1b758885b..727a20213e84256e79fc3d85f875df77f9be56a7 100644
--- a/scipost/test_services.py
+++ b/scipost/test_services.py
@@ -1,46 +1,62 @@
+import datetime
+
 from django.test import TestCase
 
-from .services import ArxivCaller
+from .services import ArxivCaller, DOICaller
 
 from submissions.models import Submission
 
 
 class ArxivCallerTest(TestCase):
-
-    def test_correct_lookup(self):
-        caller = ArxivCaller(Submission, '1611.09574v1')
-
-        caller.process()
-
-        self.assertEqual(caller.is_valid(), True)
-        self.assertIn('entries', caller.metadata)
-
-    def test_errorcode_for_non_existing_paper(self):
-        caller = ArxivCaller(Submission, '2611.09574v1')
-
-        caller.process()
-        self.assertEqual(caller.is_valid(), False)
-        self.assertEqual(caller.errorcode, 'preprint_does_not_exist')
-
-    def test_errorcode_for_bad_request(self):
-        caller = ArxivCaller(Submission, '161109574v1')
-
-        caller.process()
-        self.assertEqual(caller.is_valid(), False)
-        self.assertEqual(caller.errorcode, 'arxiv_bad_request')
-
-    def test_errorcode_for_already_published_journal_ref(self):
-        caller = ArxivCaller(Submission, '1412.0006v1')
-
-        caller.process()
-        self.assertEqual(caller.is_valid(), False)
-        self.assertEqual(caller.errorcode, 'paper_published_journal_ref')
-        self.assertNotEqual(caller.arxiv_journal_ref, '')
-
-    def test_errorcode_no_version_nr(self):
-        # Should be already caught in form validation
-        caller = ArxivCaller(Submission, '1412.0006')
-
-        caller.process()
-        self.assertEqual(caller.is_valid(), False)
-        self.assertEqual(caller.errorcode, 'bad_identifier')
+    def test_identifier_new_style(self):
+        caller = ArxivCaller('1612.07611v1')
+        self.assertTrue(caller.is_valid)
+        correct_data = {
+            'pub_abstract': 'The Berezinskii-Kosterlitz-Thouless (BKT) transitions of the six-state clock\nmodel on the square lattice are investigated by means of the corner-transfer\nmatrix renormalization group method. The classical analogue of the entanglement\nentropy $S( L, T )$ is calculated for $L$ by $L$ square system up to $L = 129$,\nas a function of temperature $T$. The entropy has a peak at $T = T^{*}_{~}( L\n)$, where the temperature depends on both $L$ and boundary conditions. Applying\nthe finite-size scaling to $T^{*}_{~}( L )$ and assuming the presence of BKT\ntransitions, the transition temperature is estimated to be $T_1^{~} = 0.70$ and\n$T_2^{~} = 0.88$. The obtained results agree with previous analyses. It should\nbe noted that no thermodynamic function is used in this study.', 'author_list': ['Roman Krčmár', 'Andrej Gendiar', 'Tomotoshi Nishino'], 'arxiv_link': 'http://arxiv.org/abs/1612.07611v1', 'pub_title': 'Phase transition of the six-state clock model observed from the\n  entanglement entropy', 'pub_date': datetime.date(2016, 12, 22)
+        }
+        self.assertEqual(caller.data, correct_data)
+
+    def test_identifier_old_style(self):
+        caller = ArxivCaller('cond-mat/0612480')
+        self.assertTrue(caller.is_valid)
+        correct_data = {
+            'author_list': ['Kouji Ueda', 'Chenglong Jin', 'Naokazu Shibata', 'Yasuhiro Hieida', 'Tomotoshi Nishino'], 'pub_date': datetime.date(2006, 12, 19), 'arxiv_link': 'http://arxiv.org/abs/cond-mat/0612480v2', 'pub_abstract': 'A kind of least action principle is introduced for the discrete time\nevolution of one-dimensional quantum lattice models. Based on this principle,\nwe obtain an optimal condition for the matrix product states on succeeding time\nslices generated by the real-time density matrix renormalization group method.\nThis optimization can also be applied to classical simulations of quantum\ncircuits. We discuss the time reversal symmetry in the fully optimized MPS.', 'pub_title': 'Least Action Principle for the Real-Time Density Matrix Renormalization\n  Group'
+        }
+        self.assertEqual(caller.data, correct_data)
+
+    def valid_but_nonexistent_identifier(self):
+        caller = ArxivCaller('1613.07611v1')
+        self.assertEqual(caller.is_valid, False)
+
+
+class DOICallerTest(TestCase):
+    def test_works_for_physrev_doi(self):
+        caller = DOICaller('10.1103/PhysRevB.92.214427')
+        correct_data = {
+            'pub_date': '2015-12-18',
+            'journal': 'Physical Review B',
+            'pages': '',
+            'author_list': [
+                'R. Vlijm', 'M. Ganahl', 'D. Fioretto', 'M. Brockmann', 'M. Haque', 'H. G. Evertz', 'J.-S. Caux'],
+            'volume': '92',
+            'pub_title': 'Quasi-soliton scattering in quantum spin chains'
+        }
+        self.assertTrue(caller.is_valid)
+        self.assertEqual(caller.data, correct_data)
+
+    def test_works_for_scipost_doi(self):
+        caller = DOICaller('10.21468/SciPostPhys.2.2.012')
+        correct_data = {
+            'pub_date': '2017-04-04',
+            'journal': 'SciPost Physics',
+            'pub_title': 'One-particle density matrix of trapped one-dimensional impenetrable bosons from conformal invariance',
+            'pages': '',
+            'volume': '2',
+            'author_list': ['Yannis Brun', 'Jerome Dubail']
+        }
+        self.assertTrue(caller.is_valid)
+        self.assertEqual(caller.data, correct_data)
+
+    def test_valid_but_non_existent_doi(self):
+        caller = DOICaller('10.21468/NonExistentJournal.2.2.012')
+        self.assertEqual(caller.is_valid, False)
diff --git a/scipost/urls.py b/scipost/urls.py
index d12d2fc75c8e25a73214656aff7faab8e44f8238..5b8809d94c62da572e14e96c9f00ed5d6e11769a 100644
--- a/scipost/urls.py
+++ b/scipost/urls.py
@@ -209,14 +209,4 @@ urlpatterns = [
         TemplateView.as_view(template_name='scipost/howto_production.html'),
         name='howto_production'),
 
-
-    #############################
-    # Supporting Partners Board #
-    #############################
-
-    url(r'^supporting_partners$', views.supporting_partners,
-        name='supporting_partners'),
-    url(r'^SPB_membership_request$', views.SPB_membership_request,
-        name='SPB_membership_request'),
-
 ]
diff --git a/scipost/views.py b/scipost/views.py
index 43d629d2551f73d26672ebf0983576e95a886fc2..92358cc472dce943b1a419266991c270ef60c4ce 100644
--- a/scipost/views.py
+++ b/scipost/views.py
@@ -25,14 +25,12 @@ from guardian.decorators import permission_required
 from .constants import SCIPOST_SUBJECT_AREAS, subject_areas_raw_dict, SciPost_from_addresses_dict
 from .models import Contributor, CitationNotification, UnavailabilityPeriod,\
                     DraftInvitation, RegistrationInvitation,\
-                    AuthorshipClaim, SupportingPartner, SPBMembershipAgreement,\
-                    EditorialCollege, EditorialCollegeFellowship
+                    AuthorshipClaim, EditorialCollege, EditorialCollegeFellowship
 from .forms import AuthenticationForm, DraftInvitationForm, UnavailabilityPeriodForm,\
                    RegistrationForm, RegistrationInvitationForm, AuthorshipClaimForm,\
                    ModifyPersonalMessageForm, SearchForm, VetRegistrationForm, reg_ref_dict,\
                    UpdatePersonalDataForm, UpdateUserDataForm, PasswordChangeForm,\
-                   EmailGroupMembersForm, EmailParticularForm, SendPrecookedEmailForm,\
-                   SupportingPartnerForm, SPBMembershipForm
+                   EmailGroupMembersForm, EmailParticularForm, SendPrecookedEmailForm
 from .utils import Utils, EMAIL_FOOTER, SCIPOST_SUMMARY_FOOTER, SCIPOST_SUMMARY_FOOTER_HTML
 
 from commentaries.models import Commentary
@@ -817,7 +815,8 @@ def personal_page(request):
     nr_thesislink_requests_to_vet = 0
     nr_authorship_claims_to_vet = 0
     if contributor.is_VE():
-        nr_commentary_page_requests_to_vet = Commentary.objects.filter(vetted=False).count()
+        nr_commentary_page_requests_to_vet = (Commentary.objects.awaiting_vetting()
+                                              .exclude(requested_by=contributor).count())
         nr_comments_to_vet = Comment.objects.filter(status=0).count()
         nr_thesislink_requests_to_vet = ThesisLink.objects.filter(vetted=False).count()
         nr_authorship_claims_to_vet = AuthorshipClaim.objects.filter(status='0').count()
@@ -1274,58 +1273,6 @@ def Fellow_activity_overview(request, Fellow_id=None):
     return render(request, 'scipost/Fellow_activity_overview.html', context)
 
 
-#############################
-# Supporting Partners Board #
-#############################
-
-def supporting_partners(request):
-    prospective_agreements = SPBMembershipAgreement.objects.filter(
-        status='Submitted').order_by('date_requested')
-    context = {'prospective_partners': prospective_agreements, }
-    return render(request, 'scipost/supporting_partners.html', context)
-
-
-@login_required
-def SPB_membership_request(request):
-    errormessage = ''
-    if request.method == 'POST':
-        SP_form = SupportingPartnerForm(request.POST)
-        membership_form = SPBMembershipForm(request.POST)
-        if SP_form.is_valid() and membership_form.is_valid():
-            partner = SupportingPartner(
-                partner_type=SP_form.cleaned_data['partner_type'],
-                status='Prospective',
-                institution=SP_form.cleaned_data['institution'],
-                institution_acronym=SP_form.cleaned_data['institution_acronym'],
-                institution_address=SP_form.cleaned_data['institution_address'],
-                contact_person=request.user.contributor,
-            )
-            partner.save()
-            agreement = SPBMembershipAgreement(
-                partner=partner,
-                status='Submitted',
-                date_requested=timezone.now().date(),
-                start_date=membership_form.cleaned_data['start_date'],
-                duration=membership_form.cleaned_data['duration'],
-                offered_yearly_contribution=membership_form.cleaned_data['offered_yearly_contribution'],
-            )
-            agreement.save()
-            ack_message = ('Thank you for your SPB Membership request. '
-                           'We will get back to you in the very near future '
-                           'with details of the proposed agreement.')
-            context = {'ack_message': ack_message, }
-            return render(request, 'scipost/acknowledgement.html', context)
-        else:
-            errormessage = 'The form was not filled properly.'
-
-    else:
-        SP_form = SupportingPartnerForm()
-        membership_form = SPBMembershipForm()
-    context = {'errormessage': errormessage,
-               'SP_form': SP_form,
-               'membership_form': membership_form, }
-    return render(request, 'scipost/SPB_membership_request.html', context)
-
 
 class AboutView(ListView):
     model = EditorialCollege
diff --git a/strings/__init__.py b/strings/__init__.py
index acb0df6ceafab9bb8a85bae4049a9ccb7fdc8cec..614455a3452553b019e2f375efa0ad241bd8f690 100644
--- a/strings/__init__.py
+++ b/strings/__init__.py
@@ -10,6 +10,28 @@ acknowledge_request_commentary = (
 acknowledge_submit_comment = (
     "Thank you for contributing a Comment. It will soon be vetted by an Editor."
 )
+acknowledge_doi_query = "Crossref query by DOI successful."
+acknowledge_arxiv_query = "Arxiv query successful."
+
+doi_query_placeholder = 'ex.: 10.21468/00.000.000000'
+doi_query_help_text = (
+    'For published papers, you can prefill the form (except for domain, subject area and abstract) using the DOI. '
+    "(Give the DOI as 10.[4 to 9 digits]/[string], without prefix, as per the placeholder)."
+)
+doi_query_invalid = (
+    "DOI does not match the expression supplied by CrossRef. Either it is very old or you made a mistake. "
+    "If you are sure it is correct, please enter the metadata manually. Sorry for the inconvenience."
+)
+
+arxiv_query_placeholder = (
+    "new style: YYMM.####(#)v#(#) or "
+    "old style: cond-mat/YYMM###v#(#)"
+)
+arxiv_query_help_text =  (
+    "For preprints, you can prefill the form using the arXiv identifier. "
+    "Give the identifier without prefix and do not forget the version number, as per the placeholder."
+)
+arxiv_query_invalid = 'ArXiv identifier is invalid. Did you include a version number?'
 
 # Arxiv response is not valid
 arxiv_caller_errormessages = {
@@ -21,11 +43,11 @@ arxiv_caller_errormessages = {
     'paper_published_doi':
         ('This paper has been published under DOI {{ arxiv_doi }}'
          '. Please comment on the published version.'),
-    'arxiv_timeout': 'Arxiv did not respond in time. Please try again later',
-    'arxiv_bad_request':
-        ('There was an error with requesting identifier ' +
-         '{{ identifier_with_vn_nr }}'
-         ' from Arxiv. Please check the identifier and try again.'),
+    # 'arxiv_timeout': 'Arxiv did not respond in time. Please try again later',
+    # 'arxiv_bad_request':
+    #     ('There was an error with requesting identifier ' +
+    #      '{{ identifier_with_vn_nr }}'
+    #      ' from Arxiv. Please check the identifier and try again.'),
     'previous_submission_undergoing_refereeing':
         ('There exists a preprint with this arXiv identifier '
          'but an earlier version number, which is still undergoing '
diff --git a/submissions/constants.py b/submissions/constants.py
index ffb506708de9a0fddee0c601d6e2ca7f97007cf4..845ecbb2ebd49f4117139978bbc36cd0d1b2fc86 100644
--- a/submissions/constants.py
+++ b/submissions/constants.py
@@ -6,6 +6,8 @@ STATUS_AWAITING_ED_REC = 'awaiting_ed_rec'
 STATUS_REVIEW_CLOSED = 'review_closed'
 STATUS_ACCEPTED = 'accepted'
 STATUS_PUBLISHED = 'published'
+STATUS_REJECTED = 'rejected'
+STATUS_REJECTED_VISIBLE = 'rejected_visible'
 STATUS_RESUBMITTED = 'resubmitted'
 STATUS_RESUBMITTED_REJECTED = 'resubmitted_and_rejected'
 STATUS_RESUBMITTED_REJECTED_VISIBLE = 'resubmitted_and_rejected_visible'
@@ -27,8 +29,8 @@ SUBMISSION_STATUS = (
     (STATUS_AWAITING_ED_REC, 'Awaiting Editorial Recommendation'),
     ('EC_vote_completed', 'Editorial College voting rounded up'),
     (STATUS_ACCEPTED, 'Publication decision taken: accept'),
-    ('rejected', 'Publication decision taken: reject'),
-    ('rejected_visible', 'Publication decision taken: reject (still publicly visible)'),
+    (STATUS_REJECTED, 'Publication decision taken: reject'),
+    (STATUS_REJECTED_VISIBLE, 'Publication decision taken: reject (still publicly visible)'),
     (STATUS_PUBLISHED, 'Published'),
     # If withdrawn:
     ('withdrawn', 'Withdrawn by the Authors'),
diff --git a/submissions/forms.py b/submissions/forms.py
index 70b2b792e8f3465889566fbb4e4ac600b6b59254..c024b162a8ae25f9aee1fa70dd9b0a43371830f6 100644
--- a/submissions/forms.py
+++ b/submissions/forms.py
@@ -1,16 +1,24 @@
 from django import forms
+from django.contrib.auth.models import Group
 from django.core.validators import RegexValidator
+from django.db import models, transaction
 
-from .constants import ASSIGNMENT_BOOL, ASSIGNMENT_REFUSAL_REASONS,\
-                       REPORT_ACTION_CHOICES, REPORT_REFUSAL_CHOICES
-from .models import Submission, RefereeInvitation, Report, EICRecommendation
+from guardian.shortcuts import assign_perm
+
+from .constants import ASSIGNMENT_BOOL, ASSIGNMENT_REFUSAL_REASONS, STATUS_RESUBMITTED,\
+                       REPORT_ACTION_CHOICES, REPORT_REFUSAL_CHOICES, STATUS_REVISION_REQUESTED,\
+                       STATUS_REJECTED, STATUS_REJECTED_VISIBLE, STATUS_RESUBMISSION_INCOMING
+from .models import Submission, RefereeInvitation, Report, EICRecommendation, EditorialAssignment
 
 from scipost.constants import SCIPOST_SUBJECT_AREAS
+from scipost.services import ArxivCaller
 from scipost.models import Contributor
 
 from crispy_forms.helper import FormHelper
 from crispy_forms.layout import Layout, Div, Field, HTML, Submit
 
+import strings
+
 
 class SubmissionSearchForm(forms.Form):
     author = forms.CharField(max_length=100, required=False, label="Author(s)")
@@ -33,46 +41,157 @@ class SubmissionSearchForm(forms.Form):
 # Submission and resubmission #
 ###############################
 
-class SubmissionIdentifierForm(forms.Form):
-    identifier = forms.CharField(
-        widget=forms.TextInput(
-            {'label': 'arXiv identifier',
-             'placeholder': 'new style (with version nr) ####.####(#)v#(#)',
-             'cols': 20}
-        ),
-        validators=[
-            RegexValidator(
-                regex="^[0-9]{4,}.[0-9]{4,5}v[0-9]{1,2}$",
-                message='The identifier you entered is improperly formatted '
-                        '(did you forget the version number?)',
-                code='invalid_identifier'
-            ),
-        ])
-
-
-class SubmissionForm(forms.ModelForm):
+class SubmissionChecks:
+    """
+    Use this class as a blueprint containing checks which should be run
+    in multiple forms.
+    """
+    is_resubmission = False
+    last_submission = None
+
+    def _submission_already_exists(self, identifier):
+        if Submission.objects.filter(arxiv_identifier_w_vn_nr=identifier).exists():
+            error_message = 'This preprint version has already been submitted to SciPost.'
+            raise forms.ValidationError(error_message, code='duplicate')
+
+    def _call_arxiv(self, identifier):
+        caller = ArxivCaller(identifier)
+        if caller.is_valid:
+            self.arxiv_data = ArxivCaller(identifier).data
+            self.metadata = ArxivCaller(identifier).metadata
+        else:
+            error_message = 'A preprint associated to this identifier does not exist.'
+            raise forms.ValidationError(error_message)
+
+    def _submission_is_already_published(self, identifier):
+        published_id = None
+        if 'arxiv_doi' in self.arxiv_data:
+            published_id = self.arxiv_data['arxiv_doi']
+        elif 'arxiv_journal_ref' in self.arxiv_data:
+            published_id = self.arxiv_data['arxiv_journal_ref']
+
+        if published_id:
+            error_message = ('This paper has been published under DOI %(published_id)s'
+                             '. Please comment on the published version.'),
+            raise forms.ValidationError(error_message, code='published',
+                                        params={'published_id': published_id})
+
+    def _submission_previous_version_is_valid_for_submission(self, identifier):
+        '''Check if previous submitted versions have the appropriate status.'''
+        identifiers = self.identifier_into_parts(identifier)
+        submission = (Submission.objects
+                      .filter(arxiv_identifier_wo_vn_nr=identifiers['arxiv_identifier_wo_vn_nr'])
+                      .order_by('-arxiv_vn_nr').last())
+
+        # If submissions are found; check their statuses
+        if submission:
+            self.last_submission = submission
+            if submission.status == STATUS_REVISION_REQUESTED:
+                self.is_resubmission = True
+            elif submission.status in [STATUS_REJECTED, STATUS_REJECTED_VISIBLE]:
+                error_message = ('This arXiv preprint has previously undergone refereeing '
+                                 'and has been rejected. Resubmission is only possible '
+                                 'if the manuscript has been substantially reworked into '
+                                 'a new arXiv submission with distinct identifier.')
+                raise forms.ValidationError(error_message)
+            else:
+                error_message = ('There exists a preprint with this arXiv identifier '
+                                 'but an earlier version number, which is still undergoing '
+                                 'peer refereeing. '
+                                 'A resubmission can only be performed after request '
+                                 'from the Editor-in-charge. Please wait until the '
+                                 'closing of the previous refereeing round and '
+                                 'formulation of the Editorial Recommendation '
+                                 'before proceeding with a resubmission.')
+                raise forms.ValidationError(error_message)
+
+    def submission_is_resubmission(self):
+        return self.is_resubmission
+
+    def identifier_into_parts(self, identifier):
+        data = {
+            'arxiv_identifier_w_vn_nr': identifier,
+            'arxiv_identifier_wo_vn_nr': identifier.rpartition('v')[0],
+            'arxiv_vn_nr': int(identifier.rpartition('v')[2])
+        }
+        return data
+
+    def do_pre_checks(self, identifier):
+        self._submission_already_exists(identifier)
+        self._call_arxiv(identifier)
+        self._submission_is_already_published(identifier)
+        self._submission_previous_version_is_valid_for_submission(identifier)
+
+
+class SubmissionIdentifierForm(SubmissionChecks, forms.Form):
+    IDENTIFIER_PATTERN_NEW = r'^[0-9]{4,}.[0-9]{4,5}v[0-9]{1,2}$'
+    IDENTIFIER_PLACEHOLDER = 'new style (with version nr) ####.####(#)v#(#)'
+
+    identifier = forms.RegexField(regex=IDENTIFIER_PATTERN_NEW, strip=True,
+                                  #   help_text=strings.arxiv_query_help_text,
+                                  error_messages={'invalid': strings.arxiv_query_invalid},
+                                  widget=forms.TextInput({'placeholder': IDENTIFIER_PLACEHOLDER}))
+
+    def clean_identifier(self):
+        identifier = self.cleaned_data['identifier']
+        self.do_pre_checks(identifier)
+        return identifier
+
+    def _gather_data_from_last_submission(self):
+        '''Return dictionary with data coming from previous submission version.'''
+        if self.submission_is_resubmission():
+            data = {
+                'is_resubmission': True,
+                'discipline': self.last_submission.discipline,
+                'domain': self.last_submission.domain,
+                'referees_flagged': self.last_submission.referees_flagged,
+                'referees_suggested': self.last_submission.referees_suggested,
+                'secondary_areas': self.last_submission.secondary_areas,
+                'subject_area': self.last_submission.subject_area,
+                'submitted_to_journal': self.last_submission.submitted_to_journal,
+                'submission_type': self.last_submission.submission_type,
+            }
+        return data or {}
+
+    def request_arxiv_preprint_form_prefill_data(self):
+        '''Return dictionary to prefill `RequestSubmissionForm`.'''
+        form_data = self.arxiv_data
+        form_data.update(self.identifier_into_parts(self.cleaned_data['identifier']))
+        if self.submission_is_resubmission():
+            form_data.update(self._gather_data_from_last_submission())
+        return form_data
+
+
+class RequestSubmissionForm(SubmissionChecks, forms.ModelForm):
     class Meta:
         model = Submission
-        fields = ['is_resubmission',
-                  'discipline', 'submitted_to_journal', 'submission_type',
-                  'domain', 'subject_area',
-                  'secondary_areas',
-                  'title', 'author_list', 'abstract',
-                  'arxiv_identifier_w_vn_nr', 'arxiv_identifier_wo_vn_nr',
-                  'arxiv_vn_nr', 'arxiv_link', 'metadata',
-                  'author_comments', 'list_of_changes',
-                  'remarks_for_editors',
-                  'referees_suggested', 'referees_flagged']
+        fields = [
+            'is_resubmission',
+            'discipline',
+            'submitted_to_journal',
+            'submission_type',
+            'domain',
+            'subject_area',
+            'secondary_areas',
+            'title',
+            'author_list',
+            'abstract',
+            'arxiv_identifier_w_vn_nr',
+            'arxiv_link',
+            'author_comments',
+            'list_of_changes',
+            'remarks_for_editors',
+            'referees_suggested',
+            'referees_flagged'
+        ]
 
     def __init__(self, *args, **kwargs):
-        super(SubmissionForm, self).__init__(*args, **kwargs)
+        self.requested_by = kwargs.pop('requested_by', None)
+        super().__init__(*args, **kwargs)
         self.fields['is_resubmission'].widget = forms.HiddenInput()
         self.fields['arxiv_identifier_w_vn_nr'].widget = forms.HiddenInput()
-        self.fields['arxiv_identifier_wo_vn_nr'].widget = forms.HiddenInput()
-        self.fields['arxiv_vn_nr'].widget = forms.HiddenInput()
         self.fields['arxiv_link'].widget.attrs.update(
             {'placeholder': 'ex.:  arxiv.org/abs/1234.56789v1'})
-        self.fields['metadata'].widget = forms.HiddenInput()
         self.fields['secondary_areas'].widget = forms.SelectMultiple(choices=SCIPOST_SUBJECT_AREAS)
         self.fields['abstract'].widget.attrs.update({'cols': 100})
         self.fields['author_comments'].widget.attrs.update({
@@ -88,7 +207,15 @@ class SubmissionForm(forms.ModelForm):
             'placeholder': 'Optional: names of referees whose reports should be treated with caution (+ short reason)',
             'rows': 3})
 
-    def check_user_may_submit(self, current_user):
+    def clean(self, *args, **kwargs):
+        """
+        Do all prechecks which are also done in the prefiller.
+        """
+        cleaned_data = super().clean(*args, **kwargs)
+        self.do_pre_checks(cleaned_data['arxiv_identifier_w_vn_nr'])
+        return cleaned_data
+
+    def clean_author_list(self):
         """
         Important check!
 
@@ -96,21 +223,87 @@ class SubmissionForm(forms.ModelForm):
         Also possibly may be extended to check permissions and give ultimate submission
         power to certain user groups.
         """
-        return current_user.last_name.lower() in self.cleaned_data['author_list'].lower()
+        author_list = self.cleaned_data['author_list']
+        if not self.requested_by.last_name.lower() in author_list.lower():
+            error_message = ('Your name does not match that of any of the authors. '
+                             'You are not authorized to submit this preprint.')
+            raise forms.ValidationError(error_message, code='not_an_author')
+        return author_list
+
+    @transaction.atomic
+    def copy_and_save_data_from_resubmission(self, submission):
+        """
+        Fill given Submission with data coming from last_submission in the SubmissionChecks
+        blueprint.
+        """
+        if not self.last_submission:
+            raise Submission.DoesNotExist
+
+        # Open for comment and reporting
+        submission.open_for_reporting = True
+        submission.open_for_commenting = True
+
+        # Close last submission
+        self.last_submission.is_current = False
+        self.last_submission.open_for_reporting = False
+        self.last_submission.status = STATUS_RESUBMITTED
+        self.last_submission.save()
+
+        # Editor-in-charge
+        submission.editor_in_charge = self.last_submission.editor_in_charge
+        submission.status = STATUS_RESUBMISSION_INCOMING
+
+        # Author claim fields
+        submission.authors.add(*self.last_submission.authors.all())
+        submission.authors_claims.add(*self.last_submission.authors_claims.all())
+        submission.authors_false_claims.add(*self.last_submission.authors_false_claims.all())
+        submission.save()
+        return submission
+
+    @transaction.atomic
+    def reassign_eic_and_admins(self, submission):
+        # Assign permissions
+        assign_perm('can_take_editorial_actions', submission.editor_in_charge.user, submission)
+        ed_admins = Group.objects.get(name='Editorial Administrators')
+        assign_perm('can_take_editorial_actions', ed_admins, submission)
+
+        # Assign editor
+        assignment = EditorialAssignment(
+            submission=submission,
+            to=submission.editor_in_charge,
+            accepted=True
+        )
+        assignment.save()
+        submission.save()
+        return submission
 
-    def update_submission_data(self):
+    @transaction.atomic
+    def save(self):
         """
-        Some fields should not be accessible in the HTML form by the user and should be
-        inserted by for example an extra call to Arxiv into the Submission instance, right
-        *after* the form is submitted.
-
-        Example fields:
-        - is_resubmission
-        - arxiv_link
-        - arxiv_identifier_w_vn_nr
-        - metadata (!)
+        Prefill instance before save.
+
+        Because of the ManyToManyField on `authors`, commit=False for this form
+        is disabled. Saving the form without the database call may loose `authors`
+        data without notice.
         """
-        raise NotImplementedError
+        submission = super().save(commit=False)
+        submission.submitted_by = self.requested_by.contributor
+
+        # Save metadata directly from ArXiv call without possible user interception
+        submission.metadata = self.metadata
+
+        # Update identifiers
+        identifiers = self.identifier_into_parts(submission.arxiv_identifier_w_vn_nr)
+        submission.arxiv_identifier_wo_vn_nr = identifiers['arxiv_identifier_wo_vn_nr']
+        submission.arxiv_vn_nr = identifiers['arxiv_vn_nr']
+
+        # Save
+        submission.save()
+        if self.submission_is_resubmission():
+            submission = self.copy_and_save_data_from_resubmission(submission)
+            submission = self.reassign_eic_and_admins(submission)
+        submission.authors.add(self.requested_by.contributor)
+        return submission
 
 
 ######################
diff --git a/submissions/models.py b/submissions/models.py
index c2364962e70eeff53d55c6d22691d6e965131bb1..9e697af3908fdf117f182ae6a0f26c337a9c900b 100644
--- a/submissions/models.py
+++ b/submissions/models.py
@@ -132,69 +132,6 @@ class Submission(ArxivCallable, models.Model):
     def reporting_deadline_has_passed(self):
         return timezone.now() > self.reporting_deadline
 
-    @transaction.atomic
-    def finish_submission(self):
-        if self.is_resubmission:
-            # If submissions is a resubmission, the submission needs to be prescreened
-            # by the EIC to choose which of the available submission cycle to assign
-            self.mark_other_versions_as_deprecated()
-            self.copy_authors_from_previous_version()
-            self.copy_EIC_from_previous_version()
-            self.set_resubmission_defaults()
-            self.status = STATUS_RESUBMISSION_INCOMING
-        else:
-            self.authors.add(self.submitted_by)
-
-        self.save()
-
-    @classmethod
-    def same_version_exists(self, identifier):
-        return self.objects.filter(arxiv_identifier_w_vn_nr=identifier).exists()
-
-    @classmethod
-    def different_versions(self, identifier):
-        return self.objects.filter(arxiv_identifier_wo_vn_nr=identifier).order_by('-arxiv_vn_nr')
-
-    def make_assignment(self):
-        assignment = EditorialAssignment(
-            submission=self,
-            to=self.editor_in_charge,
-            accepted=True,
-            date_created=timezone.now(),
-            date_answered=timezone.now(),
-        )
-        assignment.save()
-
-    def set_resubmission_defaults(self):
-        self.open_for_reporting = True
-        self.open_for_commenting = True
-        if self.other_versions()[0].submitted_to_journal == 'SciPost Physics Lecture Notes':
-            self.reporting_deadline = timezone.now() + datetime.timedelta(days=56)
-        else:
-            self.reporting_deadline = timezone.now() + datetime.timedelta(days=28)
-
-    def copy_EIC_from_previous_version(self):
-        last_version = self.other_versions()[0]
-        self.editor_in_charge = last_version.editor_in_charge
-        self.status = 'EICassigned'
-
-    def copy_authors_from_previous_version(self):
-        last_version = self.other_versions()[0]
-
-        for author in last_version.authors.all():
-            self.authors.add(author)
-        for author in last_version.authors_claims.all():
-            self.authors_claims.add(author)
-        for author in last_version.authors_false_claims.all():
-            self.authors_false_claims.add(author)
-
-    def mark_other_versions_as_deprecated(self):
-        for sub in self.other_versions():
-            sub.is_current = False
-            sub.open_for_reporting = False
-            sub.status = 'resubmitted'
-            sub.save()
-
     def other_versions(self):
         return Submission.objects.filter(
             arxiv_identifier_wo_vn_nr=self.arxiv_identifier_wo_vn_nr
diff --git a/submissions/templates/submissions/new_submission.html b/submissions/templates/submissions/new_submission.html
index e0dbc27ad7279b3d3b2ead3ff73a8489c311fc5e..7cb8592adf5481cf931fd0d78cdb0119755e5804 100644
--- a/submissions/templates/submissions/new_submission.html
+++ b/submissions/templates/submissions/new_submission.html
@@ -34,7 +34,14 @@ $(document).ready(function(){
 
 <div class="row">
     <div class="col-12">
-        <h1 class="highlight">Submit a manuscript to SciPost</h1>
+        <div class="card card-grey">
+            <div class="card-block">
+                <h1 class="card-title mb-0">Submit a manuscript to SciPost</h1>
+                {% if form.arxiv_identifier_w_vn_nr.value %}<h2 class="my-1 py-0 text-blue">{{form.arxiv_identifier_w_vn_nr.value}}{% if form.is_resubmission.value %} <small>(resubmission)</small>{% endif %}</h2>{% endif %}
+            </div>
+        </div>
+    </div>
+    <div class="col-12">
         <p class="mb-1">
             Before submitting, make sure you agree with the <a href="{% url 'journals:journals_terms_and_conditions' %}">SciPost Journals Terms and Conditions</a>.
         </p>
diff --git a/submissions/templates/submissions/prefill_using_identifier.html b/submissions/templates/submissions/prefill_using_identifier.html
index 5af8f507c3b2805aaff6ce3bb87a12e15cab0ab5..fd3781c6fceba5d9ed060ab85395be4d7cb18e8c 100644
--- a/submissions/templates/submissions/prefill_using_identifier.html
+++ b/submissions/templates/submissions/prefill_using_identifier.html
@@ -6,35 +6,6 @@
 
 {% block content %}
 
-<script>
-$(document).ready(function(){
-  $("#id_submission_type").closest('tr').hide()
-
-  $('select#id_submitted_to_journal').on('change', function (){
-  var selection = $(this).val();
-  switch(selection){
-    case "SciPost Physics":
-      $("#id_submission_type").closest('tr').show()
-      break;
-    default:
-      $("#id_submission_type").closest('tr').hide()
-  }
-});
-
-  var isresub = $("#id_is_resubmission").val();
-  switch(isresub){
-    case "True":
-      $("#id_author_comments").closest('tr').show()
-      $("#id_list_of_changes").closest('tr').show()
-      break;
-    default:
-      $("#id_author_comments").closest('tr').hide()
-      $("#id_list_of_changes").closest('tr').hide()
-  }
-
-});
-</script>
-
 <div class="row">
     <div class="col-12">
         <h1 class="highlight">Submit a manuscript to SciPost</h1>
@@ -71,11 +42,6 @@ $(document).ready(function(){
     </div>
 </div>
 
-
-    {% if resubmessage %}
-      <h3 class="text-success">{{ resubmessage }}</h3>
-    {% endif %}
-
 {% else %}
   <h3>You are currently not allowed to submit a manuscript.</h3>
 {% endif %}
diff --git a/submissions/urls.py b/submissions/urls.py
index 8465d4f9c7b572bed6e6e1437a7f904c1d377132..bdd6d86f6cc0619472dad96b3bd14996cf081713 100644
--- a/submissions/urls.py
+++ b/submissions/urls.py
@@ -17,12 +17,9 @@ urlpatterns = [
         name='submission_wo_vn_nr'),
     url(r'^(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/$',
         views.submission_detail, name='submission'),
-    # url(r'^prefill_using_identifier$',
-    #     views.prefill_using_identifier, name='prefill_using_identifier'),
-    url(r'^prefill_using_identifier$',
-        views.PrefillUsingIdentifierView.as_view(), name='prefill_using_identifier'),
-    # url(r'^submit_manuscript$', views.submit_manuscript, name='submit_manuscript'),
-    url(r'^submit_manuscript$', views.SubmissionCreateView.as_view(), name='submit_manuscript'),
+    url(r'^submit_manuscript$', views.RequestSubmission.as_view(), name='submit_manuscript'),
+    url(r'^submit_manuscript/prefill$', views.prefill_using_arxiv_identifier,
+        name='prefill_using_identifier'),
     url(r'^pool$', views.pool, name='pool'),
     url(r'^submissions_by_status/(?P<status>[a-zA-Z_]+)$',
         views.submissions_by_status, name='submissions_by_status'),
diff --git a/submissions/views.py b/submissions/views.py
index b49f185de05e6050d7eba0075423db59f14458c4..2aeb15ffceff91f7993542dc718fcf6a4ff2fc42 100644
--- a/submissions/views.py
+++ b/submissions/views.py
@@ -4,22 +4,22 @@ import feedparser
 from django.contrib import messages
 from django.contrib.auth.decorators import login_required, permission_required
 from django.contrib.auth.models import Group
-from django.core.urlresolvers import reverse
+from django.core.urlresolvers import reverse, reverse_lazy
 from django.db import transaction
 from django.http import Http404
 from django.shortcuts import get_object_or_404, render, redirect
 from django.template import Template, Context
 from django.utils import timezone
+from django.utils.decorators import method_decorator
 
 from guardian.decorators import permission_required_or_403
-from guardian.mixins import PermissionRequiredMixin
 from guardian.shortcuts import assign_perm
 
 from .constants import SUBMISSION_STATUS_VOTING_DEPRECATED,\
                        SUBMISSION_STATUS_PUBLICLY_INVISIBLE, SUBMISSION_STATUS, ED_COMM_CHOICES
 from .models import Submission, EICRecommendation, EditorialAssignment,\
                     RefereeInvitation, Report, EditorialCommunication
-from .forms import SubmissionIdentifierForm, SubmissionForm, SubmissionSearchForm,\
+from .forms import SubmissionIdentifierForm, RequestSubmissionForm, SubmissionSearchForm,\
                    RecommendationVoteForm, ConsiderAssignmentForm, AssignSubmissionForm,\
                    SetRefereeingDeadlineForm, RefereeSelectForm, RefereeRecruitmentForm,\
                    ConsiderRefereeInvitationForm, EditorialCommunicationForm,\
@@ -27,135 +27,46 @@ from .forms import SubmissionIdentifierForm, SubmissionForm, SubmissionSearchFor
                    SubmissionCycleChoiceForm
 from .utils import SubmissionUtils
 
-from journals.constants import SCIPOST_JOURNALS_SPECIALIZATIONS
 from scipost.forms import ModifyPersonalMessageForm, RemarkForm
 from scipost.models import Contributor, Remark, RegistrationInvitation
-from scipost.services import ArxivCaller
 from scipost.utils import Utils
-from strings import arxiv_caller_errormessages_submissions
 
 from comments.forms import CommentForm
+from production.models import ProductionStream
 
-from django.views.generic.edit import CreateView, FormView
+from django.views.generic.edit import CreateView
 from django.views.generic.list import ListView
 
+import strings
+
 
 ###############
 # SUBMISSIONS:
 ###############
 
-class PrefillUsingIdentifierView(PermissionRequiredMixin, FormView):
-    form_class = SubmissionIdentifierForm
-    template_name = 'submissions/prefill_using_identifier.html'
-    permission_required = 'scipost.can_submit_manuscript'
-    raise_exception = True
-
-    def post(self, request):
-        identifierform = SubmissionIdentifierForm(request.POST)
-        if identifierform.is_valid():
-            # Use the ArxivCaller class to make the API calls
-            caller = ArxivCaller(Submission, identifierform.cleaned_data['identifier'])
-            caller.process()
-
-            if caller.is_valid():
-                # Arxiv response is valid and can be shown
-
-                metadata = caller.metadata
-                is_resubmission = caller.resubmission
-                title = metadata['entries'][0]['title']
-                authorlist = metadata['entries'][0]['authors'][0]['name']
-                for author in metadata['entries'][0]['authors'][1:]:
-                    authorlist += ', ' + author['name']
-                arxiv_link = metadata['entries'][0]['id']
-                abstract = metadata['entries'][0]['summary']
-                initialdata = {'is_resubmission': is_resubmission,
-                               'metadata': metadata,
-                               'title': title, 'author_list': authorlist,
-                               'arxiv_identifier_w_vn_nr': caller.identifier_with_vn_nr,
-                               'arxiv_identifier_wo_vn_nr': caller.identifier_without_vn_nr,
-                               'arxiv_vn_nr': caller.version_nr,
-                               'arxiv_link': arxiv_link, 'abstract': abstract}
-                if is_resubmission:
-                    previous_submissions = caller.previous_submissions
-                    resubmessage = ('There already exists a preprint with this arXiv identifier '
-                                    'but a different version number. \nYour Submission will be '
-                                    'handled as a resubmission.')
-                    initialdata['submitted_to_journal'] = previous_submissions[0].submitted_to_journal
-                    initialdata['submission_type'] = previous_submissions[0].submission_type
-                    initialdata['discipline'] = previous_submissions[0].discipline
-                    initialdata['domain'] = previous_submissions[0].domain
-                    initialdata['subject_area'] = previous_submissions[0].subject_area
-                    initialdata['secondary_areas'] = previous_submissions[0].secondary_areas
-                    initialdata['referees_suggested'] = previous_submissions[0].referees_suggested
-                    initialdata['referees_flagged'] = previous_submissions[0].referees_flagged
-                else:
-                    resubmessage = ''
-
-                form = SubmissionForm(initial=initialdata)
-                context = {'identifierform': identifierform,
-                           'form': form,
-                           'resubmessage': resubmessage}
-                return render(request, 'submissions/new_submission.html', context)
-
-            else:
-                msg = caller.get_error_message(arxiv_caller_errormessages_submissions)
-                identifierform.add_error(None, msg)
-                return render(request, 'submissions/prefill_using_identifier.html',
-                              {'form': identifierform})
-        else:
-            return render(request, 'submissions/prefill_using_identifier.html',
-                          {'form': identifierform})
-
-
-class SubmissionCreateView(PermissionRequiredMixin, CreateView):
-    model = Submission
-    form_class = SubmissionForm
-
+@method_decorator(permission_required('scipost.can_submit_manuscript', raise_exception=True),
+                  name='dispatch')
+class RequestSubmission(CreateView):
+    success_url = reverse_lazy('scipost:personal_page')
+    form_class = RequestSubmissionForm
     template_name = 'submissions/new_submission.html'
-    permission_required = 'scipost.can_submit_manuscript'
-    # Required to use Guardian's CBV PermissionRequiredMixin with a CreateView
-    # (see https://github.com/django-guardian/django-guardian/pull/433)
-    permission_object = None
-    raise_exception = True
 
     def get(self, request):
-        # Only use prefilled forms
         return redirect('submissions:prefill_using_identifier')
 
+    def get_form_kwargs(self):
+        kwargs = super().get_form_kwargs()
+        kwargs['requested_by'] = self.request.user
+        return kwargs
+
     @transaction.atomic
     def form_valid(self, form):
-        submitted_by = Contributor.objects.get(user=self.request.user)
-        form.instance.submitted_by = submitted_by
-
-        # Temporary until moved to new Arxiv Caller
-        # Check submitting user for authorship !
-        # With the new Arxiv caller, this message should already be given in the prefil form!
-        if not form.check_user_may_submit(self.request.user):
-            msg = ('Your name does not match that of any of the authors. '
-                   'You are not authorized to submit this preprint.')
-            messages.error(self.request, msg)
-            return redirect('submissions:prefill_using_identifier')
-
-        # Save all the information contained in the form
         submission = form.save()
+        text = ('<h3>Thank you for your Submission to SciPost</h3>'
+                'Your Submission will soon be handled by an Editor.')
+        messages.success(self.request, text)
 
-        # Perform all extra actions and set information not contained in the form
-        submission.finish_submission()
-
-        if submission.is_resubmission:
-            # Assign permissions
-            assign_perm('can_take_editorial_actions', submission.editor_in_charge.user, submission)
-            ed_admins = Group.objects.get(name='Editorial Administrators')
-            assign_perm('can_take_editorial_actions', ed_admins, submission)
-
-            # Assign editor
-            assignment = EditorialAssignment(
-                submission=submission,
-                to=submission.editor_in_charge,
-                accepted=True
-            )
-            assignment.save()
-
+        if form.submission_is_resubmission():
             # Send emails
             SubmissionUtils.load({'submission': submission}, self.request)
             SubmissionUtils.send_authors_resubmission_ack_email()
@@ -164,23 +75,40 @@ class SubmissionCreateView(PermissionRequiredMixin, CreateView):
             # Send emails
             SubmissionUtils.load({'submission': submission})
             SubmissionUtils.send_authors_submission_ack_email()
+        return super().form_valid(form)
+
+    def form_invalid(self, form):
+        # r = form.errors
+        for error_messages in form.errors.values():
+            messages.warning(self.request, *error_messages)
+        return super().form_invalid(form)
+
+
+@permission_required('scipost.can_submit_manuscript', raise_exception=True)
+def prefill_using_arxiv_identifier(request):
+    query_form = SubmissionIdentifierForm(request.POST or None)
+    if query_form.is_valid():
+        prefill_data = query_form.request_arxiv_preprint_form_prefill_data()
+        form = RequestSubmissionForm(initial=prefill_data)
+
+        # Submit message to user
+        if query_form.submission_is_resubmission():
+            resubmessage = ('There already exists a preprint with this arXiv identifier '
+                            'but a different version number. \nYour Submission will be '
+                            'handled as a resubmission.')
+            messages.success(request, resubmessage, fail_silently=True)
+        else:
+            messages.success(request, strings.acknowledge_arxiv_query, fail_silently=True)
 
-        text = ('<h3>Thank you for your Submission to SciPost</h3>'
-                'Your Submission will soon be handled by an Editor.')
-        messages.success(self.request, text)
-        return redirect(reverse('scipost:personal_page'))
-
-    def mark_previous_submissions_as_deprecated(self, previous_submissions):
-        for sub in previous_submissions:
-            sub.is_current = False
-            sub.open_for_reporting = False
-            sub.status = 'resubmitted'
-            sub.save()
+        context = {
+            'form': form,
+        }
+        return render(request, 'submissions/new_submission.html', context)
 
-    def previous_submissions(self, form):
-        return Submission.objects.filter(
-            arxiv_identifier_wo_vn_nr=form.cleaned_data['arxiv_identifier_wo_vn_nr']
-        )
+    context = {
+        'form': query_form,
+    }
+    return render(request, 'submissions/prefill_using_identifier.html', context)
 
 
 class SubmissionListView(ListView):
@@ -1290,6 +1218,10 @@ def fix_College_decision(request, rec_id):
     if recommendation.recommendation in [1, 2, 3]:
         # Publish as Tier I, II or III
         recommendation.submission.status = 'accepted'
+        # Create a ProductionStream object
+        prodstream = ProductionStream(submission=recommendation.submission,
+                                      opened=timezone.now())
+        prodstream.save()
     elif recommendation.recommendation == -3:
         # Reject
         recommendation.submission.status = 'rejected'
diff --git a/templates/email/submission_cycle_reinvite_referee.html b/templates/email/submission_cycle_reinvite_referee.html
index 3ae8ab59c207d451b19c0b3ad4e0da54de4ebd63..f776b1bd3329c58dd2f8d3fa303403328adf1fe4 100644
--- a/templates/email/submission_cycle_reinvite_referee.html
+++ b/templates/email/submission_cycle_reinvite_referee.html
@@ -4,7 +4,7 @@ The authors of submission\n\n
 
 {{invitation.submission.title}} by {{invitation.submission.author_list}} \n\n
 
-have resubmitted their manuscript to SciPost. On behalf of the Editor-in-charge {{invitation.submission.editor_in_charge.get_title_display}} {{invitation.submission.editor_in_charge.last_name}}, we would like to invite you to quickly review this new version.\n
+have resubmitted their manuscript to SciPost. On behalf of the Editor-in-charge {{invitation.submission.editor_in_charge.get_title_display}} {{invitation.submission.editor_in_charge.user.last_name}}, we would like to invite you to quickly review this new version.\n
 Please accept or decline the invitation (login required) as soon as possible (ideally within the next 2 days).\n\n
 
 If you accept, your report can be submitted by simply clicking on the "Contribute a Report" link on the Submission's Page before the reporting deadline (currently set at {{invitation.submission.reporting_deadline|date:'N j, Y'}}; your report will be automatically recognized as an invited report).\n\n
diff --git a/templates/email/submission_cycle_reinvite_referee_html.html b/templates/email/submission_cycle_reinvite_referee_html.html
index 84ceeb3ff14ee0d82279da763c9fd7ff30a5cc53..08db162758be0ab0ed123a02ccd3b0667456d828 100644
--- a/templates/email/submission_cycle_reinvite_referee_html.html
+++ b/templates/email/submission_cycle_reinvite_referee_html.html
@@ -11,7 +11,7 @@
     <br>
     (<a href="https://scipost.org{{invitation.submission.get_absolute_url}}">see on SciPost.org</a>)
 <p>
-    have resubmitted their manuscript to SciPost. On behalf of the Editor-in-charge {{invitation.submission.editor_in_charge.get_title_display}} {{invitation.submission.editor_in_charge.last_name}}, we would like to invite you to quickly review this new version.
+    have resubmitted their manuscript to SciPost. On behalf of the Editor-in-charge {{invitation.submission.editor_in_charge.get_title_display}} {{invitation.submission.editor_in_charge.user.last_name}}, we would like to invite you to quickly review this new version.
     Please accept or decline the invitation (login required) as soon as possible (ideally within the next 2 days).
 </p>
 <p>