From 2759c0f594207bc42ace2272e4b3fbd92380c828 Mon Sep 17 00:00:00 2001 From: "J.-S. Caux" <J.S.Caux@uva.nl> Date: Sat, 21 May 2016 15:22:06 +0200 Subject: [PATCH] Major upgrade of editorial workflow, assignments complete --- .../journals_terms_and_conditions.html | 4 +- scipost/admin.py | 2 +- .../commands/add_groups_and_permissions.py | 9 +- scipost/templates/scipost/personal_page.html | 14 +- scipost/views.py | 12 +- submissions/admin.py | 14 +- submissions/models.py | 31 +++- .../submissions/assign_submission_ack.html | 2 +- .../submissions/assignment_failed_ack.html | 14 ++ .../submissions/sub_and_ref_procedure.html | 4 +- .../submissions/submission_detail.html | 13 +- .../submissions/submit_manuscript.html | 2 +- submissions/utils.py | 138 ++++++++++++++++ submissions/views.py | 154 +++++++----------- 14 files changed, 284 insertions(+), 129 deletions(-) create mode 100644 submissions/templates/submissions/assignment_failed_ack.html create mode 100644 submissions/utils.py diff --git a/journals/templates/journals/journals_terms_and_conditions.html b/journals/templates/journals/journals_terms_and_conditions.html index f33f8b409..e445c92cb 100644 --- a/journals/templates/journals/journals_terms_and_conditions.html +++ b/journals/templates/journals/journals_terms_and_conditions.html @@ -41,7 +41,7 @@ represented by the author having submitted the manuscript.</li> <li>During the evaluation phase, Editorial Fellows will follow the <a href="{% url 'scipost:EdCol_by-laws' %}">Editorial College by-laws</a> - and referees will follow the <a href="#referee_obligations">general obligations for referees</a>.</li> + and referees will follow the <a href="#referee_code_of_conduct">code of conduct for referees</a>.</li> <li>The SciPost Administration reserves the right to remove or censor reports or comments if they contain inappropriate material or if they are insufficiently substantial in nature or of insufficient direct relevance to the manuscript under consideration.</li> @@ -199,7 +199,7 @@ <br/> <hr class="hr12"/> - <h2 id="referee_obligations">Referee obligations</h2> + <h2 id="referee_code_of_conduct">Referee code of conduct</h2> <ul> <li>Contributors asked to referee should promptly accept or decline the task assigned to them.</li> diff --git a/scipost/admin.py b/scipost/admin.py index 4c855d93e..0c0d3f2ab 100644 --- a/scipost/admin.py +++ b/scipost/admin.py @@ -53,6 +53,6 @@ class GraphAdmin(GuardedModelAdmin): NodeInline, ArcInline, ] - search_fields = ['last_name', 'email'] + search_fields = ['owner___user__last_name', 'title'] admin.site.register(Graph, GraphAdmin) diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py index bec30022b..7e027f9c4 100644 --- a/scipost/management/commands/add_groups_and_permissions.py +++ b/scipost/management/commands/add_groups_and_permissions.py @@ -12,10 +12,10 @@ class Command(BaseCommand): # Create Groups SciPostAdmin, created = Group.objects.get_or_create(name='SciPost 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') VettingEditors, created = Group.objects.get_or_create(name='Vetting Editors') RegisteredContributors, created = Group.objects.get_or_create(name='Registered Contributors') - Testers, created = Group.objects.get_or_create(name='Testers') # Create Permissions @@ -36,7 +36,11 @@ class Command(BaseCommand): codename='view_bylaws', name= 'Can view By-laws of Editorial College', content_type=content_type) - + #can_take_editorial_actions, created = Permission.objects.get_or_create( + # codename='can_take_editorial_actions', + # name= 'Can take editorial actions', + # content_type=content_type) + # Contributions (not related to submissions) can_submit_comments, created = Permission.objects.get_or_create( codename='can_submit_comments', @@ -110,6 +114,7 @@ class Command(BaseCommand): can_assign_submissions, ) EditorialCollege.permissions.add(can_take_charge_of_submissions, + #can_take_editorial_actions, can_vet_submitted_reports, view_bylaws, ) diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html index d27b27037..9110e0ade 100644 --- a/scipost/templates/scipost/personal_page.html +++ b/scipost/templates/scipost/personal_page.html @@ -102,7 +102,7 @@ <ul class="personalTabMenu"> <li><a class="TabItem" id="AccountTab">Account</a></li> - {% if request.user|is_in_group:'SciPost Administrators' or request.user|is_in_group:'Editorial College' or request.user|is_in_group:'Vetting Editors' %} + {% if request.user|is_in_group:'Editorial Administrators' or request.user|is_in_group:'Editorial College' or request.user|is_in_group:'Vetting Editors' %} <li><a class="TabItem" id="EdActionTab">Editorial Actions</a></li> {% endif %} <li><a class="TabItem" id="RefereeingTab">Refereeing</a></li> @@ -137,6 +137,9 @@ {% if request.user|is_in_group:'SciPost Administrators' %} <h3>You are a SciPost Administrator.</h3> {% endif %} + {% if request.user|is_in_group:'Editorial Administrators' %} + <h3>You are a SciPost Editorial Administrator.</h3> + {% endif %} {% if request.user|is_in_group:'Advisory Board' %} <h3>You are a member of the Advisory Board.</h3> {% endif %} @@ -149,6 +152,9 @@ {% if request.user|is_in_group:'Registered Contributors' %} <h3>You are a Registered Contributor.</h3> {% endif %} + {% if request.user|is_in_group:'Testers' %} + <h3>You are a SciPost Tester.</h3> + {% endif %} <br/> <h3>Update your personal data or password</h3> <ul> @@ -160,7 +166,7 @@ </section> -{% if request.user|is_in_group:'SciPost Administrators' or request.user|is_in_group:'Editorial College' or request.user|is_in_group:'Vetting Editors' %} +{% if request.user|is_in_group:'SciPost Administrators' or request.user|is_in_group:'Editorial Administrators' or request.user|is_in_group:'Editorial College' or request.user|is_in_group:'Vetting Editors' %} <section class="TabSection" id="EdActions"> <hr class="hr12"> <div class="flex-greybox"> @@ -204,7 +210,7 @@ </ul> </div> - {% if request.user|is_in_group:'SciPost Administrators' or request.user|is_in_group:'Editorial College' %} + {% if request.user|is_in_group:'Editorial Administrators' or request.user|is_in_group:'Editorial College' %} <div class="col-4"> <h3>Submissions assignments</h3> <ul> @@ -271,7 +277,7 @@ <h3>Submissions for which you are identified as an author:</h3> <ul> {% for sub in own_submissions %} - {{ sub.header_as_li }} + {{ sub.header_as_li_for_authors }} {% endfor %} </ul> {% endif %} diff --git a/scipost/views.py b/scipost/views.py index bd8ef2539..4de235bb7 100644 --- a/scipost/views.py +++ b/scipost/views.py @@ -85,7 +85,6 @@ def get_query(query_string, search_fields): return query -#def documentsSearchResults(title_keyword, author, abstract_keyword): def documentsSearchResults(query): """ Searches through commentaries, submissions and thesislinks. @@ -184,7 +183,6 @@ def register(request): return HttpResponseRedirect(reverse('scipost:thanks_for_registering')) else: form = RegistrationForm() - # Remove invited from next two lines to open registrations without invitation invited = False context = {'form': form, 'invited': invited} return render(request, 'scipost/register.html', context) @@ -316,9 +314,12 @@ def vet_registration_request_ack(request, contributor_id): ', \n\nYour registration to the SciPost publication portal has been accepted. ' + 'You can now login at https://scipost.org and contribute. \n\n') if pending_ref_inv_exists: - email_text += 'Note that you have pending refereeing invitations; please navigate to your Personal Page to accept or decline them.\n\n' + email_text += ('Note that you have pending refereeing invitations; please navigate to ' + 'https://scipost.org/submissions/accept_or_decline_ref_invitations ' + '(login required) to accept or decline them.\n\n') email_text += 'Thank you very much in advance, \nThe SciPost Team.' - emailmessage = EmailMessage('SciPost registration accepted', email_text, 'SciPost registration <registration@scipost.org>', + emailmessage = EmailMessage('SciPost registration accepted', email_text, + 'SciPost registration <registration@scipost.org>', [contributor.user.email, 'registration@scipost.org'], reply_to=['registration@scipost.org']) emailmessage.send(fail_silently=False) @@ -443,7 +444,8 @@ def personal_page(request): pending_ref_tasks = RefereeInvitation.objects.filter(referee=contributor, accepted=True, fulfilled=False) # Verify if there exist objects authored by this contributor, whose authorship hasn't been claimed yet own_submissions = (Submission.objects - .filter(Q(authors__in=[contributor]) | Q(submitted_by=contributor)) + #.filter(Q(authors__in=[contributor]) | Q(submitted_by=contributor)) # submitters must be authors + .filter(authors__in=[contributor]) .order_by('-submission_date')) own_commentaries = (Commentary.objects .filter(authors__in=[contributor]) diff --git a/submissions/admin.py b/submissions/admin.py index a26d956b4..06447069f 100644 --- a/submissions/admin.py +++ b/submissions/admin.py @@ -1,33 +1,35 @@ from django.contrib import admin +from guardian.admin import GuardedModelAdmin + from submissions.models import * -class SubmissionAdmin(admin.ModelAdmin): - search_fields = ['submitted_by__user__username', 'title', 'abstract'] +class SubmissionAdmin(GuardedModelAdmin): + search_fields = ['submitted_by__user__last_name', 'title', 'author_list', 'abstract'] admin.site.register(Submission, SubmissionAdmin) class EditorialAssignmentAdmin(admin.ModelAdmin): - search_fields = ['submission', 'to'] + search_fields = ['submission__title', 'submission__author_list', 'to__user__last_name'] admin.site.register(EditorialAssignment, EditorialAssignmentAdmin) class RefereeInvitationAdmin(admin.ModelAdmin): - search_fields = ['submission'] + search_fields = ['submission__title'] admin.site.register(RefereeInvitation, RefereeInvitationAdmin) class ReportAdmin(admin.ModelAdmin): - search_fields = ['author__user__username'] + search_fields = ['author__user__last_name'] admin.site.register(Report, ReportAdmin) class EditorialCommunicationAdmin(admin.ModelAdmin): - search_fields = ['submission', 'referee', 'text'] + search_fields = ['submission__title', 'referee__user__last_name', 'text'] admin.site.register(EditorialCommunication, EditorialCommunicationAdmin) diff --git a/submissions/models.py b/submissions/models.py index 0b6877b83..6d3c9782c 100644 --- a/submissions/models.py +++ b/submissions/models.py @@ -66,6 +66,11 @@ class Submission(models.Model): submission_date = models.DateField(verbose_name='submission date') latest_activity = models.DateTimeField(default=timezone.now) + class Meta: + permissions = ( + ('can_take_editorial_actions', 'Can take editorial actions'), + ) + def __str__ (self): return self.title[:30] + ' by ' + self.author_list[:30] @@ -108,7 +113,8 @@ class Submission(models.Model): header = '<li><div class="flex-container">' header += '<div class="flex-whitebox0"><p><a href="/submission/{{ id }}" class="pubtitleli">{{ title }}</a></p>' header += ('<p>by {{ author_list }}</p><p> (submitted {{ submission_date }} to {{ to_journal }})' + - ' - latest activity: {{ latest_activity }}</p></div></div></li>') + ' - latest activity: {{ latest_activity }}</p>' + '</div></div></li>') context = Context({'id': self.id, 'title': self.title, 'author_list': self.author_list, 'submission_date': self.submission_date, 'to_journal': journals_submit_dict[self.submitted_to_journal], @@ -117,6 +123,22 @@ class Submission(models.Model): return template.render(context) + def header_as_li_for_authors (self): + # for search lists + header = '<li><div class="flex-container">' + header += '<div class="flex-whitebox0"><p><a href="/submission/{{ id }}" class="pubtitleli">{{ title }}</a></p>' + header += ('<p>by {{ author_list }}</p><p> (submitted {{ submission_date }} to {{ to_journal }})' + + ' - latest activity: {{ latest_activity }}</p>' + '<p>Status: {{ status }}</p></div></div></li>') + context = Context({'id': self.id, 'title': self.title, 'author_list': self.author_list, + 'submission_date': self.submission_date, + 'to_journal': journals_submit_dict[self.submitted_to_journal], + 'latest_activity': self.latest_activity.strftime('%Y-%m-%d %H:%M'), + 'status': submission_status_dict[self.status]}) + template = Template(header) + return template.render(context) + + def header_as_li_for_Fellows (self): # for submissions pool header = '<li><div class="flex-container">' @@ -257,17 +279,16 @@ class RefereeInvitation(models.Model): output = '<li>{{ first_name }} {{ last_name }}, invited {{ date_invited }}, ' if self.accepted is not None: if self.accepted: - output += 'task accepted ' + output += '<strong style="color: green">task accepted</strong> ' else: - output += 'task declined ' + output += '<strong style="color: red">task declined</strong> ' output += '{{ date_responded }}' context['date_responded'] = self.date_responded.strftime('%Y-%m-%d %H:%M') else: output += 'response pending' if self.fulfilled: output += '; Report has been delivered' - else: - output += '; (no Report yet)' + template = Template(output) return template.render(context) diff --git a/submissions/templates/submissions/assign_submission_ack.html b/submissions/templates/submissions/assign_submission_ack.html index 80965c8f3..313cdca16 100644 --- a/submissions/templates/submissions/assign_submission_ack.html +++ b/submissions/templates/submissions/assign_submission_ack.html @@ -5,7 +5,7 @@ {% block bodysup %} <section> - <h1>Your assignment request has successfully been sent.</h1> + <h1>Your assignment request has been sent successfully.</h1> <p>Return to the <a href="{% url 'submissions:pool' %}">Submissions Pool</a>.</p> </section> diff --git a/submissions/templates/submissions/assignment_failed_ack.html b/submissions/templates/submissions/assignment_failed_ack.html new file mode 100644 index 000000000..7b598e0b0 --- /dev/null +++ b/submissions/templates/submissions/assignment_failed_ack.html @@ -0,0 +1,14 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: assignment failed (ack){% endblock pagetitle %} + +{% block bodysup %} + +<section> + <h1>Assignment has failed for Submission</h1> + <p>{{ submission.title }} by {{ submission.author_list }}.</p> + <p>The Submission has been removed from the pool, and authors have been informed.</p> + <p>Return to the <a href="{% url 'submissions:pool' %}">Submissions Pool</a>.</p> +</section> + +{% endblock bodysup %} diff --git a/submissions/templates/submissions/sub_and_ref_procedure.html b/submissions/templates/submissions/sub_and_ref_procedure.html index 99e386384..5a91641ca 100644 --- a/submissions/templates/submissions/sub_and_ref_procedure.html +++ b/submissions/templates/submissions/sub_and_ref_procedure.html @@ -32,7 +32,9 @@ <ol> <li>Pre-screening: the Submission is internally forwarded to Fellows of the Editorial College, for them to consider becoming Editor-in-charge. If a Fellow expresses interest in the submission, he/she becomes Editor-in-charge and the process can move forward, otherwise the authors are informed that the paper shall not be considered further. This pre-screening process is rapid and occurs within at most 5 working days.</li> <li>Following successful pre-screening, a Submission Page is activated (this is similar to a Commentary Page, but with Reports also enabled). The Submission is immediately opened to Contributor Comments and Author Replies, all of which are vetted by an Editorial Fellow before eventually appearing online.</li> - <li>The Editor-in-charge starts a refereeing round (whose duration depends on the Journal, see below), inviting specific Contributors to provide an Invited Report. During a refereeing round, registered Contributors to SciPost can volunteer a Contributed Report, and authors can continuously provide Replies to Reports and Comments.</li> + <li>The Editor-in-charge starts a refereeing round (whose duration depends on the Journal, see below), inviting specific Contributors to provide an Invited Report. + During a refereeing round, registered Contributors to SciPost can volunteer a Contributed Report, and authors can continuously provide Replies to Reports and Comments. + The contents of Reports are publicly viewable, but the author of the Report can choose public anonymity (which is then known to Editors only).</li> <li>At the end of the refereeing round, Reports, Replies and Comments are assessed by the Editor-in-charge, who formulates an editorial recommendation. <ol> <li>If the editorial recommendation is for publication or rejection, it is forwarded to the Editorial College, which takes the binding editorial decision by consultation of the relevant specialty's Editorial Fellows. If the recommendation is to publish the paper as Select (targeting approximately the top 10% of articles considered), Editorial Fellows of all specialties get the chance to support or object to this promotion.</li> diff --git a/submissions/templates/submissions/submission_detail.html b/submissions/templates/submissions/submission_detail.html index 2583354af..2cfa21ddf 100644 --- a/submissions/templates/submissions/submission_detail.html +++ b/submissions/templates/submissions/submission_detail.html @@ -51,14 +51,19 @@ <h1>Actions</h1> </div> <ul> - {% if submission.open_for_reporting and perms.scipost.can_referee %} - <li><h3><a href="{% url 'submissions:submit_report' submission.id %}">Contribute a Report</a> - <div class="reportingDeadline">Deadline for reporting: {{ submission.reporting_deadline }}</div></h3></li> + {% if submission.open_for_reporting %} + {% if perms.scipost.can_referee %} + <li><h3><a href="{% url 'submissions:submit_report' submission.id %}">Contribute a Report</a></h3> + <div class="reportingDeadline">Deadline for reporting: {{ submission.reporting_deadline }}</div></li> + {% endif %} {% else %} <li>Reporting for this Submission is closed.</li> {% endif %} - {% if submission.open_for_commenting and perms.scipost.can_submit_comments %} + {% if submission.open_for_commenting %} + {% if perms.scipost.can_submit_comments %} <li><h3><a href="#contribute_comment">Contribute a Comment</a></h3></li> + {% endif %} + {% else %} <li>Commenting on this Submission is closed.</li> {% endif %} </ul> diff --git a/submissions/templates/submissions/submit_manuscript.html b/submissions/templates/submissions/submit_manuscript.html index f034ff78e..5886f1009 100644 --- a/submissions/templates/submissions/submit_manuscript.html +++ b/submissions/templates/submissions/submit_manuscript.html @@ -16,7 +16,7 @@ and the <a href="{% url 'journals:journals_terms_and_conditions' %}#author_obligations">author obligations</a>.</p> <p>Please prepare your manuscript according to the <a href="{% url 'submissions:author_guidelines' %}">author guidelines</a>.</p> - {% if False %} <!-- Temporary deactivate submissions --> + {% if True %} <!-- Temporary deactivate submissions --> {% if perms.scipost.can_submit_manuscript %} diff --git a/submissions/utils.py b/submissions/utils.py new file mode 100644 index 000000000..f09f2a1c3 --- /dev/null +++ b/submissions/utils.py @@ -0,0 +1,138 @@ +import datetime + +from django.core.mail import EmailMessage + +from scipost.models import title_dict + +from submissions.models import EditorialAssignment +from submissions.models import assignment_refusal_reasons_dict + + +class SubmissionUtils(object): + + @classmethod + def load(cls, dict): + for var_name in dict: + setattr(cls, var_name, dict[var_name]) + + + @classmethod + def deprecate_other_assignments(cls): + """ + Called when a Fellow has accepted or volunteered to become EIC. + """ + assignments_to_deprecate = (EditorialAssignment.objects + .filter(submission=cls.assignment.submission, accepted=None) + .exclude(to=cls.assignment.to)) + for atd in assignments_to_deprecate: + atd.deprecated = True + atd.save() + + @classmethod + def deprecate_all_assignments(cls): + """ + Called when the pre-screening has failed. + """ + assignments_to_deprecate = (EditorialAssignment.objects + .filter(submission=cls.submission, accepted=None)) + for atd in assignments_to_deprecate: + atd.deprecated = True + atd.save() + + + @classmethod + def send_EIC_appointment_email(cls): + email_text = ('Dear ' + title_dict[cls.assignment.to.title] + ' ' + + cls.assignment.to.user.last_name + + ', \n\nThank you for accepting to become Editor-in-charge of the SciPost Submission\n\n' + + cls.assignment.submission.title + ' by ' + cls.assignment.submission.author_list + '.' + + '\n\nYou can take your editorial actions from the editorial page ' + 'https://scipost.org/submission/editorial_page/' + str(cls.assignment.submission.id) + + ' (also accessible from your personal page https://scipost.org/personal_page under the Editorial Actions tab).' + '\n\nMany thanks in advance for your collaboration,' + + '\n\nThe SciPost Team.') + emailmessage = EmailMessage( + 'SciPost: assignment as EIC', email_text, + 'SciPost Editorial Admin <submissions@scipost.org>', + [cls.assignment.to.user.email, 'submissions@scipost.org'], + reply_to=['submissions@scipost.org']) + emailmessage.send(fail_silently=False) + + + @classmethod + def assignment_failed_email_authors(cls): + email_text = ('Dear ' + title_dict[cls.submission.submitted_by.title] + ' ' + + cls.submission.submitted_by.user.last_name + + ', \n\nYou recent Submission to SciPost,\n\n' + + cls.submission.title + ' by ' + cls.submission.author_list + + '\n\nhas unfortunately not passed the pre-screening stage. ' + 'We therefore regret to inform you that we will not ' + 'process your paper further towards publication, and that you ' + 'are now free to send your manuscript to an alternative journal.' + '\n\nWe nonetheless thank you very much for your contribution.' + '\n\nSincerely,' + + '\n\nThe SciPost Team.') + emailmessage = EmailMessage( + 'SciPost: pre-screening not passed', email_text, + 'SciPost Editorial Admin <submissions@scipost.org>', + [cls.submission.submitted_by.user.email, 'submissions@scipost.org'], + reply_to=['submissions@scipost.org']) + emailmessage.send(fail_silently=False) + + + @classmethod + def send_refereeing_invitation_email(cls): + email_text = ('Dear ' + title_dict[cls.invitation.referee.title] + ' ' + + cls.invitation.referee.user.last_name + + ', \n\nWe have received a Submission to SciPost ' + 'which, in view of your expertise, we would like to invite you to referee:\n\n' + + cls.invitation.submission.title + ' by ' + cls.invitation.submission.author_list + + ' (see https://scipost.org/submission/' + str(cls.invitation.submission.id) + ').' + '\n\nPlease visit https://scipost.org/submissions/accept_or_decline_ref_invitations ' + '(login required) as soon as possible (ideally within the next 2 days) ' + 'in order to accept or decline this invitation.' + '\n\nIf you accept, your report can be submitted by simply clicking on the "Contribute a Report" link at ' + 'https://scipost.org/submission/' + str(cls.invitation.submission.id) + ' before the reporting deadline ' + '(currently set at ' + datetime.datetime.strftime(cls.invitation.submission.reporting_deadline, "%Y-%m-%d") + + '; your report will be automatically recognized as an invited report). You might want to ' + 'make sure you are familiar with our refereeing code of conduct ' + 'https://scipost.org/journals/journals_terms_and_conditions and with the ' + 'refereeing procedure https://scipost.org/submissions/sub_and_ref_procedure.' + '\n\nWe would be extremely grateful for your contribution, ' + 'and thank you in advance for your consideration.' + '\n\nThe SciPost Team.') + emailmessage = EmailMessage( + 'SciPost: refereeing request', email_text, + 'SciPost Editorial Admin <submissions@scipost.org>', + [cls.invitation.referee.user.email, 'submissions@scipost.org'], + reply_to=['submissions@scipost.org']) + emailmessage.send(fail_silently=False) + + + @classmethod + def email_referee_response_to_EIC(cls): + email_text = ('Dear ' + title_dict[cls.invitation.submission.editor_in_charge.title] + ' ' + + cls.invitation.submission.editor_in_charge.user.last_name + ',' + '\n\nReferee ' + title_dict[cls.invitation.referee.title] + ' ' + + cls.invitation.referee.user.last_name + ' has ') + email_subject = 'SciPost: referee declines to review' + if cls.invitation.accepted: + email_text += 'accepted ' + email_subject = 'SciPost: referee accepts to review' + elif cls.invitation.accepted == False: + email_text += 'declined (due to reason: ' + assignment_refusal_reasons_dict[cls.invitation.refusal_reason] + ') ' + email_text += ('to referee Submission\n\n' + + cls.invitation.submission.title + ' by ' + cls.invitation.submission.author_list + '.') + if cls.invitation.accepted == False: + email_text += ('\n\nPlease invite another referee from the Submission\'s editorial page at ' + 'https://scipost.org/submissions/editorial_page/' + str(cls.invitation.submission.id) + '.') + email_text += ('\n\nMany thanks for your collaboration,' + + '\n\nThe SciPost Team.') + + emailmessage = EmailMessage( + email_subject, email_text, + 'SciPost Editorial Admin <submissions@scipost.org>', + [cls.invitation.submission.editor_in_charge.user.email, 'submissions@scipost.org'], + reply_to=['submissions@scipost.org']) + emailmessage.send(fail_silently=False) + diff --git a/submissions/views.py b/submissions/views.py index 479870617..52cd421c4 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -14,8 +14,12 @@ from django.http import HttpResponse, HttpResponseRedirect from django.views.decorators.csrf import csrf_protect from django.db.models import Avg +from guardian.decorators import permission_required_or_403 +from guardian.shortcuts import assign_perm + from .models import * from .forms import * +from .utils import SubmissionUtils from comments.models import Comment from scipost.models import Contributor, title_dict, RegistrationInvitation @@ -88,6 +92,7 @@ def prefill_using_identifier(request): return redirect(reverse('submissions:submit_manuscript')) +@login_required @permission_required('scipost.can_submit_manuscript', raise_exception=True) def submit_manuscript(request): if request.method == 'POST': @@ -118,6 +123,8 @@ def submit_manuscript(request): referees_flagged = form.cleaned_data['referees_flagged'], ) submission.save() + submission.authors.add(submitted_by) # must be author to be able to submit + submission.save() email_text = ('Dear ' + title_dict[submitted_by.title] + ' ' + submitted_by.user.last_name + ', \n\nWe have received your Submission to SciPost,\n\n' + @@ -135,9 +142,11 @@ def submit_manuscript(request): reply_to=['submissions@scipost.org']) emailmessage.send(fail_silently=False) return HttpResponseRedirect(reverse('submissions:submit_manuscript_ack')) + else: # form is invalid + pass else: - identifierform = SubmissionIdentifierForm() form = SubmissionForm() + identifierform = SubmissionIdentifierForm() return render(request, 'submissions/submit_manuscript.html', {'identifierform': identifierform, 'form': form}) @@ -258,7 +267,8 @@ def pool(request): 'assignments_to_consider': assignments_to_consider, 'form': form} return render(request, 'submissions/pool.html', context) - + +@login_required @permission_required('scipost.can_assign_submissions', raise_exception=True) def assign_submission(request, submission_id): submission_to_assign = get_object_or_404 (Submission, pk=submission_id) @@ -269,6 +279,7 @@ def assign_submission(request, submission_id): return render(request, 'submissions/assign_submission.html', context) +@login_required @permission_required('scipost.can_assign_submissions', raise_exception=True) def assign_submission_ack(request, submission_id): submission = Submission.objects.get(pk=submission_id) @@ -286,9 +297,10 @@ def assign_submission_ack(request, submission_id): 'for which we would like you to consider becoming Editor-in-charge:\n\n' + submission.title + ' by ' + submission.author_list + '.' + '\n\nPlease visit https://scipost.org/submissions/pool ' + - 'in order to accept or decline (on a first come, first serve basis: ' - 'this assignment request is automatically deprecated if another Fellow ' - 'takes charge of this Submission or if the latter fails pre-screening). ' + 'in order to accept or decline (it is important for you to inform us ' + 'even if you decline, since this affects the result of the pre-screening process). ' + 'Note that this assignment request is automatically deprecated if another Fellow ' + 'takes charge of this Submission or if pre-screening fails in the meantime.' '\n\nMany thanks in advance for your collaboration,' + '\n\nThe SciPost Team.') emailmessage = EmailMessage( @@ -302,6 +314,7 @@ def assign_submission_ack(request, submission_id): return render(request, 'submissions/assign_submission_ack.html', context) +@login_required @permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) def accept_or_decline_assignment_ack(request, assignment_id): contributor = Contributor.objects.get(user=request.user) @@ -332,28 +345,11 @@ def accept_or_decline_assignment_ack(request, assignment_id): deadline += datetime.timedelta(days=28) assignment.submission.reporting_deadline = deadline assignment.submission.open_for_commenting = True - # Deprecate all other assignments related to this submission - assignments_to_deprecate = EditorialAssignment.objects.filter( - submission=assignment.submission, accepted=None) - for atd in assignments_to_deprecate: - atd.deprecated = True - atd.save() - # Email EIC - email_text = ('Dear ' + title_dict[assignment.to.title] + ' ' + - assignment.to.user.last_name + - ', \n\nThank you for accepting to become Editor-in-charge of the SciPost Submission\n\n' + - submission.title + ' by ' + submission.author_list + '.' + - '\n\nYou can take your editorial actions from the editorial page ' - 'https://scipost.org/submission/editorial_page/' + str(submission.id) + - ' (also accessible from your personal page https://scipost.org/personal_page under the Editorial Actions tab).' - '\n\nMany thanks in advance for your collaboration,' + - '\n\nThe SciPost Team.') - emailmessage = EmailMessage( - 'SciPost: assignment as EIC', email_text, - 'SciPost Editorial Admin <submissions@scipost.org>', - [assignment.to.user.email, 'submissions@scipost.org'], - reply_to=['submissions@scipost.org']) - emailmessage.send(fail_silently=False) + + SubmissionUtils.load({'assignment': assignment}) + SubmissionUtils.deprecate_other_assignments() + assign_perm('can_take_editorial_actions', contributor.user, assignment.submission) + SubmissionUtils.send_EIC_appointment_email() else: assignment.accepted = False assignment.refusal_reason = form.cleaned_data['refusal_reason'] @@ -365,6 +361,7 @@ def accept_or_decline_assignment_ack(request, assignment_id): return render(request, 'submissions/accept_or_decline_assignment_ack.html', context) +@login_required @permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) def volunteer_as_EIC(request, submission_id): """ @@ -399,62 +396,37 @@ def volunteer_as_EIC(request, submission_id): submission.open_for_commenting = True assignment.save() submission.save() - # Deprecate all other assignments related to this submission - assignments_to_deprecate = EditorialAssignment.objects.filter( - submission=assignment.submission, accepted=None) - for atd in assignments_to_deprecate: - atd.deprecated = True - atd.save() - # Email EIC - email_text = ('Dear ' + title_dict[assignment.to.title] + ' ' + - assignment.to.user.last_name + - ', \n\nThank you for accepting to become Editor-in-charge of the SciPost Submission\n\n' + - submission.title + ' by ' + submission.author_list + '.' + - '\n\nYou can take your editorial actions from the editorial page ' - 'https://scipost.org/submission/editorial_page/' + submission.id + - ' (also accessible from your personal page https://scipost.org/personal_page under the Editorial Actions tab).' - '\n\nMany thanks in advance for your collaboration,' + - '\n\nThe SciPost Team.') - emailmessage = EmailMessage( - 'SciPost: assignment as EIC', email_text, - 'SciPost Editorial Admin <submissions@scipost.org>', - [assignment.to.user.email, 'submissions@scipost.org'], - reply_to=['submissions@scipost.org']) - emailmessage.send(fail_silently=False) + + SubmissionUtils.load({'assignment': assignment}) + SubmissionUtils.deprecate_other_assignments() + assign_perm('can_take_editorial_actions', contributor.user, submission) + SubmissionUtils.send_EIC_appointment_email() context = {'assignment': assignment} return render(request, 'submissions/accept_or_decline_assignment_ack.html', context) +@login_required @permission_required('scipost.can_assign_submissions', raise_exception=True) def assignment_failed(request, submission_id): """ No Editorial Fellow has accepted or volunteered to become Editor-in-charge. The submission is rejected. - This method is called from pool.html. + This method is called from pool.html by an Editorial Administrator. """ submission = get_object_or_404(Submission, pk=submission_id) submission.status = 'assignment_failed' submission.save() - # Email author - email_text = ('Dear ' + title_dict[submission.submitted_by.title] + ' ' + - submission.submitted_by.user.last_name + - ', \n\nYou recent Submission to SciPost,\n\n' + - submission.title + ' by ' + submission.author_list + - '\n\nhas unfortunately not passed the pre-screening stage. ' - 'We therefore regret to inform you that your paper will not be ' - 'processed further towards publication.' - '\n\nMany thanks in advance for your collaboration,' + - '\n\nThe SciPost Team.') - emailmessage = EmailMessage( - 'SciPost: pre-screening not passed', email_text, - 'SciPost Editorial Admin <submissions@scipost.org>', - [submission.submitted_by.user.email, 'submissions@scipost.org'], - reply_to=['submissions@scipost.org']) - emailmessage.send(fail_silently=False) + SubmissionUtils.load({'submission': submission}) + SubmissionUtils.deprecate_all_assignments() + SubmissionUtils.assignment_failed_email_authors() + context = {'submission': submission} + return render(request, 'submissions/assignment_failed_ack.html', context) + -@permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) +@login_required +@permission_required_or_403('can_take_editorial_actions', (Submission, 'id', 'submission_id')) def editorial_page(request, submission_id): submission = get_object_or_404(Submission, pk=submission_id) ref_invitations = RefereeInvitation.objects.filter(submission=submission) @@ -464,7 +436,8 @@ def editorial_page(request, submission_id): return render(request, 'submissions/editorial_page.html', context) -@permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) +@login_required +@permission_required_or_403('can_take_editorial_actions', (Submission, 'id', 'submission_id')) def select_referee(request, submission_id): submission = get_object_or_404(Submission, pk=submission_id) if request.method == 'POST': @@ -480,7 +453,8 @@ def select_referee(request, submission_id): return render(request, 'submissions/select_referee.html', context) -@permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) +@login_required +@permission_required_or_403('can_take_editorial_actions', (Submission, 'id', 'submission_id')) def recruit_referee(request, submission_id): """ If the Editor-in-charge does not find the desired referee among Contributors, @@ -525,7 +499,8 @@ def recruit_referee(request, submission_id): return redirect(reverse('submissions:editorial_page', kwargs={'submission_id': submission_id})) -@permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) +@login_required +@permission_required_or_403('can_take_editorial_actions', (Submission, 'id', 'submission_id')) def send_refereeing_invitation(request, submission_id, contributor_id): """ This method is called by the EIC from the submission's editorial_page, @@ -543,29 +518,8 @@ def send_refereeing_invitation(request, submission_id, contributor_id): date_invited=timezone.now(), invited_by=request.user.contributor) invitation.save() - # Email Contributor - email_text = ('Dear ' + title_dict[contributor.title] + ' ' + - contributor.user.last_name + - ', \n\nWe have received a Submission to SciPost ' - 'which, in view of your expertise, we would like to invite you to referee:\n\n' + - submission.title + ' by ' + submission.author_list + - ' (see https://scipost.org/submission/' + submission_id + ').' - '\n\nPlease visit https://scipost.org/submissions/accept_or_decline_ref_invitations ' - '(login required) as soon as possible (ideally within the next 2 days) ' - 'in order to accept or decline this invitation.' - '\n\nIf you accept, your report can be submitted by simply clicking on the "Contribute a Report" link at ' - 'https://scipost.org/submission/' + submission_id + ' before the reporting deadline ' - '(currently set at ' + datetime.datetime.strftime(submission.reporting_deadline, "%Y-%m-%d") + - '; your report will be automatically recognized as an invited report).' - '\n\nWe would be extremely grateful for your contribution, ' - 'and thank you in advance for your consideration.' - '\n\nThe SciPost Team.') - emailmessage = EmailMessage( - 'SciPost: refereeing request', email_text, - 'SciPost Editorial Admin <submissions@scipost.org>', - [contributor.user.email, 'submissions@scipost.org'], - reply_to=['submissions@scipost.org']) - emailmessage.send(fail_silently=False) + SubmissionUtils.load({'invitation': invitation}) + SubmissionUtils.send_refereeing_invitation_email() return redirect(reverse('submissions:editorial_page', kwargs={'submission_id': submission_id})) @@ -579,6 +533,7 @@ def accept_or_decline_ref_invitations(request): return render(request, 'submissions/accept_or_decline_ref_invitations.html', context) +@login_required @permission_required('scipost.can_referee', raise_exception=True) def accept_or_decline_ref_invitation_ack(request, invitation_id): contributor = Contributor.objects.get(user=request.user) @@ -593,12 +548,15 @@ def accept_or_decline_ref_invitation_ack(request, invitation_id): invitation.accepted = False invitation.refusal_reason = form.cleaned_data['refusal_reason'] invitation.save() - + SubmissionUtils.load({'invitation': invitation}) + SubmissionUtils.email_referee_response_to_EIC() + context = {'invitation': invitation} return render(request, 'submissions/accept_or_decline_ref_invitation_ack.html', context) -@permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) +@login_required +@permission_required_or_403('can_take_editorial_actions', (Submission, 'id', 'submission_id')) def extend_refereeing_deadline(request, submission_id, days): submission = get_object_or_404 (Submission, pk=submission_id) submission.reporting_deadline += datetime.timedelta(days=int(days)) @@ -606,7 +564,8 @@ def extend_refereeing_deadline(request, submission_id, days): return redirect(reverse('submissions:editorial_page', kwargs={'submission_id': submission_id})) -@permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) +@login_required +@permission_required_or_403('can_take_editorial_actions', (Submission, 'id', 'submission_id')) def close_refereeing_round(request, submission_id): submission = get_object_or_404 (Submission, pk=submission_id) submission.open_for_reporting = False @@ -645,7 +604,8 @@ def communication(request, submission_id, comtype, referee_id=None): return render(request, 'submissions/communication.html', context) -@permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) +@login_required +@permission_required_or_403('can_take_editorial_actions', (Submission, 'id', 'submission_id')) def eic_recommendation(request, submission_id): submission = get_object_or_404 (Submission, pk=submission_id) if request.method == 'POST': -- GitLab