diff --git a/scipost/models.py b/scipost/models.py
index 1b2fa091fee75a4422c4f6186913904e4c3a48a8..dd2bc50535f6bef22f63975dbebd01b167c19593 100644
--- a/scipost/models.py
+++ b/scipost/models.py
@@ -8,7 +8,6 @@ from django.contrib.postgres.fields import ArrayField
 from django.db import models
 from django.template import Template, Context
 from django.utils import timezone
-from django.utils.safestring import mark_safe
 
 from django_countries.fields import CountryField
 
diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html
index 04decd986cf50c0916fe695ee7a0acdcb963de38..883f8dcdfa54f0006aa954ec0574c7fa7cf2763b 100644
--- a/scipost/templates/scipost/personal_page.html
+++ b/scipost/templates/scipost/personal_page.html
@@ -34,20 +34,18 @@
                         <a href="#account" class="nav-link active" data-toggle="tab">Account</a>
                       </li>
                       {% if 'SciPost Administrators' in user_groups or 'Editorial Administrators' in user_groups or 'Editorial College' in user_groups or 'Advisory Board' in user_groups or 'Vetting Editors' in user_groups or 'Ambassadors' in user_groups or 'Junior Ambassadors' in user_groups %}
-                      <li class="nav-item btn btn-secondary">
-                        <a href="#editorial-actions" class="nav-link" data-toggle="tab">Editorial Actions</a>
-                      </li>
-		      {% endif %}
-		      {% if perms.scipost.can_view_production %}
-		      <li class="nav-item btn btn-secondary">
-			<a href="#production" class="nav-link" data-toggle="tab">Production</a>
-		      </li>
-		      {% endif %}
+                          <li class="nav-item btn btn-secondary">
+                            <a href="#editorial-actions" class="nav-link" data-toggle="tab">Editorial Actions</a>
+                          </li>
+        		      {% endif %}
+        		      {% if perms.scipost.can_view_production %}
+            		      <li class="nav-item btn btn-secondary">
+                			<a href="#production" class="nav-link" data-toggle="tab">Production</a>
+            		      </li>
+        		      {% endif %}
                       {% if perms.scipost.can_referee %}
                           <li class="nav-item btn btn-secondary">
-                            {% with pending_count=pending_ref_tasks|length %}
-                                <a class="nav-link" data-toggle="tab" href="#refereeing">Refereeing {% if nr_ref_inv_to_consider|add:pending_count %}({{nr_ref_inv_to_consider|add:pending_count}}){% endif %}</a>
-                            {% endwith %}
+                            <a class="nav-link" data-toggle="tab" href="#refereeing">Refereeing {% if refereeing_tab_total_count %}({{refereeing_tab_total_count}}){% endif %}</a>
                           </li>
                       {% endif %}
                       <li class="nav-item btn btn-secondary">
@@ -371,6 +369,24 @@
                         </ul>
                     </div>
                 </div>
+
+                {% if unfinished_reports %}
+                    <div class="row">
+                        <div class="col-12">
+                            <h3>Unfinished reports:</h3>
+                        </div>
+                        <div class="col-12">
+                            <ul class="list-group list-group-flush">
+                            {% for report in unfinished_reports %}
+                                <li class="list-group-item">
+                                    <div class="w-100">{% include 'submissions/_submission_card_content.html' with submission=report.submission %}</div>
+                                    <div class="px-2"><a class="px-1" href="{% url 'submissions:submit_report' report.submission.arxiv_identifier_w_vn_nr %}">Finish report</a></div>
+                                </li>
+                            {% endfor %}
+                            </ul>
+                        </div>
+                    </div>
+                {% endif %}
             </div><!-- End tab -->
         {% endif %}
 
diff --git a/scipost/views.py b/scipost/views.py
index 728cc1755b43ef6de564c15e29fb14ff2cb9dcbb..d2b41d229c45cc2c5308465d837e11c7bf6e6563 100644
--- a/scipost/views.py
+++ b/scipost/views.py
@@ -806,8 +806,8 @@ def personal_page(request):
                                       .count())
         active_assignments = EditorialAssignment.objects.filter(
             to=contributor, accepted=True, completed=False)
-        nr_reports_to_vet = Report.objects.filter(
-            status=0, submission__editor_in_charge=contributor).count()
+        nr_reports_to_vet = (Report.objects.awaiting_vetting()
+                             .filter(submission__editor_in_charge=contributor).count())
     nr_commentary_page_requests_to_vet = 0
     nr_comments_to_vet = 0
     nr_thesislink_requests_to_vet = 0
@@ -818,10 +818,16 @@ def personal_page(request):
         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()
+
+    # Refereeing
     nr_ref_inv_to_consider = RefereeInvitation.objects.filter(
         referee=contributor, accepted=None, cancelled=False).count()
     pending_ref_tasks = RefereeInvitation.objects.filter(
         referee=contributor, accepted=True, fulfilled=False)
+    unfinished_reports = Report.objects.in_draft().filter(author=contributor)
+    refereeing_tab_total_count = nr_ref_inv_to_consider + len(pending_ref_tasks)
+    refereeing_tab_total_count += len(unfinished_reports)
+
     # Verify if there exist objects authored by this contributor,
     # whose authorship hasn't been claimed yet
     own_submissions = (Submission.objects
@@ -879,11 +885,14 @@ def personal_page(request):
         'nr_thesis_authorships_to_claim': nr_thesis_authorships_to_claim,
         'nr_ref_inv_to_consider': nr_ref_inv_to_consider,
         'pending_ref_tasks': pending_ref_tasks,
+        'refereeing_tab_total_count': refereeing_tab_total_count,
+        'unfinished_reports': unfinished_reports,
         'own_submissions': own_submissions,
         'own_commentaries': own_commentaries,
         'own_thesislinks': own_thesislinks,
         'own_comments': own_comments, 'own_authorreplies': own_authorreplies,
     }
+
     return render(request, 'scipost/personal_page.html', context)
 
 
diff --git a/submissions/admin.py b/submissions/admin.py
index 10fa602e0f82a198e68d393f362720d504addee9..dfc3dff7b8c223034d8d13ac0136463d1d54be6d 100644
--- a/submissions/admin.py
+++ b/submissions/admin.py
@@ -3,7 +3,8 @@ from django import forms
 
 from guardian.admin import GuardedModelAdmin
 
-from submissions.models import *
+from submissions.models import Submission, EditorialAssignment, RefereeInvitation, Report,\
+                               EditorialCommunication, EICRecommendation
 
 from scipost.models import Contributor
 
@@ -27,6 +28,7 @@ class SubmissionAdminForm(forms.ModelForm):
         model = Submission
         fields = '__all__'
 
+
 class SubmissionAdmin(GuardedModelAdmin):
     search_fields = ['submitted_by__user__last_name', 'title', 'author_list', 'abstract']
     list_display = ('title', 'author_list', 'status', 'submission_date', 'publication',)
@@ -34,6 +36,7 @@ class SubmissionAdmin(GuardedModelAdmin):
     list_filter = ('status', 'discipline', 'submission_type', )
     form = SubmissionAdminForm
 
+
 admin.site.register(Submission, SubmissionAdmin)
 
 
@@ -45,6 +48,7 @@ class EditorialAssignmentAdminForm(forms.ModelForm):
         model = EditorialAssignment
         fields = '__all__'
 
+
 class EditorialAssignmentAdmin(admin.ModelAdmin):
     search_fields = ['submission__title', 'submission__author_list', 'to__user__last_name']
     list_display = ('to', submission_short_title, 'accepted', 'completed', 'date_created',)
@@ -52,6 +56,7 @@ class EditorialAssignmentAdmin(admin.ModelAdmin):
     list_filter = ('accepted', 'deprecated', 'completed', )
     form = EditorialAssignmentAdminForm
 
+
 admin.site.register(EditorialAssignment, EditorialAssignmentAdmin)
 
 
@@ -63,6 +68,7 @@ class RefereeInvitationAdminForm(forms.ModelForm):
         model = RefereeInvitation
         fields = '__all__'
 
