diff --git a/submissions/constants.py b/submissions/constants.py
index 43ce5c8f1c4c3a4ce895e1f10cff3e803479b708..0f6656719c8acde90789487c74b0143bd022d96c 100644
--- a/submissions/constants.py
+++ b/submissions/constants.py
@@ -156,8 +156,8 @@ 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'),
@@ -172,7 +172,7 @@ STATUS_NOT_USEFUL = 'notuseful'
 STATUS_NOT_ACADEMIC = 'notacademic'
 
 REPORT_REFUSAL_CHOICES = (
-    (STATUS_UNVETTED, '-'),
+    (None, '-'),
     (STATUS_UNCLEAR, 'insufficiently clear'),
     (STATUS_INCORRECT, 'not fully factually correct'),
     (STATUS_NOT_USEFUL, 'not useful for the authors'),
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/forms.py b/submissions/forms.py
index d5dc7376e4786b40c6a327145564778e252c2b88..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.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_DRAFT, STATUS_UNVETTED
+                       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
@@ -437,17 +440,35 @@ class ReportForm(forms.ModelForm):
             'cols': 100
         })
 
-    def save(self, commit=False):
+    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
-        if commit:
-            report.save()
+
+            # 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
 
 
@@ -458,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
 
 
 ###################