diff --git a/SciPost_v1/settings.py b/SciPost_v1/settings.py index f6b74aee36961d175d89993b52a172793a4e7f90..663a264900986b2295e2280dc8e822bc0a184f56 100644 --- a/SciPost_v1/settings.py +++ b/SciPost_v1/settings.py @@ -39,6 +39,8 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SESSION_COOKIE_SECURE = host_settings["SESSION_COOKIE_SECURE"] CSRF_COOKIE_SECURE = host_settings["CSRF_COOKIE_SECURE"] +LOGIN_URL = '/login/' + # Session expire at browser close SESSION_EXPIRE_AT_BROWSER_CLOSE = True diff --git a/comments/models.py b/comments/models.py index 658be03af99a8501071e00b9fce5191d5e21726f..b85c7d9399da9b5b4226bac5348f00c987bc95f2 100644 --- a/comments/models.py +++ b/comments/models.py @@ -70,7 +70,8 @@ class Comment(models.Model): in_disagreement = models.ManyToManyField(Contributor, related_name='in_disagreement') def __str__ (self): - return self.comment_text + return ('by ' + self.author.user.first_name + ' ' + self.author.user.last_name + + ' on ' + self.date_submitted.strftime('%Y-%m-%d') + ', ' + self.comment_text[:30]) def update_opinions(self, contributor_id, opinion): diff --git a/comments/templates/comments/reply_to_report.html b/comments/templates/comments/reply_to_report.html index c18d10ce965eb9b09e0dff74241eb53dad1a972c..633d6a36c475a1792529413e595c32a5d8525a6e 100644 --- a/comments/templates/comments/reply_to_report.html +++ b/comments/templates/comments/reply_to_report.html @@ -1,6 +1,6 @@ {% extends 'scipost/base.html' %} -{% block pagetitle %}: reply to comment{% endblock pagetitle %} +{% block pagetitle %}: reply to report{% endblock pagetitle %} {% block bodysup %} @@ -8,44 +8,35 @@ <section> <hr class="hr12"> <h1>SciPost Reply to Report Page</h1> + + {% if not is_author %} + <h3>(you are not identified as an author of this Submission; if you are, you can claim authorship on your Personal Page)</h3> + {% else %} + <hr class="hr12"> <h1>The Submission concerned:</h1> - {{ submission.header_as_table|safe }} + {{ report.submission.header_as_table|safe }} <h3>Abstract:</h3> - <p>{{ submission.abstract }}</p> + <p>{{ report.submission.abstract }}</p> <br> <hr class="hr6"> <h2>The Report you wish to reply to:</h2> - <div class="flex-container"> - <div class="flex-commentbox"> - {{ comment.print_identifier|safe }} - <div class="opinionsDisplay"> - {{ comment.opinions_as_ul|safe }} - </div> - </div> - </div> - <div class="row"> - <div class="col-1"></div> - <div class="col-10"> - <p>{{ comment.comment_text|linebreaks }}</p> - </div> - </div> + {{ report.print_identifier }} + {{ report.print_contents }} + <hr class="hr6"> - <h1>Your Reply to this Comment:</h1> - {% if is_author %} - <h3>(you are identified as an author, your reply will appear as an author reply)</h3> - {% else %} - <h3>(you are not identified as an author of this publication; if you are, you can claim authorship on your Personal Page)</h3> - {% endif %} - <form action="{% url 'comments:reply_to_comment' comment.id %}" method="post"> + <h1>Your Reply to this Report:</h1> + + <form action="{% url 'comments:reply_to_report' report_id=report.id %}" method="post"> {% csrf_token %} {% load crispy_forms_tags %} {% crispy form %} </form> + {% endif %} </section> {% endif %} diff --git a/comments/urls.py b/comments/urls.py index f605f787c877263b855c4b33939783c907faaa9e..90c8f1dd96f5bc60b2d61b138caa3b8b2df4880e 100644 --- a/comments/urls.py +++ b/comments/urls.py @@ -7,6 +7,7 @@ urlpatterns = [ # Comments url(r'^comment_submission_ack$', TemplateView.as_view(template_name='comments/comment_submission_ack.html'), name='comment_submission_ack'), url(r'^reply_to_comment/(?P<comment_id>[0-9]+)$', views.reply_to_comment, name='reply_to_comment'), + url(r'^reply_to_report/(?P<report_id>[0-9]+)$', views.reply_to_report, name='reply_to_report'), url(r'^vet_submitted_comments$', views.vet_submitted_comments, name='vet_submitted_comments'), url(r'^vet_submitted_comment_ack/(?P<comment_id>[0-9]+)$', views.vet_submitted_comment_ack, name='vet_submitted_comment_ack'), url(r'^express_opinion/(?P<comment_id>[0-9]+)$', views.express_opinion, name='express_opinion'), diff --git a/comments/views.py b/comments/views.py index 4b3771efc4d0486fe54929673c73117308f75c98..d51e192233ce2885bff755e5a3736423efd6dac9 100644 --- a/comments/views.py +++ b/comments/views.py @@ -131,6 +131,43 @@ def reply_to_comment(request, comment_id): return render(request, 'comments/reply_to_comment.html', context) +def reply_to_report(request, report_id): + report = get_object_or_404(Report, pk=report_id) + # Verify if this is from an author: + is_author = False + if report.submission.authors.filter(id=request.user.contributor.id).exists(): + is_author = True + + if is_author and request.method == 'POST': + form = CommentForm(request.POST) + if form.is_valid(): + newcomment = Comment ( + submission = report.submission, + is_author_reply = is_author, + in_reply_to_report = report, + author = Contributor.objects.get(user=request.user), + is_rem = form.cleaned_data['is_rem'], + is_que = form.cleaned_data['is_que'], + is_ans = form.cleaned_data['is_ans'], + is_obj = form.cleaned_data['is_obj'], + is_rep = form.cleaned_data['is_rep'], + is_cor = form.cleaned_data['is_cor'], + is_val = form.cleaned_data['is_val'], + is_lit = form.cleaned_data['is_lit'], + is_sug = form.cleaned_data['is_sug'], + comment_text = form.cleaned_data['comment_text'], + remarks_for_editors = form.cleaned_data['remarks_for_editors'], + date_submitted = timezone.now(), + ) + newcomment.save() + return HttpResponseRedirect(reverse('comments:comment_submission_ack')) + else: + form = CommentForm() + + context = {'report': report, 'is_author': is_author, 'form': form} + return render(request, 'comments/reply_to_report.html', context) + + def express_opinion(request, comment_id, opinion): # A contributor has expressed an opinion on a comment diff --git a/journals/models.py b/journals/models.py index d7c4ca685c0ff51a8ac6196e1c155c6b05e056bd..530c23eab7c0502acddb71fcffa07dc232490ffe 100644 --- a/journals/models.py +++ b/journals/models.py @@ -41,15 +41,3 @@ SCIPOST_JOURNALS_SPECIALIZATIONS = ( ) journals_spec_dict = dict(SCIPOST_JOURNALS_SPECIALIZATIONS) -PHYSICS_SUBJECTS = ( - ('A', 'Atomic, Molecular and Optical Physics'), - ('B', 'Biophysics'), - ('C', 'Condensed Matter Physics'), - ('F', 'Fluid Dynamics'), - ('G', 'Gravitation, Cosmology and Astroparticle Physics'), - ('H', 'High-Energy Physics'), - ('M', 'Mathematical Physics'), - ('N', 'Nuclear Physics'), - ('Q', 'Quantum Statistical Mechanics'), - ('S', 'Statistical and Soft Matter Physics'), - ) diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py index 1dcd0754265a3983ea98e334c0612e7e9ec63f54..d5995205496691a172fb098897f96b1ee1efa5fe 100644 --- a/scipost/management/commands/add_groups_and_permissions.py +++ b/scipost/management/commands/add_groups_and_permissions.py @@ -18,15 +18,17 @@ class Command(BaseCommand): # Create Permissions content_type = ContentType.objects.get_for_model(Contributor) + # Registration - can_manage_registration_invitations, created = Permission.objects.get_or_create( - codename='can_manage_registration_invitations', - name= 'Can manage registration invitations', - content_type=content_type) can_vet_registration_requests, created = Permission.objects.get_or_create( codename='can_vet_registration_requests', name= 'Can vet registration requests', content_type=content_type) + can_manage_registration_invitations, created = Permission.objects.get_or_create( + codename='can_manage_registration_invitations', + name= 'Can manage registration invitations', + content_type=content_type) + # Vetting of simple objects can_vet_commentary_requests, created = Permission.objects.get_or_create( codename='can_vet_commentary_requests', @@ -44,11 +46,22 @@ class Command(BaseCommand): codename='can_vet_comments', name= 'Can vet submitted Comments', content_type=content_type) + + # Submissions + can_submit_manuscript, created = Permission.objects.get_or_create( + codename='can_submit_manuscript', + name='Can submit manuscript', + content_type=content_type) + # Submission handling can_process_incoming_submissions, created = Permission.objects.get_or_create( codename='can_process_incoming_submissions', name= 'Can process incoming Submissions', content_type=content_type) + can_take_charge_of_submissions, created = Permission.objects.get_or_create( + codename='can_take_charge_of_submissions', + name= 'Can take charge of submissions', + content_type=content_type) can_vet_submitted_reports, created = Permission.objects.get_or_create( codename='can_vet_submitted_reports', name='Can vet submitted Reports', @@ -61,7 +74,11 @@ class Command(BaseCommand): can_vet_thesislink_requests, can_vet_authorship_claims, can_vet_comments, + can_process_incoming_submissions, ) + EditorialCollege.permissions.add(can_take_charge_of_submissions, + can_vet_submitted_reports, + ) VettingEditors.permissions.add(can_vet_commentary_requests, can_vet_thesislink_requests, can_vet_authorship_claims, diff --git a/scipost/models.py b/scipost/models.py index 80d215ad29076d83d7a2a6edaa7d990295532482..1d8d924a1708358a5edbad5a9eff65372714e59d 100644 --- a/scipost/models.py +++ b/scipost/models.py @@ -1,6 +1,7 @@ from django.utils import timezone from django.db import models from django.contrib.auth.models import User, Group +from django.contrib.postgres.fields import JSONField from django_countries.fields import CountryField @@ -11,6 +12,19 @@ SCIPOST_DISCIPLINES = ( ) disciplines_dict = dict(SCIPOST_DISCIPLINES) +PHYSICS_SPECIALIZATIONS = ( + ('A', 'Atomic, Molecular and Optical Physics'), + ('B', 'Biophysics'), + ('C', 'Condensed Matter Physics'), + ('F', 'Fluid Dynamics'), + ('G', 'Gravitation, Cosmology and Astroparticle Physics'), + ('H', 'High-Energy Physics'), + ('M', 'Mathematical Physics'), + ('N', 'Nuclear Physics'), + ('Q', 'Quantum Statistical Mechanics'), + ('S', 'Statistical and Soft Matter Physics'), + ) +physics_specializations = dict(PHYSICS_SPECIALIZATIONS) CONTRIBUTOR_STATUS = ( @@ -49,6 +63,7 @@ class Contributor(models.Model): status = models.SmallIntegerField(default=0, choices=CONTRIBUTOR_STATUS) title = models.CharField(max_length=4, choices=TITLE_CHOICES) discipline = models.CharField(max_length=20, choices=SCIPOST_DISCIPLINES, default='physics') + specializations = JSONField(default={}) orcid_id = models.CharField(max_length=20, verbose_name="ORCID id", blank=True) country_of_employment = CountryField() affiliation = models.CharField(max_length=300, verbose_name='affiliation') diff --git a/scipost/static/scipost/SciPost.css b/scipost/static/scipost/SciPost.css index 15eb31eeaf4cb8bacff65e90e9f5a20f526f8e15..d0b7647ec69791cceda2d8646943a48ca510b2c5 100644 --- a/scipost/static/scipost/SciPost.css +++ b/scipost/static/scipost/SciPost.css @@ -441,6 +441,29 @@ section ul li { background-color: #990000; } +.reportRatings { + font-family: 'Merriweather Sans'; + font-size: 10px; + margin: 0px 4px; + padding: 1px; + display: inline-block; + box-shadow: 1px 1px 1px #888888; + background: linear-gradient(to right,#fcfcfc, #f0f0f0); +} +.reportRatings ul { + display: inline-block; + font-family: 'Merriweather Sans'; + margin: 0px; + padding: 2px 1px; +} +.reportRatings ul li { + border: 1px solid black; + display: inline-block; + font-family: 'Merriweather Sans'; + margin: 1px 2px; + padding: 3px 6px; +} + article { background-color:#eeeeee; diff --git a/scipost/templates/scipost/about.html b/scipost/templates/scipost/about.html index f5e1bf5e6bc25845cbe3b0addbb44e5c3495ccf5..e6ec86e5f789bef6e70b9806d5af1107559b68a8 100644 --- a/scipost/templates/scipost/about.html +++ b/scipost/templates/scipost/about.html @@ -98,9 +98,9 @@ <div class="flex-whitebox"> <h3>The SciPost Foundation</h3> <ul> - <li>Chairman: J.-S. Caux</li> - <li>Secretary: J. van Mameren</li> - <li>Treasurer: J. van Wezel</li> + <li>Chairman: Prof. J.-S. Caux</li> + <li>Secretary: Dr J. van Mameren</li> + <li>Treasurer: Dr J. van Wezel</li> </ul> </div> <div class="flex-whitebox"> diff --git a/scipost/templates/scipost/footer.html b/scipost/templates/scipost/footer.html index 7d804f4c1aecd6362403232de77fbd3be10f6f87..ec58564dc167189b8493ff7ff4a83f8219ce5b61 100644 --- a/scipost/templates/scipost/footer.html +++ b/scipost/templates/scipost/footer.html @@ -4,12 +4,16 @@ {% if user.is_authenticated %} <div class="col-6"> Copyright © SciPost. + <br/> <a href="mailto:admin@scipost.org">Contact the administrators.</a> - </div> - <div class="col-6"> -<!-- All SciPost content is licensed under the <a href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International (CC BY 4.0) License</a>. --> + <br/> <a href="{% url 'scipost:terms_and_conditions' %}">Terms and conditions.</a> </div> + <div class="col-1"> + </div> + <div class="col-5"> + <a rel="license" href="http://creativecommons.org/licenses/by/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/4.0/80x15.png" /></a><br />Except where otherwise noted, all content on SciPost is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>. + </div> {% endif %} </div> </footer> diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html index 281f4d19d0186230e815416a4a39a866fbc49a29..f9ef5b38ec802d4e57b184e5c108c1f5a8af78a6 100644 --- a/scipost/templates/scipost/personal_page.html +++ b/scipost/templates/scipost/personal_page.html @@ -91,9 +91,13 @@ <div class="col-4"> <h3>Registration actions</h3> <ul> + {% if perms.scipost.can_vet_registration_requests %} <li><a href="{% url 'scipost:vet_registration_requests' %}">Vet Registration requests</a> ({{ nr_reg_to_vet }})</li> - <li>Awaiting validation ({{ nr_reg_awaiting_validation }}) (no action necessary)</li> - <li><a href="{% url 'scipost:registration_invitations' %}">Manage Registration Invitations</a></li> + <li>Awaiting validation ({{ nr_reg_awaiting_validation }}) (no action necessary)</li> + {% endif %} + {% if perms.scipost.can_manage_registration_invitations %} + <li><a href="{% url 'scipost:registration_invitations' %}">Manage Registration Invitations</a></li> + {% endif %} </ul> </div> {% endif %} @@ -101,26 +105,67 @@ <div class="col-4"> <h3>Vetting actions</h3> <ul> + {% if perms.scipost.can_vet_commentary_requests %} <li><a href="{% url 'commentaries:vet_commentary_requests' %}">Vet Commentary Page requests</a> ({{ nr_commentary_page_requests_to_vet }})</li> + {% endif %} + {% if perms.scipost.can_vet_comments %} <li><a href="{% url 'comments:vet_submitted_comments' %}">Vet submitted Comments</a> ({{ nr_comments_to_vet }})</li> - <li><a href="{% url 'submissions:vet_submitted_reports' %}">Vet submitted Reports</a> ({{ nr_reports_to_vet }})</li> + {% endif %} + {% if perms.scipost.can_vet_thesislink_requests %} <li><a href="{% url 'theses:vet_thesislink_requests' %}">Vet Thesis Link Requests</a> ({{ nr_thesislink_requests_to_vet }})</li> + {% endif %} + {% if perms.scipost.can_vet_authorship_claims %} <li><a href="{% url 'scipost:vet_authorship_claims' %}">Vet Authorship Claims</a> ({{ nr_authorship_claims_to_vet }})</li> + {% endif %} + {% if perms.scipost.can_vet_submitted_reports %} + <li><a href="{% url 'submissions:vet_submitted_reports' %}">Vet submitted Reports</a> ({{ nr_reports_to_vet }})</li> + {% endif %} </ul> </div> {% if request.user|is_in_group:'SciPost Administrators' or request.user|is_in_group:'Editorial College' %} <div class="col-4"> - <h3>Submissions processing</h3> + <h3>Submissions assignments</h3> <ul> - <li><a href="{% url 'submissions:process_new_submissions' %}">Process new SciPost Submissions</a> ({{ nr_submissions_to_process }})</li> + {% if perms.scipost.can_process_incoming_submissions %} + <li><a href="{% url 'submissions:assign_submissions' %}">Assign SciPost Submissions</a> ({{ nr_submissions_to_assign }})</li> + {% endif %} + {% if perms.scipost.can_take_charge_of_submissions %} + <li><a href="{% url 'submissions:accept_or_decline_assignments' %}">Accept or decline assignments</a> ({{ nr_assignments_to_consider }})</li> + {% endif %} </ul> </div> {% endif %} + </div> + <div class="flex-whitebox"> + <h3>Submissions for which you are Editor-in-charge</h3> + <ul> + {% for assignment in active_assignments %} + {{ assignment.header_as_li }} + {% endfor %} + </ul> + </div> </section> {% endif %} +<section> + <hr class="hr12"> + <div class="flex-greybox"> + <h1>Refereeing Tasks</h1> + <li><a href="{% url 'submissions:accept_or_decline_ref_invitations' %}">Accept/decline refereeing invitations</a> ({{ nr_ref_inv_to_consider }})</li> + </div> + {% if pending_ref_tasks %} + <h3>Refereeing tasks to fulfill: please provide a Report on the following Submissions + <ul> + {% for task in pending_ref_tasks %} + {{ task.submission.header_as_li|safe }} + {% endfor %} + </ul> + {% endif %} + +</section> + <section> <hr class="hr12"> <div class="flex-greybox"> diff --git a/scipost/views.py b/scipost/views.py index c51d83819377db229dfc701230af4c601c312f7d..a61001b54ef4737e0da625d1ac609cc41887e105 100644 --- a/scipost/views.py +++ b/scipost/views.py @@ -28,7 +28,7 @@ from .utils import * from commentaries.models import Commentary from commentaries.forms import CommentarySearchForm from comments.models import Comment -from submissions.models import Submission, Report +from submissions.models import Submission, EditorialAssignment, RefereeInvitation, Report from submissions.forms import SubmissionSearchForm from theses.models import ThesisLink from theses.forms import ThesisLinkSearchForm @@ -312,7 +312,7 @@ def personal_page(request): # if an editor, count the number of actions required: nr_reg_to_vet = 0 nr_reg_awaiting_validation = 0 - nr_submissions_to_process = 0 + nr_submissions_to_assign = 0 if is_SP_Admin(request.user): now = timezone.now() intwodays = now + timezone.timedelta(days=2) @@ -320,7 +320,12 @@ def personal_page(request): nr_reg_to_vet = Contributor.objects.filter(user__is_active=True, status=0).count() nr_reg_awaiting_validation = Contributor.objects.filter( user__is_active=False, key_expires__gte=now, key_expires__lte=intwodays, status=0).count() - nr_submissions_to_process = Submission.objects.filter(vetted=False).count() + nr_submissions_to_assign = Submission.objects.filter(assigned=False).count() + nr_assignments_to_consider = 0 + active_assignments = None + if is_MEC(request.user): + nr_assignments_to_consider = EditorialAssignment.objects.filter(to=contributor, accepted=None).count() + active_assignments = EditorialAssignment.objects.filter(to=contributor, accepted=True, completed=False) nr_commentary_page_requests_to_vet = 0 nr_comments_to_vet = 0 nr_reports_to_vet = 0 @@ -332,6 +337,8 @@ def personal_page(request): nr_reports_to_vet = Report.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() + nr_ref_inv_to_consider = RefereeInvitation.objects.filter(referee=contributor, accepted=None).count() + 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(authors__in=[contributor]) own_commentaries = Commentary.objects.filter(authors__in=[contributor]) @@ -345,13 +352,17 @@ def personal_page(request): 'nr_reg_awaiting_validation': nr_reg_awaiting_validation, 'nr_commentary_page_requests_to_vet': nr_commentary_page_requests_to_vet, 'nr_comments_to_vet': nr_comments_to_vet, - 'nr_reports_to_vet': nr_reports_to_vet, - 'nr_submissions_to_process': nr_submissions_to_process, 'nr_thesislink_requests_to_vet': nr_thesislink_requests_to_vet, 'nr_authorship_claims_to_vet': nr_authorship_claims_to_vet, + 'nr_reports_to_vet': nr_reports_to_vet, + 'nr_submissions_to_assign': nr_submissions_to_assign, + 'nr_assignments_to_consider': nr_assignments_to_consider, + 'active_assignments': active_assignments, 'nr_submission_authorships_to_claim': nr_submission_authorships_to_claim, 'nr_commentary_authorships_to_claim': nr_commentary_authorships_to_claim, '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, 'own_submissions': own_submissions, 'own_commentaries': own_commentaries, 'own_thesislinks': own_thesislinks, diff --git a/submissions/admin.py b/submissions/admin.py index c469608ae6b5e5ccd5b7572ef2ba2e8e4d192f9b..d454de7f08c9756bda97228897911bc7d5a3d25d 100644 --- a/submissions/admin.py +++ b/submissions/admin.py @@ -9,6 +9,18 @@ class SubmissionAdmin(admin.ModelAdmin): admin.site.register(Submission, SubmissionAdmin) +class EditorialAssignmentAdmin(admin.ModelAdmin): + search_fields = ['submission', 'to'] + +admin.site.register(EditorialAssignment, EditorialAssignmentAdmin) + + +class RefereeInvitationAdmin(admin.ModelAdmin): + search_fields = ['submission'] + +admin.site.register(RefereeInvitation, RefereeInvitationAdmin) + + class ReportAdmin(admin.ModelAdmin): search_fields = ['author__user__username'] diff --git a/submissions/forms.py b/submissions/forms.py index 5b4ea273cfb9a63ae0f44c4193bb6fa3d9e93ee1..9032ee61431d3663c6a208944a29480f9c24a412 100644 --- a/submissions/forms.py +++ b/submissions/forms.py @@ -14,8 +14,6 @@ class SubmissionForm(forms.ModelForm): self.fields['arxiv_link'].widget.attrs.update({'placeholder': 'ex.: arxiv.org/abs/1234.56789v1'}) self.fields['abstract'].widget.attrs.update({'cols': 100}) -class ProcessSubmissionForm(forms.Form): - editor_in_charge = forms.ModelChoiceField(queryset=Contributor.objects.filter(user__groups__name='Editorial College'), required=True, label='Select an Editor-in-charge') class SubmissionSearchForm(forms.Form): author = forms.CharField(max_length=100, required=False, label="Author(s)") @@ -23,6 +21,45 @@ class SubmissionSearchForm(forms.Form): abstract_keyword = forms.CharField(max_length=1000, required=False, label="Abstract") +###################### +# Editorial workflow # +###################### + +class AssignSubmissionForm(forms.Form): + + def __init__(self, *args, **kwargs): + discipline = kwargs.pop('discipline') +# specialization = kwargs.pop('specialization') # Reactivate later on, once the Editorial College is large enough + super(AssignSubmissionForm,self).__init__(*args, **kwargs) + self.fields['editor_in_charge'] = forms.ModelChoiceField( + queryset=Contributor.objects.filter(user__groups__name='Editorial College', + user__contributor__discipline=discipline, +# user__contributor__specializations__contains=[specialization,] # Reactivate later on, once the Editorial College is large enough + ), required=True, label='Select an Editor-in-charge') + + editor_in_charge = forms.ModelChoiceField(queryset=...) + + +class ConsiderAssignmentForm(forms.Form): + accept = forms.ChoiceField(widget=forms.RadioSelect, choices=ASSIGNMENT_BOOL, label="Are you willing to take charge of this Submission?") + refusal_reason = forms.ChoiceField(choices=ASSIGNMENT_REFUSAL_REASONS, required=False) + + +class RefereeSelectForm(forms.Form): +# 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() + + last_name=forms.CharField(widget=forms.TextInput(attrs={'size': 20, 'placeholder': 'search Contributors'})) + + +class ConsiderRefereeInvitationForm(forms.Form): + accept = forms.ChoiceField(widget=forms.RadioSelect, choices=ASSIGNMENT_BOOL, label="Are you willing to referee this Submission?") + refusal_reason = forms.ChoiceField(choices=ASSIGNMENT_REFUSAL_REASONS, required=False) + + ############ # Reports: ############ @@ -46,7 +83,7 @@ class ReportForm(forms.ModelForm): model = Report fields = ['qualification', 'strengths', 'weaknesses', 'report', 'requested_changes', 'validity', 'significance', 'originality', 'clarity', 'formatting', 'grammar', - 'recommendation'] + 'recommendation', 'remarks_for_editors', 'anonymous'] 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}) diff --git a/submissions/models.py b/submissions/models.py index 645a085e044741b975c34f7bc28def37cf16d328..88db9869159989ada780160ac1452d5c85e35423 100644 --- a/submissions/models.py +++ b/submissions/models.py @@ -1,11 +1,12 @@ from django.utils import timezone +from django.utils.safestring import mark_safe from django.db import models from django.contrib.auth.models import User from .models import * from scipost.models import Contributor -from scipost.models import SCIPOST_DISCIPLINES +from scipost.models import SCIPOST_DISCIPLINES, TITLE_CHOICES from journals.models import SCIPOST_JOURNALS_SUBMIT, SCIPOST_JOURNALS_DOMAINS, SCIPOST_JOURNALS_SPECIALIZATIONS from journals.models import journals_submit_dict, journals_domains_dict, journals_spec_dict @@ -15,20 +16,26 @@ from journals.models import journals_submit_dict, journals_domains_dict, journal ############### SUBMISSION_STATUS = ( - ('unassigned', 'unassigned'), - ('forwarded', 'forwarded to a specialty editor'), - ('SEICassigned', 'specialty editor-in-charge assigned'), - ('under_review', 'under review'), - ('review_completed', 'review period closed, editorial recommendation pending'), - ('SEIC_has_recommended', 'specialty editor-in-charge has provided recommendation'), - ('decided', 'journal editor-in-chief has fixed decision'), + ('unassigned', 'Unassigned'), + ('assigned', 'Assigned to a specialty editor (response pending)'), + ('SEICassigned', 'Specialty editor-in-charge assigned'), + ('under_review', 'Under review'), + ('review_completed', 'Review period closed, editorial recommendation pending'), + ('SEIC_has_recommended', 'Specialty editor-in-charge has provided recommendation'), + ('put_to_EC_voting', 'Undergoing voting at the Editorial College'), + ('EC_vote_completed', 'Editorial College voting rounded up'), + ('decided', 'Publication decision taken'), ) submission_status_dict = dict(SUBMISSION_STATUS) class Submission(models.Model): submitted_by = models.ForeignKey(Contributor) - vetted = models.BooleanField(default=False) - editor_in_charge = models.ForeignKey(Contributor, related_name="editor_in_charge", blank=True, null=True) # assigned by Journal Editor +# vetted = models.BooleanField(default=False) + assigned = models.BooleanField(default=False) +# assignment = models.ForeignKey('submissions.EditorialAssignment', related_name='assignment', blank=True, null=True) # not needed, assignment has submission as ForeignKey +# assigned_to = models.ForeignKey(Contributor, related_name="assigned_to", blank=True, null=True) +# declined_assignment = models.ManyToManyField (Contributor, related_name="declined_assignment", blank=True) +# editor_in_charge = models.ForeignKey(Contributor, related_name="editor_in_charge", blank=True, null=True) # non-null only if assignment has been accepted submitted_to_journal = models.CharField(max_length=30, choices=SCIPOST_JOURNALS_SUBMIT, verbose_name="Journal to be submitted to") discipline = models.CharField(max_length=20, choices=SCIPOST_DISCIPLINES, default='physics') domain = models.CharField(max_length=3, choices=SCIPOST_JOURNALS_DOMAINS) @@ -87,12 +94,90 @@ class Submission(models.Model): def submission_info_as_table (self): header = '<table>' - header += '<tr><td>Editor in charge: </td><td> </td><td>' + str(self.editor_in_charge) + '</td></tr>' - header += '<tr><td>Vetted: </td><td> </td><td>' + str(self.vetted) + '</td></tr>' + if self.assignment is not None: + header += '<tr><td>Editor in charge: </td><td> </td><td>' + str(self.assignment.to) + '</td></tr>' + header += '<tr><td>Assigned: </td><td> </td><td>' + str(self.assigned) + '</td></tr>' header += '<tr><td>Status: </td><td> </td><td>' + submission_status_dict[self.status] + '</td></tr>' header += '</table>' return header + +###################### +# Editorial workflow # +###################### + +ASSIGNMENT_BOOL = ((True, 'Accept'), (False, 'Decline')) +ASSIGNMENT_NULLBOOL = ((None, 'Response pending'), (True, 'Accept'), (False, 'Decline')) + +ASSIGNMENT_REFUSAL_REASONS = ( + ('BUS', 'Too busy'), + ('COI', 'Conflict of interest: coauthor in last 5 years'), + ('CCC', 'Conflict of interest: close colleague'), + ('NIR', 'Cannot give an impartial assessment'), + ('NIE', 'Not interested enough'), + ('DNP', 'SciPost should not even consider this paper'), + ) +assignment_refusal_reasons_dict = dict(ASSIGNMENT_REFUSAL_REASONS) + +class EditorialAssignment(models.Model): + submission = models.ForeignKey(Submission) + to = models.ForeignKey(Contributor) + accepted = models.NullBooleanField(choices=ASSIGNMENT_NULLBOOL, default=None) + completed = models.BooleanField(default=False) + refusal_reason = models.CharField(max_length=3, choices=ASSIGNMENT_REFUSAL_REASONS, blank=True, null=True) + date_created = models.DateTimeField(default=timezone.now) + date_answered = models.DateTimeField(blank=True, null=True) + + def __str__(self): + return (self.to.user.first_name + ' ' + self.to.user.last_name + ' to become EIC of ' + + self.submission.title[:30] + ' by ' + self.submission.author_list[:30] + + ', assigned on ' + self.date_created.strftime('%Y-%m-%d')) + + def header_as_li(self): + header = '<li><div class="flex-container">' + header += ('<div class="flex-whitebox0"><p><a href="/submissions/editorial_page/' + str(self.submission.id) + + '" class="pubtitleli">' + self.submission.title + '</a></p>') + header += ('<p>by ' + self.submission.author_list + + '</p><p> (submitted ' + str(self.submission.submission_date) + + ' to ' + journals_submit_dict[self.submission.submitted_to_journal] + + ')</p></div>') + header += '</div></li>' + return mark_safe(header) + + +class RefereeInvitation(models.Model): + submission = models.ForeignKey(Submission) + referee = models.ForeignKey(Contributor, related_name='referee') + title = models.CharField(max_length=4, choices=TITLE_CHOICES) + first_name = models.CharField(max_length=30, default='') + last_name = models.CharField(max_length=30, default='') + email_address = models.EmailField() + date_invited = models.DateTimeField(default=timezone.now) + invited_by = models.ForeignKey(Contributor, related_name='referee_invited_by', blank=True, null=True) + accepted = models.NullBooleanField(choices=ASSIGNMENT_NULLBOOL, default=None) + date_responded = models.DateTimeField(blank=True, null=True) + refusal_reason = models.CharField(max_length=3, choices=ASSIGNMENT_REFUSAL_REASONS, blank=True, null=True) + fulfilled = models.BooleanField(default=False) # True if a Report has been submitted + + def __str__(self): + return (self.referee.user.first_name + ' ' + self.referee.user.last_name + ' to referee ' + + self.submission.title[:30] + ' by ' + self.submission.author_list[:30] + + ', invited on ' + self.date_invited.strftime('%Y-%m-%d')) + + def summary_as_li(self): + output = '<li>' + self.referee.user.first_name + ' ' + self.referee.user.last_name + ', ' + output += 'invited ' + self.date_invited.strftime('%Y-%m-%d %H:%M') + ', ' + if self.accepted is not None: + if self.accepted: + output += 'accepted ' + else: + output += 'declined ' + output += self.date_responded.strftime('%Y-%m-%d %H:%M') + else: + output += 'response pending' + return mark_safe(output) + + ########### # Reports: ########### @@ -115,11 +200,14 @@ QUALITY_SPEC = ( (1, 'below threshold'), (0, 'mediocre'), ) +quality_spec_dict = dict(QUALITY_SPEC) + RANKING_CHOICES = ( (101, '-'), # Only values between 0 and 100 are kept, anything outside those limits is discarded. (100, 'top'), (80, 'high'), (60, 'good'), (40, 'ok'), (20, 'low'), (0, 'poor') ) +ranking_choices_dict = dict(RANKING_CHOICES) REPORT_REC = ( (1, 'Publish as Tier I (top 10% of papers in this journal)'), @@ -129,6 +217,7 @@ REPORT_REC = ( (-2, 'Ask for major revision'), (-3, 'Reject') ) +report_rec_dict = dict(REPORT_REC) class Report(models.Model): """ Both types of reports, invited or contributed. """ @@ -140,8 +229,9 @@ class Report(models.Model): # -3: rejected (not useful) status = models.SmallIntegerField(default=0) submission = models.ForeignKey(Submission) - date_invited = models.DateTimeField('date invited', blank=True, null=True) - invited_by = models.ForeignKey(Contributor, blank=True, null=True, related_name='invited_by') +# date_invited = models.DateTimeField('date invited', blank=True, null=True) +# invited_by = models.ForeignKey(Contributor, blank=True, null=True, related_name='invited_by') + invited = models.BooleanField(default=False) # filled from RefereeInvitation objects at moment of report submission date_submitted = models.DateTimeField('date submitted') author = models.ForeignKey(Contributor) qualification = models.PositiveSmallIntegerField(choices=REFEREE_QUALIFICATION, verbose_name="Qualification to referee this: I am ") @@ -155,8 +245,48 @@ class Report(models.Model): significance = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, default=101) originality = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, default=101) clarity = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, default=101) - formatting = models.SmallIntegerField(choices=QUALITY_SPEC, blank=True, verbose_name="Quality of paper formatting") - grammar = models.SmallIntegerField(choices=QUALITY_SPEC, blank=True, verbose_name="Quality of English grammar") + formatting = models.SmallIntegerField(choices=QUALITY_SPEC, 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') anonymous = models.BooleanField(default=True, verbose_name='Publish anonymously') + + + def print_identifier(self): + output = '<div class="reportid">\n' + output += '<h3><a id="report_id' + str(self.id) + '"></a>' + if self.anonymous: + output += 'Anonymous' + else: + output += ('<a href="/contributor/' + str(self.author.id) + '">' + + self.author.user.first_name + ' ' + self.author.user.last_name + '</a>') + output += ' on ' + self.date_submitted.strftime("%Y-%m-%d") + output += '</h3></div>' + return mark_safe(output) + + def print_contents(self): + output = ('<div class="row"><div class="col-2"><p>Strengths:</p></div><div class="col-10"><p>' + + self.strengths + '</p></div></div>' + + '<div class="row"><div class="col-2"><p>Weaknesses:</p></div><div class="col-10"><p>' + + self.weaknesses + '</p></div></div>' + + '<div class="row"><div class="col-2"><p>Report:</p></div><div class="col-10"><p>' + + self.report + '</p></div></div>' + + '<div class="row"><div class="col-2"><p>Requested changes:</p></div><div class="col-10"><p>' + + self.requested_changes + '</p></div></div>') + output += '<div class="reportRatings"><ul>' + output += '<li>validity: ' + ranking_choices_dict[self.validity] + '</li>' + output += '<li>significance: ' + ranking_choices_dict[self.significance] + '</li>' + output += '<li>originality: ' + ranking_choices_dict[self.originality] + '</li>' + output += '<li>clarity: ' + ranking_choices_dict[self.clarity] + '</li>' + output += '<li>formatting: ' + quality_spec_dict[self.formatting] + '</li>' + output += '<li>grammar: ' + quality_spec_dict[self.grammar] + '</li>' + output += '</ul></div>' + return mark_safe(output) + + def print_contents_for_editors(self): + output = ('<div class="row"><div class="col-2">Qualification:</p></div><div class="col-10"><p>' + + ref_qualif_dict[self.qualification] + '</p></div></div>') + output += self.print_contents() + output += '<h3>Recommendation: ' + report_rec_dict[self.recommendation] + '</h3>' + return mark_safe(output) diff --git a/submissions/templates/submissions/accept_or_decline_assignment_ack.html b/submissions/templates/submissions/accept_or_decline_assignment_ack.html new file mode 100644 index 0000000000000000000000000000000000000000..b004af9bd53a20e579d204a73a5d9b5587b717ac --- /dev/null +++ b/submissions/templates/submissions/accept_or_decline_assignment_ack.html @@ -0,0 +1,16 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: accept or decline assignment (ack){% endblock pagetitle %} + +{% block bodysup %} + +<section> + {% if assignment.accepted %} + <h1>Thank you for becoming Editor-in-charge of this submission.</h1> + <p>Please go to the <a href="{% url 'submissions:editorial_page' submission_id=assignment.submission.id %}">Submission's editorial page</a> and select referees now.</p> + {% else %} + <h1>Thank you for considering.</h1> + {% endif %} +</section> + +{% endblock bodysup %} diff --git a/submissions/templates/submissions/accept_or_decline_assignments.html b/submissions/templates/submissions/accept_or_decline_assignments.html new file mode 100644 index 0000000000000000000000000000000000000000..d43f8d735478164b438343f065186017a0390f73 --- /dev/null +++ b/submissions/templates/submissions/accept_or_decline_assignments.html @@ -0,0 +1,59 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: accept or decline assignments{% endblock pagetitle %} + +{% block bodysup %} + +<script> +$(document).ready(function(){ + + $('#ref_reason').hide(); + + $('#id_accept').on('change', function() { + if ($('#id_accept_1').is(':checked')) { + $('#ref_reason').show(); + } + else { + $('#ref_reason').hide(); + } + }); + }); +</script> + + + +<section> + {% if not assignment_to_consider %} + <h1>There are no Assignments for you to consider.</h1> + + {% else %} + + <div class="flex-greybox"> + <h1>SciPost Submission Assigned to you (see below to accept/decline):</h1> + </div> + <br> + <hr> + {{ assignment_to_consider.submission.header_as_table|safe }} + <br /> + <h4>Abstract:</h4> + <p>{{ assignment_to_consider.submission.abstract }}</p> + <br/> + <hr> + <div class="flex-greybox"> + <h1>Accept or Decline this Assignment</h1> + </div> + <h3>By accepting, you will be required to start a refereeing round on the next screen.</h3> + <form action="{% url 'submissions:accept_or_decline_assignment_ack' assignment_id=assignment_to_consider.id %}" method="post"> + {% csrf_token %} + {{ form.accept }} + <div id="ref_reason"> + <p>Please select a reason for declining this assignment:</p> + {{ form.refusal_reason }} + </div> + <input type="submit" value="Submit" /> + </form> + + {% endif %} +</section> + +{% endblock bodysup %} diff --git a/submissions/templates/submissions/accept_or_decline_ref_invitation_ack.html b/submissions/templates/submissions/accept_or_decline_ref_invitation_ack.html new file mode 100644 index 0000000000000000000000000000000000000000..e25c3d9d3841478938298d098ede4df99ef6403b --- /dev/null +++ b/submissions/templates/submissions/accept_or_decline_ref_invitation_ack.html @@ -0,0 +1,17 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: accept or decline refereeing invitation (ack){% endblock pagetitle %} + +{% block bodysup %} + +<section> + {% if invitation.accepted == True %} + <h1>Thank you for agree to referee this Submission.</h1> + <p>When you are ready, please go to the <a href="{% url 'submissions:submission' submission_id=invitation.submission.id %}">Submission's page</a> to submit your Report.</p> + {% else %} + <h1>You have declined to contribute a Report.</h1> + <p>Nonetheless, we thank you very much for considering this refereeing invitation.</p> + {% endif %} +</section> + +{% endblock bodysup %} diff --git a/submissions/templates/submissions/accept_or_decline_ref_invitations.html b/submissions/templates/submissions/accept_or_decline_ref_invitations.html new file mode 100644 index 0000000000000000000000000000000000000000..2b4279f1486ad0b0ef40db2917ad27a7d853f86e --- /dev/null +++ b/submissions/templates/submissions/accept_or_decline_ref_invitations.html @@ -0,0 +1,59 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: accept or decline refereeing invitations{% endblock pagetitle %} + +{% block bodysup %} + +<script> +$(document).ready(function(){ + + $('#ref_reason').hide(); + + $('#id_accept').on('change', function() { + if ($('#id_accept_1').is(':checked')) { + $('#ref_reason').show(); + } + else { + $('#ref_reason').hide(); + } + }); + }); +</script> + + + +<section> + {% if not invitation_to_consider %} + <h1>There are no Refereeing Invitations for you to consider.</h1> + + {% else %} + + <div class="flex-greybox"> + <h1>SciPost Submission which you are asked to Referee (see below to accept/decline):</h1> + </div> + <br> + <hr> + {{ invitation_to_consider.submission.header_as_table|safe }} + <br /> + <h4>Abstract:</h4> + <p>{{ invitation_to_consider.submission.abstract }}</p> + <br/> + <hr> + <div class="flex-greybox"> + <h1>Accept or Decline this Refereeing Invitation</h1> + </div> + <h3>Please let us know if you can provide us with a Report for this Submission:</h3> + <form action="{% url 'submissions:accept_or_decline_ref_invitation_ack' invitation_id=invitation_to_consider.id %}" method="post"> + {% csrf_token %} + {{ form.accept }} + <div id="ref_reason"> + <p>Please select a reason for declining this invitation:</p> + {{ form.refusal_reason }} + </div> + <input type="submit" value="Submit" /> + </form> + + {% endif %} +</section> + +{% endblock bodysup %} diff --git a/submissions/templates/submissions/process_new_submission_ack.html b/submissions/templates/submissions/assign_submission_ack.html similarity index 73% rename from submissions/templates/submissions/process_new_submission_ack.html rename to submissions/templates/submissions/assign_submission_ack.html index b66c87cbbc6e6f7661100c68bd51fc41d46aadc9..4a2a1e90ee1ba9320f60a21174ff2221b11abf26 100644 --- a/submissions/templates/submissions/process_new_submission_ack.html +++ b/submissions/templates/submissions/assign_submission_ack.html @@ -1,6 +1,6 @@ {% extends 'scipost/base.html' %} -{% block pagetitle %}: submission processed{% endblock pagetitle %} +{% block pagetitle %}: submission assigned{% endblock pagetitle %} {% block bodysup %} diff --git a/submissions/templates/submissions/assign_submissions.html b/submissions/templates/submissions/assign_submissions.html new file mode 100644 index 0000000000000000000000000000000000000000..2acffb86fa7fd6b3ae101f4972ccbce8c264f3f5 --- /dev/null +++ b/submissions/templates/submissions/assign_submissions.html @@ -0,0 +1,37 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: assign submissions{% endblock pagetitle %} + +{% block bodysup %} + +<section> + {% if not submission_to_assign %} + <h1>There are no Submissions for you to assign.</h1> + + {% else %} + + <h1>SciPost Submission to Assign</h1> + + <br> + <hr> + {{ submission_to_assign.header_as_table|safe }} + <br /> + <h4>Abstract:</h4> + <p>{{ submission_to_assign.abstract }}</p> + <br/> + <hr/> + {{ submission_to_assign.submission_info_as_table|safe }} + + <br/> + <hr> + <h1>Required actions:</h1> + <form action="{% url 'submissions:assign_submission_ack' submission_id=submission_to_assign.id %}" method="post"> + {% csrf_token %} + {{ form.as_ul }} + <input type="submit" value="Submit" /> + </form> + + {% endif %} +</section> + +{% endblock bodysup %} diff --git a/submissions/templates/submissions/editorial_page.html b/submissions/templates/submissions/editorial_page.html new file mode 100644 index 0000000000000000000000000000000000000000..a20e736a4a258cedf598f32d7541a24997a5c08b --- /dev/null +++ b/submissions/templates/submissions/editorial_page.html @@ -0,0 +1,46 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: editorial page for submission{% endblock pagetitle %} + +{% block headsup %} + +{% load scipost_extras %} + +{% endblock headsup %} + +{% block bodysup %} + +<!-- Temporary strip --> +{% if user.is_authenticated %} + +<section> + <div class="flex-greybox"> + <h1>Editorial Page for Submission</h1> + </div> + + <hr class="hr12"> + <div class="row"> + <div class="col-4"> + <h2>Submission:</h2> + </div> + </div> + {{ submission.header_as_table|safe }} + <h3>Abstract:</h3> + <p>{{ submission.abstract }}</p> + +</section> + + +<section> + <div class="flex-greybox"> + <h1>Refereeing invitations</h1> + </div> + {% for invitation in ref_invitations %} + {{ invitation.summary_as_li }} + {% endfor %} + <h3><a href="{% url 'submissions:select_referee' submission_id=submission.id %}">Select an additional referee</a></h3> +</section> + +{% endif %} <!-- Temporary strip --> + +{% endblock bodysup %} diff --git a/submissions/templates/submissions/process_new_submissions.html b/submissions/templates/submissions/process_new_submissions.html deleted file mode 100644 index 92e2399016112b80025c4ca566f35a76f5ee05e6..0000000000000000000000000000000000000000 --- a/submissions/templates/submissions/process_new_submissions.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends 'scipost/base.html' %} - -{% block pagetitle %}: process submissions{% endblock pagetitle %} - -{% block bodysup %} - -<section> - {% if not submission_to_process %} - <h1>There are no Submissions for you to process.</h1> - - {% else %} - - <h1>SciPost Submission to Process</h1> - - <br> - <hr> - {{ submission_to_process.header_as_table|safe }} -<!-- - <table> - <tr><td>Title: </td><td>{{ submission_to_process.title }}</td></tr> - <tr><td>Author(s): </td><td>{{ submission_to_process.author_list }}</td></tr> - <tr><td>arxiv Link: </td><td><a href="{{ submission_to_process.arxiv_link }}">{{ submission_to_process.arxiv_link }}</a></td></tr> - <tr><td>Date submitted: </td><td>{{ submission_to_process.submission_date }}</td></tr> - </table> ---> - <br /> - <h4>Abstract:</h4> - <p>{{ submission_to_process.abstract }}</p> - <br/> - <hr/> - {{ submission_to_process.submission_info_as_table|safe }} - - <br/> - <hr> - <h1>Required actions:</h1> - <form action="{% url 'submissions:process_new_submission_ack' submission_id=submission_to_process.id %}" method="post"> - {% csrf_token %} - {{ form.as_ul }} - <input type="submit" value="Submit" /> - </form> - - {% endif %} -</section> - -{% endblock bodysup %} diff --git a/submissions/templates/submissions/select_referee.html b/submissions/templates/submissions/select_referee.html new file mode 100644 index 0000000000000000000000000000000000000000..89c81d30a60c69023d24455a3def6aa6f84a05e0 --- /dev/null +++ b/submissions/templates/submissions/select_referee.html @@ -0,0 +1,57 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: select referee for submission{% endblock pagetitle %} + +{% block headsup %} + +{% load scipost_extras %} + +{% endblock headsup %} + +{% block bodysup %} + +<!-- Temporary strip --> +{% if user.is_authenticated %} + +<section> + <div class="flex-greybox"> + <h1>Referee Selection Page for Submission</h1> + </div> + + <hr class="hr12"> + <div class="row"> + <div class="col-4"> + <h2>Submission:</h2> + </div> + </div> + {{ submission.header_as_table|safe }} + <h3>Abstract:</h3> + <p>{{ submission.abstract }}</p> + +</section> + + +<section> + <div class="flex-greybox"> + <h1>Select an additional Referee</h1> + </div> + <form action="{% url 'submissions:select_referee' submission_id=submission.id %}" method="post"> + {% csrf_token %} + {{ ref_search_form }} + <input type="submit" value="Find referee"> + </form> + {% if contributors_found %} + <h3>Identified as contributor:</h3> + {% for contributor in contributors_found %} + <p>{{ contributor.user.first_name }} {{contributor.user.last_name }} + <a href="{% url 'submissions:send_refereeing_invitation' submission_id=submission.id contributor_id=contributor.id %}">Send refereeing invitation</a></p> + {% endfor %} + {% elif ref_search_form.has_changed %} + <p>No Contributor with this last name could be identified.</p> + {% endif %} +</section> + + +{% endif %} <!-- Temporary strip --> + +{% endblock bodysup %} diff --git a/submissions/templates/submissions/submission_detail.html b/submissions/templates/submissions/submission_detail.html index ca736098caee7f7419088d30d9bb1046e3f45b7e..86b6504a1cbd31f567ec640684e33c6e1ac906e0 100644 --- a/submissions/templates/submissions/submission_detail.html +++ b/submissions/templates/submissions/submission_detail.html @@ -8,8 +8,11 @@ <script> $(document).ready(function(){ - $("#reportsbutton").click(function(){ - $("#reportslist").toggle(); + $("#invitedreportsbutton").click(function(){ + $("#invitedreportslist").toggle(); + }); + $("#contributedreportsbutton").click(function(){ + $("#contributedreportslist").toggle(); }); $("#commentsbutton").click(function(){ $("#commentslist").toggle(); @@ -41,69 +44,134 @@ </section> -{% if reports %} +{% if invited_reports %} <section> <hr class="hr12"> <div class="flex-breybox"> - <h2>Reports on this Submission</h2> - <button id="reportsbutton">Toggle reports view</button> + <h2>Invited Reports on this Submission</h2> + <button id="invitedreportsbutton">Toggle invited reports view</button> </div> - <div id="reportslist"> - {% for report in reports %} + <div id="invitedreportslist"> + {% for report in invited_reports %} + <hr class="hr6"/> + {{ report.print_identifier }} + {% if user|is_in_group:'Editorial College' %} + {{ report.print_contents_for_editors }} + {% else %} + {{ report.print_contents }} + {% endif %} + <br/> + <h3><a href="{% url 'comments:reply_to_report' report_id=report.id %}">Reply to this Report</a> (authors only)</h3> - <hr class="hr6"> + {% for reply in author_replies %} + {% if reply.in_reply_to_report %} + {% if reply.in_reply_to_report.id = report.id %} <div class="row"> - <div class="col-3"> - <div class="reportid"> - <h3>{{ report.id }}</h3> - <h4>Date: {{ report.date_submitted }}</h4> - </div> - </div> - </div> + <div class="col-1"></div> + <hr style="border-style: dotted;" /> - <div class="row"> - <div class="col-2"> - <p>Strengths:</p> - </div> - <div class="col-10"> - <p>{{ report.strengths }}</p> - </div> - </div> - <div class="row"> - <div class="col-2"> - <p>Weaknesses:</p> - </div> - <div class="col-10"> - <p>{{ report.weaknesses }}</p> + <div class="flex-container"> + <div class="flex-commentbox"> + {{ reply.print_identifier|safe }} + {{ reply.categories_as_ul|safe }} + <div class="opinionsDisplay"> + {% if user.is_authenticated and user|is_in_group:'Registered Contributors' %} + {% if user.contributor != reply.author %} + <form action="{% url 'comments:express_opinion' comment_id=reply.id opinion='A' %}" method="post"> + {% csrf_token %} + <input type="submit" class="agree" value="Agree {{ reply.nr_A }} "/> + </form> + <form action="{% url 'comments:express_opinion' comment_id=reply.id opinion='N' %}" method="post"> + {% csrf_token %} + <input type="submit" class="notsure" value="Not sure {{ reply.nr_N }}"/> + </form> + <form action="{% url 'comments:express_opinion' comment_id=reply.id opinion='D'%}" method="post"> + {% csrf_token %} + <input type="submit" class="disagree" value="Disagree {{ reply.nr_D }}"/> + </form> + {% else %} + {{ reply.opinions_as_ul|safe }} + {% endif %} + {% endif %} + </div> + </div> </div> </div> <div class="row"> - <div class="col-2"> - <p>Report:</p> - </div> + <div class="col-1"></div> <div class="col-10"> - <p>{{ report.report }}</p> + <p>{{ reply.comment_text|linebreaks }}</p> </div> </div> + {% endif %} + {% endif %} + {% endfor %} + + {% endfor %} + </div> + +</section> +{% endif %} + +{% if contributed_reports %} +<section> + <hr class="hr12"> + <div class="flex-breybox"> + <h2>Contributed Reports on this Submission</h2> + <button id="contributedreportsbutton">Toggle contributed reports view</button> + </div> + + <div id="contributedreportslist"> + {% for report in contributed_reports %} + <hr class="hr6"/> + {{ report.print_identifier }} + {{ report.print_contents }} + + <h3><a href="{% url 'comments:reply_to_report' report_id=report.id %}">Reply to this Report</a> (authors only)</h3> + {% for reply in author_replies %} {% if reply.in_reply_to_report %} {% if reply.in_reply_to_report.id = report.id %} <div class="row"> <div class="col-1"></div> <hr style="border-style: dotted;" /> - <div class="col-3"> - {{ reply.print_identifier|safe }} + + <div class="flex-container"> + <div class="flex-commentbox"> + {{ reply.print_identifier|safe }} + {{ reply.categories_as_ul|safe }} + <div class="opinionsDisplay"> + {% if user.is_authenticated and user|is_in_group:'Registered Contributors' %} + {% if user.contributor != reply.author %} + <form action="{% url 'comments:express_opinion' comment_id=reply.id opinion='A' %}" method="post"> + {% csrf_token %} + <input type="submit" class="agree" value="Agree {{ reply.nr_A }} "/> + </form> + <form action="{% url 'comments:express_opinion' comment_id=reply.id opinion='N' %}" method="post"> + {% csrf_token %} + <input type="submit" class="notsure" value="Not sure {{ reply.nr_N }}"/> + </form> + <form action="{% url 'comments:express_opinion' comment_id=reply.id opinion='D'%}" method="post"> + {% csrf_token %} + <input type="submit" class="disagree" value="Disagree {{ reply.nr_D }}"/> + </form> + {% else %} + {{ reply.opinions_as_ul|safe }} + {% endif %} + {% endif %} + </div> + </div> </div> </div> - <div class="row"> <div class="col-1"></div> <div class="col-10"> - <p>{{ reply.reply_text|linebreaks }}</p> + <p>{{ reply.comment_text|linebreaks }}</p> </div> </div> + {% endif %} {% endif %} {% endfor %} diff --git a/submissions/templates/submissions/submit_manuscript.html b/submissions/templates/submissions/submit_manuscript.html index 616243c1a02d19d5797b4b96f0ec0c4b9d802853..3c3f1d488ee63b0a0278fc36ed2f9536cf922e18 100644 --- a/submissions/templates/submissions/submit_manuscript.html +++ b/submissions/templates/submissions/submit_manuscript.html @@ -11,6 +11,7 @@ <p id="journalsannounce">OPEN FOR SUBMISSION STARTING JUNE 2016</p> + {% if perms.scipost.can_submit_manuscript %} <form action="{% url 'submissions:submit_manuscript' %}" method="post"> {% csrf_token %} <table> @@ -18,10 +19,12 @@ {{ form.as_table }} </ul> </table> - <!-- Deactivated until launch! <input type="submit" value="Submit"/> - --> </form> + {% else %} + <h3>You are currently not allowed to submit a manuscript.</h3> + {% endif %} + </section> {% endif %} <!-- Temporary strip --> diff --git a/submissions/urls.py b/submissions/urls.py index a841c3c315e52d87613f5e3f95b4badb79827971..96927c89afc78086180a815a153cc2d463b946a9 100644 --- a/submissions/urls.py +++ b/submissions/urls.py @@ -12,8 +12,15 @@ urlpatterns = [ url(r'^(?P<submission_id>[0-9]+)/$', views.submission_detail, name='submission'), url(r'^submit_manuscript$', views.submit_manuscript, name='submit_manuscript'), url(r'^submit_manuscript_ack$', TemplateView.as_view(template_name='submissions/submit_manuscript_ack.html'), name='submit_manuscript_ack'), - url(r'^process_new_submissions$', views.process_new_submissions, name='process_new_submissions'), - url(r'^process_new_submission_ack/(?P<submission_id>[0-9]+)$', views.process_new_submission_ack, name='process_new_submission_ack'), + url(r'^assign_submissions$', views.assign_submissions, name='assign_submissions'), + url(r'^assign_submission_ack/(?P<submission_id>[0-9]+)$', views.assign_submission_ack, name='assign_submission_ack'), + url(r'^accept_or_decline_assignments$', views.accept_or_decline_assignments, name='accept_or_decline_assignments'), + url(r'^accept_or_decline_assignment_ack/(?P<assignment_id>[0-9]+)$', views.accept_or_decline_assignment_ack, name='accept_or_decline_assignment_ack'), + url(r'^editorial_page/(?P<submission_id>[0-9]+)$', views.editorial_page, name='editorial_page'), + url(r'^select_referee/(?P<submission_id>[0-9]+)$', views.select_referee, name='select_referee'), + url(r'^send_refereeing_invitation/(?P<submission_id>[0-9]+)/(?P<contributor_id>[0-9]+)$', views.send_refereeing_invitation, name='send_refereeing_invitation'), + url(r'^accept_or_decline_ref_invitations$', views.accept_or_decline_ref_invitations, name='accept_or_decline_ref_invitations'), + url(r'^accept_or_decline_ref_invitation/(?P<invitation_id>[0-9]+)$', views.accept_or_decline_ref_invitation_ack, name='accept_or_decline_ref_invitation_ack'), # Reports url(r'^submit_report/(?P<submission_id>[0-9]+)$', 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'), diff --git a/submissions/views.py b/submissions/views.py index 17b40d544b3276c3321f3f6940a060c9032d5a48..95dd3ddb7f1d854aa5b0c0f67b53ff48ffb1f42e 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -1,7 +1,8 @@ import datetime from django.utils import timezone -from django.shortcuts import get_object_or_404, render +from django.shortcuts import get_object_or_404, render, redirect from django.contrib.auth import authenticate, login, logout +from django.contrib.auth.decorators import permission_required from django.contrib.auth.models import User from django.core.mail import send_mail from django.core.urlresolvers import reverse @@ -14,8 +15,6 @@ from .forms import * from comments.models import Comment from scipost.models import Contributor, title_dict -#from scipost.forms import OpinionForm -from submissions.models import Submission from comments.forms import CommentForm @@ -25,6 +24,7 @@ from comments.forms import CommentForm # SUBMISSIONS: ############### +#@permission_required('scipost.can_submit_manuscript', raise_exception=True) def submit_manuscript(request): if request.method == 'POST': form = SubmissionForm(request.POST) @@ -50,28 +50,6 @@ def submit_manuscript(request): return render(request, 'submissions/submit_manuscript.html', {'form': form}) -def process_new_submissions(request): - submission_to_process = Submission.objects.filter(status='unassigned').first() # only handle one at at time - form = ProcessSubmissionForm() - context = {'submission_to_process': submission_to_process, 'form': form } - return render(request, 'submissions/process_new_submissions.html', context) - - -def process_new_submission_ack(request, submission_id): - if request.method == 'POST': - form = ProcessSubmissionForm(request.POST) - if form.is_valid(): - submission = Submission.objects.get(pk=submission_id) - submission.vetted = True - submission.editor_in_charge = form.cleaned_data['editor_in_charge'] - submission.status = 1 - submission.latest_activity = timezone.now() - submission.save() - - context = {} - return render(request, 'submissions/process_new_submission_ack.html', context) - - def submissions(request): if request.method == 'POST': form = SubmissionSearchForm(request.POST) @@ -134,7 +112,6 @@ def submission_detail(request, submission_id): author = Contributor.objects.get(user=request.user) newcomment = Comment ( submission = submission, - in_reply_to = None, author = author, is_rem = form.cleaned_data['is_rem'], is_que = form.cleaned_data['is_que'], @@ -161,12 +138,132 @@ def submission_detail(request, submission_id): author_replies = Comment.objects.filter(submission=submission, is_author_reply=True) except Comment.DoesNotExist: author_replies = () - context = {'submission': submission, 'comments': comments.filter(status__gte=1).order_by('-date_submitted'), - 'reports': reports.filter(status__gte=1), 'author_replies': author_replies, + context = {'submission': submission, 'comments': comments.filter(status__gte=1, is_author_reply=False).order_by('-date_submitted'), + 'invited_reports': reports.filter(status__gte=1, invited=True), + 'contributed_reports': reports.filter(status__gte=1, invited=False), + 'author_replies': author_replies, 'form': form, } return render(request, 'submissions/submission_detail.html', context) +###################### +# Editorial workflow # +###################### + + +def assign_submissions(request): + submission_to_assign = Submission.objects.filter(status='unassigned').first() # only handle one at at time +# form = AssignSubmissionForm(discipline=submission_to_assign.discipline, specialization=submission_to_assign.specialization) + form = AssignSubmissionForm(discipline=submission_to_assign.discipline) + context = {'submission_to_assign': submission_to_assign, 'form': form } + return render(request, 'submissions/assign_submissions.html', context) + + +def assign_submission_ack(request, submission_id): + submission = Submission.objects.get(pk=submission_id) + if request.method == 'POST': + form = AssignSubmissionForm(request.POST, discipline=submission.discipline) + if form.is_valid(): + editor_in_charge = form.cleaned_data['editor_in_charge'] + ed_assignment = EditorialAssignment(submission=submission, + to=editor_in_charge, + date_created=timezone.now()) + ed_assignment.save() + submission.assigned = True + submission.assignment = ed_assignment + submission.status = 'assigned' + submission.latest_activity = timezone.now() + submission.save() + context = {} + return render(request, 'submissions/assign_submission_ack.html', context) + + +def accept_or_decline_assignments(request): + contributor = Contributor.objects.get(user=request.user) + assignment = EditorialAssignment.objects.filter(to=contributor, accepted=None).first() + form = ConsiderAssignmentForm() + context = {'assignment_to_consider': assignment, 'form': form} + return render(request, 'submissions/accept_or_decline_assignments.html', context) + + +def accept_or_decline_assignment_ack(request, assignment_id): + contributor = Contributor.objects.get(user=request.user) + assignment = get_object_or_404 (EditorialAssignment, pk=assignment_id) + if request.method == 'POST': + form = ConsiderAssignmentForm(request.POST) + if form.is_valid(): + assignment.date_answered = timezone.now() + if form.cleaned_data['accept'] == 'True': + assignment.accepted = True + assignment.to = contributor + else: + assignment.accepted = False + assignment.refusal_reason = form.cleaned_data['refusal_reason'] + assignment.save() + + context = {'assignment': assignment} + return render(request, 'submissions/accept_or_decline_assignment_ack.html', context) + + +def editorial_page(request, submission_id): + submission = get_object_or_404(Submission, pk=submission_id) + ref_invitations = RefereeInvitation.objects.filter(submission=submission) + context = {'submission': submission, 'ref_invitations': ref_invitations} + return render(request, 'submissions/editorial_page.html', context) + + +def select_referee(request, submission_id): + submission = get_object_or_404(Submission, pk=submission_id) + if request.method == 'POST': + ref_search_form = RefereeSelectForm(request.POST) + if ref_search_form.is_valid(): + contributors_found = Contributor.objects.filter(user__last_name=ref_search_form.cleaned_data['last_name']) + else: + ref_search_form = RefereeSelectForm() + contributors_found = None + context = {'submission': submission, 'ref_search_form': ref_search_form, 'contributors_found': contributors_found} + return render(request, 'submissions/select_referee.html', context) + + +def send_refereeing_invitation(request, submission_id, contributor_id): + submission = get_object_or_404(Submission, pk=submission_id) + contributor = get_object_or_404(Contributor, pk=contributor_id) + invitation = RefereeInvitation(submission=submission, + referee=contributor, title=contributor.title, + first_name=contributor.user.first_name, last_name=contributor.user.last_name, + email_address=contributor.user.email, + date_invited=timezone.now(), + invited_by = request.user.contributor) + invitation.save() + return redirect(reverse('submissions:editorial_page', kwargs={'submission_id': submission_id})) + + +def accept_or_decline_ref_invitations(request): + contributor = Contributor.objects.get(user=request.user) + invitation = RefereeInvitation.objects.filter(referee=contributor, accepted=None).first() + form = ConsiderRefereeInvitationForm() + context = {'invitation_to_consider': invitation, 'form': form} + return render(request, 'submissions/accept_or_decline_ref_invitations.html', context) + + +def accept_or_decline_ref_invitation_ack(request, invitation_id): + contributor = Contributor.objects.get(user=request.user) + invitation = get_object_or_404 (RefereeInvitation, pk=invitation_id) + if request.method == 'POST': + form = ConsiderRefereeInvitationForm(request.POST) + if form.is_valid(): + invitation.date_responded = timezone.now() + if form.cleaned_data['accept'] == 'True': + invitation.accepted = True + else: + invitation.accepted = False + invitation.refusal_reason = form.cleaned_data['refusal_reason'] + invitation.save() + + context = {'invitation': invitation} + return render(request, 'submissions/accept_or_decline_ref_invitation_ack.html', context) + + ########### # Reports @@ -178,9 +275,11 @@ def submit_report(request, submission_id): form = ReportForm(request.POST) if form.is_valid(): author = Contributor.objects.get(user=request.user) + invited = RefereeInvitation.objects.filter(submission=submission, referee=request.user.contributor).exists() newreport = Report ( submission = submission, author = author, + invited = invited, qualification = form.cleaned_data['qualification'], strengths = form.cleaned_data['strengths'], weaknesses = form.cleaned_data['weaknesses'], @@ -193,6 +292,8 @@ def submit_report(request, submission_id): formatting = form.cleaned_data['formatting'], grammar = form.cleaned_data['grammar'], recommendation = form.cleaned_data['recommendation'], + remarks_for_editors = form.cleaned_data['remarks_for_editors'], + anonymous = form.cleaned_data['anonymous'], date_submitted = timezone.now(), ) newreport.save()