+
 class RefereeInvitationAdmin(admin.ModelAdmin):
     search_fields = ['submission__title', 'submission__author_list',
                      'referee__user__last_name',
@@ -72,6 +78,7 @@ class RefereeInvitationAdmin(admin.ModelAdmin):
     date_hierarchy = 'date_invited'
     form = RefereeInvitationAdminForm
 
+
 admin.site.register(RefereeInvitation, RefereeInvitationAdmin)
 
 
@@ -83,6 +90,7 @@ class ReportAdminForm(forms.ModelForm):
         model = Report
         fields = '__all__'
 
+
 class ReportAdmin(admin.ModelAdmin):
     search_fields = ['author__user__last_name', 'submission']
     list_display = ('author', 'status', submission_short_title, 'date_submitted', )
@@ -91,6 +99,7 @@ class ReportAdmin(admin.ModelAdmin):
     list_filter = ('status',)
     form = ReportAdminForm
 
+
 admin.site.register(Report, ReportAdmin)
 
 
@@ -134,4 +143,5 @@ class EICRecommendationAdmin(admin.ModelAdmin):
     search_fields = ['submission__title']
     form = EICRecommendationAdminForm
 
+
 admin.site.register(EICRecommendation, EICRecommendationAdmin)
diff --git a/submissions/constants.py b/submissions/constants.py
index 84638110f30c4a2829a9f8a29c125b2a57baf46c..2b247ffa6a78a9c83e72934b3871e42c21ec3435 100644
--- a/submissions/constants.py
+++ b/submissions/constants.py
@@ -38,16 +38,24 @@ SUBMISSION_STATUS = (
 
 SUBMISSION_HTTP404_ON_EDITORIAL_PAGE = [
     'assignment_failed',
-    'published',
+    STATUS_PUBLISHED,
     'withdrawn',
-    'rejected',
-    'rejected_visible',
+    STATUS_REJECTED,
+    STATUS_REJECTED_VISIBLE,
 ]
 
 SUBMISSION_STATUS_OUT_OF_POOL = SUBMISSION_HTTP404_ON_EDITORIAL_PAGE + [
     'resubmitted'
 ]
 
+SUBMISSION_EXCLUDE_FROM_REPORTING = SUBMISSION_HTTP404_ON_EDITORIAL_PAGE + [
+    STATUS_AWAITING_ED_REC,
+    STATUS_REVIEW_CLOSED,
+    STATUS_ACCEPTED,
+    'voting_in_preparation',
+    'put_to_EC_voting',
+    'withdrawn',
+]
 
 # Submissions which are allowed/required to submit a EIC Recommendation
 SUBMISSION_EIC_RECOMMENDATION_REQUIRED = [
@@ -156,23 +164,23 @@ REPORT_REC = (
 #
 # Reports
 #
-REPORT_ACTION_ACCEPT = 1
-REPORT_ACTION_REFUSE = 2
+REPORT_ACTION_ACCEPT = 'accept'
+REPORT_ACTION_REFUSE = 'refuse'
 REPORT_ACTION_CHOICES = (
     (REPORT_ACTION_ACCEPT, 'accept'),
     (REPORT_ACTION_REFUSE, 'refuse'),
 )
 
-STATUS_VETTED = 1
-STATUS_UNVETTED = 0
-STATUS_UNCLEAR = -1
-STATUS_INCORRECT = -2
-STATUS_NOT_USEFUL = -3
-STATUS_NOT_ACADEMIC = -4
+STATUS_DRAFT = 'draft'
+STATUS_VETTED = 'vetted'
+STATUS_UNVETTED = 'unvetted'
+STATUS_UNCLEAR = 'unclear'
+STATUS_INCORRECT = 'incorrect'
+STATUS_NOT_USEFUL = 'notuseful'
+STATUS_NOT_ACADEMIC = 'notacademic'
 
-REPORT_REFUSAL_NONE = 0
 REPORT_REFUSAL_CHOICES = (
-    (STATUS_UNVETTED, '-'),
+    (None, '-'),
     (STATUS_UNCLEAR, 'insufficiently clear'),
     (STATUS_INCORRECT, 'not fully factually correct'),
     (STATUS_NOT_USEFUL, 'not useful for the authors'),
@@ -180,6 +188,7 @@ REPORT_REFUSAL_CHOICES = (
 )
 
 REPORT_STATUSES = (
+    (STATUS_DRAFT, 'Draft'),
     (STATUS_VETTED, 'Vetted'),
     (STATUS_UNVETTED, 'Unvetted'),
     (STATUS_INCORRECT, 'Rejected (incorrect)'),
diff --git a/submissions/exceptions.py b/submissions/exceptions.py
index 19c5684bd004f175ffe4169cc2938d312d66edf0..0e8794a8cc841af0df2896138ce2ec0209ee0c7e 100644
--- a/submissions/exceptions.py
+++ b/submissions/exceptions.py
@@ -4,3 +4,11 @@ class CycleUpdateDeadlineError(Exception):
 
     def __str__(self):
         return self.name
+
+
+class InvalidReportVettingValue(Exception):
+    def __init__(self, name):
+        self.name = name
+
+    def __str__(self):
+        return self.name
diff --git a/submissions/factories.py b/submissions/factories.py
index 6c602275452772293c4f8303caf37c5baf63152a..d9c32c745fd079caed4ab17d02fd3511c0605ff9 100644
--- a/submissions/factories.py
+++ b/submissions/factories.py
@@ -9,8 +9,10 @@ from journals.constants import SCIPOST_JOURNALS_DOMAINS
 from common.helpers import random_arxiv_identifier_without_version_number, random_scipost_journal
 
 from .constants import STATUS_UNASSIGNED, STATUS_EIC_ASSIGNED, STATUS_RESUBMISSION_INCOMING,\
-                       STATUS_PUBLISHED, SUBMISSION_TYPE, STATUS_RESUBMITTED
-from .models import Submission
+                       STATUS_PUBLISHED, SUBMISSION_TYPE, STATUS_RESUBMITTED, STATUS_VETTED,\
+                       REFEREE_QUALIFICATION, RANKING_CHOICES, QUALITY_SPEC, REPORT_REC,\
+                       REPORT_STATUSES, STATUS_UNVETTED, STATUS_DRAFT
+from .models import Submission, Report, RefereeInvitation
 
 from faker import Faker
 
@@ -142,3 +144,64 @@ class PublishedSubmissionFactory(SubmissionFactory):
     status = STATUS_PUBLISHED
     open_for_commenting = False
     open_for_reporting = False
+
+
+class ReportFactory(factory.django.DjangoModelFactory):
+    class Meta:
+        model = Report
+
+    status = factory.Iterator(REPORT_STATUSES, getter=lambda c: c[0])
+    submission = factory.Iterator(Submission.objects.all())
+    date_submitted = Faker().date_time_between(start_date="-3y", end_date="now", tzinfo=pytz.UTC)
+    vetted_by = factory.Iterator(Contributor.objects.all())
+    author = factory.Iterator(Contributor.objects.all())
+    qualification = factory.Iterator(REFEREE_QUALIFICATION, getter=lambda c: c[0])
+    strengths = Faker().paragraph()
+    weaknesses = Faker().paragraph()
+    report = Faker().paragraph()
+    requested_changes = Faker().paragraph()
+    validity = factory.Iterator(RANKING_CHOICES, getter=lambda c: c[0])
+    significance = factory.Iterator(RANKING_CHOICES, getter=lambda c: c[0])
+    originality = factory.Iterator(RANKING_CHOICES, getter=lambda c: c[0])
+    clarity = factory.Iterator(RANKING_CHOICES, getter=lambda c: c[0])
+    formatting = factory.Iterator(QUALITY_SPEC, getter=lambda c: c[0])
+    grammar = factory.Iterator(QUALITY_SPEC, getter=lambda c: c[0])
+    recommendation = factory.Iterator(REPORT_REC, getter=lambda c: c[0])
+    remarks_for_editors = Faker().paragraph()
+
+
+class DraftReportFactory(ReportFactory):
+    status = STATUS_DRAFT
+    vetted_by = None
+
+
+class UnVettedReportFactory(ReportFactory):
+    status = STATUS_UNVETTED
+    vetted_by = None
+
+
+class VettedReportFactory(ReportFactory):
+    status = STATUS_VETTED
+
+
+class RefereeInvitationFactory(factory.django.DjangoModelFactory):
+    class Meta:
+        model = RefereeInvitation
+
+    submission = factory.SubFactory('submissions.factories.SubmissionFactory')
+    referee = factory.Iterator(Contributor.objects.all())
+
+    invitation_key = factory.Faker('md5')
+    invited_by = factory.Iterator(Contributor.objects.all())
+
+    @factory.post_generation
+    def contributor_fields(self, create, extracted, **kwargs):
+        self.title = self.referee.title
+        self.first_name = self.referee.user.first_name
+        self.last_name = self.referee.user.last_name
+        self.email_address = self.referee.user.email
+
+
+class AcceptedRefereeInvitationFactory(RefereeInvitationFactory):
+    accepted = True
+    date_responded = Faker().date_time_between(start_date="-1y", end_date="now", tzinfo=pytz.UTC)
diff --git a/submissions/forms.py b/submissions/forms.py
index 4b6fdcc8425010d118d279a7838a8b4a3b3c336b..7f2d39772f1086f15e09c39ef36facb7bda4cb61 100644
--- a/submissions/forms.py
+++ b/submissions/forms.py
@@ -1,13 +1,16 @@
 from django import forms
 from django.contrib.auth.models import Group
-from django.core.validators import RegexValidator
-from django.db import models, transaction
+from django.db import transaction
+from django.utils import timezone
 
 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
+                       STATUS_REJECTED, STATUS_REJECTED_VISIBLE, STATUS_RESUBMISSION_INCOMING,\
+                       STATUS_DRAFT, STATUS_UNVETTED, REPORT_ACTION_ACCEPT, REPORT_ACTION_REFUSE,\
+                       STATUS_VETTED
+from .exceptions import InvalidReportVettingValue
 from .models import Submission, RefereeInvitation, Report, EICRecommendation, EditorialAssignment
 
 from scipost.constants import SCIPOST_SUBJECT_AREAS
@@ -418,17 +421,55 @@ class ReportForm(forms.ModelForm):
 
     def __init__(self, *args, **kwargs):
         super(ReportForm, self).__init__(*args, **kwargs)
-        self.fields['strengths'].widget.attrs.update(
-            {'placeholder': 'Give a point-by-point (numbered 1-, 2-, ...) list of the paper\'s strengths',
-             'rows': 10, 'cols': 100})
-        self.fields['weaknesses'].widget.attrs.update(
-            {'placeholder': 'Give a point-by-point (numbered 1-, 2-, ...) list of the paper\'s weaknesses',
-             'rows': 10, 'cols': 100})
+        self.fields['strengths'].widget.attrs.update({
+            'placeholder': ('Give a point-by-point '
+                            '(numbered 1-, 2-, ...) list of the paper\'s strengths'),
+            'rows': 10,
+            'cols': 100
+        })
+        self.fields['weaknesses'].widget.attrs.update({
+            'placeholder': ('Give a point-by-point '
+                            '(numbered 1-, 2-, ...) list of the paper\'s weaknesses'),
+            'rows': 10,
+            'cols': 100
+        })
         self.fields['report'].widget.attrs.update({'placeholder': 'Your general remarks',
                                                    'rows': 10, 'cols': 100})
-        self.fields['requested_changes'].widget.attrs.update(
-            {'placeholder': 'Give a numbered (1-, 2-, ...) list of specifically requested changes',
-             'cols': 100})
+        self.fields['requested_changes'].widget.attrs.update({
+            'placeholder': 'Give a numbered (1-, 2-, ...) list of specifically requested changes',
+            'cols': 100
+        })
+
+    def save(self, submission, current_contributor):
+        """
+        Update meta data if ModelForm is submitted (non-draft).
+        Possibly overwrite the default status if user asks for saving as draft.
+        """
+        report = super().save(commit=False)
+
+        report.submission = submission
+        report.author = current_contributor
+        report.date_submitted = timezone.now()
+
+        # Save with right status asked by user
+        if 'save_draft' in self.data:
+            report.status = STATUS_DRAFT
+        elif 'save_submit' in self.data:
+            report.status = STATUS_UNVETTED
+
+            # Update invitation and report meta data if exist
+            invitation = submission.referee_invitations.filter(referee=current_contributor).first()
+            if invitation:
+                invitation.fulfilled = True
+                invitation.save()
+                report.invited = True
+
+            # Check if report author if the report is being flagged on the submission
+            if submission.referees_flagged:
+                if current_contributor.user.last_name in submission.referees_flagged:
+                    report.flagged = True
+        report.save()
+        return report
 
 
 class VetReportForm(forms.Form):
@@ -438,12 +479,41 @@ class VetReportForm(forms.Form):
     refusal_reason = forms.ChoiceField(choices=REPORT_REFUSAL_CHOICES, required=False)
     email_response_field = forms.CharField(widget=forms.Textarea(),
                                            label='Justification (optional)', required=False)
+    report = forms.ModelChoiceField(queryset=Report.objects.awaiting_vetting(), required=True,
+                                    widget=forms.HiddenInput())
 
     def __init__(self, *args, **kwargs):
         super(VetReportForm, self).__init__(*args, **kwargs)
-        self.fields['email_response_field'].widget.attrs.update(
-            {'placeholder': 'Optional: give a textual justification (will be included in the email to the Report\'s author)',
-             'rows': 5})
+        self.fields['email_response_field'].widget.attrs.update({
+            'placeholder': ('Optional: give a textual justification '
+                            '(will be included in the email to the Report\'s author)'),
+            'rows': 5
+        })
+
+    def clean_refusal_reason(self):
+        '''Require a refusal reason if report is rejected.'''
+        reason = self.cleaned_data['refusal_reason']
+        if self.cleaned_data['action_option'] == REPORT_ACTION_REFUSE:
+            if not reason:
+                self.add_error('refusal_reason', 'A reason must be given to refuse a report.')
+        return reason
+
+    def process_vetting(self, current_contributor):
+        '''Set the right report status and update submission fields if needed.'''
+        report = self.cleaned_data['report']
+        report.vetted_by = current_contributor
+        if self.cleaned_data['action_option'] == REPORT_ACTION_ACCEPT:
+            # Accept the report as is
+            report.status = STATUS_VETTED
+            report.submission.latest_activity = timezone.now()
+            report.submission.save()
+        elif self.cleaned_data['action_option'] == REPORT_ACTION_REFUSE:
+            # The report is rejected
+            report.status = self.cleaned_data['refusal_reason']
+        else:
+            raise InvalidReportVettingValue(self.cleaned_data['action_option'])
+        report.save()
+        return report
 
 
 ###################
diff --git a/submissions/managers.py b/submissions/managers.py
index f1ce5d8a9ec241367cdfa27d31e7624bbea4c4ac..0b9d6cd8cf831490a0974b2ba14a815ac12e90b1 100644
--- a/submissions/managers.py
+++ b/submissions/managers.py
@@ -4,7 +4,8 @@ from django.db.models import Q
 from .constants import SUBMISSION_STATUS_OUT_OF_POOL, SUBMISSION_STATUS_PUBLICLY_UNLISTED,\
                        SUBMISSION_STATUS_PUBLICLY_INVISIBLE, STATUS_UNVETTED, STATUS_VETTED,\
                        STATUS_UNCLEAR, STATUS_INCORRECT, STATUS_NOT_USEFUL, STATUS_NOT_ACADEMIC,\
-                       SUBMISSION_HTTP404_ON_EDITORIAL_PAGE
+                       SUBMISSION_HTTP404_ON_EDITORIAL_PAGE, STATUS_DRAFT,\
+                       SUBMISSION_EXCLUDE_FROM_REPORTING
 
 
 class SubmissionManager(models.Manager):
@@ -73,6 +74,13 @@ class SubmissionManager(models.Manager):
         queryset = self.exclude(status__in=SUBMISSION_STATUS_PUBLICLY_INVISIBLE)
         return self._newest_version_only(queryset)
 
+    def open_for_reporting(self):
+        """
+        This query should filter submissions that do not have the right status to receive
+        a new report.
+        """
+        return self.exclude(status__in=SUBMISSION_EXCLUDE_FROM_REPORTING)
+
 
 class EditorialAssignmentManager(models.Manager):
     def get_for_user_in_pool(self, user):
@@ -110,7 +118,7 @@ class EICRecommendationManager(models.Manager):
 
 class ReportManager(models.Manager):
     def accepted(self):
-        return self.filter(status__gte=STATUS_VETTED)
+        return self.filter(status=STATUS_VETTED)
 
     def awaiting_vetting(self):
         return self.filter(status=STATUS_UNVETTED)
@@ -118,3 +126,6 @@ class ReportManager(models.Manager):
     def rejected(self):
         return self.filter(status__in=[STATUS_UNCLEAR, STATUS_INCORRECT,
                                        STATUS_NOT_USEFUL, STATUS_NOT_ACADEMIC])
+
+    def in_draft(self):
+        return self.filter(status=STATUS_DRAFT)
diff --git a/submissions/migrations/0044_auto_20170602_1836.py b/submissions/migrations/0044_auto_20170602_1836.py
new file mode 100644
index 0000000000000000000000000000000000000000..329d7db77ef5620d4a4bc4ecb7c07d12d685660c
--- /dev/null
+++ b/submissions/migrations/0044_auto_20170602_1836.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2017-06-02 16:36
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+status_map_to_new = {
+    '1': 'vetted',
+    '0': 'unvetted',
+    '-1': 'unclear',
+    '-2': 'incorrect',
+    '-3': 'notuseful',
+    '-4': 'notacademic'
+}
+status_map_to_old = dict((v,  int(k)) for k, v in status_map_to_new.items())
+
+
+def map_reports_to_new_status_codes(apps, schema_editor):
+    Report = apps.get_model('submissions', 'Report')
+    reports = Report.objects.all()
+    for report in reports:
+        try:
+            new_status = status_map_to_new[report.status]
+        except KeyError:
+            new_status = 'unvetted'
+        report.status = new_status
+        report.save()
+    print('\nUpdated %i reports.' % len(reports))
+
+
+def map_reports_to_old_status_codes(apps, schema_editor):
+    Report = apps.get_model('submissions', 'Report')
+    reports = Report.objects.all()
+    for report in reports:
+        try:
+            new_status = status_map_to_old[report.status]
+        except KeyError:
+            new_status = 0
+        report.status = new_status
+        report.save()
+    print('\nUpdated %i reports.' % len(reports))
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('submissions', '0043_auto_20170512_0836'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='report',
+            name='status',
+            field=models.CharField(choices=[('vetted', 'Vetted'), ('unvetted', 'Unvetted'), ('incorrect', 'Rejected (incorrect)'), ('unclear', 'Rejected (unclear)'), ('notuseful', 'Rejected (not useful)'), ('notacademic', 'Rejected (not academic in style)')], default='unvetted', max_length=16),
+        ),
+        migrations.RunPython(map_reports_to_new_status_codes, map_reports_to_old_status_codes),
+    ]
diff --git a/submissions/models.py b/submissions/models.py
index 6bf5ae9c6c7b1b9378efa71e54f26a7a8074ee88..17a3643441ffd49857f4e637262444881de02fb2 100644
--- a/submissions/models.py
+++ b/submissions/models.py
@@ -1,16 +1,15 @@
 import datetime
 
 from django.utils import timezone
-from django.db import models, transaction
+from django.db import models
 from django.contrib.postgres.fields import JSONField
 from django.urls import reverse
 
 from .constants import ASSIGNMENT_REFUSAL_REASONS, ASSIGNMENT_NULLBOOL,\
                        SUBMISSION_TYPE, ED_COMM_CHOICES, REFEREE_QUALIFICATION, QUALITY_SPEC,\
                        RANKING_CHOICES, REPORT_REC, SUBMISSION_STATUS, STATUS_UNASSIGNED,\
-                       REPORT_STATUSES, STATUS_UNVETTED, STATUS_RESUBMISSION_INCOMING,\
-                       SUBMISSION_CYCLES, CYCLE_DEFAULT, CYCLE_SHORT, CYCLE_DIRECT_REC,\
-                       SUBMISSION_EIC_RECOMMENDATION_REQUIRED
+                       REPORT_STATUSES, STATUS_UNVETTED, SUBMISSION_EIC_RECOMMENDATION_REQUIRED,\
+                       SUBMISSION_CYCLES, CYCLE_DEFAULT, CYCLE_SHORT, CYCLE_DIRECT_REC
 from .managers import SubmissionManager, EditorialAssignmentManager, EICRecommendationManager,\
                       ReportManager
 from .utils import ShortSubmissionCycle, DirectRecommendationSubmissionCycle,\
@@ -148,19 +147,19 @@ class Submission(ArxivCallable, models.Model):
         return self.referee_invitations.filter(accepted=None).count()
 
     def count_invited_reports(self):
-        return self.reports.filter(status=1, invited=True).count()
+        return self.reports.accepted().filter(invited=True).count()
 
     def count_contrib_reports(self):
-        return self.reports.filter(status=1, invited=False).count()
+        return self.reports.accepted().filter(invited=False).count()
 
     def count_obtained_reports(self):
-        return self.reports.filter(status=1, invited__isnull=False).count()
+        return self.reports.accepted().filter(invited__isnull=False).count()
 
     def count_refused_reports(self):
-        return self.reports.filter(status__lte=-1).count()
+        return self.reports.rejected().count()
 
     def count_awaiting_vetting(self):
-        return self.reports.filter(status=0).count()
+        return self.reports.awaiting_vetting().count()
 
 
 ######################
@@ -236,7 +235,7 @@ class RefereeInvitation(models.Model):
 
 class Report(models.Model):
     """ Both types of reports, invited or contributed. """
-    status = models.SmallIntegerField(choices=REPORT_STATUSES, default=STATUS_UNVETTED)
+    status = models.CharField(max_length=16, choices=REPORT_STATUSES, default=STATUS_UNVETTED)
     submission = models.ForeignKey('submissions.Submission', related_name='reports',
                                    on_delete=models.CASCADE)
     vetted_by = models.ForeignKey('scipost.Contributor', related_name="report_vetted_by",
@@ -264,7 +263,7 @@ class Report(models.Model):
                                           verbose_name="Quality of paper formatting")
     grammar = models.SmallIntegerField(choices=QUALITY_SPEC,
                                        verbose_name="Quality of English grammar")
-    #
+
     recommendation = models.SmallIntegerField(choices=REPORT_REC)
     remarks_for_editors = models.TextField(default='', blank=True,
                                            verbose_name='optional remarks for the Editors only')
diff --git a/submissions/templates/submissions/submission_detail.html b/submissions/templates/submissions/submission_detail.html
index c10ff311dbc77527b3a1fd60c593d843f299b907..f255cf191d11632a6776dda1ba0baf10e76e4ffd 100644
--- a/submissions/templates/submissions/submission_detail.html
+++ b/submissions/templates/submissions/submission_detail.html
@@ -58,6 +58,12 @@
                 {% endfor %}
             </div>
         {% endif %}
+
+        {% if unfinished_report_for_user %}
+            <blockquote class="blockquote">
+              <p class="mb-0">You have an unfinished report for this submission, <a href="{% url 'submissions:submit_report' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr %}">finish your report here.</a></p>
+            </blockquote>
+        {% endif %}
     </div>
 </div>
 
@@ -110,7 +116,8 @@
             {% if submission.open_for_reporting %}
                 {% if perms.scipost.can_referee and not is_author and not is_author_unchecked %}
                     <li>
-                        <h3><a href="{% url 'submissions:submit_report' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr %}">Contribute a Report</a></h3>
+
+                        <h3><a href="{% url 'submissions:submit_report' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr %}">{% if unfinished_report_for_user %}Finish your report{% else %}Contribute a Report{% endif %}</a></h3>
                         <div class="text-danger">Deadline for reporting: {{ submission.reporting_deadline|date:"Y-m-d" }}</div>
                     </li>
                 {% elif is_author_unchecked %}
diff --git a/submissions/templates/submissions/submit_report.html b/submissions/templates/submissions/submit_report.html
index 72a253c84058ac31f315ed052402b541edd3d08b..63f80e447b69a04de026d1a51db389b58df83e29 100644
--- a/submissions/templates/submissions/submit_report.html
+++ b/submissions/templates/submissions/submit_report.html
@@ -88,8 +88,9 @@
             <form action="{% url 'submissions:submit_report' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr %}" method="post">
                 {% csrf_token %}
                 {{ form|bootstrap:'3,9' }}
-                <input class="btn btn-primary" type="submit" value="Submit your report"/>
-                <div class="mt-2">
+                <input class="btn btn-primary" type="submit" name="save_submit" value="Submit your report"/>
+                <input class="btn btn-secondary ml-2" type="submit" name="save_draft" value="Save your report as draft"/>
+                <div class="my-4">
                     <em>By clicking on Submit, you state that you abide by the <a href="{% url 'journals:journals_terms_and_conditions' %}#referee_code_of_conduct">referee code of conduct</a>.</em>
                 </div>
             </form>
diff --git a/submissions/templates/submissions/submit_report_ack.html b/submissions/templates/submissions/submit_report_ack.html
deleted file mode 100644
index 8e5b948e98fd58a108b0f265fa2b21ab02ce8ed1..0000000000000000000000000000000000000000
--- a/submissions/templates/submissions/submit_report_ack.html
+++ /dev/null
@@ -1,15 +0,0 @@
-{% extends 'scipost/base.html' %}
-
-{% block pagetitle %}: submit report (ack){% endblock pagetitle %}
-
-{% block bodysup %}
-
-<section>
-  {% if errormessage %}
-  <p>{{ errormessage }}</p>
-  {% else %}
-  <h1>Thank you for your Report.</h1>
-  {% endif %}
-</section>
-
-{% endblock bodysup %}
diff --git a/submissions/templates/submissions/vet_submitted_reports.html b/submissions/templates/submissions/vet_submitted_reports.html
index d8fe13476f55abced58e18b00decfd07cbd72976..eb2278ad82292a53d9b7195ae33dc9ff30fc68c2 100644
--- a/submissions/templates/submissions/vet_submitted_reports.html
+++ b/submissions/templates/submissions/vet_submitted_reports.html
@@ -8,18 +8,15 @@
 
 <script>
 $(document).ready(function(){
-
-  $('#refusal').hide();
-
   $('[name="action_option"]').on('change', function() {
-      if ($('#id_action_option_1').is(':checked')) {
+      if ($('[name="action_option"][value="refuse"]').is(':checked')) {
           $('#refusal').show();
       }
       else {
           $('#refusal').hide();
       }
-    });
-  });
+  }).trigger('change');
+});
 </script>
 
 {% endblock headsup %}
@@ -36,6 +33,7 @@ $(document).ready(function(){
     <div class="col-12">
         {% if not report_to_vet %}
             <h1>There are no Reports for you to vet.</h1>
+            <p>Go back to my <a href="{% url 'scipost:personal_page' %}">personal page</a>.</p>
         {% else %}
             <h1 class="highlight">SciPost Report to vet:</h1>
 
@@ -52,8 +50,9 @@ $(document).ready(function(){
 
             <hr class="small">
             <h2>Please vet this Report:</h2>
-            <form action="{% url 'submissions:vet_submitted_report_ack' report_id=report_to_vet.id %}" method="post">
+            <form action="{% url 'submissions:vet_submitted_reports' %}" method="post">
                 {% csrf_token %}
+                {{ form.report }}
                 {{ form.action_option|bootstrap }}
                 <div class="col-md-6" id="refusal">
                     {{ form.refusal_reason|bootstrap }}
diff --git a/submissions/test_views.py b/submissions/test_views.py
index 11e9c210a19bd604c2791a96a4359d6f1c7672b4..cc6905f3e018523147e0df80f8c49fecae9bd4c2 100644
--- a/submissions/test_views.py
+++ b/submissions/test_views.py
@@ -1,20 +1,21 @@
-import json
-
 from django.core.urlresolvers import reverse
-from django.test import TestCase
+from django.test import TestCase, tag
 from django.test import Client
 
 from common.helpers import random_arxiv_identifier_without_version_number
 from common.helpers.test import add_groups_and_permissions
 from scipost.factories import ContributorFactory
-# from scipost.models import Contributor
 
-from .constants import STATUS_UNASSIGNED
+from .constants import STATUS_UNASSIGNED, STATUS_DRAFT, STATUS_UNVETTED
 from .factories import UnassignedSubmissionFactory, EICassignedSubmissionFactory,\
                        ResubmittedSubmissionFactory, ResubmissionFactory,\
-                       PublishedSubmissionFactory
-from .forms import SubmissionForm, SubmissionIdentifierForm
-from .models import Submission
+                       PublishedSubmissionFactory, DraftReportFactory,\
+                       AcceptedRefereeInvitationFactory
+from .forms import RequestSubmissionForm, SubmissionIdentifierForm, ReportForm
+from .models import Submission, Report, RefereeInvitation
+
+from faker import Faker
+
 
 # This is content of a real arxiv submission. As long as it isn't published it should
 # be possible to run tests using this submission.
@@ -54,7 +55,7 @@ class BaseContributorTestCase(TestCase):
     def setUp(self):
         add_groups_and_permissions()
         ContributorFactory.create_batch(5)
-        ContributorFactory.create(
+        self.current_contrib = ContributorFactory.create(
             user__last_name='Linder',  # To pass the author check in create submissions view
             user__username='Test',
             user__password='testpw'
@@ -79,6 +80,8 @@ class PrefillUsingIdentifierTest(BaseContributorTestCase):
 
         # Registered Contributor should get 200
         response = self.client.get(self.url)
+        self.assertIsInstance(response.context['form'], SubmissionIdentifierForm)
+        self.assertFalse(response.context['form'].is_valid())
         self.assertEqual(response.status_code, 200)
 
     def test_retrieving_existing_arxiv_paper(self):
@@ -87,13 +90,11 @@ class PrefillUsingIdentifierTest(BaseContributorTestCase):
                                     {'identifier':
                                         TEST_SUBMISSION['arxiv_identifier_w_vn_nr']})
         self.assertEqual(response.status_code, 200)
-        self.assertIsInstance(response.context['form'], SubmissionForm)
-        self.assertIsInstance(response.context['identifierform'], SubmissionIdentifierForm)
-        self.assertTrue(response.context['identifierform'].is_valid())
+        self.assertIsInstance(response.context['form'], RequestSubmissionForm)
 
         # Explicitly compare fields instead of assertDictEqual as metadata field may be outdated
-        self.assertEqual(TEST_SUBMISSION['is_resubmission'],
-                         response.context['form'].initial['is_resubmission'])
+        # self.assertEqual(TEST_SUBMISSION['is_resubmission'],
+        #                  response.context['form'].initial['is_resubmission'])
         self.assertEqual(TEST_SUBMISSION['title'], response.context['form'].initial['title'])
         self.assertEqual(TEST_SUBMISSION['author_list'],
                          response.context['form'].initial['author_list'])
@@ -138,7 +139,6 @@ class SubmitManuscriptTest(BaseContributorTestCase):
             'submission_type': 'Article',
             'domain': 'T'
         })
-        params['metadata'] = json.dumps(params['metadata'], separators=(',', ':'))
 
         # Submit new Submission form
         response = client.post(reverse('submissions:submit_manuscript'), params)
@@ -179,11 +179,13 @@ class SubmitManuscriptTest(BaseContributorTestCase):
             'submission_type': 'Article',
             'domain': 'T'
         })
-        params['metadata'] = json.dumps(params['metadata'], separators=(',', ':'))
 
         # Submit new Submission form
         response = client.post(reverse('submissions:submit_manuscript'), params)
-        self.assertEqual(response.status_code, 302)
+        self.assertEqual(response.status_code, 200)
+        self.assertIsInstance(response.context['form'], RequestSubmissionForm)
+        self.assertFalse(response.context['form'].is_valid())
+        self.assertIn('author_list', response.context['form'].errors.keys())
 
         # No real check is done here to see if submission submit is aborted.
         # To be implemented after Arxiv caller.
@@ -246,3 +248,169 @@ class SubmissionListTest(BaseContributorTestCase):
         returned_submissions_ids.sort()
         visible_submission_ids.sort()
         self.assertListEqual(returned_submissions_ids, visible_submission_ids)
+
+
+class SubmitReportTest(BaseContributorTestCase):
+    TEST_DATA = {
+        'anonymous': 'on',
+        'clarity': '60',
+        'formatting': '4',
+        'grammar': '5',
+        'originality': '100',
+        'qualification': '3',
+        'recommendation': '3',
+        'remarks_for_editors': 'Lorem Ipsum1',
+        'report': 'Lorem Ipsum',
+        'requested_changes': 'Lorem Ipsum2',
+        'significance': '0',
+        'strengths': 'Lorem Ipsum3',
+        'validity': '60',
+        'weaknesses': 'Lorem Ipsum4'
+    }
+
+    def setUp(self):
+        super().setUp()
+        self.client = Client()
+        report_deadline = Faker().date_time_between(start_date="now", end_date="+30d", tzinfo=None)
+        self.submission = EICassignedSubmissionFactory(reporting_deadline=report_deadline)
+        self.submission.authors.remove(self.current_contrib)
+        self.submission.authors_false_claims.add(self.current_contrib)
+        self.target = reverse('submissions:submit_report',
+                              args=(self.submission.arxiv_identifier_w_vn_nr,))
+        self.assertTrue(self.client.login(username="Test", password="testpw"))
+
+    @tag('reports')
+    def test_status_code_200_no_report_set(self):
+        '''Test response for view if no report is submitted yet.'''
+        report_deadline = Faker().date_time_between(start_date="now", end_date="+30d", tzinfo=None)
+        submission = EICassignedSubmissionFactory(reporting_deadline=report_deadline)
+        submission.authors.remove(self.current_contrib)
+        submission.authors_false_claims.add(self.current_contrib)
+
+        target = reverse('submissions:submit_report', args=(submission.arxiv_identifier_w_vn_nr,))
+        client = Client()
+
+        # Login and call view
+        self.assertTrue(client.login(username="Test", password="testpw"))
+        response = client.get(target)
+
+        self.assertEqual(response.status_code, 200)
+        self.assertIsNone(response.context['form'].instance.id)
+
+    @tag('reports')
+    def test_status_code_200_report_in_draft(self):
+        '''Test response for view if report in draft exists.'''
+        report = DraftReportFactory(submission=self.submission, author=self.current_contrib)
+        response = self.client.get(self.target)
+
+        self.assertEqual(response.status_code, 200)
+        self.assertIsInstance(response.context['form'], ReportForm)
+        self.assertEqual(response.context['form'].instance, report)
+
+    @tag('reports')
+    def test_post_report_for_draft_status(self):
+        '''Test response of view if report is saved as draft.'''
+        response = self.client.post(self.target, {**self.TEST_DATA, 'save_draft': 'True'})
+
+        # Check if form is returned with saved report as instance
+        self.assertEqual(response.status_code, 200)
+        self.assertIsInstance(response.context['form'], ReportForm)
+        self.assertIsInstance(response.context['form'].instance, Report)
+
+        # Briefly do cross checks if report submit is complete
+        report_db = Report.objects.last()
+        self.assertEqual(response.context['form'].instance, report_db)
+        self.assertTrue(report_db.anonymous)
+        self.assertEqual(report_db.status, STATUS_DRAFT)
+        self.assertFalse(report_db.invited)  # Set by view only if non-draft
+        self.assertFalse(report_db.flagged)  # Set by view only if non-draft
+
+        self.assertEqual(report_db.clarity, 60)
+        self.assertEqual(report_db.formatting, 4)
+        self.assertEqual(report_db.grammar, 5)
+        self.assertEqual(report_db.originality, 100)
+        self.assertEqual(report_db.qualification, 3)
+        self.assertEqual(report_db.significance, 0)
+        self.assertEqual(report_db.validity, 60)
+        self.assertEqual(report_db.remarks_for_editors, 'Lorem Ipsum1')
+        self.assertEqual(report_db.requested_changes, 'Lorem Ipsum2')
+        self.assertEqual(report_db.strengths, 'Lorem Ipsum3')
+        self.assertEqual(report_db.weaknesses, 'Lorem Ipsum4')
+
+    @tag('reports')
+    def test_post_report(self):
+        '''Test response of view if report submitted.'''
+        response = self.client.post(self.target, {**self.TEST_DATA, 'save_submit': 'True'})
+
+        # Check if user is redirected
+        self.assertEqual(response.status_code, 302)
+
+        # Briefly do cross checks if report submit is complete
+        report_db = Report.objects.last()
+        self.assertEqual(report_db.status, STATUS_UNVETTED)
+
+        # Check if invited value has only changed if valid to do so
+        self.assertIsNone(self.submission.referee_invitations
+                          .filter(referee=self.current_contrib).first())
+        self.assertFalse(report_db.invited)
+
+        # Cross-check if flagged can't be assigned, as this should only happen if author is
+        # flagged on the submission involved
+        self.assertIsNone(self.submission.referees_flagged)
+        self.assertFalse(report_db.flagged)
+
+        self.assertTrue(report_db.anonymous)
+        self.assertEqual(report_db.clarity, 60)
+        self.assertEqual(report_db.formatting, 4)
+        self.assertEqual(report_db.grammar, 5)
+        self.assertEqual(report_db.originality, 100)
+        self.assertEqual(report_db.qualification, 3)
+        self.assertEqual(report_db.significance, 0)
+        self.assertEqual(report_db.validity, 60)
+        self.assertEqual(report_db.remarks_for_editors, 'Lorem Ipsum1')
+        self.assertEqual(report_db.requested_changes, 'Lorem Ipsum2')
+        self.assertEqual(report_db.strengths, 'Lorem Ipsum3')
+        self.assertEqual(report_db.weaknesses, 'Lorem Ipsum4')
+
+    @tag('reports')
+    def test_post_report_flagged_author(self):
+        '''Test if report is `flagged` if author is flagged on related submission.'''
+        report_deadline = Faker().date_time_between(start_date="now", end_date="+30d", tzinfo=None)
+        submission = EICassignedSubmissionFactory(reporting_deadline=report_deadline,
+                                                  referees_flagged=str(self.current_contrib))
+        submission.authors.remove(self.current_contrib)
+        submission.authors_false_claims.add(self.current_contrib)
+
+        target = reverse('submissions:submit_report', args=(submission.arxiv_identifier_w_vn_nr,))
+        client = Client()
+
+        # Login and call view
+        self.assertTrue(client.login(username="Test", password="testpw"))
+        self.TEST_DATA['save_submit'] = 'Submit your report'
+        response = client.post(target, self.TEST_DATA)
+        self.assertEqual(response.status_code, 302)
+
+        # Briefly checks if report is valid
+        report_db = Report.objects.last()
+        self.assertEqual(report_db.status, STATUS_UNVETTED)
+        self.assertTrue(report_db.flagged)
+
+    @tag('reports')
+    def test_post_report_with_invitation(self):
+        '''Test if report is submission is valid using invitation.'''
+        AcceptedRefereeInvitationFactory(submission=self.submission, referee=self.current_contrib)
+
+        # Post Data
+        response = self.client.post(self.target, {**self.TEST_DATA, 'save_submit': 'True'})
+        self.assertEqual(response.status_code, 302)
+
+        # Briefly checks if report is valid
+        report_db = Report.objects.last()
+        self.assertEqual(report_db.status, STATUS_UNVETTED)
+        self.assertTrue(report_db.invited)
+
+        # Check if Invitation has changed correctly
+        invitation = RefereeInvitation.objects.last()
+        self.assertEqual(invitation.referee, self.current_contrib)
+        self.assertEqual(invitation.submission, self.submission)
+        self.assertTrue(invitation.fulfilled)
diff --git a/submissions/urls.py b/submissions/urls.py
index 6191c3b6638edf6933853c961bcfc033ef83a840..6e776fcf71b60a8500c7f7fc8657ac7eb0f9e8e2 100644
--- a/submissions/urls.py
+++ b/submissions/urls.py
@@ -1,4 +1,4 @@
-from django.conf.urls import include, url
+from django.conf.urls import url
 from django.views.generic import TemplateView
 
 from . import views
@@ -6,7 +6,8 @@ from . import views
 urlpatterns = [
     # Submissions
     url(r'^$', views.SubmissionListView.as_view(), name='submissions'),
-    url(r'^browse/(?P<discipline>[a-z]+)/(?P<nrweeksback>[0-9]{1,3})/$', views.SubmissionListView.as_view(), name='browse'),
+    url(r'^browse/(?P<discipline>[a-z]+)/(?P<nrweeksback>[0-9]{1,3})/$',
+        views.SubmissionListView.as_view(), name='browse'),
     url(r'^sub_and_ref_procedure$',
         TemplateView.as_view(template_name='submissions/sub_and_ref_procedure.html'),
         name='sub_and_ref_procedure'),
@@ -69,17 +70,12 @@ urlpatterns = [
         views.communication, name='communication'),
     url(r'^eic_recommendation/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$',
         views.eic_recommendation, name='eic_recommendation'),
-    url(r'^cycle/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/submit$', views.cycle_form_submit,
-        name='cycle_confirmation'),
+    url(r'^cycle/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/submit$',
+        views.cycle_form_submit, name='cycle_confirmation'),
     # Reports
     url(r'^submit_report/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$',
         views.submit_report, name='submit_report'),
-    url(r'^submit_report_ack$',
-        TemplateView.as_view(template_name='submissions/submit_report_ack.html'), name='submit_report_ack'),
-    url(r'^vet_submitted_reports$',
-        views.vet_submitted_reports, name='vet_submitted_reports'),
-    url(r'^vet_submitted_report_ack/(?P<report_id>[0-9]+)$',
-        views.vet_submitted_report_ack, name='vet_submitted_report_ack'),
+    url(r'^vet_submitted_reports$', views.vet_submitted_reports, name='vet_submitted_reports'),
     # Voting
     url(r'^prepare_for_voting/(?P<rec_id>[0-9]+)$', views.prepare_for_voting, name='prepare_for_voting'),
     url(r'^vote_on_rec/(?P<rec_id>[0-9]+)$', views.vote_on_rec, name='vote_on_rec'),
diff --git a/submissions/utils.py b/submissions/utils.py
index 29ed7e85be4febc089c51bd4b476b66f3db4c21a..302496959f2b8ee166e16745553014af1323b828 100644
--- a/submissions/utils.py
+++ b/submissions/utils.py
@@ -4,7 +4,8 @@ from django.core.mail import EmailMessage, EmailMultiAlternatives
 from django.template import Context, Template
 from django.utils import timezone
 
-from .constants import NO_REQUIRED_ACTION_STATUSES,\
+from .constants import NO_REQUIRED_ACTION_STATUSES, STATUS_VETTED, STATUS_UNCLEAR,\
+                       STATUS_INCORRECT, STATUS_NOT_USEFUL, STATUS_NOT_ACADEMIC,\
                        STATUS_REVISION_REQUESTED, STATUS_EIC_ASSIGNED,\
                        STATUS_RESUBMISSION_INCOMING, STATUS_AWAITING_ED_REC
 from .exceptions import CycleUpdateDeadlineError
@@ -1023,7 +1024,7 @@ class SubmissionUtils(BaseMailUtil):
             '<p>Dear {{ ref_title }} {{ ref_last_name }},</p>'
             '<p>Many thanks for your Report on Submission</p>'
             '<p>{{ sub_title }}</p>\n<p>by {{ author_list }}.</p>')
-        if cls.report.status == 1:
+        if cls.report.status == STATUS_VETTED:
             email_text += ('\n\nYour Report has been vetted through and is viewable at '
                            'https://scipost.org/submissions/'
                            + cls.report.submission.arxiv_identifier_w_vn_nr + '.')
@@ -1047,7 +1048,7 @@ class SubmissionUtils(BaseMailUtil):
                        '\n\nThe SciPost Team.')
         email_text_html += ('<p>Many thanks for your collaboration,</p>'
                             '<p>The SciPost Team.</p>')
-        if cls.report.status != 1:
+        if cls.report.status != STATUS_VETTED:
             if cls.email_response is not None:
                 email_text += '\n\nAdditional info from the Editor-in-charge: \n'
                 email_text += cls.email_response
@@ -1078,7 +1079,8 @@ class SubmissionUtils(BaseMailUtil):
             'requested_changes': cls.report.requested_changes,
             'remarks_for_editors': cls.report.remarks_for_editors,
         })
