diff --git a/colleges/permissions.py b/colleges/permissions.py index edfd198daba4bae9d7879cd27f371cb384fb8ee6..1130c79fce04a6cd65a74abb9d782f8b62922f68 100644 --- a/colleges/permissions.py +++ b/colleges/permissions.py @@ -7,9 +7,7 @@ from django.core.exceptions import PermissionDenied def fellowship_required(): - """ - Require user to have any Fellowship or Administrational permissions. - """ + """Require user to have any Fellowship or Administrational permissions.""" def test(u): if u.is_authenticated(): if hasattr(u, 'contributor') and u.contributor.fellowships.exists(): @@ -20,9 +18,7 @@ def fellowship_required(): def fellowship_or_admin_required(): - """ - Require user to have any Fellowship or Administrational permissions. - """ + """Require user to have any Fellowship or Administrational permissions.""" def test(u): if u.is_authenticated(): if hasattr(u, 'contributor') and u.contributor.fellowships.exists(): diff --git a/journals/forms.py b/journals/forms.py index 3bda2b4d1a31926f423b4cbdc89a037547cf1128..ee50f2522245e2e98af619cb787c90047598e9f3 100644 --- a/journals/forms.py +++ b/journals/forms.py @@ -31,6 +31,7 @@ from production.models import ProductionEvent from production.signals import notify_stream_status_change from scipost.forms import RequestFormMixin from scipost.services import DOICaller +from submissions.constants import STATUS_PUBLISHED from submissions.models import Submission @@ -624,7 +625,7 @@ class PublicationPublishForm(RequestFormMixin, forms.ModelForm): # Mark the submission as having been published: submission = self.instance.accepted_submission submission.published_as = self.instance - submission.status = 'published' + submission.status = STATUS_PUBLISHED submission.save() # Add SubmissionEvents diff --git a/submissions/constants.py b/submissions/constants.py index 61ec2e59e4478fd97c69d06b92b3ddfa44d6ce12..80cfdc7f191bc81cb63682acebee427410b00045 100644 --- a/submissions/constants.py +++ b/submissions/constants.py @@ -181,7 +181,7 @@ DECISION_FIXED, DEPRECATED = 'decision_fixed', 'deprecated' EIC_REC_STATUSES = ( (VOTING_IN_PREP, 'Voting in preparation'), (PUT_TO_VOTING, 'Undergoing voting at the Editorial College'), - (VOTE_COMPLETED, 'Editorial College voting rounded up'), + (VOTE_COMPLETED, 'Editorial College voting rounded up'), # Seemlingly dead? (DECISION_FIXED, 'Editorial Recommendation fixed'), (DEPRECATED, 'Editorial Recommendation deprecated'), ) diff --git a/submissions/forms.py b/submissions/forms.py index 3032ccba35cb1e4d76faf19f23e9e2d3c5afd9e6..a1bc8675ecd513bfdf58ef1d0af7a66d38463255 100644 --- a/submissions/forms.py +++ b/submissions/forms.py @@ -510,12 +510,10 @@ class ConsiderAssignmentForm(forms.Form): class RefereeSelectForm(forms.Form): - last_name = forms.CharField() + """Pre-fill form to get the last name of the requested referee.""" - def __init__(self, *args, **kwargs): - super(RefereeSelectForm, self).__init__(*args, **kwargs) - self.fields['last_name'].widget.attrs.update( - {'size': 20, 'placeholder': 'Search in contributors database'}) + last_name = forms.CharField(widget=forms.TextInput({ + 'placeholder': 'Search in contributors database'})) class RefereeRecruitmentForm(forms.ModelForm): @@ -1091,7 +1089,7 @@ class FixCollegeDecisionForm(forms.ModelForm): """Fix decision of EICRecommendation.""" EICRecommendation.objects.filter(id=recommendation.id).update(status=DECISION_FIXED) submission = recommendation.submission - if recommendation in [REPORT_PUBLISH_1, REPORT_PUBLISH_2, REPORT_PUBLISH_3]: + if recommendation.recommendation in [REPORT_PUBLISH_1, REPORT_PUBLISH_2, REPORT_PUBLISH_3]: # Publish as Tier I, II or III Submission.objects.filter(id=submission.id).update( visible_public=True, status=STATUS_ACCEPTED, acceptance_date=datetime.date.today(), @@ -1124,7 +1122,7 @@ class FixCollegeDecisionForm(forms.ModelForm): EICRecommendation.objects.filter(id=recommendation.id).update( status=DEPRECATED, active=False) recommendation.submission.add_event_for_eic( - 'The Editorial Recommendation (v{version}) has been deprecated: {decision}.'.format( + 'The Editorial Recommendation (version {version}) has been deprecated: {decision}.'.format( version=recommendation.version, decision=recommendation.get_recommendation_display())) diff --git a/submissions/plagiarism.py b/submissions/plagiarism.py index 96aa68e46eafc76c525b6c0bfb00451ffe93d7da..f09bab2c44e210a0af433c59f85e00d4601ad175 100644 --- a/submissions/plagiarism.py +++ b/submissions/plagiarism.py @@ -14,14 +14,18 @@ class iThenticate: self.client = self.get_client() def get_client(self): - client = iThenticateAPI.API.Client(settings.ITHENTICATE_USERNAME, - settings.ITHENTICATE_PASSWORD) + client = iThenticateAPI.API.Client( + settings.ITHENTICATE_USERNAME, settings.ITHENTICATE_PASSWORD) if client.login(): return client self.add_error(None, "Failed to login to iThenticate.") return None def determine_folder_group(self, group_re): + """Return the folder group id to which the system should upload a new document to. + + Generates a new folder group if needed. + """ groups = self.client.groups.all() if groups['status'] != 200: raise InvalidDocumentError("Uploading failed. iThenticate didn't return" @@ -41,8 +45,7 @@ class iThenticate: return response['data'][0]['id'] def determine_folder_id(self, submission): - """ - Return the folder id to which the system should upload a new document to. + """Return the folder id to which the system should upload a new document to. Generates a new folder and id if needed. """ @@ -72,8 +75,7 @@ class iThenticate: return data['data'][0]['id'] def upload_submission(self, document, submission): - """ - Upload a document related to a submission + """Upload a document related to a submission. :document: The document to upload :submission: submission which should be uploaded @@ -96,9 +98,7 @@ class iThenticate: return None def get_url(self, document_id): - """ - Return report url for given document - """ + """Return report url for given document.""" response = self.client.reports.get(document_id) if response['status'] == 200: diff --git a/submissions/templates/submissions/submission_detail.html b/submissions/templates/submissions/submission_detail.html index 1c27d127c1bfbeb5d0239821f8e7e4a1766f4453..d9e53d58fc7d0bc5940f7321faf5fc492a950424 100644 --- a/submissions/templates/submissions/submission_detail.html +++ b/submissions/templates/submissions/submission_detail.html @@ -74,11 +74,13 @@ </div> -{% if is_author or user|is_in_group:'Editorial College' or user|is_in_group:'Editorial Administrators' %} +{% if can_read_editorial_information or is_author %} {% for recommendation in recommendations %} - {% if user|is_in_group:'Editorial College' or user|is_in_group:'Editorial Administrators' or recommendation|is_viewable_by_authors %} - {% include 'partials/submissions/recommendation_author_content.html' with recommendation=recommendation %} - {% endif %} + {% with is_author and recommendation|is_viewable_by_authors as author_viewable %} + {% if author_viewable or can_read_editorial_information %} + {% include 'partials/submissions/recommendation_author_content.html' with recommendation=recommendation %} + {% endif %} + {% endwith %} {% endfor %} <div class="mb-4"> @@ -89,8 +91,8 @@ </div> {% endif %} -{% if is_author or user|is_in_group:'Editorial Administrators' %} - {% if submission.production_stream.proofs.for_authors.exists %} +{% if is_author or perms.scipost.can_oversee_refereeing %} + <div class="mb-4" id="proofsslist"> <h3>Proofs</h3> <ul> @@ -115,7 +117,7 @@ {% endif %} {% endif %} -{% if user.is_authenticated and user|is_in_group:'Registered Contributors' %} +{% if perms.scipost.can_submit_comments %} <div class="row"> <div class="col-12"> <h2 class="highlight">Actions</h2> @@ -218,10 +220,12 @@ {# This is an apparent redundant logic block; however, it makes sure the "login to ..." links wouldn't be shown twice! #} -{% if not user.is_authenticated and submission.comments.vetted.exists %} - {% include 'comments/new_comment.html' with object_id=submission.id type_of_object='submission' open_for_commenting=submission.open_for_commenting user_is_referee=submission|user_is_referee:request.user %} -{% elif user.is_authenticated %} - {% include 'comments/new_comment.html' with object_id=submission.id type_of_object='submission' open_for_commenting=submission.open_for_commenting user_is_referee=submission|user_is_referee:request.user %} +{% if form=comment_form %} + {% if not user.is_authenticated and submission.comments.vetted.exists %} + {% include 'comments/new_comment.html' with form=comment_form object_id=submission.id type_of_object='submission' open_for_commenting=submission.open_for_commenting user_is_referee=submission|user_is_referee:request.user %} + {% elif user.is_authenticated %} + {% include 'comments/new_comment.html' with form=comment_form object_id=submission.id type_of_object='submission' open_for_commenting=submission.open_for_commenting user_is_referee=submission|user_is_referee:request.user %} + {% endif %} {% endif %} {% endblock content %} diff --git a/submissions/views.py b/submissions/views.py index a1c69ff7d62d52ba21f726bd848769200831e884..f70391cd51302e97ee151829f196226796ef47b5 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -192,7 +192,10 @@ def submission_detail_wo_vn_nr(request, arxiv_identifier_wo_vn_nr): def submission_detail(request, arxiv_identifier_w_vn_nr): """Public detail page of Submission.""" submission = get_object_or_404(Submission, arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) - context = {} + context = { + 'can_read_editorial_information': False + } + if hasattr(request.user, 'contributor'): # Check if Contributor is author of the Submission is_author = submission.authors.filter(user=request.user).exists() @@ -218,7 +221,8 @@ def submission_detail(request, arxiv_identifier_w_vn_nr): contributor__user=request.user).exists(): raise Http404 - form = CommentForm() + if submission.open_for_commenting and request.user.perms.has_perms('scipost.can_submit_comments'): + context['comment_form'] = CommentForm() invited_reports = submission.reports.accepted().invited() contributed_reports = submission.reports.accepted().contributed() @@ -228,6 +232,16 @@ def submission_detail(request, arxiv_identifier_w_vn_nr): # User is referee for the Submission if request.user.is_authenticated: invitations = submission.referee_invitations.filter(referee__user=request.user) + + # User may read eg. Editorial Recommendations if he/she is in the Pool. + context['can_read_editorial_information'] = submission.fellows.filter( + contributor__user=request.user).exists() + + # User may also read eg. Editorial Recommendations if he/she is editorial administrator. + if not context['can_read_editorial_information']: + context['can_read_editorial_information'] = request.user.has_perm( + 'can_oversee_refereeing') + else: invitations = None if invitations: @@ -244,7 +258,6 @@ def submission_detail(request, arxiv_identifier_w_vn_nr): 'contributed_reports': contributed_reports, 'unfinished_report_for_user': unfinished_report_for_user, 'author_replies': author_replies, - 'form': form, 'is_author': is_author, 'is_author_unchecked': is_author_unchecked, 'invitations': invitations, @@ -284,8 +297,8 @@ def reports_accepted_list(request): This gives an overview of Report that need a PDF update/compilation. """ - reports = (Report.objects.accepted() - .order_by('pdf_report', 'submission').prefetch_related('submission')) + reports = Report.objects.accepted().order_by( + 'pdf_report', 'submission').prefetch_related('submission') if request.GET.get('submission'): reports = reports.filter(submission__arxiv_identifier_w_vn_nr=request.GET.get('submission')) @@ -828,7 +841,8 @@ def recruit_referee(request, arxiv_identifier_w_vn_nr): @fellowship_or_admin_required() @transaction.atomic def send_refereeing_invitation(request, arxiv_identifier_w_vn_nr, contributor_id): - """ + """Send RefereeInvitation to a registered Contributor. + This method is called by the EIC from the submission's editorial_page, in the case where the referee is an identified Contributor. For a referee who isn't a Contributor yet, the method recruit_referee above @@ -874,7 +888,8 @@ def send_refereeing_invitation(request, arxiv_identifier_w_vn_nr, contributor_id @login_required @fellowship_or_admin_required() def ref_invitation_reminder(request, arxiv_identifier_w_vn_nr, invitation_id): - """ + """Send reminder email to pending RefereeInvitations. + This method is used by the Editor-in-charge from the editorial_page when a referee has been invited but hasn't answered yet. It can be used for registered as well as unregistered referees. @@ -900,7 +915,8 @@ def ref_invitation_reminder(request, arxiv_identifier_w_vn_nr, invitation_id): @login_required @permission_required('scipost.can_referee', raise_exception=True) def accept_or_decline_ref_invitations(request, invitation_id=None): - """ + """Decide on RefereeInvitation. + RefereeInvitations need to be either accepted or declined by the invited user using this view. The decision will be taken one invitation at a time. """ @@ -959,6 +975,7 @@ def accept_or_decline_ref_invitations(request, invitation_id=None): def decline_ref_invitation(request, invitation_key): + """Decline a RefereeInvitation.""" invitation = get_object_or_404(RefereeInvitation, invitation_key=invitation_key, accepted__isnull=True) @@ -991,12 +1008,12 @@ def decline_ref_invitation(request, invitation_key): @login_required def cancel_ref_invitation(request, arxiv_identifier_w_vn_nr, invitation_id): - """ - This method is used by the Editor-in-charge from the editorial_page - to remove a referee for the list of invited ones. - It can be used for registered as well as unregistered referees. + """Cancel a RefereeInvitation. - Accessible for: Editor-in-charge and Editorial Administration + This method is used by the Editor-in-charge from the editorial_page to remove a referee + from the list of invited ones. It can be used for registered as well as unregistered referees. + + Accessible for: Editor-in-charge and Editorial Administration. """ try: submissions = Submission.objects.filter_for_eic(request.user) @@ -1069,14 +1086,13 @@ def set_refereeing_deadline(request, arxiv_identifier_w_vn_nr): @login_required def close_refereeing_round(request, arxiv_identifier_w_vn_nr): - """ - Called by the Editor-in-charge when a satisfactory number of - reports have been gathered. - Automatically emails the authors to ask them if they want to - round off any replies to reports or comments before the - editorial recommendation is formulated. + """Close Submission for refereeing. - Accessible for: Editor-in-charge and Editorial Administration + Called by the Editor-in-charge when a satisfactory number of reports have been gathered. + Automatically emails the authors to ask them if they want to round off any replies to + reports or comments before the editorial recommendation is formulated. + + Accessible for: Editor-in-charge and Editorial Administration. """ submission = get_object_or_404(Submission.objects.filter_for_eic(request.user), arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) @@ -1104,9 +1120,9 @@ def refereeing_overview(request): @login_required def communication(request, arxiv_identifier_w_vn_nr, comtype, referee_id=None): - """ - Communication between editor-in-charge, author or referee - occurring during the submission refereeing. + """Send refereeing related communication. + + Communication may be between two of: editor-in-charge, author and referee. """ referee = None if comtype in ['EtoA', 'EtoR', 'EtoS']: @@ -1177,8 +1193,7 @@ def communication(request, arxiv_identifier_w_vn_nr, comtype, referee_id=None): @fellowship_or_admin_required() @transaction.atomic def eic_recommendation(request, arxiv_identifier_w_vn_nr): - """ - Write EIC Recommendation. + """Write EIC Recommendation. Accessible for: Editor-in-charge and Editorial Administration """ @@ -1350,9 +1365,7 @@ def submit_report(request, arxiv_identifier_w_vn_nr): @login_required @fellowship_or_admin_required() def vet_submitted_reports_list(request): - """ - Reports with status `unvetted` will be shown (oldest first). - """ + """List Reports with status `unvetted`.""" submissions = get_list_or_404(Submission.objects.filter_for_eic(request.user)) reports_to_vet = Report.objects.filter( submission__in=submissions).awaiting_vetting().order_by('date_submitted') @@ -1364,16 +1377,21 @@ def vet_submitted_reports_list(request): @fellowship_or_admin_required() @transaction.atomic def vet_submitted_report(request, report_id): - """ - Report with status `unvetted` will be shown. A user may only vet reports of submissions - he/she is EIC of or if he/she is SciPost Admin or Vetting Editor. + """List Reports with status `unvetted` for vetting purposes. + + A user may only vet reports of submissions he/she is EIC of or if he/she is + SciPost Administratoror Vetting Editor. After vetting an email is sent to the report author, bcc EIC. If report has not been refused, the submission author is also mailed. """ - submissions = Submission.objects.filter_for_eic(request.user) - report = get_object_or_404(Report.objects.filter( - submission__in=submissions).awaiting_vetting(), id=report_id) + if request.user.has_perms('scipost.can_vet_submitted_reports'): + # Vetting Editors may vote on everything. + report = get_object_or_404(Report.objects.awaiting_vetting(), id=report_id) + else: + submissions = Submission.objects.filter_for_eic(request.user) + report = get_object_or_404(Report.objects.filter( + submission__in=submissions).awaiting_vetting(), id=report_id) form = VetReportForm(request.POST or None, initial={'report': report}) if form.is_valid(): @@ -1394,9 +1412,9 @@ def vet_submitted_report(request, report_id): # Add SubmissionEvent to tell the author about the new report report.submission.add_event_for_author('A new Report has been submitted.') - message = 'Submitted Report vetted for <a href="%s">%s</a>.' % ( - report.submission.get_absolute_url(), - report.submission.arxiv_identifier_w_vn_nr) + message = 'Submitted Report vetted for <a href="{url}">{arxiv}</a>.'.format( + url=report.submission.get_absolute_url(), + arxiv=report.submission.arxiv_identifier_w_vn_nr) messages.success(request, message) if report.submission.editor_in_charge == request.user.contributor: @@ -1496,11 +1514,11 @@ def vote_on_rec(request, rec_id): @permission_required('scipost.can_prepare_recommendations_for_voting', raise_exception=True) def remind_Fellows_to_vote(request): - """ - This method sends an email to all Fellow with pending voting duties. + """Send an email to all Fellow with pending voting duties. + It must be called by and Editorial Administrator. - TODO: This reminder function doesn't filter per submission?! + Possible TODO: This reminder function doesn't filter per submission?! """ submissions = Submission.objects.pool_editable(request.user) recommendations = EICRecommendation.objects.active().filter( @@ -1532,6 +1550,7 @@ def remind_Fellows_to_vote(request): class PreScreeningView(SubmissionAdminViewMixin, UpdateView): """Do pre-screening of new incoming Submissions.""" + permission_required = 'scipost.can_run_pre_screening' queryset = Submission.objects.prescreening() template_name = 'submissions/admin/submission_prescreening.html' form_class = SubmissionPrescreeningForm