-        if cls.report.status < 0:
+        if cls.report.status in [STATUS_UNCLEAR, STATUS_INCORRECT,
+                                 STATUS_NOT_USEFUL, STATUS_NOT_ACADEMIC]:
             email_context['refusal_reason'] = cls.report.get_status_display()
         email_text_html += '<br/>' + EMAIL_FOOTER
         html_template = Template(email_text_html)
diff --git a/submissions/views.py b/submissions/views.py
index a3764b96f5a4e937c229c7f4026b76b3739b9952..cd0cfd8d71ee905b868db8975f5cabed7b455dc7 100644
--- a/submissions/views.py
+++ b/submissions/views.py
@@ -15,8 +15,9 @@ from django.utils.decorators import method_decorator
 from guardian.decorators import permission_required_or_403
 from guardian.shortcuts import assign_perm
 
-from .constants import SUBMISSION_STATUS_VOTING_DEPRECATED,\
-                       SUBMISSION_STATUS_PUBLICLY_INVISIBLE, SUBMISSION_STATUS, ED_COMM_CHOICES
+from .constants import SUBMISSION_STATUS_VOTING_DEPRECATED, STATUS_VETTED, STATUS_EIC_ASSIGNED,\
+                       SUBMISSION_STATUS_PUBLICLY_INVISIBLE, SUBMISSION_STATUS, ED_COMM_CHOICES,\
+                       STATUS_DRAFT
 from .models import Submission, EICRecommendation, EditorialAssignment,\
                     RefereeInvitation, Report, EditorialCommunication
 from .forms import SubmissionIdentifierForm, RequestSubmissionForm, SubmissionSearchForm,\
@@ -167,12 +168,18 @@ def submission_detail(request, arxiv_identifier_w_vn_nr):
     submission = get_object_or_404(Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr)
     try:
         is_author = request.user.contributor in submission.authors.all()
-        is_author_unchecked = (not is_author and not
-                               (request.user.contributor in submission.authors_false_claims.all()) and
-                               (request.user.last_name in submission.author_list))
+        is_author_unchecked = (not is_author and
+                               request.user.contributor not in submission.authors_false_claims.all()
+                               and request.user.last_name in submission.author_list)
+        try:
+            unfinished_report_for_user = (submission.reports.in_draft()
+                                          .get(author=request.user.contributor))
+        except Report.DoesNotExist:
+            unfinished_report_for_user = None
     except AttributeError:
         is_author = False
         is_author_unchecked = False
+        unfinished_report_for_user = None
     if (submission.status in SUBMISSION_STATUS_PUBLICLY_INVISIBLE
             and not request.user.groups.filter(name__in=['SciPost Administrators',
                                                          'Editorial Administrators',
@@ -187,8 +194,10 @@ def submission_detail(request, arxiv_identifier_w_vn_nr):
 
     invited_reports = submission.reports.accepted().filter(invited=True)
     contributed_reports = submission.reports.accepted().filter(invited=False)
-    comments = submission.comments.vetted().filter(is_author_reply=False).order_by('-date_submitted')
-    author_replies = submission.comments.vetted().filter(is_author_reply=True).order_by('-date_submitted')
+    comments = (submission.comments.vetted()
+                .filter(is_author_reply=False).order_by('-date_submitted'))
+    author_replies = (submission.comments.vetted()
+                      .filter(is_author_reply=True).order_by('-date_submitted'))
 
     try:
         recommendation = (EICRecommendation.objects.filter_for_user(request.user)
@@ -202,6 +211,7 @@ def submission_detail(request, arxiv_identifier_w_vn_nr):
                'comments': comments,
                'invited_reports': invited_reports,
                'contributed_reports': contributed_reports,
+               'unfinished_report_for_user': unfinished_report_for_user,
                'author_replies': author_replies,
                'form': form,
                'is_author': is_author,
@@ -520,8 +530,8 @@ def editorial_page(request, arxiv_identifier_w_vn_nr):
                       .filter(arxiv_identifier_wo_vn_nr=submission.arxiv_identifier_wo_vn_nr)
                       .exclude(pk=submission.id))
     ref_invitations = RefereeInvitation.objects.filter(submission=submission)
-    nr_reports_to_vet = (Report.objects
-                         .filter(status=0, submission=submission,
+    nr_reports_to_vet = (Report.objects.awaiting_vetting()
+                         .filter(submission=submission,
                                  submission__editor_in_charge=request.user.contributor)
                          .count())
     communications = (EditorialCommunication.objects
@@ -861,9 +871,9 @@ def close_refereeing_round(request, arxiv_identifier_w_vn_nr):
 
 @permission_required('scipost.can_oversee_refereeing', raise_exception=True)
 def refereeing_overview(request):
-    submissions_under_refereeing = Submission.objects.filter(
-        status='EICassigned').order_by('submission_date')
-    context= {'submissions_under_refereeing': submissions_under_refereeing,}
+    submissions_under_refereeing = (Submission.objects.filter(status=STATUS_EIC_ASSIGNED)
+                                    .order_by('submission_date'))
+    context = {'submissions_under_refereeing': submissions_under_refereeing}
     return render(request, 'submissions/refereeing_overview.html', context)
 
 
@@ -979,28 +989,42 @@ def eic_recommendation(request, arxiv_identifier_w_vn_nr):
 ###########
 # Reports
 ###########
-
 @login_required
 @permission_required('scipost.can_referee', raise_exception=True)
 @transaction.atomic
 def submit_report(request, arxiv_identifier_w_vn_nr):
-    submission = get_object_or_404(Submission.objects.all(),
+    """
+    A form to submit a report on a submission will be shown and processed here.
+
+    Important checks to be aware of include an author check for the submission,
+    has the reporting deadline not been reached yet and does there exist any invitation
+    for the current user on this submission.
+    """
+    submission = get_object_or_404(Submission.objects.open_for_reporting(),
                                    arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr)
     # Check whether the user can submit a report:
-    is_author = request.user.contributor in submission.authors.all()
+    current_contributor = request.user.contributor
+    is_author = current_contributor in submission.authors.all()
     is_author_unchecked = (not is_author and not
-                           (request.user.contributor in submission.authors_false_claims.all()) and
+                           (current_contributor in submission.authors_false_claims.all()) and
                            (request.user.last_name in submission.author_list))
-    try:
-        invitation = RefereeInvitation.objects.get(submission=submission,
-                                                   referee=request.user.contributor)
-    except RefereeInvitation.DoesNotExist:
-        invitation = None
+    invitation = submission.referee_invitations.filter(referee=current_contributor).first()
 
     errormessage = None
-    if not invitation and timezone.now() > submission.reporting_deadline + datetime.timedelta(days=1):
-        errormessage = ('The reporting deadline has passed. You cannot submit'
-                        ' a Report anymore.')
+    if not invitation:
+        if timezone.now() > submission.reporting_deadline + datetime.timedelta(days=1):
+            errormessage = ('The reporting deadline has passed. You cannot submit'
+                            ' a Report anymore.')
+        elif not submission.open_for_reporting:
+            errormessage = ('Reporting for this submission has closed. You cannot submit'
+                            ' a Report anymore.')
+
+        if errormessage:
+            # Remove old drafts from the database
+            reports_in_draft_to_remove = (submission.reports.in_draft()
+                                          .filter(author=current_contributor))
+            if reports_in_draft_to_remove:
+                reports_in_draft_to_remove.delete()
     if is_author:
         errormessage = 'You are an author of this Submission and cannot submit a Report.'
     if is_author_unchecked:
@@ -1011,27 +1035,23 @@ def submit_report(request, arxiv_identifier_w_vn_nr):
         messages.warning(request, errormessage)
         return redirect(reverse('scipost:personal_page'))
 
-    form = ReportForm(request.POST or None)
-    if form.is_valid():
-        author = request.user.contributor
-        newreport = form.save(commit=False)
-        newreport.submission = submission
-        newreport.author = request.user.contributor
-        if invitation:
-            invitation.fulfilled = True
-            newreport.invited = True
-            invitation.save()
-
-        if submission.referees_flagged is not None:
-            if author.user.last_name in submission.referees_flagged:
-                newreport.flagged = True
-
-        newreport.date_submitted = timezone.now()
-        newreport.save()
+    # Find and fill earlier version of report
+    try:
+        report_in_draft = submission.reports.in_draft().get(author=current_contributor)
+    except Report.DoesNotExist:
+        report_in_draft = None
+    form = ReportForm(request.POST or None, instance=report_in_draft)
 
-        # Update user stats
-        author.nr_reports = Report.objects.filter(author=author).count()
-        author.save()
+    # Check if data sent is valid
+    if form.is_valid():
+        newreport = form.save(submission, current_contributor)
+        if newreport.status == STATUS_DRAFT:
+            messages.success(request, ('Your Report has been saved. '
+                                       'You may leave the page and finish it later.'))
+            context = {'submission': submission, 'form': form}
+            return render(request, 'submissions/submit_report.html', context)
+
+        # Send mails if report is submitted
         SubmissionUtils.load({'report': newreport}, request)
         SubmissionUtils.email_EIC_report_delivered()
         SubmissionUtils.email_referee_report_delivered()
@@ -1049,42 +1069,39 @@ def submit_report(request, arxiv_identifier_w_vn_nr):
 @login_required
 @permission_required('scipost.can_take_charge_of_submissions', raise_exception=True)
 def vet_submitted_reports(request):
-    contributor = Contributor.objects.get(user=request.user)
-    report_to_vet = Report.objects.filter(status=0,
-                                          submission__editor_in_charge=contributor).first()
-    form = VetReportForm()
-    context = {'contributor': contributor, 'report_to_vet': report_to_vet, 'form': form}
-    return(render(request, 'submissions/vet_submitted_reports.html', context))
+    """
+    Reports with status `unvetted` will be shown one-by-one. An user may only
+    vet reports of submissions he/she is EIC of.
 
+    After vetting an email is sent to the report author, bcc EIC. If report
+    has not been refused, the submission author is also mailed.
+    """
+    contributor = Contributor.objects.get(user=request.user)
+    report_to_vet = (Report.objects.awaiting_vetting()
+                     .select_related('submission')
+                     .filter(submission__editor_in_charge=contributor).first())
 
-@permission_required('scipost.can_take_charge_of_submissions', raise_exception=True)
-@transaction.atomic
-def vet_submitted_report_ack(request, report_id):
-    report = get_object_or_404(Report, pk=report_id,
-                               submission__editor_in_charge=request.user.contributor)
-    form = VetReportForm(request.POST or None)
+    form = VetReportForm(request.POST or None, initial={'report': report_to_vet})
     if form.is_valid():
-        report.vetted_by = request.user.contributor
-        if form.cleaned_data['action_option'] == '1':
-            # accept the report as is
-            report.status = 1
-            report.save()
-            report.submission.latest_activity = timezone.now()
-            report.submission.save()
-        elif form.cleaned_data['action_option'] == '2':
-            # the report is simply rejected
-            report.status = int(form.cleaned_data['refusal_reason'])
-            report.save()
+        report = form.process_vetting(request.user.contributor)
+
         # email report author
         SubmissionUtils.load({'report': report,
                               'email_response': form.cleaned_data['email_response_field']})
         SubmissionUtils.acknowledge_report_email()  # email report author, bcc EIC
-        if report.status == 1:
+        if report.status == STATUS_VETTED:
             SubmissionUtils.send_author_report_received_email()
-        messages.success(request, 'Submitted Report vetted.')
-        return redirect(reverse('submissions:editorial_page',
-                                args=[report.submission.arxiv_identifier_w_vn_nr]))
-    return redirect(reverse('submissions:vet_submitted_reports'))
+
+        message = 'Submitted Report vetted for <a href="%s">%s</a>.' % (
+            reverse('submissions:editorial_page',
+                    args=(report.submission.arxiv_identifier_w_vn_nr,)),
+            report.submission.arxiv_identifier_w_vn_nr
+        )
+        messages.success(request, message)
+        # Redirect instead of render to loose the POST call and make it a GET
+        return redirect(reverse('submissions:vet_submitted_reports'))
+    context = {'contributor': contributor, 'report_to_vet': report_to_vet, 'form': form}
+    return render(request, 'submissions/vet_submitted_reports.html', context)
 
 
 @permission_required('scipost.can_prepare_recommendations_for_voting', raise_exception=True)