diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py index 013e1aebd949946a97e5e7ecbaddc2b27d4902e3..717768e786de473b2a0e6ec5cbb5c232e2af0999 100644 --- a/SciPost_v1/settings/base.py +++ b/SciPost_v1/settings/base.py @@ -81,7 +81,6 @@ INSTALLED_APPS = ( 'django.contrib.sites', 'django_countries', 'django_extensions', - # 'django_mathjax', 'affiliations', 'ajax_select', 'captcha', @@ -94,7 +93,6 @@ INSTALLED_APPS = ( 'finances', 'guides', 'guardian', - # 'haystack', 'invitations', 'journals', 'mailing_lists', @@ -113,6 +111,7 @@ INSTALLED_APPS = ( 'submissions', 'theses', 'virtualmeetings', + 'ontology', 'organizations', 'profiles', # TODO: partners to be deprecated in favour of sponsors @@ -153,9 +152,6 @@ SPHINXDOC_PROTECTED_PROJECTS = { 'developers': ['scipost.can_view_docs_scipost'], } -CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.math_challenge' -CAPTCHA_LETTER_ROTATION = (-15, 15) -CAPTCHA_NOISE_FUNCTIONS = ('captcha.helpers.noise_dots',) SHELL_PLUS_POST_IMPORTS = ( ('theses.factories', ('ThesisLinkFactory')), @@ -206,6 +202,7 @@ TEMPLATES = [ 'django.template.context_processors.media', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', + 'journals.context_processors.journals_processor', ], }, }, @@ -318,7 +315,6 @@ DOAJ_API_KEY = '' # https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha-v2-what-should-i-do RECAPTCHA_PUBLIC_KEY = '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI' RECAPTCHA_PRIVATE_KEY = '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe' -NOCAPTCHA = True # PASSWORDS diff --git a/SciPost_v1/signalprocessors.py b/SciPost_v1/signalprocessors.py index b5f522655a1e49149b21d4cf1026977c224ed6b5..580efaa545400187fdf03036a9e04f8c76fb1823 100644 --- a/SciPost_v1/signalprocessors.py +++ b/SciPost_v1/signalprocessors.py @@ -47,7 +47,7 @@ class AutoSearchIndexingProcessor(signals.RealtimeSignalProcessor): if isinstance(instance, Submission): # Submission have complex status handling, so a status change should lead to # more drastic reindexing. - self.remove_objects_indexes(sender, instance.thread) + self.remove_objects_indexes(sender, instance.thread.public()) self.update_instance_indexes(sender, instance) else: # Objects such as Reports, Comments, Commentaries, etc. may get rejected. This diff --git a/SciPost_v1/urls.py b/SciPost_v1/urls.py index 8a231d8c6d0a2ed0e017db8b40b4fabb88fb241c..0840aa4ce343a40cc0a942c058e01a17201b5596 100644 --- a/SciPost_v1/urls.py +++ b/SciPost_v1/urls.py @@ -58,6 +58,7 @@ urlpatterns = [ url(r'^meetings/', include('virtualmeetings.urls', namespace="virtualmeetings")), url(r'^news/', include('news.urls', namespace="news")), url(r'^notifications/', include('notifications.urls', namespace="notifications")), + url(r'^ontology/', include('ontology.urls', namespace="ontology")), url(r'^organizations/', include('organizations.urls', namespace="organizations")), url(r'^petitions/', include('petitions.urls', namespace="petitions")), url(r'^preprints/', include('preprints.urls', namespace="preprints")), diff --git a/affiliations/signals.py b/affiliations/signals.py index ba0ce268c4f12e8210d5434cd85d0a9a565b9a40..e36a5984c04749d74396aa8e22a8d069a070f3e0 100644 --- a/affiliations/signals.py +++ b/affiliations/signals.py @@ -9,9 +9,7 @@ from notifications.signals import notify def notify_new_affiliation(sender, instance, created, **kwargs): - """ - Notify the SciPost Administration about a new Affiliation created to check it. - """ + """Notify the SciPost Administration about a new Affiliation created to check it.""" if created: administrators = User.objects.filter(groups__name='SciPost Administrators') actor, __ = FakeActors.objects.get_or_create(name='A SciPost user') diff --git a/celerybeat.pid b/celerybeat.pid deleted file mode 100644 index 887a0ee2e965376d0a117e5a25d0856364dfa292..0000000000000000000000000000000000000000 --- a/celerybeat.pid +++ /dev/null @@ -1 +0,0 @@ -30193 diff --git a/colleges/templates/colleges/_potentialfellowship_card.html b/colleges/templates/colleges/_potentialfellowship_card.html index bef5812085a189c9488bb2c8b9fb2532a7122b7c..35737a4620c79577cd08ea756b3520d51523915c 100644 --- a/colleges/templates/colleges/_potentialfellowship_card.html +++ b/colleges/templates/colleges/_potentialfellowship_card.html @@ -2,37 +2,47 @@ <div class="card-body"> <div class="row"> - <div class="col-6"> - {% include 'profiles/_profile_card.html' with profile=potfel.profile %} + <div class="col-12"> + <h3 class="highlight">Potential Fellowship details for {{ potfel }}</h3> + <div id="profileAccordion"> + <div class="card"> + <div class="card-header" id="potfelProfile"> + <h4 class="mb-0"> + <button class="btn btn-link" data-toggle="collapse" data-target="#collapseProfile" aria-expanded="true" aria-controls="collapseProfile"> + View Profile + </button> + </h4> + </div> + <div id="collapseProfile" class="collapse" aria-labelledby="potfelProfile" data-parent="#profileAccordion"> + <div class="card-body"> + {% include 'profiles/_profile_card.html' with profile=potfel.profile %} + </div> + </div> + </div> + </div> </div> - <div class="col-6"> + </div> + <div class="row"> + <div class="col-md-6"> <ul> <li><a href="{% url 'colleges:potential_fellowship_update' pk=potfel.id %}">Update</a> the data</li> <li><a href="{% url 'colleges:potential_fellowship_delete' pk=potfel.id %}">Delete</a> this Potential Fellowship</li> <li><a href="{% url 'colleges:potential_fellowship_email_initial' pk=potfel.id %}">Prepare and send initial email</a></li> </ul> - </div> - </div> - <div class="row"> - <div class="col-md-6 ml-auto"> - <h3>Events</h3> - <ul> - {% for event in potfel.potentialfellowshipevent_set.all %} - {% include 'colleges/_potentialfellowship_event_li.html' with event=event %} - {% empty %} - <li>No events found.</li> - {% endfor %} - </ul> </div> - <div class="col-md-5"> + <div class="col-md-6"> <h3>Update the status of this Potential Fellowship</h3> <form class="d-block mt-2 mb-3" action="{% url 'colleges:potential_fellowship_update_status' pk=potfel.id %}" method="post"> {% csrf_token %} {{ pfstatus_form|bootstrap }} <input type="submit" name="submit" value="Update status" class="btn btn-outline-secondary"> </form> - <hr/> + </div> + </div> + + <div class="row"> + <div class="col-md-6"> <h3>Add an event for this Potential Fellowship</h3> <form class="d-block mt-2 mb-3" action="{% url 'colleges:potential_fellowship_event_create' pk=potfel.id %}" method="post"> {% csrf_token %} @@ -40,5 +50,15 @@ <input type="submit" name="submit" value="Submit" class="btn btn-outline-secondary"> </form> </div> + <div class="col-md-6"> + <h3>Events</h3> + <ul> + {% for event in potfel.potentialfellowshipevent_set.all %} + {% include 'colleges/_potentialfellowship_event_li.html' with event=event %} + {% empty %} + <li>No events found.</li> + {% endfor %} + </ul> + </div> </div> </div> diff --git a/colleges/templates/colleges/base.html b/colleges/templates/colleges/base.html new file mode 100644 index 0000000000000000000000000000000000000000..b02dd091ee5a212cbd810f90f4554beaba8ee882 --- /dev/null +++ b/colleges/templates/colleges/base.html @@ -0,0 +1,13 @@ +{% extends 'scipost/base.html' %} + +{% block breadcrumb %} + <div class="container-outside header"> + <div class="container"> + <nav class="breadcrumb hidden-sm-down"> + {% block breadcrumb_items %} + <a href="{% url 'colleges:potential_fellowships' %}" class="breadcrumb-item">Potential Fellowships</a> + {% endblock %} + </nav> + </div> + </div> +{% endblock %} diff --git a/colleges/templates/colleges/potentialfellowship_confirm_delete.html b/colleges/templates/colleges/potentialfellowship_confirm_delete.html index 36f86e25ab8d008a53c431b18e7af9613c755846..6f010dc92acb888cecece40988193b9938ae0adb 100644 --- a/colleges/templates/colleges/potentialfellowship_confirm_delete.html +++ b/colleges/templates/colleges/potentialfellowship_confirm_delete.html @@ -1,4 +1,4 @@ -{% extends 'scipost/base.html' %} +{% extends 'colleges/base.html' %} {% load bootstrap %} diff --git a/colleges/templates/colleges/potentialfellowship_detail.html b/colleges/templates/colleges/potentialfellowship_detail.html new file mode 100644 index 0000000000000000000000000000000000000000..9357ec706277609c0fb3f563a2e8e5fc68d5e93a --- /dev/null +++ b/colleges/templates/colleges/potentialfellowship_detail.html @@ -0,0 +1,21 @@ +{% extends 'colleges/base.html' %} + +{% load bootstrap %} + +{% block breadcrumb_items %} + {{ block.super }} +<span class="breadcrumb-item">Details</span> +{% endblock %} + +{% load scipost_extras %} + +{% block pagetitle %}: Potential Fellowship details{% endblock pagetitle %} + +{% block content %} +<div class="row"> + <div class="col-12"> + {% include 'colleges/_potentialfellowship_card.html' with potfel=object pfevent_form=pfevent_form %} + </div> +</div> + +{% endblock content %} diff --git a/colleges/templates/colleges/potentialfellowship_form.html b/colleges/templates/colleges/potentialfellowship_form.html index fa4e43c4fa4388ca9edec525e47d47fb7bada765..a80217f8798440bf22e9826c05c1ab07b9242826 100644 --- a/colleges/templates/colleges/potentialfellowship_form.html +++ b/colleges/templates/colleges/potentialfellowship_form.html @@ -1,7 +1,12 @@ -{% extends 'scipost/base.html' %} +{% extends 'colleges/base.html' %} {% load bootstrap %} +{% block breadcrumb_items %} + {{ block.super }} + <span class="breadcrumb-item">{% if form.instance.id %}Update{% else %}Add new{% endif %} Potential Fellowship</span> +{% endblock %} + {% block pagetitle %}: Potential Fellowships{% endblock pagetitle %} {% block content %} diff --git a/colleges/templates/colleges/potentialfellowship_list.html b/colleges/templates/colleges/potentialfellowship_list.html index 9b3cd2b9f5cd06b8e46158f938f1b209ad8ee291..0fa8ce291258052c86a14a6dc9d20599693b536f 100644 --- a/colleges/templates/colleges/potentialfellowship_list.html +++ b/colleges/templates/colleges/potentialfellowship_list.html @@ -1,14 +1,27 @@ -{% extends 'scipost/base.html' %} +{% extends 'colleges/base.html' %} {% load scipost_extras %} +{% load colleges_extras %} {% load bootstrap %} +{% block headsup %} +<script type="text/javascript"> +$(document).ready(function($) { + $(".table-row").click(function() { + window.document.location = $(this).data("href"); + }); +}); +</script> +{% endblock headsup %} + {% block pagetitle %}: Potential Fellowships{% endblock pagetitle %} {% block content %} <div class="row"> <div class="col-12"> + <a href="{% url 'colleges:potential_fellowship_create' %}">Add a Potential Fellowship</a> + <br/><br/> <p> <ul class="list-inline"> <li class="list-inline-item"> @@ -34,8 +47,6 @@ <div class="row"> <div class="col-12"> - <a href="{% url 'colleges:potential_fellowship_create' %}">Add a Potential Fellowship</a> - <br/><br/> {% if view.kwargs.discipline %} <h3>Potential Fellowships in {{ view.kwargs.discipline }}{% if view.kwargs.expertise %}, {{ view.kwargs.expertise }}{% endif %}:</h3> <br/> @@ -49,9 +60,9 @@ <th>Status</th> </tr> </thead> - <tbody id="accordion" role="tablist" aria-multiselectable="true"> + <tbody> {% for potfel in object_list %} - <tr data-toggle="collapse" data-parent="#accordion" href="#collapse{{ potfel.id }}" aria-expanded="false" aria-controls="collapse{{ potfel.id }}" style="cursor: pointer;"> + <tr class="table-row" data-href="{% url 'colleges:potential_fellowship_detail' pk=potfel.id %}" target="_blank" style="cursor: pointer;"> <td>{{ potfel.profile.last_name }}, {{ potfel.profile.get_title_display }} {{ potfel.profile.first_name }}</td> <td>{{ potfel.profile.get_discipline_display }}</td> <td> @@ -59,12 +70,7 @@ <div class="single d-inline" data-specialization="{{expertise|lower}}" data-toggle="tooltip" data-placement="bottom" title="{{expertise|get_specialization_display}}">{{expertise|get_specialization_code}}</div> {% endfor %} </td> - <td>{{ potfel.get_status_display }}</td> - </tr> - <tr id="collapse{{ potfel.id }}" class="collapse" role="tabpanel" aria-labelledby="heading{{ potfel.id }}" style="background-color: #fff;"> - <td colspan="4"> - {% include 'colleges/_potentialfellowship_card.html' with potfel=potfel pfevent_form=pfevent_form %} - </td> + <td style="color: #ffffff; background-color:{{ potfel.status|potfelstatuscolor }};">{{ potfel.get_status_display }}</td> </tr> {% empty %} <tr> @@ -73,6 +79,13 @@ {% endfor %} </tbody> </table> + + {% if is_paginated %} + <div class="col-12"> + {% include 'partials/pagination.html' with page_obj=page_obj %} + </div> + {% endif %} + </div> </div> {% endblock content %} diff --git a/colleges/templatetags/colleges_extras.py b/colleges/templatetags/colleges_extras.py new file mode 100644 index 0000000000000000000000000000000000000000..d0a10eceb9560daad8525c3d9e6be8ebda4c75ef --- /dev/null +++ b/colleges/templatetags/colleges_extras.py @@ -0,0 +1,48 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from django import template + +from ..constants import ( + POTENTIAL_FELLOWSHIP_IDENTIFIED, POTENTIAL_FELLOWSHIP_INVITED, POTENTIAL_FELLOWSHIP_REINVITED, + POTENTIAL_FELLOWSHIP_MULTIPLY_REINVITED, POTENTIAL_FELLOWSHIP_DECLINED, + POTENTIAL_FELLOWSHIP_UNRESPONSIVE, POTENTIAL_FELLOWSHIP_RETIRED, POTENTIAL_FELLOWSHIP_DECEASED, + POTENTIAL_FELLOWSHIP_INTERESTED, POTENTIAL_FELLOWSHIP_REGISTERED, + POTENTIAL_FELLOWSHIP_ACTIVE_IN_COLLEGE, POTENTIAL_FELLOWSHIP_SCIPOST_EMERITUS + ) + +from common.utils import hslColorWheel + + +register = template.Library() + + +@register.filter(name='potfelstatuscolor') +def potfelstatuscolor(status): + color = '#333333' + if status == POTENTIAL_FELLOWSHIP_IDENTIFIED: + color = hslColorWheel(12, 8) + elif status == POTENTIAL_FELLOWSHIP_INVITED: + color = hslColorWheel(12, 9) + elif status == POTENTIAL_FELLOWSHIP_REINVITED: + color = hslColorWheel(12, 10) + elif status == POTENTIAL_FELLOWSHIP_MULTIPLY_REINVITED: + color = hslColorWheel(12, 11) + elif status == POTENTIAL_FELLOWSHIP_DECLINED: + color = hslColorWheel(12, 0) + elif status == POTENTIAL_FELLOWSHIP_UNRESPONSIVE: + color = hslColorWheel(12, 1) + elif status == POTENTIAL_FELLOWSHIP_RETIRED: + color = hslColorWheel(12, 1, 75) + elif status == POTENTIAL_FELLOWSHIP_DECEASED: + color = hslColorWheel(12, 1, 10) + elif status == POTENTIAL_FELLOWSHIP_INTERESTED: + color = hslColorWheel(12, 2) + elif status == POTENTIAL_FELLOWSHIP_REGISTERED: + color = hslColorWheel(12, 3) + elif status == POTENTIAL_FELLOWSHIP_ACTIVE_IN_COLLEGE: + color = hslColorWheel(12, 4) + elif status == POTENTIAL_FELLOWSHIP_SCIPOST_EMERITUS: + color = hslColorWheel(12, 4, 40, 40) + return color diff --git a/colleges/urls.py b/colleges/urls.py index fd290b952cc27883ac2b9bdd2794fd194a69b4c8..dc6fb8c3501531edd64a093e1e439306300ff2a8 100644 --- a/colleges/urls.py +++ b/colleges/urls.py @@ -65,15 +65,20 @@ urlpatterns = [ name='potential_fellowship_delete' ), url( - r'^potentialfellowships/(?P<pk>[0-9]+)/events/add$', + r'^potentialfellowships/(?P<pk>[0-9]+)/events/add/$', views.PotentialFellowshipEventCreateView.as_view(), name='potential_fellowship_event_create' ), url( - r'^potentialfellowships/(?P<pk>[0-9]+)/email/$', + r'^potentialfellowships/(?P<pk>[0-9]+)/email_initial/$', views.PotentialFellowshipInitialEmailView.as_view(), name='potential_fellowship_email_initial' ), + url( + r'^potentialfellowships/(?P<pk>[0-9]+)/$', + views.PotentialFellowshipDetailView.as_view(), + name='potential_fellowship_detail' + ), url( r'^potentialfellowships/(?P<discipline>[a-zA-Z]+)/(?P<expertise>[a-zA-Z:]+)/$', views.PotentialFellowshipListView.as_view(), diff --git a/colleges/views.py b/colleges/views.py index 293e04dd9596f619c44a114d5924238f14114aad..d62a106bc805a2ff34c0c6ce49b1fb562254e153 100644 --- a/colleges/views.py +++ b/colleges/views.py @@ -8,6 +8,7 @@ from django.shortcuts import get_object_or_404, render, redirect from django.core.urlresolvers import reverse, reverse_lazy from django.utils import timezone from django.utils.decorators import method_decorator +from django.views.generic.detail import DetailView from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.views.generic.list import ListView @@ -24,7 +25,7 @@ from .forms import FellowshipForm, FellowshipTerminateForm, FellowshipRemoveSubm from .models import Fellowship, PotentialFellowship, PotentialFellowshipEvent from scipost.constants import SCIPOST_SUBJECT_AREAS -from scipost.mixins import PermissionsMixin +from scipost.mixins import PermissionsMixin, PaginationMixin from mails.forms import EmailTemplateForm from mails.views import MailView @@ -352,7 +353,7 @@ class PotentialFellowshipDeleteView(PermissionsMixin, DeleteView): success_url = reverse_lazy('colleges:potential_fellowships') -class PotentialFellowshipListView(PermissionsMixin, ListView): +class PotentialFellowshipListView(PermissionsMixin, PaginationMixin, ListView): """ List the PotentialFellowship object instances. """ @@ -374,6 +375,15 @@ class PotentialFellowshipListView(PermissionsMixin, ListView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['subject_areas'] = SCIPOST_SUBJECT_AREAS + return context + + +class PotentialFellowshipDetailView(PermissionsMixin, DetailView): + permission_required = 'scipost.can_manage_college_composition' + model = PotentialFellowship + + def get_context_data(self, *args, **kwargs): + context = super().get_context_data(*args, **kwargs) context['pfstatus_form'] = PotentialFellowshipStatusForm() context['pfevent_form'] = PotentialFellowshipEventForm() return context diff --git a/commentaries/templates/commentaries/base.html b/commentaries/templates/commentaries/base.html index d861de9cdc088e774345c078ee0b5d4fa36acb89..bf1617386494a8cb1c10c79a8c429895175bc807 100644 --- a/commentaries/templates/commentaries/base.html +++ b/commentaries/templates/commentaries/base.html @@ -1,7 +1,7 @@ {% extends 'scipost/base.html' %} {% block breadcrumb %} - <div class="container-outside header"> + <div class="breadcrumb-container"> <div class="container"> <nav class="breadcrumb"> {% block breadcrumb_items %} diff --git a/commentaries/templates/commentaries/commentary_list.html b/commentaries/templates/commentaries/commentary_list.html index c5fb016a6fe1fba8005fddeedf2935978c935e7e..712ba8f2ed76d68505ff972ba39a56e40a3f147b 100644 --- a/commentaries/templates/commentaries/commentary_list.html +++ b/commentaries/templates/commentaries/commentary_list.html @@ -12,33 +12,31 @@ {% block content %} <div class="row"> <div class="col-md-4"> - <div class="card card-grey"> - <div class="card-body min-height-190"> - <h1 class="card-title">SciPost Commentaries</h1> - <h3><a href="{% url 'commentaries:howto' %}">SciPost Commentaries how-to</a></h3> - <h3><a href="{% url 'commentaries:request_commentary' %}">Request a new Commentary Page</a></h3> - </div> + <div class="p-3 mb-3 bg-light scipost-bar border min-height-190"> + <h1 class="mb-3">SciPost Commentaries</h1> + <h4> + <a href="{% url 'commentaries:howto' %}">SciPost Commentaries how-to</a> + </h4> + <h3> + <a href="{% url 'commentaries:request_commentary' %}">Request a new Commentary Page</a> + </h3> </div> </div> <div class="col-md-4"> - <div class="card card-grey"> - <div class="card-body min-height-190"> - <h2 class="card-title">Search SciPost Commentaries:</h2> - <form action="{% url 'commentaries:commentaries' %}" class="small" method="get"> - {{ form|bootstrap:'4,8,sm' }} + <div class="p-3 mb-3 bg-light scipost-bar border min-height-190"> + <h2>Search SciPost Commentaries:</h2> + <form action="{% url 'commentaries:commentaries' %}" class="small" method="get"> + {{ form|bootstrap:'4,8,sm' }} <input class="btn btn-outline-secondary" type="submit" value="Search"/> - </form> - </div> + </form> </div> </div> <div class="col-md-4"> - <div class="card card-grey"> - <div class="card-body min-height-190"> - <h2 class="card-title">View SciPost Commentaries</h2> - <ul> - <li>Physics: last <a href="{% url 'commentaries:browse' discipline='physics' nrweeksback=1 %}">week</a>, <a href="{% url 'commentaries:browse' discipline='physics' nrweeksback=4 %}">month</a> or <a href="{% url 'commentaries:browse' discipline='physics' nrweeksback=52 %}">year</a> </li> - </ul> - </div> + <div class="p-3 mb-3 bg-light scipost-bar border min-height-190"> + <h2>View SciPost Commentaries</h2> + <ul> + <li>Physics: last <a href="{% url 'commentaries:browse' discipline='physics' nrweeksback=1 %}">week</a>, <a href="{% url 'commentaries:browse' discipline='physics' nrweeksback=4 %}">month</a> or <a href="{% url 'commentaries:browse' discipline='physics' nrweeksback=52 %}">year</a> </li> + </ul> </div> </div> </div> diff --git a/commentaries/templates/commentaries/request_arxiv_preprint.html b/commentaries/templates/commentaries/request_arxiv_preprint.html index 2aef0a6af7905c21f74f206cedb3c5fade9a2737..3586fc21355b7cbf63e84bc10d9f241bf25311aa 100644 --- a/commentaries/templates/commentaries/request_arxiv_preprint.html +++ b/commentaries/templates/commentaries/request_arxiv_preprint.html @@ -8,7 +8,7 @@ <div class="row"> <div class="col"> - <div class="card card-grey"> + <div class="card card-gray"> <div class="card-body"> <h1 class="card-title">Request Activation of a Commentary Page</h1> <a href="{% url 'commentaries:request_published_article' %}">Click here to request a Commentary Page on a published article</a> diff --git a/commentaries/templates/commentaries/request_published_article.html b/commentaries/templates/commentaries/request_published_article.html index d630c9662faf7491da22ad36062f670482f15953..1c8a42f7e5d5b39ac160fb696d1c97cfb557cab4 100644 --- a/commentaries/templates/commentaries/request_published_article.html +++ b/commentaries/templates/commentaries/request_published_article.html @@ -9,7 +9,7 @@ <div class="row"> <div class="col-12"> - <div class="card card-grey"> + <div class="card card-gray"> <div class="card-body"> <h1 class="card-title">Request Activation of a Commentary Page</h1> <a href="{% url 'commentaries:request_arxiv_preprint' %}">Click here to request a Commentary Page on an arXiv preprint</a> diff --git a/comments/forms.py b/comments/forms.py index 5cf2285e16822c25c14525b99e648f8533bfa255..e9ec07e3e4ce6a08e627108737331bcc077647ea 100644 --- a/comments/forms.py +++ b/comments/forms.py @@ -4,7 +4,8 @@ __license__ = "AGPL v3" from django import forms -from .constants import COMMENT_ACTION_CHOICES, COMMENT_REFUSAL_CHOICES +from .constants import COMMENT_ACTION_CHOICES, COMMENT_ACTION_REFUSE, \ + COMMENT_REFUSAL_CHOICES, COMMENT_REFUSAL_EMPTY from .models import Comment @@ -35,3 +36,13 @@ class VetCommentForm(forms.Form): refusal_reason = forms.ChoiceField(choices=COMMENT_REFUSAL_CHOICES) email_response_field = forms.CharField(widget=forms.Textarea(), label='Justification (optional)', required=False) + + def clean(self): + """ + If the comment is refused, make sure a valid refusal reason is given. + """ + data = super().clean() + if data['action_option'] == str(COMMENT_ACTION_REFUSE): + if data['refusal_reason'] == str(COMMENT_REFUSAL_EMPTY): + self.add_error(None, 'Please choose a valid refusal reason') + return data diff --git a/comments/models.py b/comments/models.py index 689ec70074d8eeeef0ff73dd1bd1e9f622907396..482df7a1e2e550b4b4dd91b3ed1feaa48d41fe3d 100644 --- a/comments/models.py +++ b/comments/models.py @@ -99,10 +99,7 @@ class Comment(TimeStampedModel): @property def title(self): - """ - This property is (mainly) used to let Comments get the title of the Submission without - annoying logic. - """ + """Get title of Submission if Comment is pointed to Submission.""" try: return self.content_object.title except: @@ -110,7 +107,7 @@ class Comment(TimeStampedModel): @cached_property def core_content_object(self): - # Import here due to circular import errors + """Return object Comment is pointed to.""" from commentaries.models import Commentary from submissions.models import Submission, Report from theses.models import ThesisLink @@ -143,10 +140,6 @@ class Comment(TimeStampedModel): """Check if Comment is rejected.""" return self.status in [STATUS_UNCLEAR, STATUS_INCORRECT, STATUS_NOT_USEFUL] - def create_doi_label(self): - self.doi_label = 'SciPost.Comment.' + str(self.id) - self.save() - @property def doi_string(self): if self.doi_label: @@ -154,6 +147,10 @@ class Comment(TimeStampedModel): else: return None + def create_doi_label(self): + self.doi_label = 'SciPost.Comment.' + str(self.id) + self.save() + def get_absolute_url(self): return self.content_object.get_absolute_url().split('#')[0] + '#comment_id' + str(self.id) diff --git a/comments/templates/comments/_add_comment_form.html b/comments/templates/comments/_add_comment_form.html index d2b541f7686639c048510843bda3f9cebeddf931..e22027838b182e90bd61220ca359efeb56edb508 100644 --- a/comments/templates/comments/_add_comment_form.html +++ b/comments/templates/comments/_add_comment_form.html @@ -64,7 +64,7 @@ <div class="col-12"> {% if form.anonymous %} {{ form.anonymous|bootstrap }} - <div class="anonymous-alert" style="display: none;"> + <div class="anonymous-alert my-3" style="display: none;"> <h3 class="anonymous-yes">Your Comment will remain anonymous.</h3> <h3 class="anonymous-no"><span class="text-danger">Your Comment will be <span class="text-underline">signed</span>.</span> Thank you very much!</h3> </div> diff --git a/comments/templates/comments/_comment_identifier.html b/comments/templates/comments/_comment_identifier.html index 618e5fca7d342a261902de36609cc5c864528968..5440283a3eeabb23f05c760f661bac2b7b81fb94 100644 --- a/comments/templates/comments/_comment_identifier.html +++ b/comments/templates/comments/_comment_identifier.html @@ -5,13 +5,15 @@ {% is_edcol_admin request.user as is_edcol_admin %} <div class="commentid" id="comment_id{{ comment.id }}"> - <h3> + {% if request.user.contributor and request.user.contributor == comment.core_content_object.editor_in_charge or is_edcol_admin %} {% if request.user|is_possible_author_of_submission:comment.core_content_object %} - Anonymous on {{comment.date_submitted|date:'Y-m-d'}} + <h3>Anonymous on {{comment.date_submitted|date:'Y-m-d'}}</h3> {% else %} - {% if comment.anonymous %}(chose public anonymity) {% endif %}<a href="{{ comment.author.get_absolute_url }}">{{ comment.author.user.first_name }} {{ comment.author.user.last_name }}</a> - on {{ comment.date_submitted|date:'Y-m-d' }} + <a href="{{ comment.author.get_absolute_url }}">{{ comment.author.user.first_name }} {{ comment.author.user.last_name }}</a> + on {{ comment.date_submitted|date:'Y-m-d' }} + <br> + {% if comment.anonymous %}<small class="text-danger py-2">Chose public anonymity</small>{% endif %} {% endif %} {% elif comment.anonymous %} Anonymous on {{comment.date_submitted|date:'Y-m-d'}} diff --git a/comments/templates/comments/_single_comment.html b/comments/templates/comments/_single_comment.html index 5c1137c2e4223106ef50268e61fc51e1a94b724d..bf3de128b768df2a44184475fb7773e4051370e3 100644 --- a/comments/templates/comments/_single_comment.html +++ b/comments/templates/comments/_single_comment.html @@ -11,7 +11,9 @@ {% include 'comments/_comment_categories.html' with comment=comment %} - {% include 'comments/_comment_voting_form.html' with comment=comment perms=perms user=user %} + {% if not hide_votes %} + {% include 'comments/_comment_voting_form.html' with comment=comment perms=perms user=user %} + {% endif %} <p class="my-3 pb-2"> {{ comment.comment_text|linebreaksbr }} diff --git a/comments/templates/comments/_vet_comment_form.html b/comments/templates/comments/_vet_comment_form.html index 2a0c768c731fc75a1d987a9ed215da593181ad42..e37e21f0e821b04cfb8c04ee9096a81eedb48025 100644 --- a/comments/templates/comments/_vet_comment_form.html +++ b/comments/templates/comments/_vet_comment_form.html @@ -3,48 +3,49 @@ {% load file_extentions %} {% load comment_extras %} -<div class="card card-vetting"> - <div class="card-header"> - <h2>From {{comment.core_content_object|get_core_content_type|capfirst}} (<a href="{{comment.get_absolute_url}}" target="_blank">link</a>)</h2> +<div> + <h3>Details of the related {{comment.core_content_object|get_core_content_type|capfirst}}</h3> + <a href="{{comment.get_absolute_url}}" target="_blank">See detail page</a> + <div class="py-2"> {% get_summary_template comment.core_content_object %} </div> - <div class="card-body"> - <h2 class="card-title">The Comment to be vetted:</h2> + <hr class="small"> - <div class="row"> - <div class="col-md-6"> - {% include 'comments/_comment_identifier.html' with comment=comment %} - <hr class="small"> + <h3 class="my-3">The Comment to be vetted:</h3> - <h3>Comment text:</h3> - <p>{{ comment.comment_text|linebreaksbr }}</p> + <div class="row"> + <div class="col-md-6"> + <!-- {% include 'comments/_comment_identifier.html' with comment=comment %} --> + {% include 'comments/_single_comment.html' with comment=comment hide_votes=1 %} - {% if comment.file_attachment %} - <h3>Attachment:</h3> - <p> - <a target="_blank" href="{{ comment.get_attachment_url }}"> - {% if comment.file_attachment|is_image %} - <img class="attachment attachment-comment" src="{{ comment.get_attachment_url }}"> - {% else %} - {{ comment.file_attachment|filename }}<br><small>{{ comment.file_attachment.size|filesizeformat }}</small> - {% endif %} - </a> - </p> - {% endif %} + <h3>Comment text:</h3> + <p>{{ comment.comment_text|linebreaksbr }}</p> - {% if comment.remarks_for_editors %} - <h3>Remarks for Editors only:</h3> - <p>{{ comment.remarks_for_editors|linebreaksbr }}</p> - {% endif %} - </div> - <div class="col-md-6"> - <form action="{% url 'comments:vet_submitted_comment' comment_id=comment.id %}" method="post"> - {% csrf_token %} - {{ form|bootstrap }} - <input class="btn btn-primary" type="submit" value="Submit" /> - </form> - </div> + {% if comment.file_attachment %} + <h3>Attachment:</h3> + <p> + <a target="_blank" href="{{ comment.get_attachment_url }}"> + {% if comment.file_attachment|is_image %} + <img class="attachment attachment-comment" src="{{ comment.get_attachment_url }}"> + {% else %} + {{ comment.file_attachment|filename }}<br><small>{{ comment.file_attachment.size|filesizeformat }}</small> + {% endif %} + </a> + </p> + {% endif %} + + {% if comment.remarks_for_editors %} + <h3>Remarks for Editors only:</h3> + <p>{{ comment.remarks_for_editors|linebreaksbr }}</p> + {% endif %} + </div> + <div class="col-md-6"> + <form action="{% url 'comments:vet_submitted_comment' comment_id=comment.id %}" method="post"> + {% csrf_token %} + {{ form|bootstrap }} + <input class="btn btn-primary" type="submit" value="Submit" /> + </form> </div> </div> </div> diff --git a/comments/templates/comments/new_comment.html b/comments/templates/comments/new_comment.html index a455c6dbd503c771017c40a0ff492fd3a93207e3..d1299702eec44c7ad2f03ac30e4ebbde77874875 100644 --- a/comments/templates/comments/new_comment.html +++ b/comments/templates/comments/new_comment.html @@ -4,7 +4,13 @@ <hr class="divider"> -<h2 class="highlight" id="contribute_comment">Contribute a Comment: {% if user_is_referee and submission %} <small class="text-danger">you are an invited referee, please <a href="{% url 'submissions:submit_report' submission.preprint.identifier_w_vn_nr %}">submit a Report</a> instead</small>{% endif %}</h2> +<h2 class="highlight" id="contribute_comment">Contribute a Comment</h2> +{% if user_is_referee and submission %} + <h4 class="text-danger my-3"> + <i class="fa fa-exclamation-triangle"></i> + You are an invited referee, please <a href="{% url 'submissions:submit_report' submission.preprint.identifier_w_vn_nr %}">submit a Report</a> instead. + </h4> +{% endif %} {% url 'comments:new_comment' object_id=object_id type_of_object=type_of_object as url %} {% include 'comments/_add_comment_form.html' with form=form url=url %} diff --git a/comments/templates/comments/reply_to_comment.html b/comments/templates/comments/reply_to_comment.html index d884188760d15dfb8081101fbf374eeb0dc6edd1..059e651e7f395f2278017aa3a086a960e6ae5398 100644 --- a/comments/templates/comments/reply_to_comment.html +++ b/comments/templates/comments/reply_to_comment.html @@ -43,7 +43,7 @@ <div class="row"> <div class="col-12"> - <div class="card card-grey"> + <div class="card card-gray"> <div class="card-body"> <h2>Your Reply to this Comment:</h2> {% if is_author %} diff --git a/comments/templates/comments/vet_submitted_comment.html b/comments/templates/comments/vet_submitted_comment.html index 3cfe9f3b2b68106b590acc3c8773477a2301bb1b..0e4ef0c5d18b6b3c4f2f44cadd7ff9bcd211f82e 100644 --- a/comments/templates/comments/vet_submitted_comment.html +++ b/comments/templates/comments/vet_submitted_comment.html @@ -8,11 +8,7 @@ {% block content %} -<div class="row"> - <div class="col-12"> - <h1 class="highlight">SciPost Comment to vet:</h1> - </div> -</div> +<h1 class="highlight">SciPost Comment to vet:</h1> {% include 'comments/_vet_comment_form.html' with comment=comment form=form %} diff --git a/comments/templatetags/comment_extras.py b/comments/templatetags/comment_extras.py index b6c074234d31fe85f5d8df8265307e5915aa3cac..690e881b2b4a1b2047af271874902f006a836357 100644 --- a/comments/templatetags/comment_extras.py +++ b/comments/templatetags/comment_extras.py @@ -14,14 +14,15 @@ register = template.Library() class CommentTemplateNode(template.Node): - """ - This template node accepts an object being a Submission, Commentary or ThesisLink - and includes its summary page. + """Render template summarizing the related object of the Comment. + + Related object be a Submission, Commentary or ThesisLink. """ def __init__(self, content_object): self.content_object = content_object def render(self, context): + """Find and render the correct template.""" content_object = self.content_object.resolve(context) if isinstance(content_object, Submission): t = context.template.engine.get_template('partials/submissions/submission_summary.html') diff --git a/comments/views.py b/comments/views.py index e9028b7881d061d5ef4957af664782727f0d1787..ed8bbb8e6d3b4e84e2a491cdf00e616a55b631f2 100644 --- a/comments/views.py +++ b/comments/views.py @@ -20,7 +20,6 @@ from .utils import validate_file_extention from commentaries.models import Commentary from mails.utils import DirectMailUtil -from submissions.utils import SubmissionUtils from submissions.models import Submission, Report from theses.models import ThesisLink @@ -28,6 +27,7 @@ from theses.models import ThesisLink @login_required @permission_required('scipost.can_submit_comments', raise_exception=True) def new_comment(request, **kwargs): + """Form view to submit new Comment.""" form = CommentForm(request.POST or None, request.FILES or None) if form.is_valid(): object_id = int(kwargs["object_id"]) @@ -55,6 +55,7 @@ def new_comment(request, **kwargs): @permission_required('scipost.can_vet_comments', raise_exception=True) def vet_submitted_comments_list(request): + """Replace by a list page instead?""" comments_to_vet = Comment.objects.awaiting_vetting().order_by('date_submitted') form = VetCommentForm() context = {'comments_to_vet': comments_to_vet, 'form': form} @@ -73,14 +74,16 @@ def vet_submitted_comment(request, comment_id): if form.is_valid(): if form.cleaned_data['action_option'] == '1': # Accept the comment as is - comment.status = 1 - comment.vetted_by = request.user.contributor - comment.save() + Comment.objects.filter(id=comment_id).update(status=1, + vetted_by=request.user.contributor) + comment.refresh_from_db() # Update `latest_activity` fields content_object = comment.content_object - content_object.latest_activity = timezone.now() - content_object.save() + if hasattr(content_object, 'latest_activity'): + content_object.__class__.objects.filter(id=content_object.id).update( + latest_activity=timezone.now()) + content_object.refresh_from_db() if isinstance(content_object, Submission): # Add events to Submission and send mail to author of the Submission @@ -121,10 +124,9 @@ def vet_submitted_comment(request, comment_id): elif form.cleaned_data['action_option'] == '2': # The comment request is simply rejected - comment.status = int(form.cleaned_data['refusal_reason']) - if comment.status == 0: - comment.status = -1 # Why's this here?? - comment.save() + Comment.objects.filter(id=comment.id).update( + status=int(form.cleaned_data['refusal_reason'])) + comment.refresh_from_db() # Send emails mail_sender = DirectMailUtil( diff --git a/common/forms.py b/common/forms.py index 3f1b8651d2b2c3d30e5389ec3d478f3f5a156df0..5fa2d913773a61c40c068238d3a7be01ab3cce36 100644 --- a/common/forms.py +++ b/common/forms.py @@ -6,6 +6,7 @@ import calendar import datetime import re +from django import forms from django.forms.widgets import Widget, Select from django.utils.dates import MONTHS from django.utils.safestring import mark_safe @@ -115,3 +116,8 @@ class MonthYearWidget(Widget): return '%s-%s-%s' % (y, m, d) return data.get(name, None) + + +class ModelChoiceFieldwithid(forms.ModelChoiceField): + def label_from_instance(self, obj): + return '%s (id = %i)' % (super().label_from_instance(obj), obj.id) diff --git a/common/utils.py b/common/utils.py index cdbb65c174398fa1f5b1df176cd9c9a534d23240..0edde7a732a107ba3beb620cbd66ed9ff8b64c43 100644 --- a/common/utils.py +++ b/common/utils.py @@ -7,6 +7,22 @@ from django.core.mail import EmailMultiAlternatives from django.template import loader +def hslColorWheel(N=10, index=0, saturation=50, lightness=50): + """ + Distributes colors into N values around a color wheel, + according to hue-saturation-lightness (HSL). + + index takes values from 0 to N-1. + """ + hue = int(index * 360/N % 360) + saturation = max(saturation, 0) + saturation = min(saturation, 100) + lightness = max(lightness, 0) + lightness = min(lightness, 100) + + return 'hsl(%s, %s%%, %s%%)' % (str(hue), str(saturation), str(lightness)) + + def workdays_between(datetime_from, datetime_until): """Return number of complete workdays. diff --git a/conflicts/tasks.py b/conflicts/tasks.py new file mode 100644 index 0000000000000000000000000000000000000000..681b7d97a3d2fead0f4b83400b3c6c80963e957a --- /dev/null +++ b/conflicts/tasks.py @@ -0,0 +1,22 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + +from scipost.models import Contributor +from SciPost_v1.celery import app +from submissions.models import Submission + +from .services import ArxivCaller + + +@app.task(bind=True) +def update_conflict_of_interest(self): + """Create new Conflict of Interest entries for incoming Submission.""" + submissions = Submission.objects.needs_conflicts_update() + for sub in submissions: + fellow_ids = sub.fellows.values_list('id', flat=True) + fellows = Contributor.objects.filter(fellowships__id__in=fellow_ids) + if 'entries' in sub.metadata: + caller = ArxivCaller(sub.metadata['entries'][0]['authors']) + caller.compare_to(fellows) + caller.add_to_db(sub) + Submission.objects.filter(id=sub.id).update(needs_conflicts_update=False) diff --git a/cronjob_production_eachhour.sh b/cronjob_production_eachhour.sh new file mode 100644 index 0000000000000000000000000000000000000000..47ae6e28c0d5c797807e4d65d363a9ecb1791c02 --- /dev/null +++ b/cronjob_production_eachhour.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Per minute cronjobs for production area + +cd /home/scipost/webapps/scipost/scipost_v1 +source venv/bin/activate + +# Do tasks +python3 manage.py check_celery diff --git a/finances/forms.py b/finances/forms.py index 70ac25a2403ded67c2bf578f9a6ddfac084272a6..7786bd89458bedb323c6ea652bec3695e2807994 100644 --- a/finances/forms.py +++ b/finances/forms.py @@ -21,7 +21,8 @@ class SubsidyForm(forms.ModelForm): class Meta: model = Subsidy fields = ['organization', 'subsidy_type', 'description', - 'amount', 'status', 'date', 'date_until'] + 'amount', 'amount_publicly_shown', 'status', + 'date', 'date_until'] class WorkLogForm(forms.ModelForm): diff --git a/finances/migrations/0008_subsidy_amount_publicly_shown.py b/finances/migrations/0008_subsidy_amount_publicly_shown.py new file mode 100644 index 0000000000000000000000000000000000000000..a13a33679406ab10f42df221e704daceb873a97d --- /dev/null +++ b/finances/migrations/0008_subsidy_amount_publicly_shown.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-12-05 18:07 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('finances', '0007_auto_20181011_2146'), + ] + + operations = [ + migrations.AddField( + model_name='subsidy', + name='amount_publicly_shown', + field=models.BooleanField(default=True), + ), + ] diff --git a/finances/models.py b/finances/models.py index f7755ead7f9b6847b1d0850c04e09b4350f2c177..c6cd606061aaea986ef4d3f6a1289b954e0f92d8 100644 --- a/finances/models.py +++ b/finances/models.py @@ -36,6 +36,7 @@ class Subsidy(models.Model): subsidy_type = models.CharField(max_length=256, choices=SUBSIDY_TYPES) description = models.TextField() amount = models.PositiveIntegerField(help_text="in € (rounded)") + amount_publicly_shown = models.BooleanField(default=True) status = models.CharField(max_length=32, choices=SUBSIDY_STATUS) date = models.DateField() date_until = models.DateField(blank=True, null=True) diff --git a/finances/templates/finances/_subsidy_card.html b/finances/templates/finances/_subsidy_card.html index d3e0a2291bcd5d50dfe2f7a3086c737b5f55869c..e7168adcd386d086873b36df8789e6ba30579b51 100644 --- a/finances/templates/finances/_subsidy_card.html +++ b/finances/templates/finances/_subsidy_card.html @@ -16,7 +16,7 @@ <table class="table"> <tr> - <td>From:</td><td><a href="{{ subsidy.organization.get_absolute_url }}">{{ subsidy.organization }}</a></td> + <td>From:</td><td>{% if subsidy.organization.details_publicly_viewable or perms.scipost.can_manage_organizations %}<a href="{{ subsidy.organization.get_absolute_url }}">{{ subsidy.organization }}</a>{% else %}{{ subsidy.organization }}{% endif %}</td> </tr> <tr> <td>Type:</td><td>{{ subsidy.get_subsidy_type_display }}</td> @@ -25,7 +25,7 @@ <td>Description:</td><td>{{ subsidy.description }}</td> </tr> <tr> - <td>Amount:</td><td>€{{ subsidy.amount }}</td> + <td>Amount:</td><td>{% if subsidy.amount_publicly_shown or perms.scipost.can_manage_subsidies %}€{{ subsidy.amount }}{% else %}-{% endif %}</td> </tr> <tr> <td>Date:</td><td>{{ subsidy.date }}</td> diff --git a/finances/templates/finances/subsidy_list.html b/finances/templates/finances/subsidy_list.html index 6fcbb6e749c6b56226cbf08ebe262029c2ffc6db..e8a11c93262fd3a946ca40c8c83ce4130167df90 100644 --- a/finances/templates/finances/subsidy_list.html +++ b/finances/templates/finances/subsidy_list.html @@ -72,7 +72,7 @@ $(document).ready(function($) { <tr class="table-row" data-href="{% url 'finances:subsidy_details' pk=subsidy.id %}" style="cursor: pointer;"> <td>{{ subsidy.organization }}</td> <td>{{ subsidy.get_subsidy_type_display }}</td> - <td>€{{ subsidy.amount }}</td> + <td>{% if subsidy.amount_publicly_shown or perms.scipost.can_manage_subsidies %}€{{ subsidy.amount }}{% else %}-{% endif %}</td> <td>{{ subsidy.date }}</td> </tr> {% empty %} diff --git a/finances/views.py b/finances/views.py index 8c574883ae90c3543466ead6a26075cce8a79bab..6b43fd16b8e712e3d4cf69dc46138ebcd84c447f 100644 --- a/finances/views.py +++ b/finances/views.py @@ -66,7 +66,7 @@ class SubsidyListView(ListView): order_by = self.request.GET.get('order_by') ordering = self.request.GET.get('ordering') if order_by == 'amount': - qs = qs.order_by('amount') + qs = qs.filter(amount_publicly_shown=True).order_by('amount') elif order_by == 'date': qs = qs.order_by('date') if ordering == 'desc': diff --git a/journals/context_processors.py b/journals/context_processors.py new file mode 100644 index 0000000000000000000000000000000000000000..3904ea33a05c2b676435774b9bfdcd1adb690dd3 --- /dev/null +++ b/journals/context_processors.py @@ -0,0 +1,9 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + +from .models import Journal + + +def journals_processor(request): + """Append all Journals to the context of all views.""" + return {'journals': Journal.objects.order_by('name')} diff --git a/journals/forms.py b/journals/forms.py index ab03f21092d8215c77826480521cacf721057723..3a7d92663422da50318673838c8657b0e775ab4d 100644 --- a/journals/forms.py +++ b/journals/forms.py @@ -392,7 +392,7 @@ class DraftPublicationForm(forms.ModelForm): if self.submission: try: self.to_journal = Journal.objects.has_individual_publications().get( - name=self.submission.submitted_to_journal) + name=self.submission.submitted_to.name) except Journal.DoesNotExist: self.to_journal = None @@ -416,7 +416,7 @@ class DraftPublicationForm(forms.ModelForm): def get_possible_issues(self): issues = Issue.objects.filter(until_date__gte=timezone.now()) if self.submission: - issues = issues.for_journal(self.submission.submitted_to_journal) + issues = issues.for_journal(self.submission.submitted_to.name) return issues def delete_secondary_fields(self): @@ -476,6 +476,7 @@ class DraftPublicationForm(forms.ModelForm): for submission_author in self.submission.authors.all(): PublicationAuthorsTable.objects.create( publication=self.instance, contributor=submission_author) + self.instance.topics.add(*self.submission.topics.all()) self.instance.authors_claims.add(*self.submission.authors_claims.all()) self.instance.authors_false_claims.add(*self.submission.authors_false_claims.all()) diff --git a/journals/migrations/0050_auto_20181028_2038.py b/journals/migrations/0050_auto_20181028_2038.py new file mode 100644 index 0000000000000000000000000000000000000000..985f7d6e0fdb2476b94088f4ef1e3c4099ab871a --- /dev/null +++ b/journals/migrations/0050_auto_20181028_2038.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-10-28 19:38 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0012_profile_topics'), + ('ontology', '0005_auto_20181028_2038'), + ('journals', '0049_auto_20181027_1807'), + ] + + operations = [ + migrations.AddField( + model_name='publication', + name='topics', + field=models.ManyToManyField(blank=True, related_name='publications', to='ontology.Topic'), + ), + migrations.AddField( + model_name='unregisteredauthor', + name='profile', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='profiles.Profile'), + ), + ] diff --git a/journals/migrations/0051_auto_20181102_1332.py b/journals/migrations/0051_auto_20181102_1332.py new file mode 100644 index 0000000000000000000000000000000000000000..5d3f6403b2110e2f62fc22605f203a8ed30ecfd9 --- /dev/null +++ b/journals/migrations/0051_auto_20181102_1332.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-02 12:32 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0050_auto_20181028_2038'), + ] + + operations = [ + migrations.AlterField( + model_name='publication', + name='author_list', + field=models.CharField(max_length=10000, verbose_name='author list'), + ), + ] diff --git a/journals/migrations/0052_journal_refereeing_period.py b/journals/migrations/0052_journal_refereeing_period.py new file mode 100644 index 0000000000000000000000000000000000000000..42ebb34af66e443290370c8ef0c83f9d91559dbd --- /dev/null +++ b/journals/migrations/0052_journal_refereeing_period.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-10 05:41 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0051_auto_20181102_1332'), + ] + + operations = [ + migrations.AddField( + model_name='journal', + name='refereeing_period', + field=models.DurationField(default=datetime.timedelta(28)), + ), + ] diff --git a/journals/migrations/0053_auto_20181118_1758.py b/journals/migrations/0053_auto_20181118_1758.py new file mode 100644 index 0000000000000000000000000000000000000000..f2a41d010006dd96dcdc203e13e60f4abc5de665 --- /dev/null +++ b/journals/migrations/0053_auto_20181118_1758.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-18 16:58 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0052_journal_refereeing_period'), + ] + + operations = [ + migrations.AlterField( + model_name='unregisteredauthor', + name='profile', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='profiles.Profile'), + ), + ] diff --git a/journals/models.py b/journals/models.py index aac5e1c57306524caf3861424b96b827a8b123c0..ee1c7a4d2caec59d040aa6fe1cc724acdaa119f1 100644 --- a/journals/models.py +++ b/journals/models.py @@ -2,6 +2,7 @@ __copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" __license__ = "AGPL v3" +import datetime from decimal import Decimal from django.contrib.contenttypes.fields import GenericForeignKey @@ -10,15 +11,16 @@ from django.contrib.postgres.fields import JSONField from django.core.exceptions import ValidationError from django.db import models from django.db.models import Avg, Min, Sum, F +from django.shortcuts import get_object_or_404 from django.utils import timezone from django.urls import reverse -from .behaviors import doi_journal_validator, doi_volume_validator,\ - doi_issue_validator, doi_publication_validator -from .constants import SCIPOST_JOURNALS, SCIPOST_JOURNALS_DOMAINS,\ - STATUS_DRAFT, STATUS_PUBLISHED, ISSUE_STATUSES, PUBLICATION_PUBLISHED,\ - CCBY4, CC_LICENSES, CC_LICENSES_URI, PUBLICATION_STATUSES,\ - JOURNAL_STRUCTURE, ISSUES_AND_VOLUMES, ISSUES_ONLY +from .behaviors import ( + doi_journal_validator, doi_volume_validator, doi_issue_validator, doi_publication_validator) +from .constants import ( + SCIPOST_JOURNALS, SCIPOST_JOURNALS_DOMAINS, STATUS_DRAFT, STATUS_PUBLISHED, ISSUE_STATUSES, + PUBLICATION_PUBLISHED, CCBY4, CC_LICENSES, CC_LICENSES_URI, PUBLICATION_STATUSES, + JOURNAL_STRUCTURE, ISSUES_AND_VOLUMES, ISSUES_ONLY) from .helpers import paper_nr_string, journal_name_abbrev_citation from .managers import IssueQuerySet, PublicationQuerySet, JournalQuerySet @@ -32,14 +34,20 @@ from scipost.fields import ChoiceArrayField ################ class UnregisteredAuthor(models.Model): + """UnregisteredAuthor is a replacement for a Contributor if an author has not registered.""" + first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) + profile = models.ForeignKey( + 'profiles.Profile', on_delete=models.SET_NULL, null=True, blank=True) def __str__(self): return self.last_name + ', ' + self.first_name class PublicationAuthorsTable(models.Model): + """PublicationAuthorsTable is an ordered link between people and Publications.""" + publication = models.ForeignKey('journals.Publication', related_name='authors') unregistered_author = models.ForeignKey('journals.UnregisteredAuthor', null=True, blank=True, related_name='+') @@ -57,16 +65,19 @@ class PublicationAuthorsTable(models.Model): return str(self.unregistered_author) def save(self, *args, **kwargs): + """Auto increment order number if not explicitly set.""" if not self.order: self.order = self.publication.authors.count() + 1 return super().save(*args, **kwargs) @property def is_registered(self): + """Check if author is registered at SciPost.""" return self.contributor is not None @property def first_name(self): + """Return first name of author.""" if self.contributor: return self.contributor.user.first_name if self.unregistered_author: @@ -74,6 +85,7 @@ class PublicationAuthorsTable(models.Model): @property def last_name(self): + """Return last name of author.""" if self.contributor: return self.contributor.user.last_name if self.unregistered_author: @@ -81,10 +93,11 @@ class PublicationAuthorsTable(models.Model): class Journal(models.Model): - """ - Journal is a container of Publications with a unique issn and doi_label. + """Journal is a container of Publications with a unique issn and doi_label. + Publications may be categorized into issues or issues and volumes. """ + name = models.CharField(max_length=100, choices=SCIPOST_JOURNALS, unique=True) doi_label = models.CharField(max_length=200, unique=True, db_index=True, validators=[doi_journal_validator]) @@ -92,6 +105,7 @@ class Journal(models.Model): active = models.BooleanField(default=True) structure = models.CharField(max_length=2, choices=JOURNAL_STRUCTURE, default=ISSUES_AND_VOLUMES) + refereeing_period = models.DurationField(default=datetime.timedelta(days=28)) objects = JournalQuerySet.as_manager() @@ -99,10 +113,12 @@ class Journal(models.Model): return self.get_name_display() def get_absolute_url(self): + """Return Journal's homepage url.""" return reverse('scipost:landing_page', args=(self.doi_label,)) @property def doi_string(self): + """Return DOI including the SciPost registrant prefix.""" return '10.21468/' + self.doi_label @property @@ -147,9 +163,7 @@ class Journal(models.Model): return 0 def citation_rate(self, tier=None): - """ - Returns the citation rate in units of nr citations per article per year. - """ + """Return the citation rate in units of nr citations per article per year.""" publications = Publication.objects.filter(in_issue__in_volume__in_journal=self) if tier: publications = publications.filter( @@ -163,8 +177,8 @@ class Journal(models.Model): return (ncites * 365.25/deltat) def citedby_impact_factor(self, year): - """ - Computes the impact factor for a given year YYYY, from Crossref cited-by data. + """Compute the impact factor for a given year YYYY, from Crossref cited-by data. + This is defined as the total number of citations in year YYYY for all papers published in years YYYY-1 and YYYY-2, divided by the number of papers published in year YYYY. @@ -184,9 +198,8 @@ class Journal(models.Model): return ncites/nrpub class Volume(models.Model): - """ - A Volume may be used as a subgroup of Publications related to a specific Issue object. - """ + """A Volume may be used as a subgroup of Publications related to a specific Issue object.""" + in_journal = models.ForeignKey('journals.Journal', on_delete=models.CASCADE) number = models.PositiveSmallIntegerField() start_date = models.DateField(default=timezone.now) @@ -203,9 +216,7 @@ class Volume(models.Model): return str(self.in_journal) + ' Vol. ' + str(self.number) def clean(self): - """ - Check if the Volume is assigned to a valid Journal. - """ + """Check if the Volume is assigned to a valid Journal.""" if not self.in_journal.has_volumes: raise ValidationError({ 'in_journal': ValidationError('This journal does not allow for the use of Volumes', @@ -232,9 +243,7 @@ class Volume(models.Model): return 0 def citation_rate(self, tier=None): - """ - Returns the citation rate in units of nr citations per article per year. - """ + """Returns the citation rate in units of nr citations per article per year.""" publications = Publication.objects.filter(in_issue__in_volume=self) if tier: publications = publications.filter( @@ -249,9 +258,8 @@ class Volume(models.Model): class Issue(models.Model): - """ - An Issue may be used as a subgroup of Publications related to a specific Journal object. - """ + """An Issue may be used as a subgroup of Publications related to a specific Journal object.""" + in_journal = models.ForeignKey( 'journals.Journal', on_delete=models.CASCADE, null=True, blank=True, help_text='Assign either an Volume or Journal to the Issue') @@ -286,10 +294,7 @@ class Issue(models.Model): return text def clean(self): - """ - Check if either a Journal or Volume is assigned to the Issue, else the Issue be floating - like Musk's red Roadster. - """ + """Check if either a Journal or Volume is assigned to the Issue.""" if not (self.in_journal or self.in_volume): raise ValidationError({ 'in_journal': ValidationError('Either assign a Journal or Volume to this Issue', @@ -348,9 +353,7 @@ class Issue(models.Model): return 0 def citation_rate(self, tier=None): - """ - Returns the citation rate in units of nr citations per article per year. - """ + """Return the citation rate in units of nr citations per article per year.""" publications = Publication.objects.filter(in_issue=self) if tier: publications = publications.filter( @@ -361,16 +364,16 @@ class Issue(models.Model): if pub.citedby and pub.latest_citedby_update: ncites += len(pub.citedby) deltat += (pub.latest_citedby_update.date() - pub.publication_date).days - return (ncites * 365.25/deltat) + return (ncites * 365.25 / deltat) class Publication(models.Model): - """ - A Publication is an object directly related to an accepted Submission. It contains metadata, - the actual publication file, author data, etc. etc. + """A Publication is an object directly related to an accepted Submission. + It contains metadata, the actual publication file, author data, etc. etc. It may be directly related to a Journal or to an Issue. """ + # Publication data accepted_submission = models.OneToOneField('submissions.Submission', on_delete=models.CASCADE, related_name='publication') @@ -386,7 +389,7 @@ class Publication(models.Model): # Core fields title = models.CharField(max_length=300) - author_list = models.CharField(max_length=1000, verbose_name="author list") + author_list = models.CharField(max_length=10000, verbose_name="author list") abstract = models.TextField() abstract_jats = models.TextField(blank=True, default='', help_text='JATS version of abstract for Crossref deposit') @@ -431,6 +434,9 @@ class Publication(models.Model): citedby = JSONField(default={}, blank=True, null=True) number_of_citations = models.PositiveIntegerField(default=0) + # Topics for semantic linking + topics = models.ManyToManyField('ontology.Topic', blank=True) + # Date fields submission_date = models.DateField(verbose_name='submission date') acceptance_date = models.DateField(verbose_name='acceptance date') @@ -453,9 +459,7 @@ class Publication(models.Model): date=self.publication_date.strftime('%Y-%m-%d')) def clean(self): - """ - Check if either a valid Journal or Issue is assigned to the Publication. - """ + """Check if either a valid Journal or Issue is assigned to the Publication.""" if not (self.in_journal or self.in_issue): raise ValidationError({ 'in_journal': ValidationError( @@ -562,9 +566,7 @@ class Publication(models.Model): @property def citation(self): - """ - Return Publication name in the preferred citation format. - """ + """Return Publication name in the preferred citation format.""" if self.in_issue and self.in_issue.in_volume: return '{journal} {volume}, {paper_nr} ({year})'.format( journal=self.in_issue.in_volume.in_journal.abbreviation_citation, @@ -586,6 +588,17 @@ class Publication(models.Model): paper_nr=self.paper_nr, year=self.publication_date.strftime('%Y')) + def get_cc_license_URI(self): + for (key, val) in CC_LICENSES_URI: + if key == self.cc_license: + return val + raise KeyError + + def get_all_funders(self): + from funders.models import Funder + return Funder.objects.filter( + models.Q(grants__publications=self) | models.Q(publications=self)).distinct() + def get_journal(self): if self.in_journal: return self.in_journal @@ -602,9 +615,7 @@ class Publication(models.Model): return paper_nr_string(self.paper_nr) def citation_rate(self): - """ - Returns the citation rate in units of nr citations per article per year. - """ + """Returns the citation rate in units of nr citations per article per year.""" if self.citedby and self.latest_citedby_update: ncites = len(self.citedby) deltat = (self.latest_citedby_update.date() - self.publication_date).days @@ -612,11 +623,19 @@ class Publication(models.Model): else: return 0 + def get_similar_publications(self): + """Return 4 Publications with same subject area.""" + return Publication.objects.published().filter( + subject_area=self.subject_area).exclude(id=self.id)[:4] + + def get_issue_related_publications(self): + """Return 4 Publications within same Issue.""" + return Publication.objects.published().filter( + in_issue=self.in_issue).exclude(id=self.id)[:4] + class Reference(models.Model): - """ - A Refence is a reference used in a specific Publication. - """ + """A Refence is a reference used in a specific Publication.""" reference_number = models.IntegerField() publication = models.ForeignKey('journals.Publication', on_delete=models.CASCADE) diff --git a/journals/templates/journals/SciPostPhysCodeb_about.html b/journals/templates/journals/SciPostPhysCodeb_about.html index 5c991fb1e729381f1bc6f7d739070dcfb174dee5..d1d85a90d73726fb1e5954f0ae74adf3e6b180ed 100644 --- a/journals/templates/journals/SciPostPhysCodeb_about.html +++ b/journals/templates/journals/SciPostPhysCodeb_about.html @@ -28,18 +28,18 @@ <div class="col-md-6"> <h2>Aims</h2> <p>SciPost Physics Codebases is a new-generation publication venue for computer codes and algorithms of relevance to research in Physics.</p> - <p>It aims to offer a high-profile venue in which top-level numerical algorithms, protocols and software packages can be disseminated as fully-featured publication objects benefitting from all things expected of top-quality papers.</p> - <p>Contributors to research-level software development can thus obtain proper recognition and find their rightful place within modern scientific literature.</p> - <p>As per other SciPost Journals, SciPost Physics Codebases is two-way open access, peer-witnessed refereed. Publications in SciPost Physics Codebases benefit from the same professional treatment given to our other Journals, including metadata handling, citable DOI, citations listing, funding information handling, and permanent archiving.</p> + <p>It aims to offer a high-profile venue in which top-level numerical algorithms, protocols and software packages can be disseminated as fully-featured publication objects, with all the benefits associated to top-quality papers.</p> + <p>Contributors to research-level software development can thus obtain proper recognition and find their rightful place within the modern scientific literature.</p> + <p>As per other SciPost Journals, SciPost Physics Codebases is two-way open access, peer-witnessed refereed. Publications in SciPost Physics Codebases benefit from the same professional treatment given to our other Journals, including metadata handling, citable DOI, citations listing, funding information handling, permanent archiving and many others.</p> </div> <div class="col-md-6"> <h2>Scope</h2> <p>SciPost Physics Codebases publishes outstanding-quality Codebases relevant to all specializations in Computational, Experimental and Theoretical Physics.</p> <p>Examples of publishable Codebases include:</p> <ul> - <li>Novel algorithms</li> - <li>Significant and original reimplementations of well-known algorithms</li> - <li>Ports of existing codebases to new languages and platforms</li> + <li>Novel algorithms;</li> + <li>Significant and original reimplementations of well-known algorithms;</li> + <li>Ports of existing codebases to new languages and platforms.</li> </ul> </div> </div> diff --git a/journals/templates/journals/SciPostPhysComm_about.html b/journals/templates/journals/SciPostPhysComm_about.html index 64ee74023bb60543bcd7773f8ad41169efe42a13..69dea8a6e0093e8d366c366ffca3e57d2e0dbc66 100644 --- a/journals/templates/journals/SciPostPhysComm_about.html +++ b/journals/templates/journals/SciPostPhysComm_about.html @@ -47,12 +47,13 @@ <div class="row"> <div class="col-md-6"> <h2>Content</h2> - <p>The journal accepts three types of content: <strong>Letters</strong>, <strong>Articles</strong> and <strong>Reviews</strong>.</p> + <p>The journal accepts three indicative types of content: <strong>Letters</strong>, <strong>Articles</strong> and <strong>Reviews</strong>.</p> <ul> <li><strong>Letters</strong> report broad-interest and high-quality advances in Physics, of interest and importance to researchers in multiple subject areas.</li> <li><strong>Articles</strong> provide in-depth, detailed reports of research achievements within one or more subject areas.</li> <li><strong>Reviews</strong> are short pieces taking a snapshot of a research area, written by recognized leaders in the field, providing a critical assessment of current frontline research and providing pointers towards future opportunities.</li> </ul> + <p>These three types of content are published side-by-side on our online portal.</p> </div> <div class="col-md-6"> <h2>Submission and Editorial Process</h2> diff --git a/journals/templates/journals/SciPostPhysLectNotes_about.html b/journals/templates/journals/SciPostPhysLectNotes_about.html index 597ecc145ff68a4b5b9e9e0bf481753140209316..7b730be0ab5d8e79c4d6f0aaf1b2baeb47fd7ecb 100644 --- a/journals/templates/journals/SciPostPhysLectNotes_about.html +++ b/journals/templates/journals/SciPostPhysLectNotes_about.html @@ -1,5 +1,5 @@ -{% extends 'journals/_base.html' %} +{% extends 'journals/base.html' %} {% block pagetitle %}{{block.super}}: About{% endblock pagetitle %} @@ -12,10 +12,13 @@ {% endblock %} {% block content %} + {{block.super}} - {% with header_text='About SciPost Physics Lecture Notes' %} - {{block.super}} - {% endwith %} + <div class="row"> + <div class="col-12"> + <h2 class="text-blue m-0 p-0 py-2">About SciPost Physics Lecture Notes</h2> + </div> + </div> <div class="row"> <div class="col-md-6"> diff --git a/journals/templates/journals/SciPostPhysLectNotes_info_for_authors.html b/journals/templates/journals/SciPostPhysLectNotes_info_for_authors.html index 9c2cef53b3b5fc98b7e93bf7f8196b100445328a..16fc11b7f45b46856b43df4c49bf757b63633502 100644 --- a/journals/templates/journals/SciPostPhysLectNotes_info_for_authors.html +++ b/journals/templates/journals/SciPostPhysLectNotes_info_for_authors.html @@ -1,5 +1,5 @@ -{% extends 'journals/_base.html' %} +{% extends 'journals/base.html' %} {% block pagetitle %}{{block.super}}: Info for Authors{% endblock pagetitle %} @@ -12,10 +12,13 @@ {% endblock %} {% block content %} + {{block.super}} - {% with header_text='Information for Authors' %} - {{block.super}} - {% endwith %} + <div class="row"> + <div class="col-12"> + <h2 class="text-blue m-0 py-2">Information for Authors</h2> + </div> + </div> <div class="row"> <div class="col-12"> diff --git a/journals/templates/journals/SciPostPhysProc_about.html b/journals/templates/journals/SciPostPhysProc_about.html index f7a9c191e4913fb37d219ed64b79cd52e966df9c..6c273a6637b365d50428246d7badb92bc54afdcd 100644 --- a/journals/templates/journals/SciPostPhysProc_about.html +++ b/journals/templates/journals/SciPostPhysProc_about.html @@ -1,5 +1,5 @@ -{% extends 'journals/_base.html' %} +{% extends 'journals/base.html' %} {% block pagetitle %}{{block.super}}: About{% endblock pagetitle %} @@ -12,12 +12,15 @@ {% endblock %} {% block content %} - {% with header_text='About SciPost Physics Proceedings' %} - {{block.super}} - {% endwith %} + {{block.super}} + <div class="row"> + <div class="col-12"> + <h2 class="text-blue m-0 p-0 py-2">About SciPost Physics Proceedings</h2> + </div> + </div> - <div class="row"> + <div class="row"> <div class="col-md-6"> <h2>Aims and Scope</h2> <p>SciPost Physics Proceedings is a premium-quality, two-way open access, peer-witnessed refereed Journal for the general field of Physics.</p> @@ -25,20 +28,20 @@ <p>SciPost Physics Proceedings publishes articles in the domains of Experimental, Theoretical and Computational physics, in all specializations.</p> </div> <div class="col-md-6"> - <h2>Setup</h2> - <p>Each Issue of SciPost Physics Proceedings corresponds to a distinct event (<i>e.g.</i> conference, workshop, school).</p> - <p>A set of guest Editors is appointed by the event's organizing committee. These guest Editors participate in the refereeing process of all submissions considered for publication in the Issue. Publication decisions are taken jointly by the set of guest Editors and the Editorial College (Physics).</p> - <p>Opening of a Proceedings Issue can be requested by <a href="mailto:proceedings@scipost.org">contacting our proceedings administrators</a>.</p> - </div> + <h2>Setup</h2> + <p>Each Issue of SciPost Physics Proceedings corresponds to a distinct event (<i>e.g.</i> conference, workshop, school).</p> + <p>A set of guest Editors is appointed by the event's organizing committee. These guest Editors participate in the refereeing process of all submissions considered for publication in the Issue. Publication decisions are taken jointly by the set of guest Editors and the Editorial College (Physics).</p> + <p>Opening of a Proceedings Issue can be requested by <a href="mailto:proceedings@scipost.org">contacting our proceedings administrators</a>.</p> + </div> </div> <div class="row"> - <div class="col-md-6"> - <h2>Submission and Editorial Process</h2> - <p>Submission to a particular Issue of SciPost Physics Proceedings is by invitation of the guest Editors only.</p> - <p>Authors should follow the <a href="{% url 'submissions:author_guidelines' %}">submission guidelines</a> to ensure seamless processing of their manuscript. The <a href="{% url 'journals:journals_terms_and_conditions' %}">SciPost Journals Terms and Conditions</a> apply to all Submissions to SciPost Physics Proceedings.</p> - <p>All incoming Submissions are checked for plagiarism and follow the peer-witnessed refereeing procedures outlined in <a href="{% url 'submissions:sub_and_ref_procedure' %}">Submission and Refereeing procedure</a>.</p> - <p>All publication decisions are taken jointly by the guest Editors and the <a href="{% url 'scipost:about' %}#editorial_college_physics">Editorial College (Physics)</a>, following the rules set out in the <a href="{% url 'scipost:EdCol_by-laws' %}">Editorial College by-laws</a>.</p> + <div class="col-md-6"> + <h2>Submission and Editorial Process</h2> + <p>Submission to a particular Issue of SciPost Physics Proceedings is by invitation of the guest Editors only.</p> + <p>Authors should follow the <a href="{% url 'submissions:author_guidelines' %}">submission guidelines</a> to ensure seamless processing of their manuscript. The <a href="{% url 'journals:journals_terms_and_conditions' %}">SciPost Journals Terms and Conditions</a> apply to all Submissions to SciPost Physics Proceedings.</p> + <p>All incoming Submissions are checked for plagiarism and follow the peer-witnessed refereeing procedures outlined in <a href="{% url 'submissions:sub_and_ref_procedure' %}">Submission and Refereeing procedure</a>.</p> + <p>All publication decisions are taken jointly by the guest Editors and the <a href="{% url 'scipost:about' %}#editorial_college_physics">Editorial College (Physics)</a>, following the rules set out in the <a href="{% url 'scipost:EdCol_by-laws' %}">Editorial College by-laws</a>.</p> </div> <div class="col-md-6"> diff --git a/journals/templates/journals/SciPostPhysProc_info_for_authors.html b/journals/templates/journals/SciPostPhysProc_info_for_authors.html index 618bc84191fc9f6aa05416798823de62001b66c5..576087d2fc25a32637bebc5c75822701c6e43628 100644 --- a/journals/templates/journals/SciPostPhysProc_info_for_authors.html +++ b/journals/templates/journals/SciPostPhysProc_info_for_authors.html @@ -1,4 +1,4 @@ -{% extends 'journals/_base.html' %} +{% extends 'journals/base.html' %} {% block pagetitle %}{{block.super}}: Info for Authors{% endblock pagetitle %} @@ -11,10 +11,13 @@ {% endblock %} {% block content %} + {{block.super}} - {% with header_text='Information for Authors' %} - {{block.super}} - {% endwith %} + <div class="row"> + <div class="col-12"> + <h2 class="text-blue m-0 py-2">Information for Authors</h2> + </div> + </div> <div class="row"> <div class="col-12"> diff --git a/journals/templates/journals/SciPostPhys_about.html b/journals/templates/journals/SciPostPhys_about.html index 00fffa51137ff52a5ec02411a2a7d8f5f9abc6e4..a53bb3dc214670dc98eddb65ab58ed70c77bce4c 100644 --- a/journals/templates/journals/SciPostPhys_about.html +++ b/journals/templates/journals/SciPostPhys_about.html @@ -1,5 +1,5 @@ -{% extends 'journals/_base.html' %} +{% extends 'journals/base.html' %} {% block pagetitle %}{{block.super}}: About{% endblock pagetitle %} @@ -12,10 +12,12 @@ {% endblock %} {% block content %} - - {% with header_text='About SciPost Physics' %} - {{block.super}} - {% endwith %} + {{ block.super }} + <div class="row"> + <div class="col-12"> + <h2 class="text-blue m-0 p-0 py-2">About SciPost Physics</h2> + </div> + </div> {% if perms.scipost.can_attend_VGMs %} diff --git a/journals/templates/journals/SciPostPhys_info_for_authors.html b/journals/templates/journals/SciPostPhys_info_for_authors.html index 73290333ee18b5d58e91f92e0196d21fad27d7a1..779c39f022b1c4ab126aefdd4ae432f67c4c3103 100644 --- a/journals/templates/journals/SciPostPhys_info_for_authors.html +++ b/journals/templates/journals/SciPostPhys_info_for_authors.html @@ -1,5 +1,5 @@ -{% extends 'journals/_base.html' %} +{% extends 'journals/base.html' %} {% block pagetitle %}{{block.super}}: Info for Authors{% endblock pagetitle %} @@ -12,10 +12,13 @@ {% endblock %} {% block content %} + {{block.super}} - {% with header_text='Information for Authors' %} - {{block.super}} - {% endwith %} + <div class="row"> + <div class="col-12"> + <h2 class="text-blue m-0 py-2">Information for Authors</h2> + </div> + </div> <div class="row"> <div class="col-12"> diff --git a/journals/templates/journals/_publication_card_content.html b/journals/templates/journals/_publication_card_content.html index b18c2eff801ff3d451803edebaf9e1a39b8ae0c4..b186920fa3e095d547d9ccd10f8f0756b338b026 100644 --- a/journals/templates/journals/_publication_card_content.html +++ b/journals/templates/journals/_publication_card_content.html @@ -1,5 +1,5 @@ <div class="card-header"> - <h3 class="py-0"><a href="{{publication.get_absolute_url}}">{{ publication.title }}</a></h3> + <h3 class="my-0"><a href="{{publication.get_absolute_url}}">{{ publication.title }}</a></h3> </div> <div class="card-body publication-{{publication.id}}"> <div class="row justify-content-between mb-0"> @@ -14,10 +14,10 @@ {% endif %} </span> <span class="mx-1">|</span> - <a href="javascript:;" data-toggle="toggle" data-target=".card-body.publication-{{publication.id}} .abstract">Toggle abstract</a> + <a href="javascript:;" data-toggle="toggle" data-target="#abstract-pub-{{ publication.id }}">Toggle abstract</a> · <a href="{{publication.get_absolute_url}}/pdf" target="_blank">pdf</a> </p> - <p class="abstract mb-0 mt-2 py-2" style="display:none;">{{ publication.abstract }}</p> + <p class="abstract mb-0 mt-2 py-2" id="abstract-pub-{{ publication.id }}" style="display:none;">{{ publication.abstract }}</p> </div> <div class="col-md-auto"> {% if include_citation_rate and publication.number_of_citations > 0 %} diff --git a/journals/templates/journals/_base.html b/journals/templates/journals/base.html similarity index 82% rename from journals/templates/journals/_base.html rename to journals/templates/journals/base.html index 59bbc2287f5c6e5678c2f371db49681158011d19..48517580e9c8cd51edadc640d79f12fa546c2631 100644 --- a/journals/templates/journals/_base.html +++ b/journals/templates/journals/base.html @@ -6,7 +6,7 @@ {% block body_class %}{{ block.super }} journals{% endblock %} {% block breadcrumb %} - <div class="container-outside breadcrumb-nav"> + <div class="breadcrumb-container"> <div class="container"> <nav class="breadcrumb hidden-sm-down"> {% block breadcrumb_items %} @@ -17,11 +17,11 @@ </div> {% endblock %} -{% block secondary_navbar %} -<div class="container mt-3"> + +{% block content %} <div class="row"> <div class="col-12 journal-head"> - <h1 class="banner"><a href="{{ journal.get_absolute_url }}">{{journal}}</a></h1> + <h2 class="banner"><a href="{{ journal.get_absolute_url }}">{{ journal.get_name_display }}</a></h2> </div> <div class="col-12 journal-sub-head"> <ul class="links"> @@ -33,17 +33,6 @@ </ul> </div> </div> -</div> -{% endblock secondary_navbar %} - - -{% block content %} - <div class="row"> - <div class="col-12"> - <h2 class="{% block header_text_class %}highlight-empty text-blue m-0 py-2 px-0{% endblock %}">{{header_text}}</h2> - {% block journal_header_block %}{% endblock %} - </div> - </div> {% endblock %} {% block secondary_footer %} diff --git a/journals/templates/journals/base_detail_page.html b/journals/templates/journals/base_detail_page.html index 155fcd7887f84e1d181be2dd5d0c72b91ad06876..5b329d251e58ac7f1e3f31d1a2730814002c9f66 100644 --- a/journals/templates/journals/base_detail_page.html +++ b/journals/templates/journals/base_detail_page.html @@ -6,7 +6,7 @@ {% block body_class %}{{block.super}} journals{% endblock %} {% block breadcrumb %} - <div class="container-outside header"> + <div class="breadcrumb-container"> <div class="container"> <nav class="breadcrumb hidden-sm-down"> diff --git a/journals/templates/journals/generic_metadata_xml_deposit.html b/journals/templates/journals/generic_metadata_xml_deposit.html index b9ff2af9c969f4f9439fe228407b3753a72b8979..3a99c53dbd1e2928db420c7f957f3dc4b31e347a 100644 --- a/journals/templates/journals/generic_metadata_xml_deposit.html +++ b/journals/templates/journals/generic_metadata_xml_deposit.html @@ -13,7 +13,7 @@ <div class="row"> <div class="col-12"> - <h1 class="highlight">Generic metadata deposit</small></h1> + <h1 class="highlight">Generic metadata deposit</h1> </div> </div> @@ -30,7 +30,9 @@ </div> <h3 class="mt-3">Response text:</h3> - <p>{{ response_text|linebreaks }}</p> + <div class="border p-3 my-3"> + {{ response_text|safe }} + </div> <h3>Return to the <a href="{% url 'journals:manage_report_metadata' %}">report</a> or to the <a href="{% url 'journals:manage_comment_metadata' %}">comment</a> metadata management page</h3> diff --git a/journals/templates/journals/journal_accepted.html b/journals/templates/journals/journal_accepted.html index d5944bf769b67b5b121d54bf598176827ca56b11..5a17fe36162bcd8d20758d253355770474a2b030 100644 --- a/journals/templates/journals/journal_accepted.html +++ b/journals/templates/journals/journal_accepted.html @@ -1,4 +1,4 @@ -{% extends 'journals/_base.html' %} +{% extends 'journals/base.html' %} {% block pagetitle %}{{block.super}}: accepted{% endblock pagetitle %} diff --git a/journals/templates/journals/journal_issue_detail.html b/journals/templates/journals/journal_issue_detail.html index 5b22c11f7d028b9f06f49c3351e72c7f33dd9193..39b55027081f48a7f4fd95b7597bb93f6f5b589e 100644 --- a/journals/templates/journals/journal_issue_detail.html +++ b/journals/templates/journals/journal_issue_detail.html @@ -1,4 +1,4 @@ -{% extends 'journals/_base.html' %} +{% extends 'journals/base.html' %} {% block pagetitle %}{{block.super}}: issue detail{% endblock pagetitle %} @@ -10,22 +10,19 @@ {% block link_class_physics_issues %}active{% endblock %} -{% block header_text_class %}text-blue m-0 p-0 pt-2{% endblock %} - -{% block journal_header_block %} - {% if prev_issue %} - <h4 class="d-inline-block"><a href="{{prev_issue.get_absolute_url}}"><i class="fa fa-long-arrow-left"></i> Previous issue | {{prev_issue.short_str}}</a></h4> - {% endif %} - {% if next_issue %} - <h4 class="float-right d-inline-block"><a href="{{next_issue.get_absolute_url}}">{{next_issue.short_str}} | Next issue <i class="fa fa-long-arrow-right"></i></a></h4> - {% endif %} -{% endblock %} - {% block content %} - - {% with header_text=issue %} - {{block.super}} - {% endwith %} + {{ block.super }} + <div class="row"> + <div class="col-12"> + <h2 class="text-blue m-0 p-0 py-2">{{ issue }}</h2> + {% if prev_issue %} + <h4 class="d-inline-block"><a href="{{prev_issue.get_absolute_url}}"><i class="fa fa-long-arrow-left"></i> Previous issue | {{prev_issue.short_str}}</a></h4> + {% endif %} + {% if next_issue %} + <h4 class="float-right d-inline-block"><a href="{{next_issue.get_absolute_url}}">{{next_issue.short_str}} | Next issue <i class="fa fa-long-arrow-right"></i></a></h4> + {% endif %} + </div> + </div> {% if issue.proceedings %} {% include 'partials/proceedings/description.html' with proceedings=issue.proceedings %} diff --git a/journals/templates/journals/journal_issues.html b/journals/templates/journals/journal_issues.html index e69b81f25d844551d18d925c6733a703ba2bb29a..17c805138c00667e29efd58b5e49c31423735016 100644 --- a/journals/templates/journals/journal_issues.html +++ b/journals/templates/journals/journal_issues.html @@ -1,4 +1,4 @@ -{% extends 'journals/_base.html' %} +{% extends 'journals/base.html' %} {% block pagetitle %}{{block.super}}: issues{% endblock pagetitle %} diff --git a/journals/templates/journals/journal_landing_page.html b/journals/templates/journals/journal_landing_page.html index efa2600875e1d6977f687bed15ef316869d1a33a..4170921588932e82bf657b05c900e1d4e3d95193 100644 --- a/journals/templates/journals/journal_landing_page.html +++ b/journals/templates/journals/journal_landing_page.html @@ -1,4 +1,4 @@ -{% extends 'journals/_base.html' %} +{% extends 'journals/base.html' %} {% block breadcrumb %}{% endblock %} @@ -8,6 +8,7 @@ {% endblock %} {% block content %} +{{ block.super }} <div class="row"> <div class="col-12"> <ul class="nav nav-tabs" id="journals-about-tab" role="tablist"> diff --git a/journals/templates/journals/journal_recent.html b/journals/templates/journals/journal_recent.html index 66c96e5019a0a7e72cedac9c31ae546c51953f74..f37b1b83506b1cef5617097cc058df3a58d8a720 100644 --- a/journals/templates/journals/journal_recent.html +++ b/journals/templates/journals/journal_recent.html @@ -1,4 +1,4 @@ -{% extends 'journals/_base.html' %} +{% extends 'journals/base.html' %} {% block pagetitle %}{{block.super}}: recent{% endblock pagetitle %} diff --git a/journals/templates/journals/journals.html b/journals/templates/journals/journals.html index 00ee9cfdd5e437421c31921acbfa79eedcf45e81..3731fde185c5e43215e9897ff6dd047435815988 100644 --- a/journals/templates/journals/journals.html +++ b/journals/templates/journals/journals.html @@ -5,30 +5,25 @@ {% block pagetitle %}: Journals{% endblock pagetitle %} {% block breadcrumb %} - <div class="container-outside header"> + <nav class="navbar navbar-expand-lg sub-nav"> <div class="container"> - <h1 class="highlight">SciPost Journals</h1> + <ul class="navbar-nav mr-auto"> + <li class="nav-item"> + <span class="nav-link">Our Journals:</span> + </li> + + {% for journal in journals %} + <li class="nav-item"> + {% if journal.active %} + <a href="{{ journal.get_absolute_url }}" class="nav-link">{{journal}}</a> + {% elif perms.scipost.can_view_pool %} + <a href="{% url 'journal:about' journal.name %}" class="nav-link">{{journal}}</a> + {% endif %} + </li> + {% endfor %} + </ul> </div> - </div> - - <div class="container-outside sub-nav lighter"> - <div class="container"> - <nav class="navbar sub-nav navbar-expand-lg"> - <ul class="navbar-nav"> - <li class="nav-item"><span class="nav-link">Our Journals:</span></li> - {% for journal in journals %} - <li class="nav-item"> - {% if journal.active %} - <a href="{{journal.get_absolute_url}}" class="nav-link">{{journal}}</a> - {% elif perms.scipost.can_view_pool %} - <a href="{% url 'journal:about' journal.name %}" class="nav-link">{{journal}}</a> - {% endif %} - </li> - {% endfor %} - </div> - </nav> - </div> - </div> + </nav> {% endblock %} @@ -89,7 +84,7 @@ </div> <div class="card"> <div class="card-header banner"> - <h1 class="m-0"><a href="{% url 'scipost:landing_page' 'SciPostPhysComm' %}">SciPost Physics Commons</a> <em><small style="color: red;">New!</small></em></h1> + <h1 class="m-0"><a href="{% url 'journal:about' 'SciPostPhysComm' %}">SciPost Physics Commons</a> <em><small style="color: red;">New!</small></em></h1> </div> <div class="card-body"> <p>SciPost Physics Commons is a premium-quality refereed Journal for the general field of Physics.</p> @@ -103,17 +98,17 @@ <div class="card-deck"> <div class="card"> <div class="card-header banner"> - <h1 class="m-0"><a href="{% url 'scipost:landing_page' 'SciPostPhysLectNotes' %}">SciPost Physics Lecture Notes</a></h1> + <h1 class="m-0"><a href="{% url 'journal:about' 'SciPostPhysLectNotes' %}">SciPost Physics Lecture Notes</a></h1> </div> <div class="card-body"> <p>SciPost Physics Lecture Notes publishes didactic material in all domains and subject areas of Physics.</p> - <p>SciPost Physics Lecture Notes is the ideal venue for research-level didactic material, (post-)graduate course notes, or for lecture notes from international-level summer/winter schools.</p> + <p>SciPost Physics Lecture Notes is the ideal venue for research-level didactic material, (post-)graduate course notes, or for lecture notes from international-level schools.</p> <p>All submissions undergo an extended peer-witnessed refereeing process, and publications benefit from all the advantages of our Genuine Open Access standards.</p> </div> </div> <div class="card"> <div class="card-header banner"> - <h1 class="m-0"><a href="{% url 'scipost:landing_page' 'SciPostPhysProc' %}">SciPost Physics Proceedings</a></h1> + <h1 class="m-0"><a href="{% url 'journal:about' 'SciPostPhysProc' %}">SciPost Physics Proceedings</a></h1> </div> <div class="card-body"> <p>SciPost Physics Proceedings is a premium-quality, two-way open access, community-run peer-witnessed refereed publishing venue for conference/workshop/school proceedings in Experimental, Theoretical and Computational physics, in all specializations.</p> @@ -122,7 +117,7 @@ </div> <div class="card"> <div class="card-header banner"> - <h1 class="m-0"><a href="{% url 'scipost:landing_page' 'SciPostPhysCodeb' %}">SciPost Physics Codebases</a> <em><small style="color: red;">New!</small></em></h1> + <h1 class="m-0"><a href="{% url 'journal:about' 'SciPostPhysCodeb' %}">SciPost Physics Codebases</a> <em><small style="color: red;">New!</small></em></h1> </div> <div class="card-body"> <p>SciPost Physics Codebases is an innovative peer-reviewed publication venue for modern forms of scientific contributions which too often remain unrecognized: codes and algorithms at the heart of contemporary research.</p> @@ -139,7 +134,8 @@ <div class="row"> <div class="col-12"> - <h2>View and comment on current <a href="{% url 'submissions:submissions' %}">Submissions</a> <small>(login required)</small></h2> + <h1 class="highlight">SciPost Physics Journals</h1> + <h3>View and comment on current <a href="{% url 'submissions:submissions' %}">Submissions</a> <small>(login required)</small></h3> </div> </div> diff --git a/journals/templates/journals/manage_report_metadata.html b/journals/templates/journals/manage_report_metadata.html index 65c4229a33b0acc681dd47a233aaece3de6c0d23..128f453430694866f0877a5e78f49b000700c452 100644 --- a/journals/templates/journals/manage_report_metadata.html +++ b/journals/templates/journals/manage_report_metadata.html @@ -20,6 +20,7 @@ event: "focusin" {% endblock %} {% block content %} +<h1 class="highlight">Manage Report metadata</h1> <div class="rol"> <div class="col-12"> @@ -33,7 +34,7 @@ event: "focusin" </div> </div> -<table class="table table-hover mb-5"> +<table class="table table-hover mt-4"> <thead class="thead-default"> <tr> <th>Submission</th> @@ -52,52 +53,54 @@ event: "focusin" <td>{{ report.submission.preprint.identifier_w_vn_nr }}</td> <td>{{ report.associated_published_doi }}</td> <td>{{ report.report_nr }}</td> - <td>{{ report.needs_doi }}</td> + <td>{{ report.needs_doi|yesno:'Yes,No,-' }}</td> <td>{{ report.doi_label }}</td> <td>{{ report|latest_successful_crossref_deposit_report }}</td> - <td>{{ report.doideposit_needs_updating }}</td> + <td>{{ report.doideposit_needs_updating|yesno:'Yes,No,-' }}</td> </tr> <tr id="collapse{{ report.id }}" class="collapse" role="tabpanel" aria-labelledby="heading{{ report.id }}" style="background-color: #fff;"> - <td colspan="7"> - <p><a href="{{ report.submission.get_absolute_url }}">{{ report.submission.preprint.identifier_w_vn_nr }}</a>, <a href="{{ report.get_absolute_url }}">{{ report.report_nr }}</a></p> + <td colspan="3"> + <p><a href="{{ report.submission.get_absolute_url }}">{{ report.submission.preprint.identifier_w_vn_nr }}</a>, <a href="{{ report.get_absolute_url }}">Report {{ report.report_nr }}</a></p> - <h2 class="ml-3">Actions</h2> + <h2 class="ml-3">Actions</h2> <ul> - <li>Mark DOI as <a href="{% url 'journals:mark_report_doi_needed' report_id=report.id needed=1 %}">needed</a> / <a href="{% url 'journals:mark_report_doi_needed' report_id=report.id needed=0 %}">not needed</a></li> - <li><a href="{% url 'journals:generic_metadata_xml_deposit' type_of_object='report' object_id=report.id %}">Create the metadata and deposit it to Crossref</a></li> - <li><a href="{% url 'journals:email_object_made_citable' type_of_object='report' object_id=report.id %}">Email report author: made citable</a> - </ul> - - <h2 class="ml-3">Crossref Deposits</h2> - <table class="ml-5"> - <thead class="thead-default"> - <th>Timestamp</th> - <th>batch id</th> - <th>deposition date</th> - <th>Successful?</th> - <th>actions</th> - </thead> - <tbody> - {% for deposit in report.genericdoideposit.all %} - <tr> - <td>{{ deposit.timestamp }}</td> - <td>{{ deposit.doi_batch_id }}</td> - <td>{% if deposit.deposition_date %}{{ deposit.deposition_date }}{% else %}Not deposited{% endif %}</td> - <td>{{ deposit.deposit_successful }}</td> - <td>Mark deposit as - <ul> - <li><a href="{% url 'journals:mark_generic_deposit_success' deposit_id=deposit.id success=1 %}">successful</a></li> - <li><a href="{% url 'journals:mark_generic_deposit_success' deposit_id=deposit.id success=0 %}">unsuccessful</a></li> - </ul> - </td> - </tr> - {% empty %} - <tr> - <td colspan="5">No Deposits found for this Report</td> - </tr> - {% endfor %} - </tbody> - </table> + <li>Mark DOI as <a href="{% url 'journals:mark_report_doi_needed' report_id=report.id needed=1 %}">needed</a> / <a href="{% url 'journals:mark_report_doi_needed' report_id=report.id needed=0 %}">not needed</a></li> + <li><a href="{% url 'journals:generic_metadata_xml_deposit' type_of_object='report' object_id=report.id %}">Create the metadata and deposit it to Crossref</a></li> + <li><a href="{% url 'journals:email_object_made_citable' type_of_object='report' object_id=report.id %}">Email report author: made citable</a> + </ul> + + </td> + <td colspan="4"> + <h2 class="mt-4">Crossref Deposits</h2> + <table class="table"> + <thead class="thead-light"> + <th>Timestamp</th> + <th>batch id</th> + <th>deposition date</th> + <th>Successful?</th> + <th>actions</th> + </thead> + <tbody> + {% for deposit in report.genericdoideposit.all %} + <tr> + <td>{{ deposit.timestamp }}</td> + <td>{{ deposit.doi_batch_id }}</td> + <td>{% if deposit.deposition_date %}{{ deposit.deposition_date }}{% else %}Not deposited{% endif %}</td> + <td>{{ deposit.deposit_successful|yesno:'Yes,No,-' }}</td> + <td>Mark deposit as + <ul> + <li><a href="{% url 'journals:mark_generic_deposit_success' deposit_id=deposit.id success=1 %}">successful</a></li> + <li><a href="{% url 'journals:mark_generic_deposit_success' deposit_id=deposit.id success=0 %}">unsuccessful</a></li> + </ul> + </td> + </tr> + {% empty %} + <tr> + <td colspan="5">No Deposits found for this Report</td> + </tr> + {% endfor %} + </tbody> + </table> </td> </tr> diff --git a/journals/templates/journals/publication_detail.html b/journals/templates/journals/publication_detail.html index 8c03853e661a4aa85ac6021b587ac5bdaa3b77e7..3c80d5322cde6f3070e214a6cf7a44560fddd939 100644 --- a/journals/templates/journals/publication_detail.html +++ b/journals/templates/journals/publication_detail.html @@ -1,4 +1,4 @@ -{% extends 'journals/_base.html' %} +{% extends 'journals/base.html' %} {% load journals_extras %} {% load publication_administration %} @@ -6,187 +6,227 @@ {% load scipost_extras %} {% load user_groups %} -{% is_scipost_admin request.user as is_scipost_admin %} -{% is_edcol_admin request.user as is_edcol_admin %} - {% block pagetitle %}: {{ publication.citation }} - {{ publication.title }}{% endblock pagetitle %} {% block body_class %}{{ block.super }} publication{% endblock %} {% block breadcrumb_items %} -{{block.super}} -<a href="{{ journal.get_absolute_url }}" class="breadcrumb-item">{{ journal }}</a> -{% if publication.in_issue %} -<a href="{{ publication.in_issue.get_absolute_url }}" class="breadcrumb-item">{{ publication.in_issue.short_str }}</a> -{% endif %} -<span class="breadcrumb-item active">{{publication.title}}</span> + {{block.super}} + <a href="{{ journal.get_absolute_url }}" class="breadcrumb-item">{{ journal }}</a> + {% if publication.in_issue %} + <a href="{{ publication.in_issue.get_absolute_url }}" class="breadcrumb-item">{{ publication.in_issue.short_str }}</a> + {% endif %} + <span class="breadcrumb-item active">{{publication.title}}</span> {% endblock %} {% block headsup %} -<meta name="citation_title" content="{{ publication.title }}"/> -{% for author in publication.authors.all %} -{% if author.contributor %} -<meta name="citation_author" content="{{ author.contributor.user.last_name }}, {{ author.contributor.user.first_name }}"/> -{% elif author.unregistered_author %} -<meta name="citation_author" content="{{ author.unregistered_author.last_name }}, {{ author.unregistered_author.first_name }}"/> -{% endif %} -{% endfor %} -<meta name="citation_doi" content="{{ publication.doi_string }}"/> -<meta name="citation_publication_date" content="{{ publication.publication_date|date:'Y/m/d' }}"/> -<meta name="citation_journal_title" content="{{ journal }}"/> -<meta name="citation_issn" content="{{ journal.issn }}"/> -{% if publication.in_issue %} -<meta name="citation_volume" content="{{ publication.in_issue.in_volume.number }}"/> -<meta name="citation_issue" content="{{ publication.in_issue.number }}"/> -{% endif %} -<meta name="citation_firstpage" content="{{ publication.paper_nr|paper_nr_string_filter }}"/> -<meta name="citation_pdf_url" content="https://scipost.org/{{ publication.doi_string }}/pdf"/> -<meta name="dc.identifier" content="doi:{{ publication.doi_string }}"/> - -<script> - $(document).ready(function(){ - $("#citationslist").hide(); - - $("#citationslistbutton").click(function(){ - $("#citationslist").toggle(); - }); - }); -</script> + <meta name="citation_title" content="{{ publication.title }}"/> + {% for author in publication.authors.all %} + {% if author.contributor %} + <meta name="citation_author" content="{{ author.contributor.user.last_name }}, {{ author.contributor.user.first_name }}"/> + {% elif author.unregistered_author %} + <meta name="citation_author" content="{{ author.unregistered_author.last_name }}, {{ author.unregistered_author.first_name }}"/> + {% endif %} + {% endfor %} + <meta name="citation_doi" content="{{ publication.doi_string }}"/> + <meta name="citation_publication_date" content="{{ publication.publication_date|date:'Y/m/d' }}"/> + <meta name="citation_journal_title" content="{{ journal }}"/> + <meta name="citation_issn" content="{{ journal.issn }}"/> + {% if publication.in_issue %} + <meta name="citation_volume" content="{{ publication.in_issue.in_volume.number }}"/> + <meta name="citation_issue" content="{{ publication.in_issue.number }}"/> + {% endif %} + <meta name="citation_firstpage" content="{{ publication.paper_nr|paper_nr_string_filter }}"/> + <meta name="citation_pdf_url" content="https://scipost.org/{{ publication.doi_string }}/pdf"/> + <meta name="dc.identifier" content="doi:{{ publication.doi_string }}"/> + + <script> + $(document).ready(function(){ + $("#citationslist").hide(); + + $("#citationslistbutton").click(function(){ + $("#citationslist").toggle(); + }); + }); + </script> {% endblock headsup %} {% block content %} -{% if not publication.is_published and perms.can_publish_accepted_submission %} -<div class="card bg-warning text-white"> - <div class="card-body"> - <p class="card-text text-center"> - This Publication is not published yet. - Current status: {{ publication.get_status_display }} + {% is_scipost_admin request.user as is_scipost_admin %} + {% is_edcol_admin request.user as is_edcol_admin %} + + {{ block.super }} + + {% if not publication.is_published and perms.can_publish_accepted_submission %} + <div class="card bg-warning text-white mb-3"> + <div class="card-body"> + <p class="card-text text-center"> + This Publication is not published yet. + Current status: {{ publication.get_status_display }} + </p> + </div> + </div> + {% endif %} + + {% include 'partials/journals/publication_summary.html' with publication=publication %} + + {% if publication.commentary and publication.commentary.comments.vetted.exists %} + <h3>Post-publication commentaries</h3> + <p> + This Publication ({{ publication.commentary.comments.vetted.count }}) has been commented on, see <a href="{{ publication.commentary.get_absolute_url }}">this Publication's Commentary page</a> for details. </p> - </div> -</div> -{% endif %} - -{% include 'partials/journals/publication_summary.html' with publication=publication %} - -{% if publication.commentary and publication.commentary.comments.vetted.exists %} -<h3>Post-publication commentaries</h3> -<p> - This Publication ({{ publication.commentary.comments.vetted.count }}) has been commented on, see <a href="{{ publication.commentary.get_absolute_url }}">this Publication's Commentary page</a> for details. -</p> -{% endif %} - -{% if publication.citedby|length >= 1 %} - -<div class="row"> - <div class="col-6 col-md-2"> - <h3 class="mb-2">Cited by {{ publication.citedby|length }}</h3> - <a href="javascript:;" data-toggle="toggle" data-target="#citationslist">Toggle view</a> - </div> - <div class="col-6 col-md-2"> - <img src="{% static 'scipost/images/citedby.gif' %}" alt="Crossref Cited-by" width="64" /> - </div> -</div> -<div class="row" id="citationslist" style="display: none;"> - <div class="col-12"> - {% include 'journals/_publication_citations.html' with publication=publication %} - </div> -</div> -{% endif %} -<hr/> - -<div class="card"> - <div class="card-header"> - Author{{ publication.authors.all|length|pluralize }}/Affiliation{{ affiliations_list|length|pluralize }}: mappings to Contributors and <a href="{% url 'organizations:organizations' %}" target="_blank">Organizations</a> - </div> - <div class="card-body"> - <ul class="list-inline m-1"> + {% endif %} + + {% if publication.citedby|length >= 1 %} + + <div class="row"> + <div class="col-6 col-md-2"> + <h3 class="mb-2">Cited by {{ publication.citedby|length }}</h3> + <a href="javascript:;" data-toggle="toggle" data-target="#citationslist">Toggle view</a> + </div> + <div class="col-6 col-md-2"> + <img src="{% static 'scipost/images/citedby.gif' %}" alt="Crossref Cited-by" width="64" /> + </div> + </div> + <div class="row" id="citationslist" style="display: none;"> + <div class="col-12"> + {% include 'journals/_publication_citations.html' with publication=publication %} + </div> + </div> + {% endif %} + <hr class="mt-5 mb-4"/> + + {% if publication.topics.all or perms.scipost.can_manage_ontology %} + <h3 class="mt-2">Ontology / Topics</h3> + See full <a href="{% url 'ontology:ontology' %}">Ontology</a> or <a href="{% url 'ontology:topics' %}">Topics</a> database. + <br> + <br> + + <div> + {% for topic in publication.topics.all %} + <span class="label label-secondary"><a href="{% url 'ontology:topic_details' slug=topic.slug %}">{{ topic }}</a>{% if perms.scipost.can_manage_ontology %} <a href="{% url 'journals:publication_remove_topic' doi_label=publication.doi_label slug=topic.slug %}"><i class="fa fa-times-circle text-danger"></i></a>{% endif %}</span> + {% empty %} + <div>No Topic has yet been associated to this Publication</div> + {% endfor %} + </div> + + {% if perms.scipost.can_manage_ontology %} + + <br> + <ul class="list-inline"> + <li class="list-inline-item"> + <form class="form-inline" action="{% url 'journals:publication_add_topic' doi_label=publication.doi_label %}" method="post"> + <ul class="list-inline"> + <li class="list-inline-item">Add an existing Topic:</li> + <li class="list-inline-item">{% csrf_token %}{{ select_topic_form }}</li> + <li class="list-inline-item"><input class="btn btn-outline-secondary" type="submit" value="Link"></li> + </ul> + </form> + </li> + <li class="list-inline-item p-2">Can't find the Topic you need? <a href="{% url 'ontology:topic_create' %}" target="_blank">Create it</a> (opens in new window)</li> + </ul> + {% endif %} + + {% endif %} + + <h3 class="mt-4"> + Author{{ publication.authors.all|length|pluralize }} / Affiliation{{ affiliations_list|length|pluralize }}: mappings to Contributors and Organizations + </h3> + See all <a href="{% url 'organizations:organizations' %}" target="_blank">Organizations</a>. + <br> + + <ul class="list-inline my-2"> {% for author in publication.authors.all %} - <li class="list-inline-item mr-1"> - {% for aff in affiliations_list %} - {% if aff in author.affiliations.all %} - <sup>{{ forloop.counter }} </sup> - {% endif %}{% endfor %} - {% if author.is_registered %} - <a href="{{ author.contributor.get_absolute_url }}">{{ author.contributor.user.first_name }} {{ author.contributor.user.last_name }}</a>{% else %}{{ author.unregistered_author.first_name }} {{ author.unregistered_author.last_name }}{% endif %}{% if not forloop.last %}, {% endif %} - </li> + <li class="list-inline-item mr-1"> + {% for aff in affiliations_list %} + {% if aff in author.affiliations.all %} + <sup>{{ forloop.counter }} </sup> + {% endif %} + {% endfor %} + {% if author.is_registered %} + <a href="{{ author.contributor.get_absolute_url }}">{{ author.contributor.user.first_name }} {{ author.contributor.user.last_name }}</a>{% else %}{{ author.unregistered_author.first_name }} {{ author.unregistered_author.last_name }}{% endif %}{% if not forloop.last %}, + {% endif %} + </li> {% endfor %} </ul> - <ul class="list list-unstyled m-2"> + <ul class="list list-unstyled my-2 mx-3"> {% for aff in affiliations_list %} - <li><sup>{{ forloop.counter }}</sup> <a href="{{ aff.get_absolute_url }}">{{ aff.full_name_with_acronym }}</a></li> - {% endfor %} - </ul> - </div> -</div> - -{% if publication.get_all_funders %} -<div class="card"> - <div class="card-header"> - Funder{{ publication.get_all_funders|length|pluralize }} for the research work leading to this publication - </div> - <div class="card-content"> - <ul class="m-2"> - {% for funder in publication.get_all_funders %} - {% if funder.organization %} - {% if funder.name != funder.organization.name and funder.name != funder.organization.name_original %} - <li>{{ funder }} (through Organization: <a href="{{ funder.organization.get_absolute_url }}">{{ funder.organization.full_name_with_acronym }}</a>)</li> - {% else %} - <li><a href="{{ funder.organization.get_absolute_url }}">{{ funder.organization.full_name_with_acronym }}</a></li> - {% endif %} - {% else %} - <li><a href="{{ funder.get_absolute_url }}">{{ funder }}</a></li> - {% endif %} + <li><sup>{{ forloop.counter }}</sup> <a href="{{ aff.get_absolute_url }}">{{ aff.full_name_with_acronym }}</a></li> {% endfor %} </ul> - </div> -</div> -{% endif %} - -{% if is_scipost_admin or is_edcol_admin %} -{% if publication.institutions.all %} -<div class="card"> - <div class="card-header"> - Institution{{ publication.institutions.all|pluralize }} related to this Publication (<span class="text-danger">Admin-only view, to be removed</span>) - </div> - <div class="card-content"> - <ul class="m-2"> - {% for institution in publication.institutions.all %} - <li><a href="{{ institution.get_absolute_url }}">{{ institution }}</a></li> - {% endfor %} - </ul> - </div> -</div> -{% endif %} -{% endif %} - - -{% if request.user and request.user.contributor in publication.registered_authors.all %} -<h3>Author actions</h3> -<ul> - <li><a href="{% url 'commentaries:comment_on_publication' publication.doi_label %}">Place a comment on this publication</a></li> -</ul> -{% endif %} - -{% if publication.status == 'draft' and perms.scipost.can_draft_publication %} -<hr class="divider"> -<div class="row"> - <div class="col-12"> - {% include 'partials/journals/publication_preparation.html' with publication=publication %} - </div> -</div> -{% endif %} - -{% if is_edcol_admin %} -<hr class="divider"> -<div class="row"> - <div class="col-12"> - <h3>Editorial Administration tools</h3> - {% include 'partials/journals/admin/publication_actions.html' with publication=publication %} - </div> -</div> -{% endif %} + + {% if publication.get_all_funders %} + <div class="card"> + <div class="card-header"> + Funder{{ publication.get_all_funders|length|pluralize }} for the research work leading to this publication + </div> + <div class="card-content"> + <ul class="m-2"> + {% for funder in publication.get_all_funders %} + {% if funder.organization %} + {% if funder.name != funder.organization.name and funder.name != funder.organization.name_original %} + <li>{{ funder }} (through Organization: <a href="{{ funder.organization.get_absolute_url }}">{{ funder.organization.full_name_with_acronym }}</a>)</li> + {% else %} + <li><a href="{{ funder.organization.get_absolute_url }}">{{ funder.organization.full_name_with_acronym }}</a></li> + {% endif %} + {% else %} + <li><a href="{{ funder.get_absolute_url }}">{{ funder }}</a></li> + {% endif %} + {% endfor %} + </ul> + </div> + </div> + {% endif %} + + {% if is_scipost_admin or is_edcol_admin %} + {% if publication.institutions.all %} + <div class="card"> + <div class="card-header"> + Institution{{ publication.institutions.all|pluralize }} related to this Publication (<span class="text-danger">Admin-only view, to be removed</span>) + </div> + <div class="card-content"> + <ul class="m-2"> + {% for institution in publication.institutions.all %} + <li><a href="{{ institution.get_absolute_url }}">{{ institution }}</a></li> + {% endfor %} + </ul> + </div> + </div> + {% endif %} + {% endif %} + + {% if publication.status == 'draft' and perms.scipost.can_draft_publication %} + <hr class="divider"> + <div class="row"> + <div class="col-12"> + {% include 'partials/journals/publication_preparation.html' with publication=publication %} + </div> + </div> + {% endif %} + {% if is_edcol_admin %} + <hr class="divider"> + <div class="row"> + <div class="col-12"> + <h3>Editorial Administration tools</h3> + {% include 'partials/journals/admin/publication_actions.html' with publication=publication %} + </div> + </div> + {% endif %} + + + {% if request.user.contributor in publication.registered_authors.all %} + <h3>Author actions</h3> + <ul> + <li><a href="{% url 'commentaries:comment_on_publication' publication.doi_label %}">Place a comment on this publication</a></li> + </ul> + {% endif %} {% endblock content %} + +{% block footer_script %} + {{ block.super }} + {{ select_topic_form.media }} +{% endblock footer_script %} diff --git a/journals/templates/journals/publication_list.html b/journals/templates/journals/publication_list.html index 1a9c8dab6d977c98434019dd3006f980e6cc18e5..062baa6a090e37900ab739ecc3a1847b09a3b38a 100644 --- a/journals/templates/journals/publication_list.html +++ b/journals/templates/journals/publication_list.html @@ -11,27 +11,26 @@ {% block page_header %}<h1 class="highlight">Recent SciPost Publications</h1>{% endblock page_header %} {% block breadcrumb_items %} - <a href="{% url 'journals:journals' %}" class="breadcrumb-item">Journals</a> - <span class="breadcrumb-item">Publications</span> + <li class="breadcrumb-item"><a href="{% url 'journals:journals' %}">Journals</a></li> + <li class="breadcrumb-item active">Publications</li> {% endblock %} {% block content %} <div class="row"> <div class="col-12 ordering"> + <h1>Publications</h1> <p> - Found {{ page_obj.paginator.count }} Publications - <br> Order by: <a href="?{% url_replace orderby='date' page='' %}" class="d-inline-block mb-1 ml-2 {% active_get_request 'orderby' 'date' %}">publication date</a> <a href="?{% url_replace orderby='citations' page='' %}" class="d-inline-block mb-1 ml-2 {% active_get_request 'orderby' 'citations' %}">number of citations</a> </p> - <hr class="divider"> + <ul class="list-unstyled"> {% for publication in object_list %} - <li> - <div class="card card-grey card-publication"> + <li class=""> + <div class="card card-gray card-publication"> {% include 'journals/_publication_card_content.html' with publication=publication include_citation_rate=1 %} </div> </li> @@ -53,7 +52,7 @@ {% block sidebar %} <div class="row"> <div class="col-12"> - <h2 class="mb-2">Recent Issues</h2> + <h2>Recent Issues</h2> <div class="mb-1 pl-2"> <a href="?{% url_replace issue='' page='' subject='' %}" class="{% active_get_request 'issue' '' %}">All Issues</a> </div> diff --git a/journals/templates/partials/journals/publication_card.html b/journals/templates/partials/journals/publication_card.html index e327036ab9e37ccd2bded9d6fbb3b23b299b53c3..ad3b53b84fa9ee00f010b5f8687a166e8a543821 100644 --- a/journals/templates/partials/journals/publication_card.html +++ b/journals/templates/partials/journals/publication_card.html @@ -1,3 +1,3 @@ -<div id="{{publication.doi_label}}" class="card card-grey card-publication"> +<div id="{{publication.doi_label}}" class="card card-gray card-publication"> {% include 'journals/_publication_card_content.html' with publication=publication %} </div> diff --git a/journals/templates/partials/journals/publication_li_content.html b/journals/templates/partials/journals/publication_li_content.html index 9bb1406455a4bc4b18b6c3516416460ce667c9e3..4df9e3715121beb480c7de2bddca9beb5d79cc17 100644 --- a/journals/templates/partials/journals/publication_li_content.html +++ b/journals/templates/partials/journals/publication_li_content.html @@ -1,5 +1,5 @@ <div class="li publication"> - <h5 class="subject">{{publication.get_subject_area_display}}</h5> + <h5 class="subject"><a href="{% url 'journals:publications' %}?subject={{ publication.subject_area }}" class="muted-link">{{ publication.get_subject_area_display }}</a></h5> <h3 class="title"><a href="{{publication.get_absolute_url}}">{{publication.title}}</a></h3> <p class="authors">{{ publication.author_list }}</p> diff --git a/journals/templates/partials/journals/publication_summary.html b/journals/templates/partials/journals/publication_summary.html index 98b36a1b0b70c7fccece2e2e2cc2ba84415241a1..08819f6163ed4012c443e836f5b2f68d7767aa00 100644 --- a/journals/templates/partials/journals/publication_summary.html +++ b/journals/templates/partials/journals/publication_summary.html @@ -1,7 +1,7 @@ <div class="row"> <div class="col-12"> - <h2 class="pb-1 text-blue">{{publication.title}}{% if publication.status == 'draft' %} <label class="label label-warning label-sm">{{ publication.get_status_display }}</label>{% endif %}</h2> + <h2 class="text-blue">{{publication.title}}{% if publication.status == 'draft' %} <label class="label label-warning label-sm">{{ publication.get_status_display }}</label>{% endif %}</h2> <p class="mb-1">{{ publication.author_list }}</p> <p class="text-muted mb-0"> diff --git a/journals/urls/general.py b/journals/urls/general.py index 3ddd5a071c652791c1fe0b5ac6e04bb388f02334..65ce483561024601291220e74fdbd537507739fb 100644 --- a/journals/urls/general.py +++ b/journals/urls/general.py @@ -135,6 +135,14 @@ urlpatterns = [ journals_views.email_object_made_citable, name='email_object_made_citable'), + # Topics: + url(r'^publication_add_topic/(?P<doi_label>{regex})$'.format(regex=PUBLICATION_DOI_REGEX), + journals_views.publication_add_topic, + name='publication_add_topic'), + url(r'^publication_remove_topic/(?P<doi_label>{regex})/(?P<slug>[-\w]+)/$'.format(regex=PUBLICATION_DOI_REGEX), + journals_views.publication_remove_topic, + name='publication_remove_topic'), + # PubFraction allocation: url(r'^allocate_orgpubfractions/(?P<doi_label>{regex})$'.format(regex=PUBLICATION_DOI_REGEX), journals_views.allocate_orgpubfractions, diff --git a/journals/views.py b/journals/views.py index 5169628d3b5b865e26adf5528aa052ec71a70c80..8fe74e5972b623afe6dd833521441438b757bb2c 100644 --- a/journals/views.py +++ b/journals/views.py @@ -46,6 +46,8 @@ from comments.models import Comment from funders.forms import FunderSelectForm, GrantSelectForm from funders.models import Grant from mails.views import MailEditingSubView +from ontology.models import Topic +from ontology.forms import SelectTopicForm from organizations.models import Organization from submissions.constants import STATUS_PUBLISHED from submissions.models import Submission, Report @@ -161,7 +163,7 @@ def landing_page(request, doi_label): 'most_cited': Publication.objects.for_journal(journal.name).published().most_cited(5), 'latest_publications': Publication.objects.for_journal(journal.name).published()[:5], 'accepted_submissions': Submission.objects.accepted().filter( - submitted_to_journal=journal.name).order_by('-latest_activity'), + submitted_to=journal).order_by('-latest_activity'), } return render(request, 'journals/journal_landing_page.html', context) @@ -181,12 +183,14 @@ def redirect_to_about(request, doi_label): reverse('journal:about', kwargs={'doi_label': journal.doi_label}), permanent=True) def info_for_authors(request, doi_label): + """Author information about the Journal.""" journal = get_object_or_404(Journal, doi_label=doi_label) context = {'journal': journal} return render(request, 'journals/%s_info_for_authors.html' % doi_label, context) def about(request, doi_label): + """Journal specific about page.""" journal = get_object_or_404(Journal, doi_label=doi_label) context = { 'subject_areas': SCIPOST_SUBJECT_AREAS, @@ -196,6 +200,7 @@ def about(request, doi_label): def issue_detail(request, doi_label): + """Issue detail page.""" issue = get_object_or_404(Issue.objects.published(), doi_label=doi_label) journal = issue.in_journal or issue.in_volume.in_journal @@ -221,9 +226,7 @@ def issue_detail(request, doi_label): # Publication process # ####################### class PublicationGrantsView(PermissionsMixin, UpdateView): - """ - Add/update grants associated to a Publication. - """ + """Add/update grants associated to a Publication.""" permission_required = 'scipost.can_draft_publication' queryset = Publication.objects.drafts() slug_field = slug_url_kwarg = 'doi_label' @@ -770,6 +773,38 @@ def metadata_DOAJ_deposit(request, doi_label): kwargs={'doi_label': publication.doi_label})) +@permission_required('scipost.can_manage_ontology', return_403=True) +def publication_add_topic(request, doi_label): + """ + Add a predefined Topic to an existing Publication object. + This also adds the Topic to all Submissions of this Publication. + """ + publication = get_object_or_404(Publication, doi_label=doi_label) + select_topic_form = SelectTopicForm(request.POST or None) + if select_topic_form.is_valid(): + publication.topics.add(select_topic_form.cleaned_data['topic']) + for sub in publication.accepted_submission.thread: + sub.topics.add(select_topic_form.cleaned_data['topic']) + messages.success(request, 'Successfully linked Topic to this publication') + return redirect(reverse('scipost:publication_detail', + kwargs={'doi_label': publication.doi_label})) + + +@permission_required('scipost.can_manage_ontology', return_403=True) +def publication_remove_topic(request, doi_label, slug): + """ + Remove the Topic from the Publication, and from all associated Submissions. + """ + publication = get_object_or_404(Publication, doi_label=doi_label) + topic = get_object_or_404(Topic, slug=slug) + publication.topics.remove(topic) + for sub in publication.accepted_submission.thread: + sub.topics.remove(topic) + messages.success(request, 'Successfully removed Topic') + return redirect(reverse('scipost:publication_detail', + kwargs={'doi_label': publication.doi_label})) + + @login_required def allocate_orgpubfractions(request, doi_label): """ @@ -1268,6 +1303,7 @@ def publication_detail(request, doi_label): 'publication': publication, 'affiliations_list': publication.get_all_affiliations(), 'journal': publication.get_journal(), + 'select_topic_form': SelectTopicForm(), } return render(request, 'journals/publication_detail.html', context) diff --git a/mails/admin.py b/mails/admin.py index d7d26b99e714c27c50f251942892f2fe9976d30c..6967486fda3158c00f55d1233d4b8b1d568be0a8 100644 --- a/mails/admin.py +++ b/mails/admin.py @@ -8,7 +8,7 @@ from .models import MailLog class MailLogAdmin(admin.ModelAdmin): - list_display = ['__str__', 'processed'] + list_display = ['__str__', 'to_recipients', 'created', 'processed'] readonly_fields = ('created', 'latest_activity') diff --git a/news/templates/news/news_card_content.html b/news/templates/news/news_card_content.html index 312340de922aaabdb87f237a9e0675127862d926..f80216f05b273731cd57510e8e57a716c1f3da4c 100644 --- a/news/templates/news/news_card_content.html +++ b/news/templates/news/news_card_content.html @@ -1,22 +1,26 @@ -<div class="card-body news-item" id="news_{{news.id}}"> - {% if news.image %} - <div class="row"> +<div class="p-3 mb-3 bg-light news-item scipost-bar" id="news_{{ news.id }}"> + <h3 class="title">{{ news.headline }}</h3> + <h5 class="sub-title">{{ news.date|date:'j F Y' }}</h5> + {% if news.image %} + <div class="row"> + <div class="col-sm-3 col-lg-2"> + <img class="mb-3 mb-sm-0 {{ news.image.css_class }}" src="{{ news.image.url }}" alt="image"/> + </div> + <div class="col-sm-9 col-lg-10"> + <p class="mb-0">{{ news.blurb|safe }}</p> + </div> + </div> + {% else %} + <p class="mb-0">{{ news.blurb|safe }}</p> + {% endif %} + {% if news.followup_link %} + <a class="mt-3 d-inline-block" href="{{ news.followup_link }}">{{ news.followup_link_text }}</a> + {% endif %} + {% if perms.scipost.can_manage_news %} + <br> + <br> + <a href="{% url 'news:newsitem_update' pk=news.id %}">Edit news item</a> · + <a href="{% url 'news:newsitem_delete' pk=news.id %}" class="text-danger">Delete</a> + {% endif %} - <div class="col-2"> - <img class="d-flex mr-3 {{ news.image.css_class }}" src="{{ news.image.url }}" alt="image"/> - </div> - <div class="col-10"> - {% endif %} - <div> - <h2 class="card-title">{{news.headline}}</h2> - <div class="text-muted date">{{news.date|date:'j F Y'}}</div> - <div class="pb-3">{{news.blurb|safe}}</div> - {% if news.followup_link %} - <a href="{{news.followup_link}}">{{news.followup_link_text}}</a> - {% endif %} - </div> - {% if news.image %} - </div> - </div> - {% endif %} </div> diff --git a/news/templates/news/news_card_content_for_api.html b/news/templates/news/news_card_content_for_api.html index 5815a30d32e69e9235ba758878f0e155072f3468..99e86c1da02d165476f43db9500719b2b455fc7f 100644 --- a/news/templates/news/news_card_content_for_api.html +++ b/news/templates/news/news_card_content_for_api.html @@ -1,12 +1,8 @@ -<div class="card-body px-0 py-2 news-item"> - <h3 class="card-title mb-0 pb-0">{{headline}}</h3> - <div> - <h5 class="text-muted mb-2">{{date}}</h5> - <div> - {{blurb|truncatechars_html:180|safe}} - - <br> - <a href="{% url 'news:news' %}#news_{{id}}" class="my-1 d-inline-block">Read more</a> - </div> - </div> +<div class="news-item-short"> + <h3 class="title">{{headline}}</h3> + <h5 class="sub-title">{{date}}</h5> + <p> + {{blurb|truncatechars_html:180|safe}} + </p> + <a href="{% url 'news:news' %}#news_{{id}}" class="my-1 d-inline-block">Read more →</a> </div> diff --git a/news/templates/news/news_card_content_short.html b/news/templates/news/news_card_content_short.html index 2f796a59052dbe0c60c8aea137a5fdd250c72036..3d08370c75adead655291bdf41a918b6ec0771e8 100644 --- a/news/templates/news/news_card_content_short.html +++ b/news/templates/news/news_card_content_short.html @@ -1,12 +1,8 @@ -<div class="card-body px-0 py-2 news-item"> - <h3 class="card-title mb-0 pb-0">{{news.headline}}</h3> - <div> - <h5 class="text-muted mb-2">{{news.date|date:'j F Y'}}</h5> - <div> - {{news.blurb|truncatechars_html:180|safe}} - - <br> - <a href="{% url 'news:news' %}#news_{{news.id}}" class="my-1 d-inline-block">Read more</a> - </div> - </div> +<div class="news-item-short"> + <h3 class="title">{{news.headline}}</h3> + <h5 class="sub-title">{{news.date|date:'j F Y'}}</h5> + <p> + {{news.blurb|truncatechars_html:180|safe}} + </p> + <a href="{% url 'news:news' %}#news_{{news.id}}" class="my-1">Read more →</a> </div> diff --git a/news/templates/news/news_manage.html b/news/templates/news/news_manage.html index 315a294a155f10c435c3da1d8795d677ef538798..6d734f0f360277c872af7214933e7691e064dba0 100644 --- a/news/templates/news/news_manage.html +++ b/news/templates/news/news_manage.html @@ -4,86 +4,99 @@ {% block pagetitle %}: News Management{% endblock pagetitle %} +{% block breadcrumb %} + <nav class="breadcrumb-nav"> + <div class="container"> + <ol class="breadcrumb"> + <li class="breadcrumb-item"><a href="{% url 'scipost:index' %}" >Home</a></li> + <li class="breadcrumb-item"><a href="{% url 'news:news' %}" >News</a></span> + <li class="breadcrumb-item active">News Management</span> + </ol> + </div> + </nav> +{% endblock %} + {% block content %} <div class="row"> - <div class="col-12"> - <h1>News Management</h1> - </div> -</div> + <div class="col-12"> + <h1 class="highlight">News Management</h1> - -<hr/> -<div class="row"> - <div class="col-12"> - <h1>NewsLetters</h1> - <a href="{% url 'news:newsletter_create' %}">Add a NewsLetter</a> - <br/><br/> - {% for nl in newsletters %} - <div class="card"> - <div class="card-header" data-toggle="collapse" href="#collapse{{ nl.id }}" aria-expanded="false" aria-controls="collapse{{ nl.id }}"> - {{ nl }} - (status: {% if nl.published %}published{% else %}unpublished{% endif %}) - </div> - <div class="card-body collapse" id="collapse{{ nl.id }}"> - <div class="row"> - <div class="col-6"> - <a href="{{ nl.get_absolute_url }}" target="_blank">View this Newsletter in a separate window</a> - <br/><br/> - <h3>NewsItems included in this Newsletter:</h3> - <ul> - {% for nt in nl.newsletternewsitemstable_set.all|dictsort:'order' %} - <li>{{ nt.newsitem }}{% if not nt.newsitem.published %} <span class="text-danger">WARNING: unpublished</span>{% endif %}</li> - {% empty %} - <li>No associated NewsItems found</li> - {% endfor %} - </ul> - </div> - <div class="col-6"> - <h3>Actions:</h3> - <ul> - <li><a href="{% url 'news:newsletter_update' pk=nl.id %}">Update</a></li> - <li><a href="{% url 'news:newsletter_update_ordering' pk=nl.id %}">Update items ordering</a></li> - <li><a href="{% url 'news:newsletter_delete' pk=nl.id %}">Delete</a></li> - <li> - Add a News Item to this Newsletter: - <form class="d-block mt-2 mb-3" action="{% url 'news:add_newsitem_to_newsletter' nlpk=nl.id %}" method="post"> - {% csrf_token %} - {{ add_ni_to_nl_form|bootstrap }} - <input type="submit" name="submit" value="Add" class="btn btn-outline-secondary"> - </form> - </li> - </ul> - </div> - </div> - </div> - {% endfor %} + <h2 class="highlight">Newsletters</h2> + <a href="{% url 'news:newsletter_create' %}">Add a Newsletter</a> + <br/> + <br/> + {% for nl in newsletters %} + <div class="card mb-2"> + <div class="card-header" data-toggle="collapse" href="#collapse{{ nl.id }}" aria-expanded="false" aria-controls="collapse{{ nl.id }}"> + {{ nl }} + (status: {{ nl.published|yesno:'published,unpublished' }}) + </div> + <div class="card-body collapse" id="collapse{{ nl.id }}"> + <div class="row"> + <div class="col-6"> + <a href="{{ nl.get_absolute_url }}" target="_blank">View this Newsletter in a separate window</a> + <br/> + <br/> + <h3>News Items included in this Newsletter:</h3> + <ul> + {% for nt in nl.newsletternewsitemstable_set.all|dictsort:'order' %} + <li>{{ nt.newsitem }}{% if not nt.newsitem.published %} <span class="text-danger">WARNING: unpublished</span>{% endif %}</li> + {% empty %} + <li>No associated NewsItems found</li> + {% endfor %} + </ul> + </div> + <div class="col-6"> + <h3>Actions:</h3> + <ul> + <li><a href="{% url 'news:newsletter_update' pk=nl.id %}">Update</a></li> + <li><a href="{% url 'news:newsletter_update_ordering' pk=nl.id %}">Update items ordering</a></li> + <li><a href="{% url 'news:newsletter_delete' pk=nl.id %}">Delete</a></li> + <li> + Add a News Item to this Newsletter: + <form class="d-block mt-2 mb-3" action="{% url 'news:add_newsitem_to_newsletter' nlpk=nl.id %}" method="post"> + {% csrf_token %} + {{ add_ni_to_nl_form|bootstrap }} + <input type="submit" name="submit" value="Add" class="btn btn-outline-secondary"> + </form> + </li> + </ul> + </div> + </div> + </div> + </div> + {% endfor %} </div> - </div> - -<hr/> +</div> <div class="row"> - <div class="col-12"> - <h1>NewsItems</h1> - <a href="{% url 'news:newsitem_create' %}">Add a NewsItem</a> - <br/><br/> - <table class="table"> - <thead> - <th>Item</th> - <th>Published?</th> - <th>On homepage?</th> - <th>Actions</th> - <th></th> - </thead> - {% for ni in newsitems %} - <tr> - <td>{{ ni }}</td> - <td>{{ ni.published }}</td> - <td>{{ ni.on_homepage }}</td> - <td><a href="{% url 'news:newsitem_update' pk=ni.id %}">Update</a></td> - <td><a href="{% url 'news:newsitem_delete' pk=ni.id %}">Delete</a></td> - </tr> - {% endfor %} - </table> - </div> + <div class="col-12"> + <h2 class="highlight">News items</h2> + <a href="{% url 'news:newsitem_create' %}">Add a News item</a> + <br/> + <br/> + <table class="table table-hover"> + <thead> + <th>Item</th> + <th>Publication date</th> + <th>Published?</th> + <th>On homepage?</th> + <th>Actions</th> + </thead> + </tbody> + {% for ni in newsitems %} + <tr> + <td>{{ ni.headline }}</td> + <td>{{ ni.date }}</td> + <td><i class="fa fa-{{ ni.published|yesno:'check text-success,times text-danger' }}"></i> {{ ni.published|yesno:'Yes,No' }}</td> + <td><i class="fa fa-{{ ni.on_homepage|yesno:'check text-success,times text-danger' }}"></i> {{ ni.on_homepage|yesno:'Yes,No' }}</td> + <td> + <a href="{% url 'news:newsitem_update' pk=ni.id %}">Update</a> · + <a href="{% url 'news:newsitem_delete' pk=ni.id %}" class="text-danger">Delete</a> + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> </div> {% endblock content %} diff --git a/news/templates/news/newsitem_confirm_delete.html b/news/templates/news/newsitem_confirm_delete.html index a2d678e164e830abe25fb5da88ff797da8640f1b..cd82b762d74ccde92ddff0b6bdd7ddab7f14e56a 100644 --- a/news/templates/news/newsitem_confirm_delete.html +++ b/news/templates/news/newsitem_confirm_delete.html @@ -2,17 +2,29 @@ {% load bootstrap %} -{% block pagetitle %}: Delete NewsItem{% endblock pagetitle %} +{% block pagetitle %}: Delete News Item{% endblock pagetitle %} + + +{% block breadcrumb %} + <nav class="breadcrumb-nav"> + <div class="container"> + <ol class="breadcrumb"> + <li class="breadcrumb-item"><a href="{% url 'scipost:index' %}" >Home</a></li> + <li class="breadcrumb-item"><a href="{% url 'news:news' %}" >News</a></span> + <li class="breadcrumb-item"><a href="{% url 'news:manage' %}">News Management</a></span> + <li class="breadcrumb-item active" aria-current="page">Delete {{ object.headline|truncatechars:20 }}</span> + </ol> + </div> + </nav> +{% endblock %} {% block content %} <div class="row"> <div class="col-12"> - <h1 class="highlight">Delete NewsItem</h1> - {{ object }} - </div> -</div> -<div class="row"> - <div class="col-12"> + <h1 class="highlight">Delete News Item</h1> + {% include 'news/news_card_content.html' with news=object %} + + <form method="post"> {% csrf_token %} <h3 class="mb-2">Are you sure you want to delete this NewsItem?</h3> diff --git a/news/templates/news/newsitem_create.html b/news/templates/news/newsitem_create.html index fafb194eb0675d2a731236c9b4077babaf39140f..d063d7a57251cacd314a956666f94152503c6ac7 100644 --- a/news/templates/news/newsitem_create.html +++ b/news/templates/news/newsitem_create.html @@ -2,11 +2,28 @@ {% load bootstrap %} -{% block pagetitle %}: NewsItems{% endblock pagetitle %} +{% block pagetitle %}: Create News Item{% endblock pagetitle %} + +{% block breadcrumb %} + <nav class="breadcrumb-nav"> + <div class="container"> + <ol class="breadcrumb"> + <li class="breadcrumb-item"><a href="{% url 'scipost:index' %}" >Home</a></li> + <li class="breadcrumb-item"><a href="{% url 'news:news' %}" >News</a></span> + <li class="breadcrumb-item"><a href="{% url 'news:manage' %}">News Management</a></span> + <li class="breadcrumb-item active" aria-current="page">Create News Item</span> + </ol> + </div> + </nav> +{% endblock %} + {% block content %} + <div class="row"> <div class="col-12"> + <h1 class="highlight">Create News Item</h1> + <form action="{% url 'news:newsitem_create' %}" method="post" enctype="multipart/form-data"> {% csrf_token %} {{ form|bootstrap }} diff --git a/news/templates/news/newsitem_list.html b/news/templates/news/newsitem_list.html index b6d2a756affb7f7122dad3731b3eb2ff0dbee334..c1a590bdadd3752b8738131a831853a59597581d 100644 --- a/news/templates/news/newsitem_list.html +++ b/news/templates/news/newsitem_list.html @@ -5,6 +5,19 @@ {% load request_filters %} {% load staticfiles %} + +{% block breadcrumb %} + <nav class="breadcrumb-nav"> + <div class="container"> + <ol class="breadcrumb"> + <li class="breadcrumb-item"><a href="{% url 'scipost:index' %}" >Home</a></li> + <li class="breadcrumb-item active" aria-current="page">News</span> + </ol> + </div> + </nav> +{% endblock %} + + {% block content %} <div class="row"> @@ -18,39 +31,37 @@ <div class="row"> <div class="col-12"> - {% if is_paginated %} - <p> - {% if page_obj.has_previous %} - <a href="?{% url_replace page=page_obj.previous_page_number %}">Previous</a> - {% endif %} - Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}. - {% if page_obj.has_next %} - <a href="?{% url_replace page=page_obj.next_page_number %}">Next</a> - {% endif %} - <span class="float-right"><a href="{% url 'scipost:index' %}">Go back to the homepage.</a></span> - </p> - {% endif %} + {% if is_paginated %} + <p> + {% if page_obj.has_previous %} + <a href="?{% url_replace page=page_obj.previous_page_number %}">Previous</a> + {% endif %} + Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}. + {% if page_obj.has_next %} + <a href="?{% url_replace page=page_obj.next_page_number %}">Next</a> + {% endif %} + <span class="float-right"><a href="{% url 'scipost:index' %}">Go back to the homepage.</a></span> + </p> + {% endif %} - {% for item in object_list %} - <div class="card card-grey card-news"> - {% include 'news/news_card_content.html' with news=item %} - </div> - {% empty %} - <div>No news found.</div> - {% endfor %} - - {% if is_paginated %} - <p class="mt-4"> - {% if page_obj.has_previous %} - <a href="?{% url_replace page=page_obj.previous_page_number %}">Previous</a> - {% endif %} - Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}. - {% if page_obj.has_next %} - <a href="?{% url_replace page=page_obj.next_page_number %}">Next</a> - {% endif %} - <span class="float-right"><a href="{% url 'scipost:index' %}">Go back to the homepage.</a></span> - </p> - {% endif %} + {% for item in object_list %} + {% include 'news/news_card_content.html' with news=item %} + {% empty %} + <div>No news found.</div> + {% endfor %} + + {% if is_paginated %} + <p class="mt-4"> + {% if page_obj.has_previous %} + <a href="?{% url_replace page=page_obj.previous_page_number %}">Previous</a> + {% endif %} + Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}. + {% if page_obj.has_next %} + <a href="?{% url_replace page=page_obj.next_page_number %}">Next</a> + {% endif %} + <span class="float-right"><a href="{% url 'scipost:index' %}">Go back to the homepage.</a></span> + </p> + {% endif %} </div> </div> diff --git a/news/templates/news/newsitem_update.html b/news/templates/news/newsitem_update.html index c4b6415688da9843a1bf619acb3518e8241041cd..85af2b0902b4f4506ce094bbb9d2804096aafde7 100644 --- a/news/templates/news/newsitem_update.html +++ b/news/templates/news/newsitem_update.html @@ -2,32 +2,30 @@ {% load bootstrap %} -{% block pagetitle %}: NewsItems{% endblock pagetitle %} +{% block pagetitle %}: News Items{% endblock pagetitle %} -{% block breadcrumb_items %} -{{ block.super }} -<a href="{% url 'news:news' %}" class="breadcrumb-item">News</a> -<a href="{% url 'news:manage' %}" class="breadcrumb-item">Manage</a> -<span class="breadcrumb-item">Update NewsItem</span> -{% endblock breadcrumb_items %} +{% block breadcrumb %} + <nav class="breadcrumb-nav"> + <div class="container"> + <ol class="breadcrumb"> + <li class="breadcrumb-item"><a href="{% url 'scipost:index' %}" >Home</a></li> + <li class="breadcrumb-item"><a href="{% url 'news:news' %}" >News</a></span> + <li class="breadcrumb-item"><a href="{% url 'news:manage' %}">News Management</a></span> + <li class="breadcrumb-item active" aria-current="page">{{ object.headline|truncatechars:20 }}</span> + </ol> + </div> + </nav> +{% endblock %} -{% block content %} -<div class="row"> - <div class="col-12"> - <h1>News Item to update:</h1> - <div class="card card-grey card-news"> - {% include 'news/news_card_content.html' with news=object %} - </div> - </div> -</div> - - <hr/> +{% block content %} <div class="row"> <div class="col-12"> - <h1>Edit it here:</h1> + <h1 class="highlight">Update News Item</h1> + {% include 'news/news_card_content.html' with news=object %} + <br> <form action="{% url 'news:newsitem_update' pk=object.id %}" method="post" enctype="multipart/form-data"> {% csrf_token %} {{ form|bootstrap }} diff --git a/news/templates/news/newsletter_create.html b/news/templates/news/newsletter_create.html index 1313af25d9e64feded62fe20c49e575c9097ab9f..f9e0c71bf0b9e67c5d61bb21a3aa56682be066e4 100644 --- a/news/templates/news/newsletter_create.html +++ b/news/templates/news/newsletter_create.html @@ -2,11 +2,28 @@ {% load bootstrap %} -{% block pagetitle %}: NewsLetters{% endblock pagetitle %} +{% block pagetitle %}: Create Newsletter{% endblock pagetitle %} + +{% block breadcrumb %} + <nav class="breadcrumb-nav"> + <div class="container"> + <ol class="breadcrumb"> + <li class="breadcrumb-item"><a href="{% url 'scipost:index' %}" >Home</a></li> + <li class="breadcrumb-item"><a href="{% url 'news:news' %}" >News</a></span> + <li class="breadcrumb-item"><a href="{% url 'news:manage' %}">News Management</a></span> + <li class="breadcrumb-item active" aria-current="page">Create Newsletter</span> + </ol> + </div> + </nav> +{% endblock %} + {% block content %} + <div class="row"> <div class="col-12"> + <h1 class="highlight">Create Newsletter</h1> + <form action="{% url 'news:newsletter_create' %}" method="post"> {% csrf_token %} {{ form|bootstrap }} diff --git a/news/views.py b/news/views.py index 9e25b239fcda166bf2ff3ad70b298e3016e545d5..e9878b9d1b6824c87652afd0931d007d682dbad0 100644 --- a/news/views.py +++ b/news/views.py @@ -7,7 +7,6 @@ from django.core.urlresolvers import reverse_lazy from django.shortcuts import get_object_or_404, render, redirect from django.views.generic.base import TemplateView from django.views.generic.edit import CreateView, UpdateView, DeleteView -from django.views.generic.detail import DetailView from django.views.generic.list import ListView from guardian.decorators import permission_required diff --git a/notifications/constants.py b/notifications/constants.py index 52871544a54425a7d5e603e1fffd0a8b947cab25..a2c0e29fec4317e773344324e64a9c20d02b6adc 100644 --- a/notifications/constants.py +++ b/notifications/constants.py @@ -4,8 +4,10 @@ __license__ = "AGPL v3" NOTIFICATION_REFEREE_DEADLINE = 'referee_task_deadline' NOTIFICATION_REFEREE_OVERDUE = 'referee_task_overdue' +NOTIFICATION_REPORT_UNFINISHED = 'report_unfinished' NOTIFICATION_TYPES = ( (NOTIFICATION_REFEREE_DEADLINE, 'Refereeing Task is approaching its deadline'), - (NOTIFICATION_REFEREE_OVERDUE, 'Refereeing Task is overdue') + (NOTIFICATION_REFEREE_OVERDUE, 'Refereeing Task is overdue'), + (NOTIFICATION_REPORT_UNFINISHED, 'Report is in draft'), ) diff --git a/notifications/managers.py b/notifications/managers.py index 5f4447eb7890b24ccd7591271d261f3a95173fb4..b193c31122b8c37da153e2a71574cc1f2e0e0a95 100644 --- a/notifications/managers.py +++ b/notifications/managers.py @@ -3,26 +3,37 @@ __license__ = "AGPL v3" from django.db import models +from django.utils import timezone + +from datetime import timedelta class NotificationQuerySet(models.query.QuerySet): + """Queryset for Notification model.""" def unsent(self): + """Return all non-emailed Notications.""" return self.filter(emailed=False) def sent(self): + """Return all emailed Notications.""" return self.filter(emailed=True) def unread(self): - """Return only unread items in the current queryset""" + """Return only unread items in the current queryset.""" return self.filter(unread=True) + def unread_or_today(self): + """Return only unread items and those created in the last 24 hours.""" + now_min_24 = timezone.now() - timedelta(hours=24) + return self.filter(models.Q(unread=True) | models.Q(created__gt=now_min_24)).distinct() + def pseudo_unread(self): - """Return only unread items in the current queryset""" + """Return only unread items in the current queryset.""" return self.filter(pseudo_unread=True) def read(self): - """Return only read items in the current queryset""" + """Return only read items in the current queryset.""" return self.filter(unread=False) def mark_all_as_read(self, recipient=None): @@ -55,12 +66,12 @@ class NotificationQuerySet(models.query.QuerySet): return qs.update(unread=True) def deleted(self): - """Return only deleted items in the current queryset""" + """Return only deleted items in the current queryset.""" raise DeprecationWarning return self.filter(deleted=True) def active(self): - """Return only active(un-deleted) items in the current queryset""" + """Return only active(un-deleted) items in the current queryset.""" raise DeprecationWarning return self.filter(deleted=False) diff --git a/notifications/migrations/0002_auto_20180819_1343.py b/notifications/migrations/0002_auto_20180819_1343.py new file mode 100644 index 0000000000000000000000000000000000000000..69b20125feaee4a21b8d371fa231d0cda89d438c --- /dev/null +++ b/notifications/migrations/0002_auto_20180819_1343.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-08-19 11:43 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifications', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='notification', + name='internal_type', + field=models.CharField(blank=True, choices=[('referee_task_deadline', 'Refereeing Task is approaching its deadline'), ('referee_task_overdue', 'Refereeing Task is overdue'), ('report_unfinished', 'Report is in draft')], max_length=255), + ), + ] diff --git a/notifications/migrations/0002_notification_url_code.py b/notifications/migrations/0002_notification_url_code.py new file mode 100644 index 0000000000000000000000000000000000000000..b8922d06cd68ebd87772913a30232203c46fe343 --- /dev/null +++ b/notifications/migrations/0002_notification_url_code.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-12-04 19:11 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifications', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='notification', + name='url_code', + field=models.CharField(blank=True, max_length=255), + ), + ] diff --git a/notifications/migrations/0003_notification_url_code.py b/notifications/migrations/0003_notification_url_code.py new file mode 100644 index 0000000000000000000000000000000000000000..3c3e177b32c61d7f7c2830f1a4096b661a09ea31 --- /dev/null +++ b/notifications/migrations/0003_notification_url_code.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-08-20 13:11 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifications', '0002_auto_20180819_1343'), + ] + + operations = [ + migrations.AddField( + model_name='notification', + name='url_code', + field=models.CharField(blank=True, max_length=16), + ), + ] diff --git a/notifications/migrations/0004_merge_20181207_1008.py b/notifications/migrations/0004_merge_20181207_1008.py new file mode 100644 index 0000000000000000000000000000000000000000..354bbc87353d1f66f2135966d12138501f6949f9 --- /dev/null +++ b/notifications/migrations/0004_merge_20181207_1008.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-12-07 09:08 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifications', '0002_notification_url_code'), + ('notifications', '0003_notification_url_code'), + ] + + operations = [ + ] diff --git a/notifications/models.py b/notifications/models.py index 3f0d1dd553cfac820324b9030c688b2d5558b251..fa02efaef64b47e0052615cabf37a62940e89475 100644 --- a/notifications/models.py +++ b/notifications/models.py @@ -13,9 +13,8 @@ from .managers import NotificationQuerySet class FakeActors(models.Model): - """ - This Model acts as a surrogate person that either is unknown, deceased, fake, etc. etc. - """ + """This Model acts as a surrogate person that either is unknown, deceased, fake, etc. etc.""" + name = models.CharField(max_length=256) def __str__(self): @@ -23,7 +22,8 @@ class FakeActors(models.Model): class Notification(models.Model): - """ + """A short message meant for one user. + Action model describing the actor acting out a verb (on an optional target). Nomenclature based on http://activitystrea.ms/specs/atom/1.0/ @@ -40,6 +40,7 @@ class Notification(models.Model): justquick reached level 60 1 minute ago mitsuhiko closed issue 70 on mitsuhiko/flask 3 hours ago """ + LEVELS = (('success', 'Success'), ('info', 'Info'), ('warning', 'Warning'), ('error', 'Error')) level = models.CharField(choices=LEVELS, default='info', max_length=20) @@ -60,6 +61,8 @@ class Notification(models.Model): target_object_id = models.CharField(max_length=255, blank=True, null=True) target = GenericForeignKey('target_content_type', 'target_object_id') + url_code = models.CharField(max_length=16, blank=True) + action_object_content_type = models.ForeignKey(ContentType, blank=True, null=True, related_name='notify_action_object') action_object_object_id = models.CharField(max_length=255, blank=True, null=True) @@ -71,6 +74,8 @@ class Notification(models.Model): # of notifications. internal_type = models.CharField(max_length=255, blank=True, choices=NOTIFICATION_TYPES) + url_code = models.CharField(max_length=255, blank=True) + objects = NotificationQuerySet.as_manager() class Meta: diff --git a/notifications/signals.py b/notifications/signals.py index 24aa4da23d2135412f4412e672610f4804f45889..f386cf4eb3419d4bd376c460ec22397489d5c317 100644 --- a/notifications/signals.py +++ b/notifications/signals.py @@ -8,12 +8,13 @@ from .models import Notification notify = Signal(providing_args=[ - 'recipient', 'actor', 'verb', 'action_object', 'target', 'description', 'level', 'type' -]) + 'recipient', 'actor', 'verb', 'action_object', 'target', + 'description', 'level', 'type', 'url_code']) @receiver(notify) def notify_receiver(sender, **kwargs): + """Create a new Notification.""" if not type(kwargs['recipient']) == list: recipient = [kwargs['recipient']] else: @@ -28,10 +29,11 @@ def notify_receiver(sender, **kwargs): target=kwargs.get('target'), description=kwargs.get('description'), level=kwargs.get('level', 'info'), - internal_type=kwargs.get('type', '') + internal_type=kwargs.get('type', ''), + url_code=kwargs.get('url_code', '') ) notification.save() - print("Request finished!") + return # Basic working method to send a notification to a user using signals: diff --git a/notifications/templates/notifications/partials/notification_list_popover.html b/notifications/templates/notifications/partials/notification_list_popover.html index 86cb6344269f533abd00158d4f7834347225ac3b..2d2c399921517e693313c49aef240c415ebeff71 100644 --- a/notifications/templates/notifications/partials/notification_list_popover.html +++ b/notifications/templates/notifications/partials/notification_list_popover.html @@ -30,7 +30,7 @@ {% endif %} {% if is_financial_admin %} - <a class="item {% active 'finances:finances' %}" href="{% url 'finances:finance' %}">Financial Administration</a> + <a class="item {% active 'finances:finances' %}" href="{% url 'finances:finances' %}">Financial Administration</a> {% endif %} {% if perms.scipost.can_view_all_funding_info %} diff --git a/notifications/templatetags/notifications_tags.py b/notifications/templatetags/notifications_tags.py index 5694968bd269a2c50b8da240e7a89ef8e48da3d7..347616c83b90b205300b7c2d3abf04eef36a693b 100644 --- a/notifications/templatetags/notifications_tags.py +++ b/notifications/templatetags/notifications_tags.py @@ -12,7 +12,7 @@ register = Library() @register.simple_tag(takes_context=True) def live_notify_badge(context, classes=''): - html = "<span class='live_notify_badge {classes}'>0</span>".format(classes=classes) + html = "<span id='live_notify_badge' class='live_notify_badge {classes}'>0</span>".format(classes=classes) return format_html(html) diff --git a/notifications/views.py b/notifications/views.py index 8fdb0ac508309cf437ebabbc9b5abaf2acbe0cc6..4bd31d8bbcad49ae37614f638f0be0ab1bf1614c 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -13,24 +13,32 @@ from .utils import id2slug, slug2id def is_test_user(user): + """Check if user is test user. + + To be removed after test-phase is over. + """ + return True return user.groups.filter(name='Testers').exists() @login_required @user_passes_test(is_test_user) def forward(request, slug): - """ - Open the url of the target object of the notification and redirect. + """Open the url of the target object of the notification and redirect. + In addition, mark the notification as read. """ notification = get_object_or_404(Notification, recipient=request.user, id=slug2id(slug)) notification.mark_as_read() + if hasattr(notification.target, 'get_notification_url'): + return redirect(notification.target.get_notification_url(notification.url_code)) return redirect(notification.target.get_absolute_url()) @login_required @user_passes_test(is_test_user) def mark_toggle(request, slug=None): + """Toggle mark as read.""" id = slug2id(slug) notification = get_object_or_404(Notification, recipient=request.user, id=id) @@ -47,6 +55,7 @@ def mark_toggle(request, slug=None): def live_unread_notification_count(request): + """Return JSON of unread messages count.""" if not request.user.is_authenticated(): data = {'unread_count': 0} else: @@ -55,10 +64,11 @@ def live_unread_notification_count(request): def live_notification_list(request): + """Return JSON of unread count and content of messages.""" if not request.user.is_authenticated(): data = { - 'unread_count': 0, - 'list': [] + 'unread_count': 0, + 'list': [] } return JsonResponse(data) @@ -69,11 +79,16 @@ def live_notification_list(request): except ValueError: num_to_fetch = 5 + try: + offset = int(request.GET.get('offset', 0)) + except ValueError: + offset = 0 + list = [] - for n in request.user.notifications.all()[:num_to_fetch]: + for n in request.user.notifications.all()[offset:offset + num_to_fetch]: struct = model_to_dict(n) - struct['unread'] = struct['pseudo_unread'] + # struct['unread'] = struct['pseudo_unread'] struct['slug'] = id2slug(n.id) if n.actor: if isinstance(n.actor, User): diff --git a/ontology/__init__.py b/ontology/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/ontology/admin.py b/ontology/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..b96b7a073459ca7b67327c9a34f5d176d20f7b56 --- /dev/null +++ b/ontology/admin.py @@ -0,0 +1,23 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from django.contrib import admin + +from .models import Tag, Topic, RelationAsym, RelationSym + + +class TagAdmin(admin.ModelAdmin): + pass + +admin.site.register(Tag, TagAdmin) + + +class TopicAdmin(admin.ModelAdmin): + pass + +admin.site.register(Topic, TopicAdmin) + + +admin.site.register(RelationAsym) +admin.site.register(RelationSym) diff --git a/ontology/constants.py b/ontology/constants.py new file mode 100644 index 0000000000000000000000000000000000000000..b3b1e8df00473de09288c0b55c5ac5cffb57eb14 --- /dev/null +++ b/ontology/constants.py @@ -0,0 +1,26 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +IS_INSTANCE_OF = 'is_instance_of' +IS_SPECIALIZATION_OF = 'is_specialization_of' +IS_REFINEMENT_OF = 'is_refinement_of' +IS_USED_IN = 'is_used_in' +IS_EXAMPLE_OF = 'is_example_of' + +TOPIC_RELATIONS_ASYM = ( + (IS_INSTANCE_OF, 'is an instance of'), + (IS_SPECIALIZATION_OF, 'is a specialization of'), + (IS_REFINEMENT_OF, 'is a refinement of'), + (IS_USED_IN, 'is used in'), + (IS_EXAMPLE_OF, 'is an example of'), +) + + +ARE_RELATED = 'are_related' +ARE_SYNONYMS = 'are_synonyms' + +TOPIC_RELATIONS_SYM = ( + (ARE_RELATED, 'are related'), + (ARE_SYNONYMS, 'are synonyms'), +) diff --git a/ontology/forms.py b/ontology/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..ab23a61ebc930726011364278c631fa462a56064 --- /dev/null +++ b/ontology/forms.py @@ -0,0 +1,33 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from django import forms + +from ajax_select.fields import AutoCompleteSelectField + +from .constants import TOPIC_RELATIONS_ASYM + + +class SelectTagForm(forms.Form): + tag = AutoCompleteSelectField('tag_lookup', label='', help_text='') + + +class SelectTopicForm(forms.Form): + topic = AutoCompleteSelectField('topic_lookup', label='', help_text='') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['topic'].widget.attrs.update({ + 'placeholder':'type here to find topic'}) + + +class SelectLinkedTopicForm(forms.Form): + topic = AutoCompleteSelectField('linked_topic_lookup', + label='Find a topic (click to see it) ', help_text='') + + +class AddRelationAsymForm(forms.Form): + A = AutoCompleteSelectField('topic_lookup', label='', help_text='') + relation = forms.ChoiceField(choices=TOPIC_RELATIONS_ASYM, label='') + B = AutoCompleteSelectField('topic_lookup', label='', help_text='') diff --git a/ontology/migrations/0001_initial.py b/ontology/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..56b3e3058fc0e9fa29b651c009707f252673376a --- /dev/null +++ b/ontology/migrations/0001_initial.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-10-27 15:31 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Relation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + ), + migrations.CreateModel( + name='Tag', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=32, unique=True)), + ], + ), + migrations.CreateModel( + name='Topic', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=256, unique=True)), + ('slug', models.SlugField(allow_unicode=True, unique=True)), + ('tags', models.ManyToManyField(to='ontology.Tag')), + ], + ), + ] diff --git a/ontology/migrations/0002_auto_20181027_1748.py b/ontology/migrations/0002_auto_20181027_1748.py new file mode 100644 index 0000000000000000000000000000000000000000..6618082b249524997ae99895c1301bd3290c61b6 --- /dev/null +++ b/ontology/migrations/0002_auto_20181027_1748.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-10-27 15:48 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ontology', '0001_initial'), + ] + + operations = [ + migrations.DeleteModel( + name='Relation', + ), + migrations.AlterModelOptions( + name='tag', + options={'ordering': ['name']}, + ), + migrations.AlterField( + model_name='topic', + name='tags', + field=models.ManyToManyField(blank=True, null=True, to='ontology.Tag'), + ), + ] diff --git a/ontology/migrations/0003_auto_20181027_1748.py b/ontology/migrations/0003_auto_20181027_1748.py new file mode 100644 index 0000000000000000000000000000000000000000..4d3c2cfca1d5e61894a813481d159d4636720069 --- /dev/null +++ b/ontology/migrations/0003_auto_20181027_1748.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-10-27 15:48 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ontology', '0002_auto_20181027_1748'), + ] + + operations = [ + migrations.AlterField( + model_name='topic', + name='tags', + field=models.ManyToManyField(blank=True, to='ontology.Tag'), + ), + ] diff --git a/ontology/migrations/0004_relationasym_relationsym.py b/ontology/migrations/0004_relationasym_relationsym.py new file mode 100644 index 0000000000000000000000000000000000000000..e940ba07e7f5daf2a24208f0f28e2bb369325c45 --- /dev/null +++ b/ontology/migrations/0004_relationasym_relationsym.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-10-27 18:33 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('ontology', '0003_auto_20181027_1748'), + ] + + operations = [ + migrations.CreateModel( + name='RelationAsym', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('relation', models.CharField(choices=[('is_instance_of', 'is an instance of'), ('is_specialization_of', 'is a specialization of'), ('is_refinement_of', 'is a refinement of'), ('is_used_in', 'is used in'), ('is_example_of', 'is an example of')], max_length=32)), + ('A', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='relation_LHS', to='ontology.Topic')), + ('B', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='relation_RHS', to='ontology.Topic')), + ], + ), + migrations.CreateModel( + name='RelationSym', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('relation', models.CharField(choices=[('are_related', 'are related'), ('are_synonyms', 'are synonyms')], max_length=32)), + ('topics', models.ManyToManyField(to='ontology.Topic')), + ], + ), + ] diff --git a/ontology/migrations/0005_auto_20181028_2038.py b/ontology/migrations/0005_auto_20181028_2038.py new file mode 100644 index 0000000000000000000000000000000000000000..1f69bbf6331990bcde21c968629024d254fd0b46 --- /dev/null +++ b/ontology/migrations/0005_auto_20181028_2038.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-10-28 19:38 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ontology', '0004_relationasym_relationsym'), + ] + + operations = [ + migrations.AlterModelOptions( + name='topic', + options={'ordering': ['name']}, + ), + ] diff --git a/ontology/migrations/__init__.py b/ontology/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/ontology/models.py b/ontology/models.py new file mode 100644 index 0000000000000000000000000000000000000000..c429495c82b1b14822d30ab78d12912be403aa48 --- /dev/null +++ b/ontology/models.py @@ -0,0 +1,67 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from django.db import models +from django.urls import reverse + +from .constants import TOPIC_RELATIONS_ASYM, TOPIC_RELATIONS_SYM + + +class Tag(models.Model): + """ + Tags can be attached to a Topic to specify which category it fits. + Examples: Concept, Device, Model, Theory, ... + """ + name = models.CharField(max_length=32, unique=True) + + class Meta: + ordering = ['name'] + + def __str__(self): + return self.name + + +class Topic(models.Model): + """ + A Topic represents one of the nodes in the ontology. + """ + name = models.CharField(max_length=256, unique=True) + slug = models.SlugField(unique=True, allow_unicode=True) + tags = models.ManyToManyField('ontology.Tag', blank=True) + + class Meta: + ordering = ['name'] + + def __str__(self): + return self.name + + def get_abolute_url(self): + return reverse('ontology:topic_details', kwargs={'slug': self.slug}) + + +class RelationAsym(models.Model): + """ + An asymmetric Relation between two Topics. + """ + A = models.ForeignKey('ontology.Topic', on_delete=models.CASCADE, + related_name='relation_LHS') + relation = models.CharField(max_length=32, choices=TOPIC_RELATIONS_ASYM) + B = models.ForeignKey('ontology.Topic', on_delete=models.CASCADE, + related_name='relation_RHS') + + def __str__(self): + return '%s %s %s' % (self.A, self.get_relation_display(), self.B) + + +class RelationSym(models.Model): + """ + A symmetric relation between multiple Topics. + """ + topics = models.ManyToManyField('ontology.Topic') + relation = models.CharField(max_length=32, choices=TOPIC_RELATIONS_SYM) + + def __str__(self): + text = ', '.join(self.topics.values_list('name', flat=True)) + text += self.get_relation_display() + return text diff --git a/ontology/templates/ontology/_topic_card.html b/ontology/templates/ontology/_topic_card.html new file mode 100644 index 0000000000000000000000000000000000000000..977868b955bddc8127f56f52b9c2c0e95e34bf48 --- /dev/null +++ b/ontology/templates/ontology/_topic_card.html @@ -0,0 +1,141 @@ +{% load bootstrap %} + +{% load profiles_extras %} + +<script> + $(document).ready(function() { + $("#id_A_text").keyup(function() { + $("#id_B_text").val("{{ topic }}"); + $("#id_B").val({{ topic.id }}); + }); + $("#id_B_text").keyup(function() { + $("#id_A_text").val("{{ topic }}"); + $("#id_A").val({{ topic.id }}); + }); + }); +</script> + +<div class="card"> + <div class="card-header"> + <ul class="list-inline"> + <li class="list-inline-item"><h3>{{ topic }}</h3></li> + {% if perms.scipost.can_manage_ontology %} + <li class="list-inline-item small"> + <a href="{% url 'ontology:topic_update' slug=topic.slug %}">Update</a> + </li> + {% endif %} + </ul> + {% if topic.tags.all %} + <ul class="list list-inline mb-0"> + <li class="list-inline-item"><strong>Tags</strong>:</li> + {% for tag in topic.tags.all %} + <li class="list-inline-item">{{ tag }}{% if perms.scipost.can_manage_ontology %} <a href="{% url 'ontology:topic_remove_tag' slug=topic.slug tag_id=tag.id %}"><i class="fa fa-times-circle text-danger"></i></a>{% endif %}</li> + {% endfor %} + {% if perms.scipost.can_manage_ontology %} + <li class="list-inline-item pull-right"> + <form class="form-inline" action="{% url 'ontology:topic_add_tag' slug=topic.slug %}" method="post"> + <ul class="list-inline"> + <li class="list-inline-item">{% csrf_token %}{{ select_tag_form }}</li> + <li class="list-inline-item"><input type="submit" class="form-control btn btn-outline-secondary" value="Add Tag"/></li> + </ul> + </form> + </li> + {% endif %} + </ul> + {% endif %} + </div> + + <div class="card-body"> + {% if relations_asym or topic.relationsym_set.all or perms.scipost.can_manage_ontology %} + <h4>Relations to other Topics</h4> + <div class="row mb-0"> + <div class="col-6"> + <h5>asymmetric:</h5> + <ul> + {% for rel in relations_asym %} + <li>{% if rel.A != topic %}<a href="{% url 'ontology:topic_details' slug=rel.A.slug %}">{{ rel.A}}</a>{% else %}{{ rel.A }}{% endif %} <em>{{ rel.get_relation_display }}</em> {% if rel.B != topic %}<a href="{% url 'ontology:topic_details' slug=rel.B.slug %}">{{ rel.B }}</a>{% else %}{{ rel.B }}{% endif %} {% if perms.scipost.can_manage_ontology %}<a href="{% url 'ontology:delete_relation_asym' relation_id=rel.id slug=topic.slug %}"><i class="fa fa-times-circle text-danger"></i></a>{% endif %}</li> + {% empty %} + <li>No relations have been defined</li> + {% endfor %} + </ul> + {% if perms.scipost.can_manage_ontology %} + <h5>Add an asymmetric relation:</h5> + <form action = "{% url 'ontology:add_relation_asym' slug=topic.slug %}" method="post"> + {% csrf_token %} + {{ add_relation_asym_form }} + <input type="submit" class="btn btn-outline-secondary" value="Add"/> + </form> + {% endif %} + </div> + <div class="col-6"> + <h5>symmetric:</h5> + <ul> + {% for rel in topic.relationsym_set.all %} + <li>{% for reltopic in rel.topics.all %}{% if reltopic != topic %}<a href="{% url 'ontology:topic_details' slug=reltopic.slug %}">{{ reltopic }}</a>{% else %}{{ reltopic }}{% endif %}, {% endfor %} <em>{{ rel.get_relation_display }}</em></li> + {% empty %} + <li>No symmetric relations have been defined</li> + {% endfor %} + </ul> + </div> + </div> + <hr/> + {% endif %} + <div class="card-columns"> + <div class="card"> + <div class="card-header"> + Publications + </div> + <div class="card-body"> + <ul> + {% for pub in topic.publications.all %} + <li> + <a href="{{ pub.get_absolute_url }}">{{ pub.title }}</a> + <br>by {{ pub.author_list }}, + <br>{{ pub.citation }} + </li> + {% empty %} + <li>No Publication found</li> + {% endfor %} + </ul> + </div> + </div> + <div class="card"> + <div class="card-header"> + Submissions (unpublished only) + </div> + <div class="card-body"> + <ul> + {% for sub in topic.submission_set.public_newest.unpublished %} + <li> + <a href="{{ sub.get_absolute_url }}">{{ sub.title }}</a> + <br>by {{ sub.author_list }} + <br>(submitted {{ sub.submission_date|date:"Y-m-d" }} to {{ sub.submitted_to }}) + </li> + {% empty %} + <li>No Submission found</li> + {% endfor %} + </ul> + </div> + </div> + <div class="card"> + <div class="card-header"> + Top experts + </div> + <div class="card-body"> + <ul> + {% get_profiles topic.slug as profiles %} + {% for profile in profiles %} + {% if profile.contributor %} + <li><a href="{{ profile.contributor.get_absolute_url }}">{{ profile }}</a></li> + {% else %} + <li>{{ profile }}</li> + {% endif %} + {% empty %} + <li>No Profile found</li> + {% endfor %} + </ul> + </div> + </div> + </div> + </div> +</div> diff --git a/ontology/templates/ontology/base.html b/ontology/templates/ontology/base.html new file mode 100644 index 0000000000000000000000000000000000000000..55ce8fa41eee915627eaf01665111aeaa8c60b8b --- /dev/null +++ b/ontology/templates/ontology/base.html @@ -0,0 +1,13 @@ +{% extends 'scipost/base.html' %} + +{% block breadcrumb %} + <div class="breadcrumb-container"> + <div class="container"> + <nav class="breadcrumb hidden-sm-down"> + {% block breadcrumb_items %} + <a href="{% url 'ontology:ontology' %}" class="breadcrumb-item">Ontology</a> + {% endblock %} + </nav> + </div> + </div> +{% endblock %} diff --git a/ontology/templates/ontology/ontology.html b/ontology/templates/ontology/ontology.html new file mode 100644 index 0000000000000000000000000000000000000000..49096ef6ce9a03c77d0d6791496cdf9ed45a5402 --- /dev/null +++ b/ontology/templates/ontology/ontology.html @@ -0,0 +1,48 @@ +{% extends 'ontology/base.html' %} + +{% block pagetitle %}: Ontology{% endblock pagetitle %} + +{% block breadcrumb_items %} + {{ block.super }} + <span class="breadcrumb-item">Welcome</span> +{% endblock %} + +{% block content %} + +<div class="row"> + <div class="col-12"> + <h3 class="highlight">Ontology</h3> + <ul class="list-inline"> + {% if perms.scipost.can_manage_ontology %} + <li class="list-inline-item ml-2 mr-2"><a href="{% url 'ontology:topic_create' %}">Add a Topic</a></li> + {% endif %} + <li class="list-inline-item ml-2 mr-2"><a href="{% url 'ontology:topics' %}">View list of Topics</a></li> + <li class="list-inline-item ml-2 mr-2">{{ select_linked_topic_form }}</li> + </ul> + <p> + Welcome to SciPost's Ontology, which is a curated set of interlinked Topics + pertaining to all of the sciences. + </p> + <p> + Topics and their relations are defined by Editorial-level personnel. + The ontology is used at many levels within SciPost, including but not limited to: + <ul> + <li>identifying the best potential Editors-in-charge for incoming submissions</li> + <li>identifying thematic areas for which the editorial workforce should be increased</li> + <li>providing Editors-in-charge with referee suggestions</li> + <li>giving readers a way to identify interesting published content</li> + <li>assisting our internal metadata- and content-driven engines.</li> + </ul> + </p> + <p> + The Ontology is currently under development. + </p> + </div> +</div> + +{% endblock content %} + +{% block footer_script %} +{{ block.super }} +{{ select_linked_topic_form.media }} +{% endblock footer_script %} diff --git a/ontology/templates/ontology/topic_detail.html b/ontology/templates/ontology/topic_detail.html new file mode 100644 index 0000000000000000000000000000000000000000..d5c211e708363b674c59b3bf6442a8448e9f3bae --- /dev/null +++ b/ontology/templates/ontology/topic_detail.html @@ -0,0 +1,24 @@ +{% extends 'ontology/base.html' %} + +{% load bootstrap %} + +{% block breadcrumb_items %} + {{ block.super }} +<span class="breadcrumb-item"><a href="{% url 'ontology:topics' %}">Topics</a></span> +<span class="breadcrumb-item">{{ topic }}</span> +{% endblock %} + +{% block pagetitle %}: Topic details{% endblock pagetitle %} + +{% block content %} +<div class="row"> + <div class="col-12"> + {% include 'ontology/_topic_card.html' with topic=topic select_tag_form=select_tag_form %} + </div> +</div> +{% endblock content %} + +{% block footer_script %} +{{ block.super }} +{{ select_tag_form.media }} +{% endblock footer_script %} diff --git a/ontology/templates/ontology/topic_form.html b/ontology/templates/ontology/topic_form.html new file mode 100644 index 0000000000000000000000000000000000000000..d63f2706956c132d9c146e312ca4cdfb1fc243ca --- /dev/null +++ b/ontology/templates/ontology/topic_form.html @@ -0,0 +1,42 @@ +{% extends 'ontology/base.html' %} + +{% load bootstrap %} + +{% block headsup %} +<script> +$(document).ready(function() { + +$("#id_name").keyup(function() { + slug_value = this.value.split(" ").join("_"); + $("#id_slug").val(slug_value); +}); + +}); +</script> +{% endblock headsup %} + +{% block breadcrumb_items %} + {{ block.super }} +<span class="breadcrumb-item"><a href="{% url 'ontology:topics' %}">Topics</a></span> +<span class="breadcrumb-item">{% if form.instance.id %}Update {{ form.instance }}{% else %}Add new Topic{% endif %}</span> +{% endblock %} + +{% block pagetitle %}: Topics{% endblock pagetitle %} + +{% block content %} +<div class="row"> + <div class="col-12"> + <h4>Please use the following conventions:</h4> + <ul> + <li>Start with a capital letter</li> + <li>Use plural words (<em>e.g.</em> <strong>superconductors</strong> instead of <strong>superconductor</strong>)</li> + <li>If an acronym exists, put it in parentheses at the end (<em>e.g.</em> <strong>Renormalization group (RG)</strong>). <strong class="text-danger">Remove any parentheses from the slug!</strong></li> + <li>Mix equivalent words by using a slash, <em>e.g.</em> <strong>Superconductivity⁄superconductors</strong>. <strong class="text-danger">You will similarly need to remove the slash from the slug!</strong></li> + </ul> + <form action="" method="post"> + {% csrf_token %} + {{ form|bootstrap }} + <input type="submit" value="Submit" class="btn btn-primary"> + </div> +</div> +{% endblock content %} diff --git a/ontology/templates/ontology/topic_list.html b/ontology/templates/ontology/topic_list.html new file mode 100644 index 0000000000000000000000000000000000000000..fd383a1a18f7d6911d3361358771d9c196130744 --- /dev/null +++ b/ontology/templates/ontology/topic_list.html @@ -0,0 +1,60 @@ +{% extends 'ontology/base.html' %} + +{% block pagetitle %}: Topics{% endblock pagetitle %} + +{% block breadcrumb_items %} +{{ block.super }} +<span class="breadcrumb-item">Topics</span> +{% endblock %} + +{% block content %} + +<div class="row"> + <div class="col-12"> + <h3 class="highlight">Topics</h3> + <ul> + {% if perms.scipost.can_manage_ontology %} + <li><a href="{% url 'ontology:topic_create' %}">Add a Topic</a></li> + {% endif %} + <li> + <form action="" method="get"> + <ul class="list-inline"> + <li class="list-inline-item">Filter to Topic name containing:</li> + <li class="list-inline-item"> + {{ searchform }} + </li> + <li class="list-inline-item"><input class="btn btn-outline-secondary" type="submit" value="Filter"></li> + </ul> + </form> + </li> + <li>{{ select_linked_topic_form }}</li> + </ul> + </div> +</div> + + +<div class="row"> + <div class="col-12"> + <h3>Topics</h3> + <ul class="list-inline"> + {% for topic in object_list %} + <li class="list-inline-item p-1"><a href="{% url 'ontology:topic_details' slug=topic.slug %}">{{ topic }}</a></li> + {% endfor %} + </ul> + + + {% if is_paginated %} + <div class="col-12"> + {% include 'partials/pagination.html' with page_obj=page_obj %} + </div> + {% endif %} + + </div> +</div> + +{% endblock content %} + +{% block footer_script %} +{{ block.super }} +{{ select_linked_topic_form.media }} +{% endblock footer_script %} diff --git a/ontology/templatetags/lookup.py b/ontology/templatetags/lookup.py new file mode 100644 index 0000000000000000000000000000000000000000..1e2b3d5616f9bda2e215ab523d13a083eeacb04a --- /dev/null +++ b/ontology/templatetags/lookup.py @@ -0,0 +1,64 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from django.core.exceptions import PermissionDenied +from django.shortcuts import render, reverse + +from ajax_select import register, LookupChannel + +from ..models import Tag, Topic + + +@register('tag_lookup') +class TagLookup(LookupChannel): + model = Tag + + def get_query(self, q, request): + return (self.model.objects.filter(name__icontains=q)[:10]) + + def format_item_display(self, item): + return "<span class='auto_lookup_display'>%s</span>" % item + + def format_match(self, item): + return item.name + + def check_auth(self, request): + if not request.user.has_perm('scipost.can_manage_ontology'): + raise PermissionDenied + + +@register('topic_lookup') +class TopicLookup(LookupChannel): + model = Topic + + def get_query(self, q, request): + return (self.model.objects.filter(name__icontains=q)[:10]) + + def format_item_display(self, item): + return "<span class='auto_lookup_display'>%s</span>" % item + + def format_match(self, item): + return item.name + + def check_auth(self, request): + if not request.user.has_perm('scipost.can_manage_ontology'): + raise PermissionDenied + + +@register('linked_topic_lookup') +class LinkedTopicLookup(LookupChannel): + model = Topic + + def get_query(self, q, request): + return (self.model.objects.filter(name__icontains=q)[:10]) + + def format_item_display(self, item): + return + + def format_match(self, item): + return "<span class='auto_lookup_display'><a href='%s'>%s</a></span>" % ( + reverse('ontology:topic_details', kwargs={'slug': item.slug}), item) + + def check_auth(self, request): + pass diff --git a/ontology/urls.py b/ontology/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..e7c1ad356250154f8f48591541de866b3ec57e9b --- /dev/null +++ b/ontology/urls.py @@ -0,0 +1,55 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from django.conf.urls import url + +from . import views + +urlpatterns = [ + url( + r'^$', + views.ontology, + name='ontology' + ), + url( + r'^topic/add/$', + views.TopicCreateView.as_view(), + name='topic_create' + ), + url( + r'^topic/(?P<slug>[-\w]+)/add_tag/$', + views.topic_add_tag, + name='topic_add_tag' + ), + url( + r'^topic/(?P<slug>[-\w]+)/remove_tag/(?P<tag_id>[0-9]+)/$', + views.topic_remove_tag, + name='topic_remove_tag' + ), + url( + r'^topic/(?P<slug>[-\w]+)/update/$', + views.TopicUpdateView.as_view(), + name='topic_update' + ), + url( + r'^topic/(?P<slug>[-\w]+)/$', + views.TopicDetailView.as_view(), + name='topic_details' + ), + url( + r'^topics/$', + views.TopicListView.as_view(), + name='topics' + ), + url( + r'^add_relation_asym/(?P<slug>[-\w]+)/$', + views.add_relation_asym, + name='add_relation_asym' + ), + url( + r'^delete_relation_asym/(?P<relation_id>[0-9]+)/(?P<slug>[-\w]+)/$', + views.delete_relation_asym, + name='delete_relation_asym' + ), +] diff --git a/ontology/views.py b/ontology/views.py new file mode 100644 index 0000000000000000000000000000000000000000..b5fbbb88c37fb08612b5fabc53b41916eeb94ffe --- /dev/null +++ b/ontology/views.py @@ -0,0 +1,127 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from django.contrib import messages +from django.core.urlresolvers import reverse, reverse_lazy +from django.db.models import Q +from django.shortcuts import get_object_or_404, redirect, render +from django.views.generic.detail import DetailView +from django.views.generic.edit import CreateView, UpdateView +from django.views.generic.list import ListView + +from guardian.decorators import permission_required + +from .models import Tag, Topic, RelationAsym +from .forms import SelectTagForm, SelectLinkedTopicForm, AddRelationAsymForm + +from scipost.forms import SearchTextForm +from scipost.mixins import PaginationMixin, PermissionsMixin + + +def ontology(request): + context = { + 'select_linked_topic_form': SelectLinkedTopicForm(), + } + return render(request, 'ontology/ontology.html', context=context) + + +class TopicCreateView(PermissionsMixin, CreateView): + """ + Create a new Topic for an Ontology. + """ + permission_required = 'scipost.can_manage_ontology' + model = Topic + fields = '__all__' + template_name = 'ontology/topic_form.html' + success_url = reverse_lazy('ontology:topics') + + +class TopicUpdateView(PermissionsMixin, UpdateView): + """ + Update a Topic for an Ontology. + """ + permission_required = 'scipost.can_manage_ontology' + model = Topic + fields = '__all__' + template_name = 'ontology/topic_form.html' + success_url = reverse_lazy('ontology:topics') + + +@permission_required('scipost.can_manage_ontology', return_403=True) +def topic_add_tag(request, slug): + topic = get_object_or_404(Topic, slug=slug) + select_tag_form = SelectTagForm(request.POST or None) + if select_tag_form.is_valid(): + topic.tags.add(select_tag_form.cleaned_data['tag']) + topic.save() + messages.success(request, 'Tag %s added to Topic %s' % ( + select_tag_form.cleaned_data['tag'], str(topic))) + return redirect(reverse('ontology:topic_details', kwargs={'slug': topic.slug})) + + +@permission_required('scipost.can_manage_ontology', return_403=True) +def topic_remove_tag(request, slug, tag_id): + topic = get_object_or_404(Topic, slug=slug) + tag = get_object_or_404(Tag, pk=tag_id) + topic.tags.remove(tag) + topic.save() + return redirect(reverse('ontology:topic_details', kwargs={'slug': topic.slug})) + + +class TopicListView(PaginationMixin, ListView): + model = Topic + paginate_by = 100 + + def get_queryset(self): + """ + Return a queryset of Topics using optional GET data. + """ + queryset = Topic.objects.all() + if self.request.GET.get('text'): + queryset = queryset.filter(name__icontains=self.request.GET['text']) + return queryset + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'searchform': SearchTextForm(initial={'text': self.request.GET.get('text')}), + 'select_linked_topic_form': SelectLinkedTopicForm(), + }) + return context + + +class TopicDetailView(DetailView): + model = Topic + + def get_context_data(self, *args, **kwargs): + context = super().get_context_data(*args, **kwargs) + context['select_tag_form'] = SelectTagForm() + context['add_relation_asym_form'] = AddRelationAsymForm() + context['relations_asym'] = RelationAsym.objects.filter(Q(A=self.object) | Q(B=self.object)) + return context + + +@permission_required('scipost.can_manage_ontology', return_403=True) +def add_relation_asym(request, slug): + form = AddRelationAsymForm(request.POST or None) + if form.is_valid(): + relation, created = RelationAsym.objects.get_or_create( + A=form.cleaned_data['A'], relation=form.cleaned_data['relation'], + B=form.cleaned_data['B']) + if created: + messages.success(request, 'Relation successfully created') + else: + messages.info(request, 'This relation already exists') + else: + for error_messages in form.errors.values(): + messages.warning(request, *error_messages) + return redirect(reverse('ontology:topic_details', kwargs={'slug': slug})) + + +@permission_required('scipost.can_manage_ontology', return_403=True) +def delete_relation_asym(request, relation_id, slug): + relation = get_object_or_404(RelationAsym, pk=relation_id) + relation.delete() + messages.success(request, 'Relation deleted') + return redirect(reverse('ontology:topic_details', kwargs={'slug': slug})) diff --git a/organizations/models.py b/organizations/models.py index f47a81b1210f0a44306228ae95a01b089c9928f4..291f8203e494316af8e9f1f2846ca652eb2d33e4 100644 --- a/organizations/models.py +++ b/organizations/models.py @@ -12,7 +12,8 @@ from django.urls import reverse from django_countries.fields import CountryField -from .constants import ORGANIZATION_TYPES, ORGANIZATION_STATUSES, ORGSTATUS_ACTIVE +from .constants import ORGANIZATION_TYPES, ORGTYPE_PRIVATE_BENEFACTOR,\ + ORGANIZATION_STATUSES, ORGSTATUS_ACTIVE from .managers import OrganizationQuerySet from scipost.models import Contributor @@ -90,6 +91,10 @@ class Organization(models.Model): def get_absolute_url(self): return reverse('organizations:organization_details', kwargs = {'pk': self.id}) + @property + def details_publicly_viewable(self): + return self.orgtype != ORGTYPE_PRIVATE_BENEFACTOR + def get_publications(self): org_and_children_ids = [k['id'] for k in list(self.children.all().values('id'))] org_and_children_ids += [self.id] diff --git a/organizations/templates/organizations/organization_detail.html b/organizations/templates/organizations/organization_detail.html index 47455259fbce4232acd3ce9e980ffa03e449e260..5382e49446fbd4f83a871f5167be7c7c7256a409 100644 --- a/organizations/templates/organizations/organization_detail.html +++ b/organizations/templates/organizations/organization_detail.html @@ -11,31 +11,30 @@ {% block content %} - - <table class="table highlight"> - <tr> - <td><img src="{{ organization.country.flag }}" style="width:20px;" alt="{{ organization.country }} flag"/> <span class="text-muted"><small>[{{ organization.country }}]</small></span> {{ organization.get_country_display }}</td> - <td> - <h2>{{ organization.full_name }} <small>{% if organization.acronym %}[{{ organization.acronym }}]{% endif %}</small></h2> - {% if organization.parent %} - <small class="text-muted"><p>Parent: <a href="{{ organization.parent.get_absolute_url }}">{{ organization.parent }}</a></p></small> - {% endif %} - {% if organization.children.all %} - <small class="text-muted"> - <p>Parent of: - {% for child in organization.children.all %} - <a href="{{ child.get_absolute_url }}">{{ child }}</a>{% if not forloop.last %}, {% endif %} - {% endfor %} - </p> - </small> - {% endif %} - {% if organization.superseded_by %} - <small class="text-muted"><p>Superseded by {{ organization.superseded_by }}</p></small> - {% endif %} - </td> - </tr> - </table> - +<div class="card bg-light"> + <div class="card-body"> + <img src="{{ organization.country.flag }}" style="width:20px;" alt="{{ organization.country }} flag"/> + <small class="text-muted">[{{ organization.country }}]</small> {{ organization.get_country_display }} + <h2 class="mb-0 mt-2">{{ organization.full_name }} <small>{% if organization.acronym %}[{{ organization.acronym }}]{% endif %}</small></h2> + + {% if organization.parent %} + <small class="text-muted">Parent: <a href="{{ organization.parent.get_absolute_url }}">{{ organization.parent }}</a></small> + {% endif %} + {% if organization.children.all %} + <br> + <small class="text-muted"> + Parent of: + {% for child in organization.children.all %} + <a href="{{ child.get_absolute_url }}">{{ child }}</a>{% if not forloop.last %}, {% endif %} + {% endfor %} + </small> + {% endif %} + {% if organization.superseded_by %} + <br> + <small class="text-muted">Superseded by {{ organization.superseded_by }}</small> + {% endif %} + </div> +</div> <div class="row"> <div class="col-12"> diff --git a/organizations/views.py b/organizations/views.py index d11f618d346fd1f555cc14993282ae07b8c01a18..30dfbff9c0da0a16686f571080a7e2599de326d2 100644 --- a/organizations/views.py +++ b/organizations/views.py @@ -3,12 +3,12 @@ __license__ = "AGPL v3" from django.core.urlresolvers import reverse_lazy -from django.shortcuts import render from django.utils import timezone from django.views.generic.detail import DetailView from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.views.generic.list import ListView +from .constants import ORGTYPE_PRIVATE_BENEFACTOR from .models import Organization from funders.models import Funder @@ -60,7 +60,7 @@ class OrganizationListView(ListView): return context def get_queryset(self): - qs = super().get_queryset() + qs = super().get_queryset().exclude(orgtype=ORGTYPE_PRIVATE_BENEFACTOR) order_by = self.request.GET.get('order_by') ordering = self.request.GET.get('ordering') if order_by == 'country': @@ -81,3 +81,12 @@ class OrganizationDetailView(DetailView): context = super().get_context_data(*args, **kwargs) context['pubyears'] = range(int(timezone.now().strftime('%Y')), 2015, -1) return context + + def get_queryset(self): + """ + Restrict view to permitted people if Organization details not publicly viewable. + """ + queryset = super().get_queryset() + if not self.request.user.has_perm('scipost.can_manage_organizations'): + queryset = queryset.exclude(orgtype=ORGTYPE_PRIVATE_BENEFACTOR) + return queryset diff --git a/petitions/templates/petitions/petition_email.html b/petitions/templates/petitions/petition_email.html index eb4b4543014eccc45b54335eab833b576e94fbfb..355560c70969968c9a2843acad2635685c890875 100644 --- a/petitions/templates/petitions/petition_email.html +++ b/petitions/templates/petitions/petition_email.html @@ -1,4 +1,4 @@ -[PLEASE FILL IN THE TO FIELD ABOVE (keeping partners@scipost.org in cc)]%0D%0A +[PLEASE FILL IN THE TO FIELD ABOVE (keeping sponsors@scipost.org in cc)]%0D%0A %0D%0A Dear ...%0D%0A %0D%0A @@ -15,4 +15,4 @@ SciPost (https://scipost.org) is a top-quality next-generation Open Access publi %0D%0A SciPost follows a different funding model than most traditional publishers. It operates on an entirely not-for-profit basis, and charges neither subscription fees nor article processing charges; instead, its activities are financed through a cost-slashing consortial model.%0D%0A %0D%0A -By making a small financial commitment, the institutions and organizations that benefit from SciPost’s activities can become Supporting Partners. This enables SciPost to perform all of its publication-related activities, maintain its online portal and implement its long-term development plan. Details of the consortial funding scheme and how to join can be found at https://scipost.org/partners or by emailing partners@scipost.org.%0D%0A +By making a small financial commitment, the institutions and organizations that benefit from SciPost’s activities can become Sponsors. This enables SciPost to perform all of its publication-related activities, maintain its online portal and implement its long-term development plan. Details of the consortial funding scheme and how to join can be found at https://scipost.org/sponsors or by emailing sponsors@scipost.org.%0D%0A diff --git a/preprints/helpers.py b/preprints/helpers.py index d5437b287c6539ca03ce34265425af7b6438b19c..1af7fba21670c593835cbbb0a1f1a3a6360490d8 100644 --- a/preprints/helpers.py +++ b/preprints/helpers.py @@ -5,24 +5,40 @@ __license__ = "AGPL v3" from django.db.models import Max from django.utils import timezone +from submissions.models import Submission + from .models import Preprint -def generate_new_scipost_identifier(): - """Return an identifier for a new SciPost preprint series without version number.""" +def generate_new_scipost_identifier(old_preprint=None): + """ + Return an identifier for a new SciPost preprint series without version number. + + TODO: This method will explode as soon as it will be used similtaneously by two or more people. + """ now = timezone.now() - existing_identifier = Preprint.objects.filter( - created__year=now.year, created__month=now.month).aggregate( - identifier=Max('scipost_preprint_identifier'))['identifier'] - if not existing_identifier: - existing_identifier = '1' - else: - existing_identifier = str(existing_identifier + 1) - return '{year}{month}_{identifier}'.format( - year=now.year, month=str(now.month).rjust(2, '0'), - identifier=existing_identifier.rjust(5, '0')), int(existing_identifier) + if isinstance(old_preprint, Submission): + old_preprint = old_preprint.preprint + + if old_preprint: + # Generate new version number of existing series. + preprint_series = Preprint.objects.filter( + scipost_preprint_identifier=old_preprint.scipost_preprint_identifier).values_list( + 'vn_nr', flat=True) + identifier = '{}v{}'.format(old_preprint.identifier_wo_vn_nr, max(preprint_series) + 1) + return identifier, old_preprint.scipost_preprint_identifier + else: + # New series of Preprints. + existing_identifier = Preprint.objects.filter( + created__year=now.year, created__month=now.month).aggregate( + identifier=Max('scipost_preprint_identifier'))['identifier'] + if not existing_identifier: + existing_identifier = '1' + else: + existing_identifier = str(existing_identifier + 1) -def format_scipost_identifier(identifier, version=1): - return 'scipost_{identifier}v{version}'.format( - identifier=identifier, version=version) + identifier = 'scipost_{year}{month}_{identifier}v1'.format( + year=now.year, month=str(now.month).rjust(2, '0'), + identifier=existing_identifier.rjust(5, '0')) + return identifier, int(existing_identifier) diff --git a/preprints/migrations/0008_auto_20180819_1343.py b/preprints/migrations/0008_auto_20180819_1343.py new file mode 100644 index 0000000000000000000000000000000000000000..38b75a4e4d96a2bf10d07de3709e9dd8355bae7d --- /dev/null +++ b/preprints/migrations/0008_auto_20180819_1343.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-08-19 11:43 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('preprints', '0007_auto_20180619_2033'), + ] + + operations = [ + migrations.AlterField( + model_name='preprint', + name='url', + field=models.URLField(blank=True), + ), + ] diff --git a/preprints/migrations/0009_auto_20181123_1000.py b/preprints/migrations/0009_auto_20181123_1000.py new file mode 100644 index 0000000000000000000000000000000000000000..8ef8494faec9d72742f6d402143c113a3998ffd5 --- /dev/null +++ b/preprints/migrations/0009_auto_20181123_1000.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-23 09:00 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('preprints', '0008_auto_20180913_2112'), + ] + + operations = [ + migrations.AlterModelOptions( + name='preprint', + options={'ordering': ['-identifier_w_vn_nr']}, + ), + ] diff --git a/preprints/migrations/0009_merge_20180915_1337.py b/preprints/migrations/0009_merge_20180915_1337.py new file mode 100644 index 0000000000000000000000000000000000000000..84a7f2683d2ccf68b31c28cd94696df316ea0457 --- /dev/null +++ b/preprints/migrations/0009_merge_20180915_1337.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-09-15 11:37 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('preprints', '0008_auto_20180819_1343'), + ('preprints', '0008_auto_20180913_2112'), + ] + + operations = [ + ] diff --git a/preprints/migrations/0010_merge_20181207_1008.py b/preprints/migrations/0010_merge_20181207_1008.py new file mode 100644 index 0000000000000000000000000000000000000000..3b60e6cdc383669bd2f7b577eec85ca3af949a0f --- /dev/null +++ b/preprints/migrations/0010_merge_20181207_1008.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-12-07 09:08 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('preprints', '0009_merge_20180915_1337'), + ('preprints', '0009_auto_20181123_1000'), + ] + + operations = [ + ] diff --git a/preprints/models.py b/preprints/models.py index 41e89808518abb67c5cc6adce6963b5677c18015..443f8ddc584cbeab11181939437e09eb69258a7c 100644 --- a/preprints/models.py +++ b/preprints/models.py @@ -32,6 +32,10 @@ class Preprint(models.Model): modified = models.DateTimeField(auto_now=True) created = models.DateTimeField(auto_now_add=True) + class Meta: + ordering = ['-identifier_w_vn_nr'] + + def __str__(self): return 'Preprint {}'.format(self.identifier_w_vn_nr) diff --git a/preprints/views.py b/preprints/views.py index 9c291adb42e9a6cfc4cf3e729b96ff84d8e2f293..340afc48895810952fb842b9099badb7aa184c6b 100644 --- a/preprints/views.py +++ b/preprints/views.py @@ -1,6 +1,7 @@ __copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" __license__ = "AGPL v3" +import os from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404, redirect @@ -28,7 +29,13 @@ def preprint_pdf(request, identifier_w_vn_nr): contributor__user=request.user).exists(): raise Http404 - response = HttpResponse(preprint._file.read(), content_type='application/pdf') - filename = '{}.pdf'.format(preprint.identifier_w_vn_nr) - response['Content-Disposition'] = ('filename=' + filename) + __, extension = os.path.splitext(preprint._file.name) + if extension == '.pdf': + response = HttpResponse(preprint._file.read(), content_type='application/pdf') + filename = '{}.pdf'.format(preprint.identifier_w_vn_nr) + response['Content-Disposition'] = ('filename=' + filename) + else: + response = HttpResponse(preprint._file.read(), content_type='application/force-download') + filename = '{}{}'.format(preprint.identifier_w_vn_nr, extension) + response['Content-Disposition'] = ('filename=' + filename) return response diff --git a/production/templates/production/production.html b/production/templates/production/production.html index 4c5d7dd330be40f88d76207c2f15f6917c8d04f2..57f40db1fa14160965e598088669d7216f752d6b 100644 --- a/production/templates/production/production.html +++ b/production/templates/production/production.html @@ -24,14 +24,9 @@ <div class="tab-nav-container"> <div class="tab-nav-inner"> <ul class="nav btn-group personal-page-nav" role="tablist"> - {% if perms.scipost.can_view_all_production_streams %} - <li class="nav-item btn btn-outline-secondary"> - <a href="#summary" class="nav-link active" data-toggle="tab">Summary</a> - </li> - {% endif %} - <li class="nav-item btn btn-outline-secondary"> - <a href="#streams" class="nav-link{% if not perms.scipost.can_view_all_production_streams %} active{% endif %}" data-toggle="tab">{{ perms.scipost.can_assign_production_officer|yesno:"Streams,My Streams" }}</a> - </li> + <li class="nav-item btn btn-outline-secondary"> + <a href="#summary" class="nav-link active" data-toggle="tab">Summary</a> + </li> <li class="nav-item btn btn-outline-secondary"> <a href="#mytimesheet" class="nav-link" data-toggle="tab">My Timesheet</a> </li> @@ -47,141 +42,76 @@ </div> <div class="tab-content"> - {% if perms.scipost.can_view_all_production_streams %} - <div class="tab-pane active" id="summary" role="tabpanel"> - <div class="row"> - <div class="col-12"> - <h2 class="highlight">Streams summary</h2> - <table class="table table-fixed"> - <thead> + <div class="tab-pane active" id="summary" role="tabpanel"> + <div class="row"> + <div class="col-12"> + <h2 class="highlight">Streams summary</h2> + <table class="table table-fixed"> + <thead> + <tr> + <th style="width: 30%;">Submission</th> + <th>Target Journal<br/>(Tier)</th> + <th>Status</th> + <th class="py-1"> + Latest activity + <br> + Submission accepted + </th> + <th>Has supervisor</th> + <th> + Production officer + <br> + Invitations officer + </th> + </tr> + </thead> + <tbody> + {% for stream in streams %} <tr> - <th style="width: 30%;">Submission</th> - <th>Target Journal<br/>(Tier)</th> - <th>Status</th> - <th class="py-1"> - Latest activity - <br> - Submission accepted - </th> - <th>Has supervisor</th> - <th> - Production officer - <br> - Invitations officer - </th> - </tr> - </thead> - <tbody> - {% for stream in streams %} - <tr> - <td> - <a href="{{ stream.get_absolute_url }}">{{ stream.submission.title }}</a> - <br> - by {{ stream.submission.author_list }} - </td> - <td>{{ stream.submission.get_submitted_to_journal_display }}{% for rec in stream.submission.eicrecommendations.all %}<br/>({{ rec|Tier }}){% endfor %}</td> - <td> - <div class="label label-{% if stream.status == 'initiated' %}outline-danger{% else %}secondary{% endif %} label-sm">{{ stream.get_status_display }}</div> - </td> - <td> - {{ stream.latest_activity|timesince }} ago - <br> - <span class="text-muted">{{ stream.opened|timesince }} ago</span> - </td> - <td> - {% if stream.supervisor %} - <i class="fa fa-check text-success"></i> - {{ stream.supervisor }} - {% else %} - <i class="fa fa-times text-danger"></i> - {% endif %} - </td> - <td> - {% if stream.officer %} - <i class="fa fa-check text-success"></i> - {{ stream.officer }} - {% else %} - <i class="fa fa-times text-danger"></i> - {% endif %} - <br> - {% if stream.invitations_officer %} - <i class="fa fa-check text-success"></i> - {{ stream.invitations_officer }} - {% else %} - <i class="fa fa-times text-danger"></i> - {% endif %} - </td> - </tr> - {% endfor %} - </tbody> - </table> - </div> - </div> - </div> - {% endif %} - - - <div class="tab-pane{% if not perms.scipost.can_view_all_production_streams %} active{% endif %}" id="streams" role="tabpanel"> - <div class="row"> - <div class="col-12"> - <h2 class="highlight">{{ perms.scipost.can_assign_production_officer|yesno:"Streams,My Streams" }}</h2> - <a href="{% url 'production:completed' %}">View completed streams</a> - </div> - </div> - - <div class="row"> - <div class="col-6"> - <ul class="list-unstyled" data-target="active-list"> - {% for stream in streams %} - <li class="px-2 py-1"> - <div class="d-flex justify-content-start"> - <div class="icons pr-2"> - <i class="fa fa-users" data-toggle="tooltip" data-html="true" title="" data-original-title=" - Supervisor: {% if stream.supervisor %}{{ stream.supervisor.user.first_name }} {{ stream.supervisor.user.last_name }}{% else %}<em>Unassigned</em>{% endif %}<br> - Officer: {% if stream.officer %}{{ stream.officer.user.first_name }} {{ stream.officer.user.last_name }}{% else %}<em>Unassigned</em>{% endif %} - "></i> - {% if stream.supervisor.user == request.user %} + <td> + <a href="{{ stream.get_absolute_url }}">{{ stream.submission.title }}</a> <br> - <i class="fa fa-info-circle" data-toggle="tooltip" data-html="true" title="" data-original-title="You are assigned as supervisor"></i> - {% endif %} - </div> - <div> - <p class="mb-1"> - <a href="{% url 'production:stream' stream.id %}" data-toggle="dynamic" data-target="#details">{{ stream.submission.title }}</a><br> - <em>by {{ stream.submission.author_list }}</em> - </p> - <div class="py-1"> - <p class="mb-1">Submission accepted <strong>{{ stream.opened|timesince }} ago</strong></p> + by {{ stream.submission.author_list }} + </td> + <td>{{ stream.submission.submitted_to }}{% for rec in stream.submission.eicrecommendations.all %}<br/>({{ rec|Tier }}){% endfor %}</td> + <td> <div class="label label-{% if stream.status == 'initiated' %}outline-danger{% else %}secondary{% endif %} label-sm">{{ stream.get_status_display }}</div> + </td> + <td> + {{ stream.latest_activity|timesince }} ago <br> - <a href="{% url 'production:stream' stream.id %}" data-toggle="dynamic" data-target="#details" class="btn btn-default px-0">See stream details</a> - </div> - - - </div> - </div> - </li> - {% endfor %} - </ul> - - </div> - <div class="col-6"> - <div class="card center-loader bg-white" id="details"> - {% if stream %} - {% include 'production/partials/production_stream_card.html' %} - {% else %} - <div class="text-center py-4"> - <i class="fa fa-hand-o-left fa-3x" aria-hidden="true"></i> - - <h3>Choose a Stream to see more details</h3> - </div> - {% endif %} + <span class="text-muted">{{ stream.opened|timesince }} ago</span> + </td> + <td> + {% if stream.supervisor %} + <i class="fa fa-check text-success"></i> + {{ stream.supervisor }} + {% else %} + <i class="fa fa-times text-danger"></i> + {% endif %} + </td> + <td> + {% if stream.officer %} + <i class="fa fa-check text-success"></i> + {{ stream.officer }} + {% else %} + <i class="fa fa-times text-danger"></i> + {% endif %} + <br> + {% if stream.invitations_officer %} + <i class="fa fa-check text-success"></i> + {{ stream.invitations_officer }} + {% else %} + <i class="fa fa-times text-danger"></i> + {% endif %} + </td> + </tr> + {% endfor %} + </tbody> + </table> </div> - </div> </div> - </div> - <div class="tab-pane" id="mytimesheet" role="tabpanel"> <div class="row"> diff --git a/profiles/admin.py b/profiles/admin.py index c6d620652d2311c56bc1c2a7292ac4e114255b07..771c7c3b119dc26e079bbe7e7db65b47737a9853 100644 --- a/profiles/admin.py +++ b/profiles/admin.py @@ -4,7 +4,7 @@ __license__ = "AGPL v3" from django.contrib import admin -from .models import Profile, ProfileEmail +from .models import Profile, ProfileEmail, ProfileNonDuplicates class ProfileEmailInline(admin.TabularInline): @@ -13,7 +13,11 @@ class ProfileEmailInline(admin.TabularInline): class ProfileAdmin(admin.ModelAdmin): + list_display = ['__str__', 'email', 'discipline', 'expertises', 'has_active_contributor'] search_fields = ['first_name', 'last_name', 'emails__email', 'orcid_id'] inlines = [ProfileEmailInline] admin.site.register(Profile, ProfileAdmin) + + +admin.site.register(ProfileNonDuplicates) diff --git a/profiles/constants.py b/profiles/constants.py new file mode 100644 index 0000000000000000000000000000000000000000..c0298acf36a387695c3ef9a060b2e9eb55766c8f --- /dev/null +++ b/profiles/constants.py @@ -0,0 +1,11 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +DIFFERENT_PEOPLE = 'DifferentPeople' +MULTIPLE_ALLOWED = 'MultipleAllowed' + +PROFILE_NON_DUPLICATE_REASONS = ( + (DIFFERENT_PEOPLE, 'These are different people'), + (MULTIPLE_ALLOWED, 'Multiple Profiles allowed for this person'), +) diff --git a/profiles/forms.py b/profiles/forms.py index e8dd84e0d5c9e9a0091eb9812c78198ddbd53611..0ec066920a5839c5e207eb1800df5f6cb4c4dfc5 100644 --- a/profiles/forms.py +++ b/profiles/forms.py @@ -3,44 +3,152 @@ __license__ = "AGPL v3" from django import forms +from django.shortcuts import get_object_or_404 + +from common.forms import ModelChoiceFieldwithid +from invitations.models import RegistrationInvitation +from journals.models import UnregisteredAuthor +from ontology.models import Topic +from scipost.models import Contributor +from submissions.models import RefereeInvitation from .models import Profile, ProfileEmail class ProfileForm(forms.ModelForm): - email = forms.EmailField() + email = forms.EmailField(required=False) + # If the Profile is created from an existing object (so we can update the object): + instance_from_type = forms.CharField(max_length=32, required=False) + instance_pk = forms.IntegerField(required=False) class Meta: model = Profile fields = ['title', 'first_name', 'last_name', 'discipline', 'expertises', 'orcid_id', 'webpage', - 'accepts_SciPost_emails', 'accepts_refereeing_requests'] + 'accepts_SciPost_emails', 'accepts_refereeing_requests', + 'instance_from_type', 'instance_pk'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['email'].initial = self.instance.email + self.fields['instance_from_type'].widget = forms.HiddenInput() + self.fields['instance_pk'].widget = forms.HiddenInput() def clean_email(self): """Check that the email isn't yet associated to an existing Profile.""" cleaned_email = self.cleaned_data['email'] - if ProfileEmail.objects.filter(email=cleaned_email).exclude(profile__id=self.instance.id).exists(): + if cleaned_email and ProfileEmail.objects.filter( + email=cleaned_email).exclude(profile__id=self.instance.id).exists(): raise forms.ValidationError('A Profile with this email already exists.') return cleaned_email + def clean_instance_from_type(self): + """ + Check that only recognized types are used. + """ + cleaned_instance_from_type = self.cleaned_data['instance_from_type'] + if cleaned_instance_from_type not in ['', 'contributor', 'unregisteredauthor', + 'refereeinvitation', 'registrationinvitation']: + raise forms.ValidationError('The from_type hidden field is inconsistent.') + return cleaned_instance_from_type + def save(self): profile = super().save() - profile.emails.update(primary=False) - email, __ = ProfileEmail.objects.get_or_create( - profile=profile, email=self.cleaned_data['email']) - profile.emails.filter(id=email.id).update(primary=True, still_valid=True) + if self.cleaned_data['email']: + profile.emails.update(primary=False) + email, __ = ProfileEmail.objects.get_or_create( + profile=profile, email=self.cleaned_data['email']) + profile.emails.filter(id=email.id).update(primary=True, still_valid=True) + instance_pk = self.cleaned_data['instance_pk'] + if instance_pk: + if self.cleaned_data['instance_from_type'] == 'contributor': + Contributor.objects.filter(pk=instance_pk).update(profile=profile) + elif self.cleaned_data['instance_from_type'] == 'unregisteredauthor': + UnregisteredAuthor.objects.filter(pk=instance_pk).update(profile=profile) + elif self.cleaned_data['instance_from_type'] == 'refereeinvitation': + RefereeInvitation.objects.filter(pk=instance_pk).update(profile=profile) + elif self.cleaned_data['instance_from_type'] == 'registrationinvitation': + RegistrationInvitation.objects.filter(pk=instance_pk).update(profile=profile) return profile +class SimpleProfileForm(ProfileForm): + """ + Simple version of ProfileForm, displaying only required fields. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['expertises'].widget = forms.HiddenInput() + self.fields['orcid_id'].widget = forms.HiddenInput() + self.fields['webpage'].widget = forms.HiddenInput() + self.fields['accepts_SciPost_emails'].widget = forms.HiddenInput() + self.fields['accepts_refereeing_requests'].widget = forms.HiddenInput() + + +class ProfileMergeForm(forms.Form): + to_merge = ModelChoiceFieldwithid(queryset=Profile.objects.all(), empty_label=None) + to_merge_into = ModelChoiceFieldwithid(queryset=Profile.objects.all(), empty_label=None) + + def clean(self): + """ + To merge Profiles, they must be distinct, and it must not be the + case that they both are associated to a Contributor instance + (which would mean two Contributor objects for the same person). + """ + data = super().clean() + if self.cleaned_data['to_merge'] == self.cleaned_data['to_merge_into']: + self.add_error(None, 'A Profile cannot be merged into itself.') + if self.cleaned_data['to_merge'].has_active_contributor and \ + self.cleaned_data['to_merge_into'].has_active_contributor: + self.add_error(None, 'Each of these two Profiles has an active Contributor. ' + 'Merge the Contributors first.\n' + 'If these are distinct people or if two separate ' + 'accounts are needed, a ProfileNonDuplicate instance should be created; ' + 'contact techsupport.') + return data + + def save(self): + """ + Perform the actual merge: save all data from to-be-deleted profile + into the one to be kept. + """ + profile = self.cleaned_data['to_merge_into'] + profile_old = self.cleaned_data['to_merge'] + + # Merge information from old to new Profile. + profile.expertises = list( + set(profile_old.expertises or []) | set(profile.expertises or [])) + if profile.orcid_id is None: + profile.orcid_id = profile_old.orcid_id + if profile.webpage is None: + profile.webpage = profile_old.webpage + if profile_old.has_active_contributor and not profile.has_active_contributor: + profile.contributor = profile_old.contributor + profile.save() # Save all the field updates. + + profile.topics.add(*profile_old.topics.all()) + + UnregisteredAuthor.objects.filter(profile=profile_old).update(profile=profile) + + # Merge email + profile_old.emails.exclude( + email__in=profile.emails.values_list('email', flat=True)).update( + primary=False, profile=profile) + + # Move all invitations to the "new" profile. + profile_old.refereeinvitation_set.all().update(profile=profile) + profile_old.registrationinvitation_set.all().update(profile=profile) + + profile_old.delete() + return Profile.objects.get(id=profile.id) # Retrieve again because of all the db updates. + + class ProfileEmailForm(forms.ModelForm): class Meta: model = ProfileEmail - fields = ['email', 'still_valid'] + fields = ['email', 'still_valid', 'primary'] def __init__(self, *args, **kwargs): self.profile = kwargs.pop('profile', None) @@ -57,7 +165,3 @@ class ProfileEmailForm(forms.ModelForm): """Save to a profile.""" self.instance.profile = self.profile return super().save() - - -class SearchTextForm(forms.Form): - text = forms.CharField(label='') diff --git a/profiles/managers.py b/profiles/managers.py index 6f02127666b19f039c0c35176acb96dcbadc9eb6..804e0b7ccf5b72e48bf8e4267027a957c2394fa4 100644 --- a/profiles/managers.py +++ b/profiles/managers.py @@ -3,6 +3,8 @@ __license__ = "AGPL v3" from django.db import models +from django.db.models import Count +from django.db.models.functions import Concat class ProfileQuerySet(models.QuerySet): @@ -15,3 +17,17 @@ class ProfileQuerySet(models.QuerySet): except self.model.MultipleObjectsReturned: pass return None + + def potential_duplicates(self): + """ + Returns only potential duplicate Profiles (as identified by first and + last names). + """ + from .models import ProfileNonDuplicates + profiles = self.annotate(full_name=Concat('last_name', 'first_name')) + nonduplicate_full_names = [dup.full_name for dup in ProfileNonDuplicates.objects.all()] + duplicates = profiles.values('full_name').annotate( + nr_count=Count('full_name') + ).filter(nr_count__gt=1).exclude(full_name__in=nonduplicate_full_names) + return profiles.filter(full_name__in=[item['full_name'] for item in duplicates] + ).order_by('last_name', 'first_name', '-id') diff --git a/profiles/migrations/0012_profile_topics.py b/profiles/migrations/0012_profile_topics.py new file mode 100644 index 0000000000000000000000000000000000000000..82b5c56d86bb92c533d7c7e0be3e23401b34756f --- /dev/null +++ b/profiles/migrations/0012_profile_topics.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-10-28 19:38 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ontology', '0005_auto_20181028_2038'), + ('profiles', '0011_auto_20181006_2341'), + ] + + operations = [ + migrations.AddField( + model_name='profile', + name='topics', + field=models.ManyToManyField(blank=True, to='ontology.Topic'), + ), + ] diff --git a/profiles/migrations/0013_profilenonduplicates.py b/profiles/migrations/0013_profilenonduplicates.py new file mode 100644 index 0000000000000000000000000000000000000000..19c9592afc30638baa0bc0285817da37eaa6ac62 --- /dev/null +++ b/profiles/migrations/0013_profilenonduplicates.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-04 19:28 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0012_profile_topics'), + ] + + operations = [ + migrations.CreateModel( + name='ProfileNonDuplicates', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('reason', models.CharField(choices=[('DifferentPeople', 'These are different people'), ('MultipleAllowed', 'Multiple Profiles allowed for this person')], max_length=32)), + ('profiles', models.ManyToManyField(to='profiles.Profile')), + ], + ), + ] diff --git a/profiles/migrations/0014_auto_20181110_0637.py b/profiles/migrations/0014_auto_20181110_0637.py new file mode 100644 index 0000000000000000000000000000000000000000..7d3d07cb2498f21f09d149149bf31be4dd23de47 --- /dev/null +++ b/profiles/migrations/0014_auto_20181110_0637.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-10 05:37 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0013_profilenonduplicates'), + ] + + operations = [ + migrations.AlterModelOptions( + name='profilenonduplicates', + options={'verbose_name': 'Profile non-duplicates', 'verbose_name_plural': 'Profile non-duplicates'}, + ), + ] diff --git a/profiles/migrations/0015_auto_20181118_0849.py b/profiles/migrations/0015_auto_20181118_0849.py new file mode 100644 index 0000000000000000000000000000000000000000..83aff5b2ddbf1c71623d30e3463424c11335943c --- /dev/null +++ b/profiles/migrations/0015_auto_20181118_0849.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-18 07:49 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0014_auto_20181110_0637'), + ] + + operations = [ + migrations.AlterField( + model_name='profile', + name='title', + field=models.CharField(blank=True, choices=[('PR', 'Prof.'), ('DR', 'Dr'), ('MR', 'Mr'), ('MRS', 'Mrs'), ('MS', 'Ms')], max_length=4, null=True), + ), + ] diff --git a/profiles/models.py b/profiles/models.py index 7787262628c45a2d2b71c7dae54b81b00183c452..93da3c30ebafd65a0c8367848581e800581c4c27 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -2,13 +2,23 @@ __copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" __license__ = "AGPL v3" +from django.core.urlresolvers import reverse from django.db import models +from django.db.models.functions import Concat +from django.shortcuts import get_object_or_404 from scipost.behaviors import orcid_validator from scipost.constants import ( TITLE_CHOICES, SCIPOST_DISCIPLINES, DISCIPLINE_PHYSICS, SCIPOST_SUBJECT_AREAS) from scipost.fields import ChoiceArrayField +from scipost.models import Contributor +from comments.models import Comment +from journals.models import Publication, PublicationAuthorsTable +from ontology.models import Topic +from theses.models import ThesisLink + +from .constants import PROFILE_NON_DUPLICATE_REASONS from .managers import ProfileQuerySet @@ -35,7 +45,7 @@ class Profile(models.Model): #. mark somebody as a non-referee (if that person does not want to referee for SciPost) """ - title = models.CharField(max_length=4, choices=TITLE_CHOICES) + title = models.CharField(max_length=4, choices=TITLE_CHOICES, blank=True, null=True) first_name = models.CharField(max_length=64) last_name = models.CharField(max_length=64) discipline = models.CharField(max_length=20, choices=SCIPOST_DISCIPLINES, @@ -47,6 +57,9 @@ class Profile(models.Model): blank=True, validators=[orcid_validator]) webpage = models.URLField(blank=True) + # Topics for semantic linking + topics = models.ManyToManyField('ontology.Topic', blank=True) + # Preferences for interactions with SciPost: accepts_SciPost_emails = models.BooleanField(default=True) accepts_refereeing_requests = models.BooleanField(default=True) @@ -57,16 +70,51 @@ class Profile(models.Model): ordering = ['last_name'] def __str__(self): - return '%s, %s %s' % (self.last_name, self.get_title_display(), self.first_name) + return '%s, %s %s' % (self.last_name, + self.get_title_display() if self.title != None else '', + self.first_name) @property def email(self): return getattr(self.emails.filter(primary=True).first(), 'email', '') + @property + def has_active_contributor(self): + has_active_contributor = False + try: + has_active_contributor = (self.contributor is not None and + self.contributor.is_active) + except Contributor.DoesNotExist: + pass + return has_active_contributor + + def get_absolute_url(self): + return reverse('profiles:profile_detail', kwargs={'pk': self.id}) + + def publications(self): + """ + Returns all the publications associated to this Profile. + """ + return Publication.objects.published().filter( + models.Q(authors__unregistered_author__profile=self) | + models.Q(authors__contributor__profile=self)) + + def comments(self): + """ + Returns all the Comments associated to this Profile. + """ + return Comment.objects.filter(author__profile=self) + + def theses(self): + """ + Returns all the Theses associated to this Profile. + """ + return ThesisLink.objects.filter(author_as_cont__profile=self) + class ProfileEmail(models.Model): """Any email related to a Profile instance.""" - profile = models.ForeignKey(Profile, on_delete=models.CASCADE) + profile = models.ForeignKey('profiles.Profile', on_delete=models.CASCADE) email = models.EmailField() still_valid = models.BooleanField(default=True) primary = models.BooleanField(default=False) @@ -78,3 +126,41 @@ class ProfileEmail(models.Model): def __str__(self): return self.email + + +def get_profiles(slug): + """ + Returns a list of Profiles for which there exists at least one + Publication/Submission object carrying this Topic. + """ + topic = get_object_or_404(Topic, slug=slug) + publications = PublicationAuthorsTable.objects.filter(publication__topics__in=[topic,]) + cont_id_list = [tbl.contributor.id for tbl in publications.all() \ + if tbl.contributor is not None] + unreg_id_list = [tbl.unregistered_author.id for tbl in publications.all() \ + if tbl.unregistered_author is not None] + return Profile.objects.filter(models.Q(contributor__id__in=cont_id_list) | + models.Q(unregisteredauthor__id__in=unreg_id_list + )).distinct() + + +class ProfileNonDuplicates(models.Model): + """ + Sets of Profiles which are not duplicates of each other, + and thus can be filtered out of any dynamically generated list of potential duplicates. + """ + profiles = models.ManyToManyField('profiles.Profile') + reason = models.CharField(max_length=32, choices=PROFILE_NON_DUPLICATE_REASONS) + + class Meta: + verbose_name = 'Profile non-duplicates' + verbose_name_plural = 'Profile non-duplicates' + + def __str__(self): + return '%s, %s (%i)' % (self.profiles.first().last_name, + self.profiles.first().first_name, + self.profiles.count()) + + @property + def full_name(self): + return '%s%s' % (self.profiles.first().last_name, self.profiles.first().first_name) diff --git a/profiles/templates/profiles/_profile_card.html b/profiles/templates/profiles/_profile_card.html index 6692bc6b340188e5c0908a75aa2ad25a0201e1fb..503bd04d65b0f3df805f52d734073582574cbae0 100644 --- a/profiles/templates/profiles/_profile_card.html +++ b/profiles/templates/profiles/_profile_card.html @@ -1,23 +1,31 @@ {% load bootstrap %} {% load scipost_extras %} +{% load user_groups %} -<div class="card-body"> - <div class="row"> - <div class="col-6"> +{% is_edcol_admin request.user as is_edcol_admin %} +{% is_scipost_admin request.user as is_scipost_admin %} + + + <div class="card"> + <div class="card-header"> + Details for profile {{ profile.id }} + </div> + <div class="card-body"> <table class="table"> <tr> - <td>Name:</td><td>{{ profile.last_name }}, {{ profile.get_title_display }} {{ profile.first_name }}</td> + <td>Name:</td> + <td>{{ profile }}</td> </tr> <tr> <td>Email(s)</td> <td> - <table class="table table-sm table-borderless"> + <table class="table table-sm"> <thead> - <tr> - <th colspan="2">Email</th> - <th>Still valid</th> - <th></th> - </tr> + <tr> + <th colspan="2">Email</th> + <th>Still valid</th> + <th></th> + </tr> </thead> {% for profile_mail in profile.emails.all %} <tr> @@ -28,8 +36,9 @@ </td> <td class="d-flex"> - <form method="post" action="{% url 'profiles:toggle_email_status' profile_mail.id %}">{% csrf_token %}<button type="submit" class="btn btn-link p-0">{{ profile_mail.still_valid|yesno:'Deprecate,Mark valid' }}</button></form> - <form method="post" action="{% url 'profiles:delete_profile_email' profile_mail.id %}">{% csrf_token %}<button type="submit" class="btn btn-link text-danger p-0 ml-2" onclick="return confirm('Sure you want to delete {{ profile_mail.email }}?')"><i class="fa fa-trash"></i></button></form> + <form method="post" action="{% url 'profiles:toggle_email_status' profile_mail.id %}">{% csrf_token %}<button type="submit" class="btn btn-link">{{ profile_mail.still_valid|yesno:'Deprecate,Mark valid' }}</button></form> + <form method="post" action="{% url 'profiles:email_make_primary' profile_mail.id %}">{% csrf_token %}<button type="submit" class="btn btn-link">Make primary</button></form> + <form method="post" action="{% url 'profiles:delete_profile_email' profile_mail.id %}">{% csrf_token %}<button type="submit" class="btn btn-link text-danger ml-2" onclick="return confirm('Sure you want to delete {{ profile_mail.email }}?')"><i class="fa fa-trash"></i></button></form> </td> </tr> {% endfor %} @@ -42,7 +51,7 @@ <tr> <td>Expertises</td> <td>{% for expertise in profile.expertises %} - <div class="single d-inline" data-specialization="{{expertise|lower}}" data-toggle="tooltip" data-placement="bottom" title="{{expertise|get_specialization_display}}">{{ expertise|get_specialization_code }}</div> + <div class="single d-inline" data-specialization="{{expertise|lower}}" data-toggle="tooltip" data-placement="bottom" title="{{expertise|get_specialization_display}}">{{ expertise|get_specialization_code }}</div> {% endfor %} </td> </tr> @@ -50,27 +59,141 @@ <tr><td>Webpage</td><td><a href="{{ profile.webpage }}" target="_blank">{{ profile.webpage }}</a></td></tr> <tr><td>Accepts SciPost emails</td><td>{{ profile.accepts_SciPost_emails }}</td></tr> <tr><td>Accepts refereeing requests</td><td>{{ profile.accepts_refereeing_requests }}</td></tr> - <tr><td>Contributor</td><td>{% if profile.contributor %}Yes (<a href="{% url 'scipost:contributor_info' contributor_id=profile.contributor.id %}" target="_blank">info link</a>){% else %}No{% endif %}</td></tr> + <tr><td>Contributor</td><td>{% if profile.contributor %}Yes, id: {{ profile.contributor.pk }}, status: {{ profile.contributor.get_status_display }}, user active: {{ profile.contributor.user.is_active }} (<a href="{% url 'scipost:contributor_info' contributor_id=profile.contributor.id %}" target="_blank">info link</a>){% else %}No{% endif %}</td></tr> </table> </div> - <div class="col-6"> - <h4>Actions:</h4> + </div> + +<div class="card-columns"> + + <div class="card"> + <div class="card-header"> + Publications + </div> + <div class="card-body"> + <ul> + {% for pub in profile.publications.all %} + <li><a href="{{ pub.get_absolute_url }}">{{ pub.citation }}</a></li> + {% empty %} + <li>No Publication found</li> + {% endfor %} + </ul> + </div> + </div> + + <div class="card"> + <div class="card-header"> + Comments + </div> + <div class="card-body"> + {% for comment in profile.comments.all %} + <li><a href="{{ comment.get_absolute_url }}">{{ comment }}</a></li> + {% empty %} + <li>No Comment found</li> + {% endfor %} + </div> + </div> + + <div class="card"> + <div class="card-header"> + Theses + </div> + <div class="card-body"> + {% for thesis in profile.theses.all %} + <li><a href="{{ thesis.get_absolute_url }}">{{ thesis }}</a></li> + {% empty %} + <li>No Thesis found</li> + {% endfor %} + </div> + </div> + +</div> + +{% if is_scipost_admin or is_edcol_admin %} +<h4 class="highlight p-2 text-danger">Admin-level info</h4> +<div class="card-columns"> + + <div class="card"> + <div class="card-header"> + Registration invitations + </div> + <div class="card-body"> <ul> - <li><a href="{% url 'profiles:profile_update' pk=profile.id %}">Update</a> this Profile</li> - <li><a href="{% url 'profiles:profile_delete' pk=profile.id %}">Delete</a> this Profile</li> - {% if email_form %} - <li> - <div> - Add an email to this Profile: - <form action="{% url 'profiles:add_profile_email' profile_id=profile.id %}" method="post"> - {% csrf_token %} - {{ email_form|bootstrap }} - <input class="btn btn-outline-secondary" type="submit" value="Add"> - </form> - </div> - </li> - {% endif %} + {% for reginv in profile.registrationinvitation_set.all %} + <li>{{ reginv }}<br/>status: {{ reginv.get_status_display }}</li> + {% empty %} + <li>No invitation found</li> + {% endfor %} </ul> </div> </div> + + <div class="card"> + <div class="card-header"> + Fellowships and Potential Fellowships + </div> + <div class="card-body"> + <h5>Fellowships</h5> + <ul> + {% for fellowship in profile.contributor.fellowships.all %} + <li><a href="{{ fellowship.get_absolute_url }}">{{ fellowship }}</a></li> + {% empty %} + <li>No fellowships found</li> + {% endfor %} + </ul> + <h5>Potential Fellowships</h5> + <ul> + {% for potfellowship in profile.potentialfellowship_set.all %} + <li>{{ potfellowship }}</li> + {% empty %} + <li>No Potential Fellowships found</li> + {% endfor %} + </ul> + </div> + </div> + + + + <div class="card"> + <div class="card-header"> + Refereeing invitations + </div> + <div class="card-body"> + <ul> + {% for inv in profile.refereeinvitation_set.all %} + <li>{{ inv.submission.title }}<br/>(invited {{ inv.date_invited }}; fulfilled: {% if inv.fulfilled %}<i class="fa fa-check-square text-success"></i>{% else %}<i class="fa fa-times-circle"></i>{% endif %})</li> + {% empty %} + <li>No refereeing invitation found</li> + {% endfor %} + </ul> + </div> + </div> +</div> + +<div class="card-columns"> + <div class="card"> + <div class="card-header"> + Actions + </div> + <div class="card-body"> + <ul> + <li><a href="{% url 'profiles:profile_update' pk=profile.id %}">Update</a> this Profile</li> + <li><a href="{% url 'profiles:profile_delete' pk=profile.id %}" class="text-danger">Delete</a> this Profile</li> + {% if email_form %} + <li> + <div> + Add an email to this Profile: + <form class="form-inline" action="{% url 'profiles:add_profile_email' profile_id=profile.id %}" method="post"> + {% csrf_token %} + {{ email_form|bootstrap }} + <input class="btn btn-outline-secondary" type="submit" value="Add"> + </form> + </div> + </li> + {% endif %} + </ul> + </div> + </div> + </div> +{% endif %} diff --git a/profiles/templates/profiles/profile_detail.html b/profiles/templates/profiles/profile_detail.html new file mode 100644 index 0000000000000000000000000000000000000000..3376adbc12a4ee0f66945d1c1b8244fb30c66e30 --- /dev/null +++ b/profiles/templates/profiles/profile_detail.html @@ -0,0 +1,21 @@ +{% extends 'profiles/base.html' %} + +{% load bootstrap %} + +{% block breadcrumb_items %} + {{ block.super }} +<span class="breadcrumb-item">Details</span> +{% endblock %} + +{% load scipost_extras %} + +{% block pagetitle %}: Profile details{% endblock pagetitle %} + +{% block content %} +<div class="row"> + <div class="col-12"> + {% include 'profiles/_profile_card.html' with profile=object %} + </div> +</div> + +{% endblock content %} diff --git a/profiles/templates/profiles/profile_duplicate_list.html b/profiles/templates/profiles/profile_duplicate_list.html new file mode 100644 index 0000000000000000000000000000000000000000..a446fbae5791a745d92eb1a9a534c1a02c994a57 --- /dev/null +++ b/profiles/templates/profiles/profile_duplicate_list.html @@ -0,0 +1,44 @@ +{% extends 'profiles/base.html' %} + +{% load bootstrap %} + +{% block breadcrumb_items %} + {{ block.super }} +<span class="breadcrumb-item">Duplicates</span> +{% endblock %} + +{% load scipost_extras %} + +{% block pagetitle %}: Profile duplicates{% endblock pagetitle %} + +{% block content %} +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Potentially duplicate Profiles</h1> + {% if merge_form %} + <form action="{% url 'profiles:merge' %}" method="get"> + {{ merge_form|bootstrap }} + <input class="btn btn-outline-secondary" type="submit" value="Check"> + </form> + {% endif %} + + <br> + <h3>All found duplicates</h3> + <ul> + {% for prof_dup in object_list %} + <li>{{ prof_dup }} (<em>id={{ prof_dup.id }}</em>)</li> + {% empty %} + <li<em>No duplicates found</em></li> + {% endfor %} + </ul> + + {% if is_paginated %} + <div class="col-12"> + {% include 'partials/pagination.html' with page_obj=page_obj %} + </div> + {% endif %} + + </div> +</div> + +{% endblock content %} diff --git a/profiles/templates/profiles/profile_form.html b/profiles/templates/profiles/profile_form.html index e94df9dfc1589fce9b17a87b7c1c57172ba30aac..b5e021cfcd8da36d25df6b56fd5325c926068176 100644 --- a/profiles/templates/profiles/profile_form.html +++ b/profiles/templates/profiles/profile_form.html @@ -4,7 +4,7 @@ {% block breadcrumb_items %} {{ block.super }} - <span class="breadcrumb-item">{% if form.instance.id %}Update {{ form.instance }}{% else %}Add new Profile{% endif %}</span> + <span class="breadcrumb-item">{% if form.instance.id %}Update {{ form.instance }}{% else %}Add new Profile {% if from_type %}(from {{ from_type }}){% endif %}{% endif %}</span> {% endblock %} {% block pagetitle %}: Profiles{% endblock pagetitle %} @@ -12,6 +12,17 @@ {% block content %} <div class="row"> <div class="col-12"> + {% if matching_profiles %} + <h4>Matching profiles found for this {{ from_type }}</h4> + <ul> + {% for matching_profile in matching_profiles %} + <li>{{ matching_profile }} (id {{ matching_profile.id }}, {{ matching_profile.email }}) <a href="{% url 'profiles:profile_match' profile_id=matching_profile.id from_type=from_type pk=pk %}"><i class="fa fa-arrow-right"></i> Match this {{ from_type }} to this Profile</a> + </li> + {% endfor %} + </ul> + {% endif %} + + <form action="" method="post"> {% csrf_token %} {{ form|bootstrap }} diff --git a/profiles/templates/profiles/profile_list.html b/profiles/templates/profiles/profile_list.html index 9a9bb702428b67827e26d6997e2d6b9401709c74..a28bbeed51d5a5a44e2a17663263925a58838c94 100644 --- a/profiles/templates/profiles/profile_list.html +++ b/profiles/templates/profiles/profile_list.html @@ -1,96 +1,132 @@ -{% extends 'scipost/_personal_page_base.html' %} +{% extends 'profiles/base.html' %} {% load bootstrap %} {% load add_get_parameters %} +{% load scipost_extras %} +{% load user_groups %} + {% block breadcrumb_items %} {{ block.super }} <span class="breadcrumb-item">Profiles</span> {% endblock %} -{% load scipost_extras %} +{% block headsup %} +<script type="text/javascript"> +$(document).ready(function($) { + $(".table-row").click(function() { + window.document.location = $(this).data("href"); + }); +}); +</script> +{% endblock headsup %} {% block pagetitle %}: Profiles{% endblock pagetitle %} {% block content %} + +{% is_edcol_admin request.user as is_edcol_admin %} +{% is_scipost_admin request.user as is_scipost_admin %} + <div class="row"> <div class="col-12"> <h4>Profiles-related Actions:</h4> - <ul> - {% if contributors_w_duplicate_email %} - <li class="text-danger"> - {{ contributors_w_duplicate_email|length }} Contributor duplicates (via email) identified - <ul> - {% for dup in contributors_w_duplicate_email %} - <li>{{ dup }}, {{ dup.user.email }}, id {{ dup.id }}</li> - {% empty %} - <li>No duplicates found</li> - {% endfor %} - </ul> - Please take action by merging these Contributor instances. - </li> - {% endif %} - <li><a href="{% url 'profiles:profile_create' %}">Add a Profile</a></li> - {% if next_reginv_wo_profile %} - <li>Create a Profile for <a href="{% url 'profiles:profile_create' from_type='registrationinvitation' pk=next_reginv_wo_profile.id %}">the next</a> Registration Invitation without one ({{ nr_reginv_wo_profile }} to handle)</li> - {% endif %} - {% if next_contributor_wo_profile %} - <li>Create a Profile for <a href="{% url 'profiles:profile_create' from_type='contributor' pk=next_contributor_wo_profile.id %}">the next</a> Contributor without one ({{ nr_contributors_wo_profile }} to handle)</li> - {% endif %} - {% if next_refinv_wo_profile %} - <li>Create a Profile for <a href="{% url 'profiles:profile_create' from_type='refereeinvitation' pk=next_refinv_wo_profile.id %}">the next</a> Referee Invitation without one ({{ nr_refinv_wo_profile }} to handle)</li> - {% endif %} - </ul> - <h4>Specialize the list:</h4> - <ul> - <li> - <ul class="list-inline"> - <li class="list-inline-item"> - <a href="{% url 'profiles:profiles' %}">View all</a> or view by discipline/subject area: - </li> - {% for discipline in subject_areas %} - <li class="list-inline-item"> - <div class="dropdown"> - <button class="btn btn-primary dropdown-toggle" type="button" id="dropdownMenuButton{{ discipline.0|cut:" " }}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{ discipline.0 }}</button> - <div class="dropdown-menu" aria-labelledby="dropdownMenuButton{{ discipline.0|cut:" " }}"> - <a class="dropdown-item" href="{% add_get_parameters discipline=discipline.0|cut:' ' %}">View all in {{ discipline.0 }}</a> - {% for area in discipline.1 %} - <a class="dropdown-item" href="{% add_get_parameters discipline=discipline.0|cut:' ' expertise=area.0 %}">{{ area.0 }}</a> - {% endfor %} - </div> + <ul> + {% if is_scipost_admin or is_edcol_admin %} + {% if nr_contributors_w_duplicate_names > 0 %} + <li><i class="fa fa-exclamation-circle text-warning"></i> <a href="{% url 'scipost:contributor_duplicates' %}?kind=names">Handle Contributors with duplicate names ({{ nr_contributors_w_duplicate_names }} to handle)</a></li> + {% else %} + <li><i class="fa fa-check-circle text-success"></i> No name-duplicate Contributors found</li> + {% endif %} + {% if nr_contributors_w_duplicate_emails > 0 %} + <li><a href="{% url 'scipost:contributor_duplicates' %}?kind=names">Handle Contributors with duplicate emails ({{ nr_contributors_w_duplicate_emails }} to handle)</a></li> + {% else %} + <li><i class="fa fa-check-circle text-success"></i> No email-duplicate Contributors found</li> + {% endif %} + {% if next_contributor_wo_profile %} + <li><i class="fa fa-exclamation-circle text-warning"></i> Create a Profile for <a href="{% url 'profiles:profile_create' from_type='contributor' pk=next_contributor_wo_profile.id %}">the next</a> Contributor without one ({{ nr_contributors_wo_profile }} to handle)</li> + {% else %} + <li><i class="fa fa-check-circle text-success"></i> All registered Contributors have a Profile</li> + {% endif %} + {% if nr_potential_duplicate_profiles > 0 %} + <li><i class="fa fa-exclamation-circle text-warning"></i> <a href="{% url 'profiles:duplicates' %}">Check for duplicate Profiles ({{ nr_potential_duplicate_profiles }} to handle)</a></li> + {% else %} + <li><i class="fa fa-check-circle text-success"></i> No potential duplicate Profiles detected</li> + {% endif %} + {% if next_reginv_wo_profile %} + <li><i class="fa fa-exclamation-circle text-warning"></i> Create a Profile for <a href="{% url 'profiles:profile_create' from_type='registrationinvitation' pk=next_reginv_wo_profile.id %}">the next</a> Registration Invitation without one ({{ nr_reginv_wo_profile }} to handle)</li> + {% else %} + <li><i class="fa fa-check-circle text-success"></i> All Registration Invitations have a Profile</li> + {% endif %} + {% if next_unreg_auth_wo_profile %} + <li><i class="fa fa-exclamation-circle text-warning"></i> Create a Profile for <a href="{% url 'profiles:profile_create' from_type='unregisteredauthor' pk=next_unreg_auth_wo_profile.id %}">the next</a> UnregisteredAuthor without one ({{ nr_unreg_auth_wo_profile }} to handle)</li> + {% else %} + <li><i class="fa fa-check-circle text-success"></i> All UnregisteredAuthors have a Profile</li> + {% endif %} + {% if next_refinv_wo_profile %} + <li><i class="fa fa-exclamation-circle text-warning"></i> Create a Profile for <a href="{% url 'profiles:profile_create' from_type='refereeinvitation' pk=next_refinv_wo_profile.id %}">the next</a> Referee Invitation without one ({{ nr_refinv_wo_profile }} to handle)</li> + {% else %} + <li><i class="fa fa-check-circle text-success"></i> All Referee Invitations have a Profile</li> + {% endif %} + {% endif %} + <li><a href="{% url 'profiles:profile_create' %}">Add a Profile</a></li> + </ul> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <h4>Specialize the list:</h4> + <ul> + <li> + <ul class="list-inline"> + <li class="list-inline-item"> + <a href="{% url 'profiles:profiles' %}">View all</a> or view by discipline/subject area: + </li> + {% for discipline in subject_areas %} + <li class="list-inline-item"> + <div class="dropdown"> + <button class="btn btn-primary dropdown-toggle" type="button" id="dropdownMenuButton{{ discipline.0|cut:" " }}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{ discipline.0 }}</button> + <div class="dropdown-menu" aria-labelledby="dropdownMenuButton{{ discipline.0|cut:" " }}"> + <a class="dropdown-item" href="{% add_get_parameters discipline=discipline.0|cut:' ' %}">View all in {{ discipline.0 }}</a> + {% for area in discipline.1 %} + <a class="dropdown-item" href="{% add_get_parameters discipline=discipline.0|cut:' ' expertise=area.0 %}">{{ area.0 }}</a> + {% endfor %} </div> - </li> - {% endfor %} - </ul> - </li> - <li>View only Profiles <a href="{% add_get_parameters contributor=True %}">with</a> or <a href="{% add_get_parameters contributor=False %}">without</a> an associated Contributor</li> - <li> - <ul class="list-inline"> - <li class="list-inline-item">Last name startswith:</li> - <li class="list-inline-item"> - <form action="" method="get">{{ searchform }} - {% if request.GET.discipline %} - <input type="hidden" name="discipline" value="{{ request.GET.discipline }}"> - {% if request.GET.expertise %} - <input type="hidden" name="expertise" value="{{ request.GET.expertise }}"> - {% endif %} - {% endif %} - {% if request.GET.contributor %} - <input type="hidden" name="contributor" value="{{ request.GET.contributor }}"> - {% endif %} - </li> - <li class="list-inline-item"><input class="btn btn-outline-secondary" type="submit" value="Search"></form> - </li> - </ul> - </li> + </div> + </li> + {% endfor %} + </ul> + </li> + <li>View only Profiles <a href="{% add_get_parameters contributor=True %}">with</a> or <a href="{% add_get_parameters contributor=False %}">without</a> an associated Contributor</li> + <li> + <ul class="list-inline"> + <li class="list-inline-item">Last name startswith:</li> + <li class="list-inline-item"> + <form action="" method="get">{{ searchform }} + {% if request.GET.discipline %} + <input type="hidden" name="discipline" value="{{ request.GET.discipline }}"> + {% if request.GET.expertise %} + <input type="hidden" name="expertise" value="{{ request.GET.expertise }}"> + {% endif %} + {% endif %} + {% if request.GET.contributor %} + <input type="hidden" name="contributor" value="{{ request.GET.contributor }}"> + {% endif %} + </li> + <li class="list-inline-item"><input class="btn btn-outline-secondary" type="submit" value="Search"></form> + </li> </ul> - </div> +</li> +</ul> +</div> </div> <div class="row"> <div class="col-12"> <h3>Profiles {% if request.GET.text %}with last name starting with {{ request.GET.text }}{% endif %} {% if request.GET.discipline %}in {{ request.GET.discipline }}{% if request.GET.expertise %}, {{ request.GET.expertise }}{% endif %}{% endif %} ({% if request.GET.contributor == "True" %}registered Contributors{% elif request.GET.contributor == "False" %}unregistered as Contributors{% else %}all registered/unregistered{% endif %}): {{ page_obj.paginator.count }} found</h3> <br/> + <table class="table table-hover mb-5"> <thead class="thead-default"> <tr> @@ -100,22 +136,17 @@ <th>Contributor?</th> </tr> </thead> - <tbody id="accordion" role="tablist" aria-multiselectable="true"> + <tbody> {% for profile in object_list %} - <tr data-toggle="collapse" data-parent="#accordion" href="#collapse{{ profile.id }}" aria-expanded="false" aria-controls="collapse{{ profile.id }}" style="cursor: pointer;"> - <td>{{ profile.last_name }}, {{ profile.get_title_display }} {{ profile.first_name }}</td> + <tr class="table-row" data-href="{% url 'profiles:profile_detail' pk=profile.id %}" target="_blank" style="cursor: pointer;"> + <td>{{ profile }}</td> <td>{{ profile.get_discipline_display }}</td> <td> {% for expertise in profile.expertises %} <div class="single d-inline" data-specialization="{{expertise|lower}}" data-toggle="tooltip" data-placement="bottom" title="{{expertise|get_specialization_display}}">{{expertise|get_specialization_code}}</div> {% endfor %} </td> - <td>{% if profile.contributor %}<i class="fa fa-check-circle text-success"></i>{% else %}<i class="fa fa-times-circle text-danger"></i>{% endif %}</td> - </tr> - <tr id="collapse{{ profile.id }}" class="collapse" role="tabpanel" aria-labelledby="heading{{ profile.id }}" style="background-color: #fff;"> - <td colspan="4"> - {% include 'profiles/_profile_card.html' with profile=profile %} - </td> + <td>{% if profile.has_active_contributor %}<i class="fa fa-check-circle text-success"></i>{% else %}<i class="fa fa-times-circle text-danger"></i>{% endif %}</td> </tr> {% empty %} <tr> diff --git a/profiles/templates/profiles/profile_merge.html b/profiles/templates/profiles/profile_merge.html new file mode 100644 index 0000000000000000000000000000000000000000..3952b44fda30e416f12293ef68d3b751daab1501 --- /dev/null +++ b/profiles/templates/profiles/profile_merge.html @@ -0,0 +1,50 @@ +{% extends 'profiles/base.html' %} + +{% load bootstrap %} + +{% block breadcrumb_items %} + {{ block.super }} +<span class="breadcrumb-item"><a href="{% url 'profiles:duplicates' %}">Duplicates</a></span> +<span class="breadcrumb-item">Merge Profiles {{ profile_to_merge.id }} and {{ profile_to_merge_into.id }}</span> +{% endblock %} + +{% load scipost_extras %} + +{% block pagetitle %}: Profile duplicates: merge{% endblock pagetitle %} + +{% block content %} +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Merge Profiles {{ profile_to_merge.id }} and {{ profile_to_merge_into.id }}</h1> + {% if profile_to_merge.has_active_contributor and not profile_to_merge_into.has_active_contributor %} + <h3 class="text-danger">Warning: the Profile to merge is associated to an active Contributor, while the one to merge into is not</h3> + <p>Consider <a href="{% url 'profiles:merge' %}?to_merge={{ profile_to_merge_into.id }}&to_merge_into={{ profile_to_merge.id }}" method="get">merging the other way around</a></p> + {% endif %} + </div> +</div> +<div class="row"> + <div class="col-12"> + <h3 class="highlight">Profile {{ profile_to_merge.id }}</h3> + {% include 'profiles/_profile_card.html' with profile=profile_to_merge %} + </div> +</div> +<div class="row"> + <div class="col-12"> + <h3 class="highlight">Profile {{ profile_to_merge_into.id }}</h3> + {% include 'profiles/_profile_card.html' with profile=profile_to_merge_into %} + </div> +</div> + +<div class="row"> + <div class="col-12"> + <h3 class="highlight">Merge:</h3> + <form method="post"> + {% csrf_token %} + {{ merge_form|bootstrap }} + <input class="btn btn-primary" type="submit" value="Confirm merge"> + <a class="text-warning" href="{% url 'profiles:merge' %}?to_merge={{ profile_to_merge_into.id }}&to_merge_into={{ profile_to_merge.id }}" method="get">Merge the other way around</a></p> + </form> + </div> +</div> + +{% endblock content %} diff --git a/profiles/templatetags/profiles_extras.py b/profiles/templatetags/profiles_extras.py new file mode 100644 index 0000000000000000000000000000000000000000..f8c0ec82941ef7a4352a239f06ba922a4fbd870d --- /dev/null +++ b/profiles/templatetags/profiles_extras.py @@ -0,0 +1,14 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from django import template + +from ..models import get_profiles as profiles_get_profiles + +register = template.Library() + + +@register.simple_tag +def get_profiles(slug): + return profiles_get_profiles(slug) diff --git a/profiles/urls.py b/profiles/urls.py index 36ea79c056544d7405791826258a90de7bc4ada3..9d693d9f8044dda2ea5b2c32402ca05ab5cfa66d 100644 --- a/profiles/urls.py +++ b/profiles/urls.py @@ -17,6 +17,11 @@ urlpatterns = [ views.ProfileCreateView.as_view(), name='profile_create' ), + url( + r'^match/(?P<profile_id>[0-9]+)/(?P<from_type>[a-z]+)/(?P<pk>[0-9]+)$', + views.profile_match, + name='profile_match' + ), url( r'^(?P<pk>[0-9]+)/update/$', views.ProfileUpdateView.as_view(), @@ -32,11 +37,31 @@ urlpatterns = [ views.ProfileListView.as_view(), name='profiles' ), + url( + r'^(?P<pk>[0-9]+)/$', + views.ProfileDetailView.as_view(), + name='profile_detail' + ), + url( + r'^merge/$', + views.profile_merge, + name='merge' + ), + url( + r'^duplicates/$', + views.ProfileDuplicateListView.as_view(), + name='duplicates' + ), url( r'^(?P<profile_id>[0-9]+)/add_email$', views.add_profile_email, name='add_profile_email' ), + url( + r'^emails/(?P<email_id>[0-9]+)/make_primary$', + views.email_make_primary, + name='email_make_primary' + ), url( r'^emails/(?P<email_id>[0-9]+)/toggle$', views.toggle_email_status, diff --git a/profiles/views.py b/profiles/views.py index d447b1676d32a2b59e5fe93c08593e925032bdfd..4ece47f98d620478e2d9520997534d56f85ba973 100644 --- a/profiles/views.py +++ b/profiles/views.py @@ -4,9 +4,12 @@ __license__ = "AGPL v3" from django.contrib import messages from django.core.urlresolvers import reverse, reverse_lazy -from django.db import IntegrityError +from django.db import transaction +from django.db.models import Q +from django.http import Http404, HttpResponseRedirect from django.shortcuts import get_object_or_404, render, redirect from django.views.decorators.http import require_POST +from django.views.generic.detail import DetailView from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.views.generic.list import ListView @@ -15,12 +18,14 @@ from guardian.decorators import permission_required from scipost.constants import SCIPOST_SUBJECT_AREAS from scipost.mixins import PermissionsMixin, PaginationMixin from scipost.models import Contributor +from scipost.forms import SearchTextForm from invitations.models import RegistrationInvitation +from journals.models import UnregisteredAuthor from submissions.models import RefereeInvitation from .models import Profile, ProfileEmail -from .forms import ProfileForm, ProfileEmailForm, SearchTextForm +from .forms import ProfileForm, ProfileMergeForm, ProfileEmailForm @@ -33,6 +38,42 @@ class ProfileCreateView(PermissionsMixin, CreateView): template_name = 'profiles/profile_form.html' success_url = reverse_lazy('profiles:profiles') + def get_context_data(self, *args, **kwargs): + """ + When creating a Profile, if initial data obtained from another model + (Contributor, UnregisteredAuthor, RefereeInvitation or RegistrationInvitation) + is provided, this fills the context with possible already-existing Profiles. + """ + context = super().get_context_data(*args, **kwargs) + from_type = self.kwargs.get('from_type', None) + pk = self.kwargs.get('pk', None) + context['from_type'] = from_type + context['pk'] = pk + if pk and from_type: + matching_profiles = Profile.objects.all() + if from_type == 'contributor': + contributor = get_object_or_404(Contributor, pk=pk) + matching_profiles = matching_profiles.filter( + Q(last_name=contributor.user.last_name) | + Q(emails__email__in=contributor.user.email)) + elif from_type == 'unregisteredauthor': + unreg_auth = get_object_or_404(UnregisteredAuthor, pk=pk) + matching_profiles = matching_profiles.filter(last_name=unreg_auth.last_name) + elif from_type == 'refereeinvitation': + print ('Here refinv') + refinv = get_object_or_404(RefereeInvitation, pk=pk) + matching_profiles = matching_profiles.filter( + Q(last_name=refinv.last_name) | + Q(emails__email__in=refinv.email_address)) + elif from_type == 'registrationinvitation': + reginv = get_object_or_404(RegistrationInvitation, pk=pk) + matching_profiles = matching_profiles.filter( + Q(last_name=reginv.last_name) | + Q(emails__email__in=reginv.email)) + context['matching_profiles'] = matching_profiles.distinct().order_by( + 'last_name', 'first_name') + return context + def get_initial(self): """ Provide initial data based on kwargs. @@ -57,6 +98,12 @@ class ProfileCreateView(PermissionsMixin, CreateView): 'webpage': contributor.personalwebpage, 'accepts_SciPost_emails': contributor.accepts_SciPost_emails, }) + elif from_type == 'unregisteredauthor': + unreg_auth = get_object_or_404(UnregisteredAuthor, pk=pk) + initial.update({ + 'first_name': unreg_auth.first_name, + 'last_name': unreg_auth.last_name, + }) elif from_type == 'refereeinvitation': refinv = get_object_or_404(RefereeInvitation, pk=pk) initial.update({ @@ -75,8 +122,64 @@ class ProfileCreateView(PermissionsMixin, CreateView): 'last_name': reginv.last_name, 'email': reginv.email, }) + initial.update({ + 'instance_from_type': from_type, + 'instance_pk': pk, + }) return initial +@permission_required('scipost.can_create_profiles') +def profile_match(request, profile_id, from_type, pk): + """ + Links an existing Profile to one of existing + Contributor, UnregisteredAuthor, RefereeInvitation or RegistrationInvitation. + + Profile relates to Contributor as OneToOne. + Matching is thus only allowed if there are no duplicate objects for these elements. + + For matching the Profile to a Contributor, the following preconditions are defined: + - the Profile has no association to another Contributor + - the Contributor has no association to another Profile + If these are not met, no action is taken. + """ + profile = get_object_or_404(Profile, pk=profile_id) + nr_rows = 0 + if from_type == 'contributor': + if hasattr(profile, 'contributor') and profile.contributor.id != pk: + messages.error(request, + 'Error: cannot math this Profile to this Contributor, ' + 'since this Profile already has a different Contributor.\n' + 'Please merge the duplicate Contributors first.') + return redirect(reverse('profiles:profiles')) + contributor = get_object_or_404(Contributor, pk=pk) + if contributor.profile and contributor.profile.id != profile.id: + messages.error(request, + 'Error: cannot match this Profile to this Contributor, ' + 'since this Contributor already has a different Profile.\n' + 'Please merge the duplicate Profiles first.') + return redirect(reverse('profiles:profiles')) + # Preconditions are met, match: + nr_rows = Contributor.objects.filter(pk=pk).update(profile=profile) + # Give priority to the email coming from Contributor + profile.emails.update(primary=False) + email, __ = ProfileEmail.objects.get_or_create( + profile=profile, email=contributor.user.email) + profile.emails.filter(id=email.id).update(primary=True, still_valid=True) + elif from_type == 'unregisteredauthor': + nr_rows = UnregisteredAuthor.objects.filter(pk=pk).update(profile=profile) + elif from_type == 'refereeinvitation': + nr_rows = RefereeInvitation.objects.filter(pk=pk).update(profile=profile) + elif from_type == 'registrationinvitation': + nr_rows = RegistrationInvitation.objects.filter(pk=pk).update(profile=profile) + if nr_rows == 1: + messages.success(request, 'Profile matched with %s' % from_type) + else: + messages.error( + request, + 'Error: Profile matching with %s: updated %s rows instead of 1!' + 'Please contact techsupport' % (from_type, nr_rows)) + return redirect(reverse('profiles:profiles')) + class ProfileUpdateView(PermissionsMixin, UpdateView): """ @@ -98,6 +201,16 @@ class ProfileDeleteView(PermissionsMixin, DeleteView): success_url = reverse_lazy('profiles:profiles') +class ProfileDetailView(PermissionsMixin, DetailView): + permission_required = 'scipost.can_view_profiles' + model = Profile + + def get_context_data(self, *args, **kwargs): + context = super().get_context_data(*args, **kwargs) + context['email_form'] = ProfileEmailForm() + return context + + class ProfileListView(PermissionsMixin, PaginationMixin, ListView): """ List Profile object instances. @@ -125,16 +238,24 @@ class ProfileListView(PermissionsMixin, PaginationMixin, ListView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - contributors_wo_profile = Contributor.objects.filter(profile__isnull=True) + contributors_w_duplicate_email = Contributor.objects.with_duplicate_email() + contributors_w_duplicate_names = Contributor.objects.with_duplicate_names() + contributors_wo_profile = Contributor.objects.active().filter(profile__isnull=True) + nr_potential_duplicate_profiles = Profile.objects.potential_duplicates().count() + unreg_auth_wo_profile = UnregisteredAuthor.objects.filter(profile__isnull=True) refinv_wo_profile = RefereeInvitation.objects.filter(profile__isnull=True) reginv_wo_profile = RegistrationInvitation.objects.filter(profile__isnull=True) context.update({ 'subject_areas': SCIPOST_SUBJECT_AREAS, 'searchform': SearchTextForm(initial={'text': self.request.GET.get('text')}), - 'contributors_w_duplicate_email': Contributor.objects.have_duplicate_email(), + 'nr_contributors_w_duplicate_emails': contributors_w_duplicate_email.count(), + 'nr_contributors_w_duplicate_names': contributors_w_duplicate_names.count(), 'nr_contributors_wo_profile': contributors_wo_profile.count(), + 'nr_potential_duplicate_profiles': nr_potential_duplicate_profiles, 'next_contributor_wo_profile': contributors_wo_profile.first(), + 'nr_unreg_auth_wo_profile': unreg_auth_wo_profile.count(), + 'next_unreg_auth_wo_profile': unreg_auth_wo_profile.first(), 'nr_refinv_wo_profile': refinv_wo_profile.count(), 'next_refinv_wo_profile': refinv_wo_profile.first(), 'nr_reginv_wo_profile': reginv_wo_profile.count(), @@ -144,6 +265,69 @@ class ProfileListView(PermissionsMixin, PaginationMixin, ListView): return context +class ProfileDuplicateListView(PermissionsMixin, PaginationMixin, ListView): + """ + List Profiles with potential duplicates; allow to merge if necessary. + """ + permission_required = 'scipost.can_create_profiles' + model = Profile + template_name = 'profiles/profile_duplicate_list.html' + paginate_by = 16 + + def get_queryset(self): + return Profile.objects.potential_duplicates() + + def get_context_data(self, *args, **kwargs): + context = super().get_context_data(*args, **kwargs) + + if len(context['object_list']) > 1: + initial = { + 'to_merge': context['object_list'][0].id, + 'to_merge_into': context['object_list'][1].id + } + context['merge_form'] = ProfileMergeForm(initial=initial) + return context + + +@transaction.atomic +@permission_required('scipost.can_create_profiles') +def profile_merge(request): + """ + Merges one Profile into another. + """ + merge_form = ProfileMergeForm(request.POST or None, initial=request.GET) + context = {'merge_form': merge_form} + + if request.method == 'POST': + if merge_form.is_valid(): + profile = merge_form.save() + messages.success(request, 'Profiles merged') + return redirect(profile.get_absolute_url()) + else: + try: + context.update({ + 'profile_to_merge': get_object_or_404( + Profile, pk=merge_form.cleaned_data['to_merge'].id), + 'profile_to_merge_into': get_object_or_404( + Profile, pk=merge_form.cleaned_data['to_merge_into'].id) + }) + except ValueError: + raise Http404 + + elif request.method == 'GET': + try: + context.update({ + 'profile_to_merge': get_object_or_404(Profile, + pk=int(request.GET['to_merge'])), + 'profile_to_merge_into': get_object_or_404(Profile, + pk=int(request.GET['to_merge_into'])) + }) + except ValueError: + raise Http404 + + return render(request, 'profiles/profile_merge.html', context) + + @permission_required('scipost.can_create_profiles') def add_profile_email(request, profile_id): """ @@ -157,7 +341,22 @@ def add_profile_email(request, profile_id): else: for field, err in form.errors.items(): messages.warning(request, err[0]) - return redirect(reverse('profiles:profiles')) + if request.POST.get('next', None): + return HttpResponseRedirect(request.POST.get('next')) + return redirect(profile.get_absolute_url()) + + +@require_POST +@permission_required('scipost.can_create_profiles') +def email_make_primary(request, email_id): + """ + Make this email the primary one for this Profile. + """ + profile_email = get_object_or_404(ProfileEmail, pk=email_id) + ProfileEmail.objects.filter(profile=profile_email.profile).update(primary=False) + profile_email.primary = True + profile_email.save() + return redirect(profile_email.profile.get_absolute_url()) @require_POST @@ -167,7 +366,7 @@ def toggle_email_status(request, email_id): profile_email = get_object_or_404(ProfileEmail, pk=email_id) ProfileEmail.objects.filter(id=email_id).update(still_valid=not profile_email.still_valid) messages.success(request, 'Email updated') - return redirect('profiles:profiles') + return redirect(profile_email.profile.get_absolute_url()) @require_POST @@ -177,4 +376,4 @@ def delete_profile_email(request, email_id): profile_email = get_object_or_404(ProfileEmail, pk=email_id) profile_email.delete() messages.success(request, 'Email deleted') - return redirect('profiles:profiles') + return redirect(profile_email.profile.get_absolute_url()) diff --git a/requirements.txt b/requirements.txt index 2f75da286cca0ebbac9c2976edd5c2f757da950e..d96ced8b7e1612849193b4f8f85fabf60d5c11e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,7 +20,6 @@ django-mathjax==0.0.8 django-mptt==0.8.6 # Dead django-sphinxdoc==1.5.1 django-silk==2.0.0 -django-recaptcha==1.3.1 django-webpack-loader==0.5 django-maintenancemode-2==1.1.11 diff --git a/scipost/admin.py b/scipost/admin.py index 097b6c7f38f195d2998fb863965c5f2e6d085719..5eeb7a855d3aee19a0c8f6b606f1eb37610d15ae 100644 --- a/scipost/admin.py +++ b/scipost/admin.py @@ -41,8 +41,14 @@ class UserAdmin(UserAdmin): ContactToUserInline, ProductionUserInline ] + list_display = ['username', 'email', 'first_name', 'last_name', + 'is_active', 'is_staff', 'is_duplicate'] search_fields = ['last_name', 'email'] + def is_duplicate(self, obj): + return obj.contributor.is_duplicate + is_duplicate.short_description = 'Is duplicate?' + is_duplicate.boolean = True admin.site.unregister(User) admin.site.register(Contributor, ContributorAdmin) diff --git a/scipost/fields.py b/scipost/fields.py index dc3a5137a2e12b72e2b2de2a77ab8a3bc529b0e4..eea67bdc958c6b0f287ab16403f6cfa16eb46f3c 100644 --- a/scipost/fields.py +++ b/scipost/fields.py @@ -1,9 +1,16 @@ __copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" __license__ = "AGPL v3" +import json +import requests from django import forms +from django.core.exceptions import ValidationError +from django.conf import settings from django.contrib.postgres.fields import ArrayField +from django.utils.encoding import force_text + +from .widgets import ReCaptcha class ChoiceArrayField(ArrayField): @@ -21,3 +28,62 @@ class ChoiceArrayField(ArrayField): } defaults.update(kwargs) return super(ArrayField, self).formfield(**defaults) + + +class ReCaptchaField(forms.CharField): + default_error_messages = { + 'captcha_invalid': 'Incorrect, please try again.', + 'captcha_error': 'Error verifying input, please try again.', + } + + def __init__(self, use_ssl=None, attrs=None, *args, **kwargs): + """ + ReCaptchaField can accepts attributes which is a dictionary of + attributes to be passed to the ReCaptcha widget class. The widget will + loop over any options added and create the RecaptchaOptions + JavaScript variables as specified in + https://developers.google.com/recaptcha/docs/display#render_param + """ + if attrs is None: + attrs = {} + + public_key = settings.RECAPTCHA_PUBLIC_KEY + self.use_ssl = getattr(settings, 'RECAPTCHA_USE_SSL', True) + self.widget = ReCaptcha(public_key=public_key, attrs=attrs) + self.required = True + self.verify_url = 'https://www.recaptcha.net/recaptcha/api/siteverify' + super().__init__(*args, **kwargs) + + def clean(self, values): + super().clean(values[0]) + recaptcha_response = force_text(values[0]) + + if not self.required: + return + + data = { + 'secret': settings.RECAPTCHA_PRIVATE_KEY, + 'response': recaptcha_response + } + + r = requests.post( + self.verify_url, + data=data, + headers={ + 'Content-type': 'application/x-www-form-urlencoded', + 'User-agent': 'reCAPTCHA Python' + }) + try: + r.raise_for_status() + response = r.json() + catpcha_success = response.get('success', False) + except (requests.exceptions.HTTPError, requests.exceptions.Timeout): + raise ValidationError( + self.error_messages['captcha_error'] + ) + + if not catpcha_success: + raise ValidationError( + self.error_messages['captcha_invalid'] + ) + return values[0] diff --git a/scipost/forms.py b/scipost/forms.py index 8dfebf75ad30411b7f0e9d3e8e212695910483eb..f2c7ed99a716539fdf156a0b47dd8a953f4b26a7 100644 --- a/scipost/forms.py +++ b/scipost/forms.py @@ -8,6 +8,7 @@ from django import forms from django.contrib.auth import authenticate from django.contrib.auth.models import User, Group from django.contrib.auth.password_validation import validate_password +from django.contrib.auth.validators import UnicodeUsernameValidator from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse_lazy from django.db.models import Q @@ -18,7 +19,6 @@ from django.utils.http import is_safe_url from django_countries import countries from django_countries.widgets import CountrySelectWidget from django_countries.fields import LazyTypedChoiceField -from captcha.fields import ReCaptchaField from ajax_select.fields import AutoCompleteSelectField from haystack.forms import ModelSearchForm as HayStackSearchForm @@ -28,15 +28,25 @@ from .constants import ( SCIPOST_DISCIPLINES, TITLE_CHOICES, SCIPOST_FROM_ADDRESSES, NO_SCIENTIST, DOUBLE_ACCOUNT, BARRED) from .decorators import has_contributor -from .models import Contributor, DraftInvitation, UnavailabilityPeriod, PrecookedEmail +from .fields import ReCaptchaField +from .models import Contributor, DraftInvitation, UnavailabilityPeriod, \ + Remark, AuthorshipClaim, PrecookedEmail from affiliations.models import Affiliation, Institution -from common.forms import MonthYearWidget +from common.forms import MonthYearWidget, ModelChoiceFieldwithid from partners.decorators import has_contact +from colleges.models import Fellowship, PotentialFellowshipEvent +from commentaries.models import Commentary from comments.models import Comment -from journals.models import Publication -from submissions.models import Report +from funders.models import Grant +from invitations.models import CitationNotification +from journals.models import PublicationAuthorsTable, Publication +from mails.utils import DirectMailUtil +from submissions.models import Submission, EditorialAssignment, RefereeInvitation, Report, \ + EditorialCommunication, EICRecommendation +from theses.models import ThesisLink +from virtualmeetings.models import Feedback, Nomination, Motion REGISTRATION_REFUSAL_CHOICES = ( @@ -98,11 +108,12 @@ class RegistrationForm(forms.Form): personalwebpage = forms.URLField( label='Personal web page', required=False, widget=forms.TextInput({'placeholder': 'full URL, e.g. http://www.[yourpage].com'})) - username = forms.CharField(label='* Username', max_length=100) + username = forms.CharField(label='* Username', max_length=100, + validators=[UnicodeUsernameValidator,]) password = forms.CharField(label='* Password', widget=forms.PasswordInput()) password_verif = forms.CharField(label='* Verify password', widget=forms.PasswordInput(), help_text='Your password must contain at least 8 characters') - captcha = ReCaptchaField(attrs={'theme': 'clean'}, label='*Please verify to continue:') + captcha = ReCaptchaField(label='*Please verify to continue:') subscribe = forms.BooleanField( required=False, initial=False, label='Stay informed, subscribe to the SciPost newsletter.') @@ -413,6 +424,203 @@ class UnavailabilityPeriodForm(forms.ModelForm): return end +class ContributorMergeForm(forms.Form): + to_merge = ModelChoiceFieldwithid(queryset=Contributor.objects.all(), empty_label=None) + to_merge_into = ModelChoiceFieldwithid(queryset=Contributor.objects.all(), empty_label=None) + + def clean(self): + data = super().clean() + if self.cleaned_data['to_merge'] == self.cleaned_data['to_merge_into']: + self.add_error(None, 'A Contributor cannot be merged into itself.') + return data + + def save(self): + """ + Merge one Contributor into another. Set the previous Contributor to inactive. + """ + contrib_from = self.cleaned_data['to_merge'] + contrib_into = self.cleaned_data['to_merge_into'] + + both_contribs_active = contrib_from.is_active and contrib_into.is_active + + contrib_from_qs = Contributor.objects.filter(pk=contrib_from.id) + contrib_into_qs = Contributor.objects.filter(pk=contrib_into.id) + + # Step 1: update all fields within Contributor + if contrib_from.profile and not contrib_into.profile: + profile = contrib_from.profile + contrib_from_qs.update(profile=None) + contrib_into_qs.update(profile=profile) + User.objects.filter(pk=contrib_from.user.id).update(is_active=False) + User.objects.filter(pk=contrib_into.user.id).update(is_active=True) + if contrib_from.invitation_key and not contrib_into.invitation_key: + contrib_into_qs.update(invitation_key=contrib_into.invitation_key) + if contrib_from.activation_key and not contrib_into.activation_key: + contrib_into_qs.update(activation_key=contrib_into.activation_key) + contrib_from_qs.update(status=DOUBLE_ACCOUNT) + if contrib_from.orcid_id and not contrib_into.orcid_id: + contrib_into_qs.update(orcid_id=contrib_from.orcid_id) + if contrib_from.personalwebpage and not contrib_into.personalwebpage: + contrib_into_qs.update(personalwebpage=contrib_from.personalwebpage) + + # Specify duplicate_of for deactivated Contributor + contrib_from_qs.update(duplicate_of=contrib_into) + + # Step 2: update all ForeignKey relations + Affiliation.objects.filter(contributor=contrib_from).update(contributor=contrib_into) + Fellowship.objects.filter(contributor=contrib_from).update(contributor=contrib_into) + PotentialFellowshipEvent.objects.filter( + noted_by=contrib_from).update(noted_by=contrib_into) + Commentary.objects.filter(requested_by=contrib_from).update(requested_by=contrib_into) + Commentary.objects.filter(vetted_by=contrib_from).update(vetted_by=contrib_into) + Comment.objects.filter(vetted_by=contrib_from).update(vetted_by=contrib_into) + Comment.objects.filter(author=contrib_from).update(author=contrib_into) + Grant.objects.filter(recipient=contrib_from).update(recipient=contrib_into) + CitationNotification.objects.filter( + contributor=contrib_from).update(contributor=contrib_into) + PublicationAuthorsTable.objects.filter( + contributor=contrib_from).update(contributor=contrib_into) + UnavailabilityPeriod.objects.filter( + contributor=contrib_from).update(contributor=contrib_into) + Remark.objects.filter( + contributor=contrib_from).update(contributor=contrib_into) + DraftInvitation.objects.filter( + drafted_by=contrib_from).update(drafted_by=contrib_into) + AuthorshipClaim.objects.filter( + claimant=contrib_from).update(claimant=contrib_into) + AuthorshipClaim.objects.filter( + vetted_by=contrib_from).update(vetted_by=contrib_into) + Submission.objects.filter( + editor_in_charge=contrib_from).update(editor_in_charge=contrib_into) + Submission.objects.filter( + submitted_by=contrib_from).update(submitted_by=contrib_into) + EditorialAssignment.objects.filter(to=contrib_from).update(to=contrib_into) + RefereeInvitation.objects.filter(referee=contrib_from).update(referee=contrib_into) + RefereeInvitation.objects.filter(invited_by=contrib_from).update(invited_by=contrib_into) + Report.objects.filter(vetted_by=contrib_from).update(vetted_by=contrib_into) + Report.objects.filter(author=contrib_from).update(author=contrib_into) + EditorialCommunication.objects.filter( + referee=contrib_from).update(referee=contrib_into) + ThesisLink.objects.filter(requested_by=contrib_from).update(requested_by=contrib_into) + ThesisLink.objects.filter(vetted_by=contrib_from).update(vetted_by=contrib_into) + Feedback.objects.filter(by=contrib_from).update(by=contrib_into) + Nomination.objects.filter(by=contrib_from).update(by=contrib_into) + Motion.objects.filter(put_forward_by=contrib_from).update(put_forward_by=contrib_into) + + # Step 3: update all ManyToMany + commentaries = Commentary.objects.filter(authors__in=[contrib_from,]).all() + for commentary in commentaries: + commentary.authors.remove(contrib_from) + commentary.authors.add(contrib_into) + commentaries = Commentary.objects.filter(authors_claims__in=[contrib_from,]).all() + for commentary in commentaries: + commentary.authors_claims.remove(contrib_from) + commentary.authors_claims.add(contrib_into) + commentaries = Commentary.objects.filter(authors_false_claims__in=[contrib_from,]).all() + for commentary in commentaries: + commentary.authors_false_claims.remove(contrib_from) + commentary.authors_false_claims.add(contrib_into) + comments = Comment.objects.filter(in_agreement__in=[contrib_from,]).all() + for comment in comments: + comment.in_agreement.remove(contrib_from) + comment.in_agreement.add(contrib_into) + comments = Comment.objects.filter(in_notsure__in=[contrib_from,]).all() + for comment in comments: + comment.in_notsure.remove(contrib_from) + comment.in_notsure.add(contrib_into) + comments = Comment.objects.filter(in_disagreement__in=[contrib_from,]).all() + for comment in comments: + comment.in_disagreement.remove(contrib_from) + comment.in_disagreement.add(contrib_into) + publications = Publication.objects.filter(authors_registered__in=[contrib_from,]).all() + for publication in publications: + publication.authors_registered.remove(contrib_from) + publication.authors_registered.add(contrib_into) + publications = Publication.objects.filter(authors_claims__in=[contrib_from,]).all() + for publication in publications: + publication.authors_claims.remove(contrib_from) + publication.authors_claims.add(contrib_into) + publications = Publication.objects.filter(authors_false_claims__in=[contrib_from,]).all() + for publication in publications: + publication.authors_false_claims.remove(contrib_from) + publication.authors_false_claims.add(contrib_into) + submissions = Submission.objects.filter(authors__in=[contrib_from,]).all() + for submission in submissions: + submission.authors.remove(contrib_from) + submission.authors.add(contrib_into) + submissions = Submission.objects.filter(authors_claims__in=[contrib_from,]).all() + for submission in submissions: + submission.authors_claims.remove(contrib_from) + submission.authors_claims.add(contrib_into) + submissions = Submission.objects.filter(authors_false_claims__in=[contrib_from,]).all() + for submission in submissions: + submission.authors_false_claims.remove(contrib_from) + submission.authors_false_claims.add(contrib_into) + eicrecs = EICRecommendation.objects.filter(eligible_to_vote__in=[contrib_from,]).all() + for eicrec in eicrecs: + eicrec.eligible_to_vote.remove(contrib_from) + eicrec.eligible_to_vote.add(contrib_into) + eicrecs = EICRecommendation.objects.filter(voted_for__in=[contrib_from,]).all() + for eicrec in eicrecs: + eicrec.voted_for.remove(contrib_from) + eicrec.voted_for.add(contrib_into) + eicrecs = EICRecommendation.objects.filter(voted_against__in=[contrib_from,]).all() + for eicrec in eicrecs: + eicrec.voted_against.remove(contrib_from) + eicrec.voted_against.add(contrib_into) + eicrecs = EICRecommendation.objects.filter(voted_abstain__in=[contrib_from,]).all() + for eicrec in eicrecs: + eicrec.voted_abstain.remove(contrib_from) + eicrec.voted_abstain.add(contrib_into) + thesislinks = ThesisLink.objects.filter(author_as_cont__in=[contrib_from,]).all() + for tl in thesislinks: + tl.author_as_cont.remove(contrib_from) + tl.author_as_cont.add(contrib_into) + thesislinks = ThesisLink.objects.filter(author_claims__in=[contrib_from,]).all() + for tl in thesislinks: + tl.author_claims.remove(contrib_from) + tl.author_claims.add(contrib_into) + thesislinks = ThesisLink.objects.filter(author_false_claims__in=[contrib_from,]).all() + for tl in thesislinks: + tl.author_false_claims.remove(contrib_from) + tl.author_false_claims.add(contrib_into) + thesislinks = ThesisLink.objects.filter(supervisor_as_cont__in=[contrib_from,]).all() + for tl in thesislinks: + tl.supervisor_as_cont.remove(contrib_from) + tl.supervisor_as_cont.add(contrib_into) + nominations = Nomination.objects.filter(in_agreement__in=[contrib_from,]).all() + for nom in nominations: + nom.in_agreement.remove(contrib_from) + nom.in_agreement.add(contrib_into) + nominations = Nomination.objects.filter(in_notsure__in=[contrib_from,]).all() + for nom in nominations: + nom.in_notsure.remove(contrib_from) + nom.in_notsure.add(contrib_into) + nominations = Nomination.objects.filter(in_disagreement__in=[contrib_from,]).all() + for nom in nominations: + nom.in_disagreement.remove(contrib_from) + nom.in_disagreement.add(contrib_into) + motions = Motion.objects.filter(in_agreement__in=[contrib_from,]).all() + for nom in motions: + nom.in_agreement.remove(contrib_from) + nom.in_agreement.add(contrib_into) + motions = Motion.objects.filter(in_notsure__in=[contrib_from,]).all() + for nom in motions: + nom.in_notsure.remove(contrib_from) + nom.in_notsure.add(contrib_into) + motions = Motion.objects.filter(in_disagreement__in=[contrib_from,]).all() + for nom in motions: + nom.in_disagreement.remove(contrib_from) + nom.in_disagreement.add(contrib_into) + # If both accounts were active, inform the Contributor of the merge + if both_contribs_active: + mail_sender = DirectMailUtil( + mail_code='contributors/inform_contributor_duplicate_accounts_merged', + contrib_from=Contributor.objects.get(id=contrib_from.id)) + mail_sender.send() + return Contributor.objects.get(id=contrib_into.id) + + class RemarkForm(forms.Form): remark = forms.CharField(widget=forms.Textarea(), label='') @@ -431,6 +639,13 @@ def get_date_filter_choices(): return months, years +class SearchTextForm(forms.Form): + """ + Simple text-based search form. + """ + text = forms.CharField(label='') + + class SearchForm(HayStackSearchForm): # The date filters don't function well... start = forms.DateField(widget=MonthYearWidget(), required=False) # Month diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py index f701e296f42347dbd93874192f404088b6531354..c8f3fdf54204861c1df382f308db7252c1432fb7 100644 --- a/scipost/management/commands/add_groups_and_permissions.py +++ b/scipost/management/commands/add_groups_and_permissions.py @@ -318,6 +318,13 @@ class Command(BaseCommand): name='Can manage Mailchimp settings', content_type=content_type) + # Ontology + can_manage_ontology, created = Permission.objects.get_or_create( + codename='can_manage_ontology', + name='Can manage ontology', + content_type=content_type) + + # Assign permissions to groups SciPostAdmin.permissions.set([ can_read_all_privacy_sensitive_data, @@ -347,6 +354,7 @@ class Command(BaseCommand): can_view_statistics, can_create_profiles, can_view_profiles, + can_manage_ontology, ]) FinancialAdmin.permissions.set([ @@ -388,13 +396,17 @@ class Command(BaseCommand): can_view_statistics, can_create_profiles, can_view_profiles, + can_manage_ontology, ]) EditorialCollege.permissions.set([ can_view_pool, can_take_charge_of_submissions, + can_create_profiles, + can_view_profiles, can_attend_VGMs, can_view_statistics, + can_manage_ontology, ]) VettingEditors.permissions.set([ diff --git a/scipost/management/commands/check_celery.py b/scipost/management/commands/check_celery.py new file mode 100644 index 0000000000000000000000000000000000000000..4d5aa0dc702f437948fc5d4006fab2d9c005271f --- /dev/null +++ b/scipost/management/commands/check_celery.py @@ -0,0 +1,40 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +import datetime + +from django.core import mail +from django.core.management.base import BaseCommand +from django.utils import timezone + +from django_celery_results.models import TaskResult + + +class Command(BaseCommand): + help = 'Check if Celery is still running, or at least not failing.' + + def handle(self, *args, **kwargs): + # check failed. + compare_dt = timezone.now() - datetime.timedelta(hours=1) + results_failed = TaskResult.objects.filter( + status='FAILURE', date_done__gt=compare_dt).order_by('date_done').last() + if results_failed: + # Mail failed + body = 'Celery has failed task results. Last failed ID: {}'.format( + results_failed.id) + mail.mail_admins('Celery failed', body) + self.stdout.write( + self.style.SUCCESS('Celery failed, last ID: {}.'.format(results_failed.id))) + else: + last_result = TaskResult.objects.filter( + date_done__gt=compare_dt).order_by('date_done').last() + if last_result and last_result.date_done < compare_dt: + # Mail inactive + body = 'No results for Celery found. Celery seems to be inactive.' + body += ' Last result ID: {}'.format(last_result.id) + mail.mail_admins('Celery inactive', body) + self.stdout.write( + self.style.SUCCESS('Celery inactive, last ID: {}.'.format(last_result.id))) + if not results_failed and not last_result: + self.stdout.write(self.style.SUCCESS('Celery alive!')) diff --git a/scipost/managers.py b/scipost/managers.py index 8d698f4011d692b77452d0de652db8e234fe704f..abf9abfe38b39e1f658563fd9d21abca2c243613 100644 --- a/scipost/managers.py +++ b/scipost/managers.py @@ -4,14 +4,17 @@ __license__ = "AGPL v3" from django.db import models from django.db.models import Count, Q -from django.db.models.functions import Lower +from django.db.models.functions import Concat, Lower from django.utils import timezone -from .constants import NORMAL_CONTRIBUTOR, NEWLY_REGISTERED, AUTHORSHIP_CLAIM_PENDING +from .constants import NORMAL_CONTRIBUTOR, NEWLY_REGISTERED, DOUBLE_ACCOUNT, AUTHORSHIP_CLAIM_PENDING class FellowManager(models.Manager): + """Custom defined filters for the Fellow model.""" + def active(self): + """Filter Fellows active within its set date ranges.""" today = timezone.now().date() return self.filter( Q(start_date__lte=today, until_date__isnull=True) | @@ -28,13 +31,6 @@ class ContributorQuerySet(models.QuerySet): """Return all validated and vetted Contributors.""" return self.filter(user__is_active=True, status=NORMAL_CONTRIBUTOR) - def have_duplicate_email(self): - """ Return Contributors having duplicate emails. """ - duplicates = self.values(lower_email=Lower('user__email')).annotate( - Count('id')).order_by('user__last_name').filter(id__count__gt=1) - return self.annotate(lower_email=Lower('user__email') - ).filter(lower_email__in=[dup['lower_email'] for dup in duplicates]) - def available(self): """Filter out the Contributors that have active unavailability periods.""" today = timezone.now().date() @@ -48,12 +44,38 @@ class ContributorQuerySet(models.QuerySet): def awaiting_vetting(self): """Filter Contributors that have not been vetted through.""" - return self.filter(user__is_active=True, status=NEWLY_REGISTERED) + return self.filter(user__is_active=True, status=NEWLY_REGISTERED + ).exclude(status=DOUBLE_ACCOUNT) def fellows(self): """TODO: NEEDS UPDATE TO NEW FELLOWSHIP RELATIONS.""" return self.filter(fellowships__isnull=False).distinct() + def with_duplicate_names(self): + """ + Returns only potential duplicate Contributors (as identified by first and + last names). + Admins and superusers are explicitly excluded. + """ + contribs = self.exclude(status=DOUBLE_ACCOUNT + ).exclude(user__is_superuser=True).exclude(user__is_staff=True + ).annotate(full_name=Concat('user__last_name', 'user__first_name')) + duplicates = contribs.values('full_name').annotate( + nr_count=Count('full_name')).filter(nr_count__gt=1).values_list('full_name', flat=True) + return contribs.filter( + full_name__in=duplicates).order_by('user__last_name', 'user__first_name', '-id') + + def with_duplicate_email(self): + """ + Return Contributors having duplicate emails. + """ + qs = self.exclude(status=DOUBLE_ACCOUNT + ).exclude(user__is_superuser=True).exclude( + user__is_staff=True).annotate(lower_email=Lower('user__email')) + duplicates = qs.values('lower_email').annotate( + Count('id')).filter(id__count__gt=1).values_list('lower_email', flat=True) + return qs.filter(user__email__in=duplicates) + class UnavailabilityPeriodManager(models.Manager): def today(self): diff --git a/scipost/migrations/0017_auto_20181115_2150.py b/scipost/migrations/0017_auto_20181115_2150.py new file mode 100644 index 0000000000000000000000000000000000000000..0cb61cbe8813cb8ea31cf7c58be61e428b3174f5 --- /dev/null +++ b/scipost/migrations/0017_auto_20181115_2150.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-15 20:50 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('scipost', '0016_auto_20180930_1801'), + ] + + operations = [ + migrations.AlterModelOptions( + name='contributor', + options={'ordering': ['user__last_name', 'user__first_name']}, + ), + ] diff --git a/scipost/migrations/0018_contributor_duplicate_of.py b/scipost/migrations/0018_contributor_duplicate_of.py new file mode 100644 index 0000000000000000000000000000000000000000..cbaa585ff9b61428aa0615c77c2549e726e4f1cf --- /dev/null +++ b/scipost/migrations/0018_contributor_duplicate_of.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-17 17:01 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('scipost', '0017_auto_20181115_2150'), + ] + + operations = [ + migrations.AddField( + model_name='contributor', + name='duplicate_of', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='duplicates', to='scipost.Contributor'), + ), + ] diff --git a/scipost/models.py b/scipost/models.py index a4bec62c981e70b44de3b2c03002cb395f2345e1..a64cb490d47f4b98acf1b8f69a13bb6177259765 100644 --- a/scipost/models.py +++ b/scipost/models.py @@ -16,7 +16,7 @@ from django.utils import timezone from .behaviors import TimeStampedModel, orcid_validator from .constants import ( - SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS, subject_areas_dict, DISABLED, + SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS, subject_areas_dict, NORMAL_CONTRIBUTOR, DISABLED, TITLE_CHOICES, INVITATION_STYLE, INVITATION_TYPE, INVITATION_CONTRIBUTOR, INVITATION_FORMAL, AUTHORSHIP_CLAIM_PENDING, AUTHORSHIP_CLAIM_STATUS, CONTRIBUTOR_STATUSES, NEWLY_REGISTERED) from .fields import ChoiceArrayField @@ -63,9 +63,15 @@ class Contributor(models.Model): related_name="contrib_vetted_by", blank=True, null=True) accepts_SciPost_emails = models.BooleanField( default=True, verbose_name="I accept to receive SciPost emails") + # If this Contributor is merged into another, then this field is set to point to the new one: + duplicate_of = models.ForeignKey('scipost.Contributor', on_delete=models.SET_NULL, + null=True, blank=True, related_name='duplicates') objects = ContributorQuerySet.as_manager() + class Meta: + ordering = ['user__last_name', 'user__first_name'] + def __str__(self): return '%s, %s' % (self.user.last_name, self.user.first_name) @@ -79,6 +85,21 @@ class Contributor(models.Model): """Return public information page url.""" return reverse('scipost:contributor_info', args=(self.id,)) + @property + def formal_str(self): + return '%s %s' % (self.get_title_display(), self.user.last_name) + + def is_active(self): + """ + Checks if the Contributor is registered, vetted, + and has not been deactivated for any reason. + """ + return self.user.is_active and self.status == NORMAL_CONTRIBUTOR + + @property + def is_duplicate(self): + return self.duplicate_of is not None + @property def is_currently_available(self): """Check if Contributor is currently not marked as unavailable.""" @@ -142,6 +163,8 @@ class UnavailabilityPeriod(models.Model): class Remark(models.Model): + """A form of non-public communication for VGMs and/or submissions and recommendations.""" + contributor = models.ForeignKey(Contributor, on_delete=models.CASCADE) feedback = models.ForeignKey('virtualmeetings.Feedback', on_delete=models.CASCADE, blank=True, null=True) @@ -172,9 +195,8 @@ class Remark(models.Model): ############### class DraftInvitation(models.Model): - """ - Draft of an invitation, filled in by an officer. - """ + """Draft of an invitation, filled in by an officer.""" + title = models.CharField(max_length=4, choices=TITLE_CHOICES) first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) @@ -197,9 +219,8 @@ class DraftInvitation(models.Model): class RegistrationInvitation(models.Model): - """ - Deprecated: Use the `invitations` app - """ + """Deprecated: Use the `invitations` app""" + title = models.CharField(max_length=4, choices=TITLE_CHOICES) first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) @@ -232,9 +253,8 @@ class RegistrationInvitation(models.Model): class CitationNotification(models.Model): - """ - Deprecated: Use the `invitations` app - """ + """Deprecated: Use the `invitations` app""" + contributor = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE) cited_in_submission = models.ForeignKey('submissions.Submission', on_delete=models.CASCADE, diff --git a/scipost/signals.py b/scipost/signals.py index 92bc9d1429f3309e49b564564cb3064bf40a76e3..d169108e790dd247a7ac450e4b7eaec1a2577229 100644 --- a/scipost/signals.py +++ b/scipost/signals.py @@ -15,12 +15,15 @@ from .models import Contributor def link_created_profile_to_contributor(sender, instance, created, **kwargs): """ When a new Profile is created, it is linked to a corresponding - existing Contributor object, provided it is unique (as defined by the email). + existing Contributor object, provided it is unique (as defined by the name and email). If it is not unique, no action is taken. """ if created: try: - contributor = Contributor.objects.get(user__email=instance.email) + contributor = Contributor.objects.get( + user__first_name=instance.first_name, + user__last_name=instance.last_name, + user__email=instance.email) contributor.profile = instance contributor.save() except Contributor.DoesNotExist: diff --git a/scipost/static/scipost/assets/config/preconfig.scss b/scipost/static/scipost/assets/config/preconfig.scss index d4cf18740f3c0ab5dbfd15163f4e6b59676ba2db..f088db22de2bb3dab0775cb03e44aa960cf0568f 100644 --- a/scipost/static/scipost/assets/config/preconfig.scss +++ b/scipost/static/scipost/assets/config/preconfig.scss @@ -48,10 +48,11 @@ $text-muted: #636c72; // Body // -$body-bg: $gray-100; +$body-bg: #f3f3f3; // $gray-100 $body-color: $scipost-darkblue; + // Alerts // $alert-border-radius: $base-border-radius; @@ -62,17 +63,24 @@ $card-border-radius: $base-border-radius; $card-spacer-x: 0.75rem; $card-spacer-y: 0.5rem; $card-shadow-color: #ccc; -$card-grey-border-bottom-color: #d0d1d5; -$card-grey-bg: $gray-100; -$card-grey-border-color: $scipost-lightblue $gray-100 $gray-100; +$card-gray-border-bottom-color: #d0d1d5; +$card-gray-bg: $gray-100; +$card-gray-border-color: $scipost-lightblue $gray-100 $gray-100; // breadcrumb // -$breadcrumb-bg: $gray-100; +$breadcrumb-bg: #f3f3f3; $breadcrumb-active-color: $scipost-darkblue; $breadcrumb-divider-color: $scipost-orange; +// Dropdown +// +$dropdown-border-radius: $base-border-radius; +$dropdown-border-color: $scipost-darkblue; +$dropdown-item-padding-y: 0.35rem; +$dropdown-item-padding-x: 1.0rem; + // Forms // $input-btn-padding-y-sm: 0.1rem; @@ -104,6 +112,7 @@ $list-group-active-border-color: $scipost-lightestblue; // $font-family-sans-serif: 'Merriweather Sans', "Helvetica Neue", Arial, sans-serif; $headings-font-family: $font-family-sans-serif; +// $headings-line-height: $line-height-lg; $font-family-serif: $font-family-sans-serif; $font-family-monospace: $font-family-sans-serif; $font-family-base: $font-family-sans-serif; @@ -112,12 +121,12 @@ $font-size-base: 0.8rem; $font-size-sm: 0.7rem; $font-size-lg: 1.0rem; -$h1-font-size: 1.8em; -$h2-font-size: 1.5em; -$h3-font-size: 1.3em; -$h4-font-size: 1.2em; -$h5-font-size: 1.0em; -$h6-font-size: 1.0em; +$h1-font-size: 1.5rem; +$h2-font-size: 1.25rem; +$h3-font-size: 1.05rem; +$h4-font-size: 1.0rem; +$h5-font-size: 0.8rem; +$h6-font-size: 0.8rem; $close-font-weight: 100; @@ -135,7 +144,7 @@ $tooltip-font-size: $font-size-sm; $navbar-light-color: $scipost-darkblue; $navbar-light-hover-color: $scipost-darkblue; $navbar-padding-x: 0.3rem; -$navbar-padding-y: 0.35rem; +$navbar-padding-y: 0.2rem; $nav-tabs-border-color: $scipost-darkblue; $nav-tabs-link-active-border-color: $scipost-darkblue $scipost-darkblue $nav-tabs-link-active-bg; diff --git a/scipost/static/scipost/assets/css/_breadcrumb.scss b/scipost/static/scipost/assets/css/_breadcrumb.scss index 5cc416301fa869a9cc88185de1c647211e8e5360..630bdf93ad36766fa2e3ec866e4295ee5f88d33c 100644 --- a/scipost/static/scipost/assets/css/_breadcrumb.scss +++ b/scipost/static/scipost/assets/css/_breadcrumb.scss @@ -1,22 +1,23 @@ -.container-outside.breadcrumb-nav { - background-color: $gray-200; - - .breadcrumb { - background-color: transparent; - } +.breadcrumb-container { + border-bottom: 1px solid #e0e0e0; } .breadcrumb { margin-bottom: 0; white-space: nowrap; overflow: auto; - padding: 0.5rem 1.0rem; + padding: 0.5rem; display: flex; flex-direction: row; + background-color: transparent; .breadcrumb-item { margin-bottom: 0; } + + .breadcrumb-item:first-child::before { + content: none; + } } nav.as-submenu .breadcrumb-item + .breadcrumb-item::before { diff --git a/scipost/static/scipost/assets/css/_cards.scss b/scipost/static/scipost/assets/css/_cards.scss index 2a927df994d200cac69f44f5dcfd6df64767e8a1..831d8f277e21a5bbfe43ec8150c4cf58ff0fa6e2 100644 --- a/scipost/static/scipost/assets/css/_cards.scss +++ b/scipost/static/scipost/assets/css/_cards.scss @@ -1,41 +1,4 @@ -.card { - margin-bottom: 10px; - &.card-grey { - background-color: $card-grey-bg; - border: 0; - border-top: 2px solid $scipost-lightblue; - box-shadow: 0 1px 0 0 $card-shadow-color; - - .card-header { - background: transparent; - border-bottom-color: #fff; - } - - .card-footer { - background: transparent; - } - } - - &.card-publication { - box-shadow: 0 1px 0 0 #ddd; - border: 1px solid #ddd; - - .card-header { - background-color: #eee; - border-bottom: 1px solid #ddd; - } - - .card-body { - border-top: 1px solid #fff; - background-color: #f8f8f8; - } - } -} - -.card-outline-secondary { - border-color: #f1f1f1; -} .list-group-item > .card-body { padding: 0.5rem; } @@ -49,46 +12,36 @@ line-height: 1.5; } -.card-news { - .news-item .card-title { - color: $scipost-darkblue; - padding: 0.5rem 0 0.25rem 0; - border-bottom: 3px solid $scipost-light; - display: inline-block; - margin-bottom: 0.5rem; +.scipost-bar { + border-top: 3px solid $scipost-lightblue !important; +} + +.card.radio-option { + label { + cursor: pointer; } - .news-item .date { - margin-bottom: 1.5rem; + &.checked, + &:hover { + border-color: $scipost-darkblue; } } + .card-publication { - width: 100%; + margin-bottom: 0.5rem; - &.card-grey { - background-color: #f8f8f8; + .card-header { + background-color: rgba(0, 0, 0, 0.05); } .card-body { padding-top: 0.5rem; padding-bottom: 0.5rem; - - .card-title { - background-color: #f4f4f4; - margin: -0.5rem -0.75rem 0.5rem -0.75rem; - padding: .75rem 0.75rem; - } + background-color: #f9f9f9; } } -.card.radio-option { - label { - cursor: pointer; - } - - &.checked, - &:hover { - border-color: $scipost-darkblue; - } +.news-item { + border: 1px solid #e6e6e6; } diff --git a/scipost/static/scipost/assets/css/_general.scss b/scipost/static/scipost/assets/css/_general.scss index dcde2bb4522ce2fe09773a0a788fc29c091d7c9e..9acf59de060a28ed5c12b46b7d4077d06af614ee 100644 --- a/scipost/static/scipost/assets/css/_general.scss +++ b/scipost/static/scipost/assets/css/_general.scss @@ -1,3 +1,6 @@ +// html { +// font-size: 12.8px; // Base size for site! +// } .fa { display: inline-block; @@ -55,11 +58,11 @@ header { img { padding: 0; - margin: 10px 20px; + margin: .65rem 0rem .25rem; } p { - margin: 25px; + margin: 15px 25px; } .content { @@ -80,4 +83,49 @@ header { text-align: right; } } + + .navbar-nav { + flex-direction: row; + } + + .nav-item { + display: flex; + align-items: center; + } + + .separator, + .nav-link { + display: inline-block; + } + + .separator { + margin: 0 8px; + } + + .nav-link { + color: $white; + + &:hover { + text-decoration: underline; + } + } +} + +@media screen and (min-width: 768px) { + header { + img { + margin: .75rem 0.5rem; + } + } +} + +.loading { + text-align: center; +} + + +.sidebar { + .bg-light { + background-color: #f2f2f2 !important; + } } diff --git a/scipost/static/scipost/assets/css/_grid.scss b/scipost/static/scipost/assets/css/_grid.scss index b74fc9fe4c8fafb000db178d7e398e3460bded47..4fcd112cae3af016784543967cbd52bdc50261df 100644 --- a/scipost/static/scipost/assets/css/_grid.scss +++ b/scipost/static/scipost/assets/css/_grid.scss @@ -3,6 +3,24 @@ min-height: 190px; } +main { + margin-top: 0; + margin-bottom: 5rem; + padding-top: 0.5rem; + background-color: $white; + box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, .05); + + &.container { + margin-top: -1px; + padding-top: $grid-gutter-width; + padding-bottom: $grid-gutter-width; + border-left: 1px solid; + border-right: 1px solid; + border-top: 1px solid; + border-color: #e0e0e0; + } +} + .row { margin-bottom: 1rem; } @@ -39,21 +57,18 @@ footer.secondary { } -.container-inner { - padding: ($grid-gutter-width / 2); - background-color: $white; -} +// .container-inner { +// padding: ($grid-gutter-width / 2); +// background-color: $white; +// } .container { max-width: 1500px; } @media screen and (min-width: 768px) { - .container-inner { - padding: $grid-gutter-width; - } .container { - padding-left: 30px; - padding-right: 30px; + padding-left: $grid-gutter-width; + padding-right: $grid-gutter-width; } } diff --git a/scipost/static/scipost/assets/css/_homepage.scss b/scipost/static/scipost/assets/css/_homepage.scss index 4bd7bd7ac673a3576ba68391e2caeac66a8c023c..49536af0c6108c405b7d61020d594abe5b1dd6c6 100644 --- a/scipost/static/scipost/assets/css/_homepage.scss +++ b/scipost/static/scipost/assets/css/_homepage.scss @@ -1,65 +1,76 @@ -.has-sidebar { - .content-wrapper { - display: flex; - flex-wrap: wrap; - position: relative; - } - - .sidebar, - .main-panel { - padding-top: 0; - overflow: auto; - max-height: 100%; - height: 100%; - -webkit-overflow-scrolling: touch; - } - - .main-panel { - position: relative; - z-index: 2; - float: left; - min-height: 100%; - } - - .sidebar { - width: 100%; - padding-left: 1rem; - padding-right: 1rem; - .card { - margin-bottom: 1rem; - } - } +// .has-sidebar { +// .content-wrapper { +// display: flex; +// flex-wrap: wrap; +// position: relative; +// } +// +// .sidebar, +// .main-panel { +// padding-top: 0; +// overflow: auto; +// max-height: 100%; +// height: 100%; +// -webkit-overflow-scrolling: touch; +// } +// +// .main-panel { +// position: relative; +// z-index: 2; +// float: left; +// min-height: 100%; +// } +// +// .sidebar { +// width: 100%; +// padding-left: 1rem; +// padding-right: 1rem; +// .card { +// margin-bottom: 1rem; +// } +// } +// +// &.sidebar-left { +// .main-panel { +// order: 2; +// } +// .sidebar { +// order: 1; +// } +// } +// } +// +// .sidebar { +// margin-bottom: 2rem; +// +// a { +// &.active { +// font-weight: 700; +// } +// } +// } +// +// .container.header { +// padding-top: 0.5rem; +// margin-bottom: 1.5rem; +// +// .highlight { +// background-color: $white; +// } +// } - &.sidebar-left { - .main-panel { - order: 2; - } - .sidebar { - order: 1; - } - } -} - -.sidebar { - margin-bottom: 2rem; - - a { - &.active { - font-weight: 700; - } - } +.granting-institutions > a { + width: 25%; + display: inline-block; + max-width: 100px; + padding: 0 0.25rem; } -.container.header { - padding-top: 0.5rem; - margin-bottom: 1.5rem; +.supporting-partners ul { + height: 100px !important; + list-style: none; - .highlight { - background-color: $white; - } -} -.homepage { - .sponsors ul { + li { height: 100px !important; list-style: none; @@ -74,41 +85,48 @@ } } } +// +// +// @media (min-width: 768px) { +// .has-sidebar { +// .main-panel { +// width: calc(100% - 350px); +// padding-left: 0; +// padding-right: 1rem; +// } +// .sidebar { +// width: 350px; +// padding-right: 0; +// } +// +// &.sidebar-left { +// .main-panel { +// padding-right: 0; +// padding-left: 1rem; +// } +// .sidebar { +// padding-right: 1rem; +// padding-left: 0; +// } +// } +// } +// } +// +// @media (min-width: 1280px) { +// .has-sidebar { +// .main-panel { +// width: calc(100% - 400px); +// } +// +// .sidebar { +// width: 400px; +// } +// } +// } -@media (min-width: 768px) { - .has-sidebar { - .main-panel { - width: calc(100% - 350px); - padding-left: 0; - padding-right: 1rem; - } - .sidebar { - width: 350px; - padding-right: 0; - } - - &.sidebar-left { - .main-panel { - padding-right: 0; - padding-left: 1rem; - } - .sidebar { - padding-right: 1rem; - padding-left: 0; - } - } - } -} - -@media (min-width: 1280px) { - .has-sidebar { - .main-panel { - width: calc(100% - 400px); - } - - .sidebar { - width: 400px; - } - } +.teaser-box { + padding: 1rem; + border: 2px solid $scipost-orange; + border-radius: 3px; } diff --git a/scipost/static/scipost/assets/css/_journals.scss b/scipost/static/scipost/assets/css/_journals.scss index f50b7493f412c0e334ce8bcaebf356e5e3411b57..697836581a146ecba882470dcfcbd32406ff0ec9 100644 --- a/scipost/static/scipost/assets/css/_journals.scss +++ b/scipost/static/scipost/assets/css/_journals.scss @@ -87,3 +87,25 @@ ul.publicationClickables { margin-bottom: 0; } } + +.journal-mini { + li { + padding: 0.15rem 0; + } +} + +.related-publications { + .title { + font-size: 0.85rem; + } + + .meta { + margin-top: 0.25rem; + margin-bottom: 0; + color: $text-muted; + + a { + color: $text-muted; + } + } +} diff --git a/scipost/static/scipost/assets/css/_labels.scss b/scipost/static/scipost/assets/css/_labels.scss index 9b475466ed430c8ef917cd8b8ed04cfe667e46ca..5d5a4108092e561bb08cda51e68d5d7665db30bd 100644 --- a/scipost/static/scipost/assets/css/_labels.scss +++ b/scipost/static/scipost/assets/css/_labels.scss @@ -48,9 +48,9 @@ $label-label-spacing-y: .5rem !default; // Allows for customizing button radius independently from global border radius $label-border-width: $input-btn-border-width; -$label-border-radius: 4px; -$label-border-radius-lg: 4px; -$label-border-radius-sm: 4px; +$label-border-radius: 2px; +$label-border-radius-lg: 2px; +$label-border-radius-sm: 2px; $label-transition: all .2s ease-in-out !default; @@ -62,11 +62,13 @@ $label-transition: all .2s ease-in-out !default; white-space: nowrap; vertical-align: middle; user-select: none; + margin-right: 3px; + margin-bottom: 3px; // cursor: default; border: $label-border-width solid transparent; box-shadow: $label-box-shadow; - @include button-size($label-padding-y, $label-padding-x, $label-font-size, $label-border-radius, $btn-border-radius); + @include button-size($label-padding-y, $label-padding-x, $label-font-size, $label-border-radius, $label-border-radius); @include transition($label-transition); } diff --git a/scipost/static/scipost/assets/css/_list_group.scss b/scipost/static/scipost/assets/css/_list_group.scss index 5b7cf40e37cfd9b0350c3b5f1d912fc3b891238b..264f3b0cc35a92fc8ecc210a1b31a0c501241250 100644 --- a/scipost/static/scipost/assets/css/_list_group.scss +++ b/scipost/static/scipost/assets/css/_list_group.scss @@ -1,3 +1,4 @@ + .list-group-noborder, .list-group-noborder .list-group-item { border: 0; @@ -17,9 +18,9 @@ ul.events-list { border: 0; .time { - max-width: 9rem; - min-width: 9rem; - width: 9rem; + max-width: 10rem; + min-width: 10rem; + width: 10rem; } } } @@ -73,12 +74,14 @@ li, .publication { .subject { margin-bottom: 0; - padding-bottom: 0; + padding-top: 5px; color: $text-muted; } .title { margin-bottom: 0; + padding-top: 5px; + padding-bottom: 5px; } .authors { @@ -101,3 +104,139 @@ li, } } } + + +ul.news-list { + list-style: none; + margin: 0; + padding: 0; + + h4 { + margin: 0.25rem 0; + + a { + color: $scipost-darkblue; + } + } + + li { + margin-right: 0.5rem; + padding: 0.35rem 0; + border-top: 1px solid #bbb; + + &:first-child { + border-top: 0; + } + + p { + max-height: 0; + overflow: hidden; + padding: 0; + margin: 0.5rem 0; + transition: max-height 0.3s ease-in-out; + } + + &:first-child p, + &:hover p { + max-height: 1000px; + } + } +} + + +ul.communications { + list-style: none; + padding: 0; + max-width: 750px; + + .date { + position: relative; + text-align: center; + padding: 0.5rem 0; + + &:before { + content: ''; + height: 1px; + width: 100%; + background-color: $scipost-darkblue; + display: block; + position: absolute; + top: 50%; + z-index: 1; + } + + span { + background-color: $white; + z-index: 20; + position: relative; + padding: 0.35rem; + } + } + + .comm { + position: relative; + padding: 0.5rem 0; + display: flex; + flex-wrap: nowrap; + justify-content: flex-start; + } + + p { + margin-bottom: 0; + } + + .header { + font-weight: 700; + } + + .datetime { + display: none; + } + + .datetime, + .time { + max-width: 3rem; + min-width: 3rem; + width: 3rem; + padding: 0; + color: $scipost-darkblue; + font-weight: 100; + } + + &.wide { + max-width: none; + + .datetime { + display: block; + } + + .time, + .date { + display: none; + } + + .datetime { + font-weight: normal; + color: $text-muted; + max-width: 10rem; + min-width: 10rem; + width: 10rem; + } + + .comm { + margin: 0.25rem 0; + display: block; + } + + @media screen and (min-width: 768px) { + .comm { + margin: 0; + display: flex; + } + } + } +} + +.actions-list { + margin-bottom: 0; +} diff --git a/scipost/static/scipost/assets/css/_modal.scss b/scipost/static/scipost/assets/css/_modal.scss index 499e54911f67b08c9a9e9306329af20b35d7dd4c..ba982c80b515bce857a6e544b4dc51787c986236 100644 --- a/scipost/static/scipost/assets/css/_modal.scss +++ b/scipost/static/scipost/assets/css/_modal.scss @@ -1,3 +1,8 @@ +.modal-header { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + .modal-content { border-radius: 1.4px; } diff --git a/scipost/static/scipost/assets/css/_nav.scss b/scipost/static/scipost/assets/css/_nav.scss index 5a9f1c1d588ab35601b5031c4f5d789a0ed8d627..7ee6529783fa1c9c8e7df862893e7e4328957745 100644 --- a/scipost/static/scipost/assets/css/_nav.scss +++ b/scipost/static/scipost/assets/css/_nav.scss @@ -1,69 +1,127 @@ -.nav.main-nav, -nav.main-nav { - background-color: #f0f0f0; -} +// .nav.main-nav, +// nav.main-nav { +// background-color: #f0f0f0; +// } +// +// .nav.btn-group .nav-item { +// padding: 0 !important; +// } +// +// .nav > .btn { +// border-radius: 1.4px; +// } +// +// .tab-nav-container { +// text-align: center; +// white-space: nowrap; +// overflow-x: auto; +// overflow-y: hidden; +// position: relative; +// -webkit-overflow-scrolling: touch; +// +// &:after { +// content: ""; +// display: block; +// height: 3px; +// width: 100%; +// position: absolute; +// background-color: $scipost-darkblue; +// z-index: 1; +// bottom: 1px; +// border-radius: 99px; +// box-shadow: 0 1px 0 0 $scipost-lightblue; +// } +// +// .tab-nav-inner { +// display: inline-block; +// padding-bottom: 2px; +// } +// } +// +// .nav.personal-page-nav { +// .nav-item { +// background-color: transparent; +// color: $scipost-lightblue; +// border: 0; +// margin-bottom: -3px; +// +// .nav-link.active { +// text-decoration: underline; +// color: $scipost-darkblue; +// +// &:before { +// border-bottom: 11px solid $scipost-darkblue; +// border-left: 11px solid transparent; +// border-right: 11px solid transparent; +// content: ""; +// display: inline-block; +// position: absolute; +// right: 40%; +// bottom: 3px; +// } +// } +// +// a { +// padding-top: 0.5rem; +// padding-bottom: 1.5rem; +// } +// } +// } +// +// .nav { +// flex-wrap: nowrap; +// } +// +// .tab-pane .loading { +// text-align: center; +// padding: 3rem; +// } -.nav.btn-group .nav-item { - padding: 0 !important; -} +.nav-tabs .nav-link { + background-color: #f8f8f8; + border-radius: 0; + border-color: #e0e0e0; + border-bottom: 0; + margin: 0 3px; -.nav > .btn { - border-radius: 1.4px; + &.active { + border-top: 3px solid $scipost-darkblue; + } } .tab-nav-container { + display: block; text-align: center; - white-space: nowrap; - overflow-x: auto; - overflow-y: hidden; - position: relative; - -webkit-overflow-scrolling: touch; - - &:after { - content: ""; - display: block; - height: 3px; - width: 100%; - position: absolute; - background-color: $scipost-darkblue; - z-index: 1; - bottom: 1px; - border-radius: 99px; - box-shadow: 0 1px 0 0 $scipost-lightblue; - } + border-bottom: 3px solid $scipost-darkblue; .tab-nav-inner { display: inline-block; - padding-bottom: 2px; - } -} - -.nav.personal-page-nav { - .nav-item { - background-color: transparent; - color: $scipost-lightblue; - border: 0; - margin-bottom: -3px; - .nav-link.active { - text-decoration: underline; - color: $scipost-darkblue; + .nav li { + border: 0; + background-color: initial; - &:before { - border-bottom: 11px solid $scipost-darkblue; - border-left: 11px solid transparent; - border-right: 11px solid transparent; - content: ""; - display: inline-block; - position: absolute; - right: 40%; - bottom: 3px; + &:hover, + &:active { + background-color: initial; } - } - a { - padding-top: 0.5rem; - padding-bottom: 1.5rem; + a:hover, + a.active { + color: $scipost-darkblue; + text-decoration: underline; + + &:before { + border-bottom: 11px solid $scipost-darkblue; + border-left: 11px solid transparent; + border-right: 11px solid transparent; + content: ""; + display: inline-block; + position: absolute; + right: 40%; + bottom: 0; + } + } } } } @@ -71,21 +129,3 @@ nav.main-nav { .nav { flex-wrap: nowrap; } - -.tab-pane .loading { - text-align: center; - padding: 3rem; -} - - -.nav-tabs .nav-link { - background-color: #f8f8f8; - border-radius: 0; - border-color: #e0e0e0; - border-bottom: 0; - margin: 0 3px; - - &.active { - border-top: 3px solid $scipost-darkblue; - } -} diff --git a/scipost/static/scipost/assets/css/_navbar.scss b/scipost/static/scipost/assets/css/_navbar.scss index 0b19649d36ea691cb5273cb4235edf80fd695ba8..326c7e6937f6324c32be33d89204e052b030ac8e 100644 --- a/scipost/static/scipost/assets/css/_navbar.scss +++ b/scipost/static/scipost/assets/css/_navbar.scss @@ -2,174 +2,354 @@ * Navbar * */ + .navbar { - a:hover { - text-decoration: underline; - } + padding-left: 1rem; + padding-right: 1rem; - .nav-item.active a { - font-weight: 700; - text-decoration: underline; + .separator, + .nav-link { + display: inline-block; } +} - .nav-link { +.main-nav { + background-color: $scipost-lightblue; + + .separator { color: $white; + display: none; } - .navbar-nav { - flex-direction: row; - overflow: auto; - -webkit-overflow-scrolling: touch; - - .nav-link { - padding-left: .5rem; - padding-right: .5rem; - white-space: nowrap; + a { + color: $white; + + &:hover { + color: $scipost-lightestblue; + text-decoration: underline; } } -} -.container-outside { - &.main-nav { + .nav-item.active a { + font-weight: 700; + text-decoration: underline; + } + + .navbar-nav { + z-index: 99; background-color: $scipost-lightblue; + white-space: nowrap; + } - .navbar { - background-color: $scipost-lightblue; + @media (min-width: 992px) { + .full-height-bar { + z-index: 1; + position: absolute; + top: 0; + height: 100%; + width: 100%; + left: 0; - a { - color: $white; + > .container { + height: 100%; + } + + .form-wrapper { + margin-right: - $grid-gutter-width; + float: right; + height: 100%; + padding: $navbar-padding-y 0.5rem; + background: #343a40; } } - } - &.sub-nav { - background-color: $gray-200; + .separator { + display: inline-block; + } + } - .navbar { - background-color: $gray-200; + .search-form { + position: relative; - a { - color: $scipost-lightblue; - } + input { + border: 1px solid $scipost-darkblue; + border-radius: 0; + outline: none; + padding-right: 5rem; + box-shadow: none; } - span.nav-link { - color: #465d8e; - } + .btn { + border: 0; + outline: none; + color: $scipost-darkblue; + position: absolute; + right: 0; + background: transparent; + height: 100%; + width: auto; + padding: 4px 10px; - &.lighter { - background-color: #f2f2f2; + &:hover { + color: $scipost-lightblue; - .navbar { - background-color: #f2f2f2; + span { + text-decoration: underline; + } } } } } - -.navbar-scroll { - white-space: nowrap; - overflow-x: auto; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - - .navbar-scroll-inner { - display: flex !important; - width: 100%; +@media (max-width: 992px) { + .main-nav .search-form input { + padding-top: 0.6rem; + padding-bottom: 0.6rem; } } -.navbar-brand { - height: 38px; - margin: 0; +.sub-nav { + background-color: $breadcrumb-bg; + border-bottom: 1px solid #e0e0e0; } -.navbar-toggler-icon { - background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 43, 73, 1.0)"); + +.navbar-toggler { + margin: 4px 0; + outline: none !important; } -.navbar-secondary { - background-color: transparent; +.navbar-toggler-icon { + display: inline-block; + cursor: pointer; + margin: -3px 0 3px 3px; - > div { - overflow-x: auto; - white-space: nowrap; + .bar1, + .bar2, + .bar3 { + width: 100%; + height: 5px; + border-radius: 1px; + background-color: $scipost-lightestblue; + margin: 5px 0; + transition: 0.2s; } -} -.navbar-counter { - position: relative; + &.change { + .bar1 { + -webkit-transform: rotate(-45deg) translate(-9px, 6px); + transform: rotate(-45deg) translate(-9px, 6px); + } + + .bar2 { + opacity: 0; + } - a.dropdown-toggle { - min-width: 45px; + .bar3 { + -webkit-transform: rotate(45deg) translate(-8px, -8px); + transform: rotate(45deg) translate(-8px, -5px); + } } } -.live_notify_badge { - vertical-align: top; - margin-left: -15px; - margin-top: -5px; - height: 16px; - min-width: 16px; - line-height: 10px; - display: none; - padding: 0.2em; - border-radius: 99px; - border: 1px solid $scipost-lightblue; - background-color: $white; - color: $scipost-lightblue; +// .container-outside { +// &.main-nav { +// background-color: $scipost-lightblue; +// +// .navbar { +// background-color: $scipost-lightblue; +// +// a { +// color: $white; +// } +// } +// } +// +// &.sub-nav { +// background-color: $gray-200; +// +// .navbar { +// background-color: $gray-200; +// +// a { +// color: $scipost-lightblue; +// } +// } +// +// span.nav-link { +// color: #465d8e; +// } +// +// &.lighter { +// background-color: #f2f2f2; +// +// .navbar { +// background-color: #f2f2f2; +// } +// } +// } +// } + + +// .navbar-scroll { +// white-space: nowrap; +// overflow-x: auto; +// overflow-y: auto; +// -webkit-overflow-scrolling: touch; +// +// .navbar-scroll-inner { +// display: flex !important; +// width: 100%; +// } +// } +// +// .navbar-brand { +// height: 38px; +// margin: 0; +// } +// .navbar-toggler-icon { +// background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 43, 73, 1.0)"); +// } +// +// .navbar-secondary { +// background-color: transparent; +// +// > div { +// overflow-x: auto; +// white-space: nowrap; +// } +// } +// +// .navbar-counter { +// position: relative; +// +// a.dropdown-toggle { +// min-width: 45px; +// } +// } +// +// .live_notify_badge { +// vertical-align: top; +// margin-left: -15px; +// margin-top: -5px; +// height: 16px; +// min-width: 16px; +// line-height: 10px; +// display: none; +// padding: 0.2em; +// border-radius: 99px; +// border: 1px solid $scipost-lightblue; +// background-color: $white; +// color: $scipost-lightblue; +// } +// +// .search-nav-form { +// display: none; +// position: absolute; +// right: 0; +// top: 0; +// height: 100%; +// +// input[name="q"] { +// position: relative; +// outline: none; +// border-radius: 0; +// font-size: 110%; +// margin: 0 !important; +// padding: 0.5rem 0.75rem; +// height: 100%; +// border: 6px solid $gray-800; +// border-left-width: 8px; +// width: 100px; +// transition: 0.1s; +// } +// +// &:active input[name="q"], +// &:focus input[name="q"], +// &:hover input[name="q"], +// input[name="q"]:active, +// input[name="q"]:focus, +// input[name="q"]:hover { +// width: 200px; +// } +// +// [type="submit"] { +// background-color: $gray-800; +// color: $white; +// border-radius: 0; +// padding-left: 0.75rem; +// padding-right: 0.75rem; +// height: 100%; +// position: relative; +// } +// [type="submit"]:hover { +// background-color: $gray-700; +// } +// } +// +// @media (min-width: 992px) { +// .search-nav-form { +// display: flex; +// } +// +// .nav-item.search-item { +// display: none; +// } +// } + +header .nav-item { + position: relative; } -.search-nav-form { - display: none; - position: absolute; +#header-search-form { right: 0; - top: 0; + width: 600px; height: 100%; + position: absolute; - input[name="q"] { - position: relative; + form { + display: block; + top: 15px; + position: absolute; + width: 100%; + height: 50px; + background-color: $white; + padding: 0.25rem 1.0rem; + margin: 0; + border: 1px solid $scipost-lightblue; outline: none; - border-radius: 0; - font-size: 110%; - margin: 0 !important; - padding: 0.5rem 0.75rem; - height: 100%; - border: 6px solid $gray-800; - border-left-width: 8px; - width: 100px; - transition: 0.1s; - } + // border-radius: 100px; - &:active input[name="q"], - &:focus input[name="q"], - &:hover input[name="q"], - input[name="q"]:active, - input[name="q"]:focus, - input[name="q"]:hover { - width: 200px; - } + input[type='text'] { + width: 100%; + height: 100%; + border: 0; + outline: none; + padding-right: 100px; + background: none; + } - [type="submit"] { - background-color: $gray-800; - color: $white; - border-radius: 0; - padding-left: 0.75rem; - padding-right: 0.75rem; - height: 100%; - position: relative; - } - [type="submit"]:hover { - background-color: $gray-700; - } -} + .btn-group { + position: absolute; + right: 0; + top: 0; + height: 100%; + padding: 0.25rem 1.0rem 0.25rem 1.25rem; + width: 140px; + } -@media (min-width: 992px) { - .search-nav-form { - display: flex; - } + .btn { + color: $scipost-darkblue; + font-size: 110%; + padding: 0.375rem 0; + margin: 0 0.5rem; - .nav-item.search-item { - display: none; + &:hover { + color: $scipost-lightblue; + } + + &.close-form { + font-size: 16px; + } + } } } diff --git a/scipost/static/scipost/assets/css/_notifications.scss b/scipost/static/scipost/assets/css/_notifications.scss index 3607c064c9515997d5fe15b174317e2ae7136d2f..facb75880a6059254ca8357acb2aa9477f0fcf5c 100644 --- a/scipost/static/scipost/assets/css/_notifications.scss +++ b/scipost/static/scipost/assets/css/_notifications.scss @@ -1,13 +1,13 @@ .notifications_container { - .badge_link { - color: $scipost-lightestblue; + > a { + position: relative; } .fa-inbox { vertical-align: bottom; - margin: 0 0.25rem; + margin: 0 0 0 0.25rem; min-width: 17px; &:before { @@ -19,15 +19,25 @@ color: $scipost-darkblue; } - &.positive_count { + .positive_count { .fa { color: $scipost-orange; } - .live_notify_badge { + .badge { display: inline-block; + background-color: $scipost-orange; + border: 2px solid $scipost-lightblue; + color: $white; } } + + .badge { + position: absolute; + right: -3px; + top: -1px; + display: none; + } } @@ -37,7 +47,9 @@ .notifications { padding: 0; - min-width: 450px; + width: 450px; + // min-width: 450px; + max-width: 100%; border-color: $gray-600; border-radius: 1px; @@ -140,31 +152,6 @@ } } - .actions { - // display: none; - opacity: 0.0; - transition: opacity 0.1s; - width: 20px; - height: 100%; - padding-left: 1rem; - padding-right: 0.5rem; - padding-bottom: 0.1rem; - - .fa[data-toggle="tooltip"] { - font-size: 1em; - } - - a:hover { - .fa-circle-o:before { - content: '\f111'; - } - - .fa-circle:before { - content: '\f10c'; - } - } - } - .links .item { &.active, @@ -193,3 +180,109 @@ } } } + +.notification-center { + .links li { + padding: 0.25rem 0; + } +} + +.notifications { + .item { + padding: 0.4rem 0.25rem 0.4rem 0.75rem; + border-radius: 0; + border-top: 1px solid $white; + border-left: 0; + border-right: 0; + margin-bottom: 5px; + flex-direction: row; + justify-content: space-between; + display: flex; + position: relative; + + &:hover { + background-color: #f4f4f4 !important; + } + + &[href] { + cursor: pointer; + } + + &:before { + content: ''; + height: 100%; + width: 5px; + display: block; + position: absolute; + left: 0; + top: 0; + background: $scipost-darkblue; + } + + > div { + white-space: normal; + } + + &:last-child { + border-bottom: 0; + } + + .show-unread, + .show-read { + display: none; + } + + &.unread { + background-color: rgba(114, 141, 199, 0.2); + + .show-unread { + display: inherit; + } + } + &.read .show-read { + display: inherit; + } + + .actions { + opacity: 0.0; + transition: opacity 0.1s; + width: 20px; + height: 100%; + padding-left: 0.1rem; + padding-right: 0.1rem; + + .fa[data-toggle="tooltip"] { + font-size: 1em; + } + + a:hover { + .fa-circle-o:before { + content: '\f111'; + } + + .fa-circle:before { + content: '\f10c'; + } + } + } + + .item:hover .actions { + opacity: 1.0; + } + } +} + +.loading-link { + .fa { + display: none; + } + + &.loading { + .fa { + display: inline-block; + } + a { + display: none; + } + } +} diff --git a/scipost/static/scipost/assets/css/_personal_page.scss b/scipost/static/scipost/assets/css/_personal_page.scss index d2e06dd17613933bf3f8b4e3386a4c8183a0d872..c3fa6ee4b2f1cd8d6ce039445bdedbb12fb8cd85 100644 --- a/scipost/static/scipost/assets/css/_personal_page.scss +++ b/scipost/static/scipost/assets/css/_personal_page.scss @@ -1,3 +1,12 @@ table.availabilities { width: 100%; } + + +.personal-page .tab-pane { + min-height: 5rem; + + .loading { + margin: 5rem; + } +} diff --git a/scipost/static/scipost/assets/css/_reports.scss b/scipost/static/scipost/assets/css/_reports.scss index 724b9644b2fc77e9cd69e448f135e71e2bfcdba7..01290f0684125ba1c842cb7b7bcb49aaf67b1d40 100644 --- a/scipost/static/scipost/assets/css/_reports.scss +++ b/scipost/static/scipost/assets/css/_reports.scss @@ -2,11 +2,11 @@ ul.clickables { display: inline-block; list-style: none; - margin: 0.5rem 0 0 0; + margin: 0.25rem 0 0 0; padding: 0; li { - padding: 0 0 0.25rem 0; + padding: 0.15rem 0; } .citation { diff --git a/scipost/static/scipost/assets/css/_submissions.scss b/scipost/static/scipost/assets/css/_submissions.scss index 9f53652a62bb23a828b1700ccfeaeb0eade00acb..c3f8e25751ed3ebaf7824de8d337a3875fdc37c8 100644 --- a/scipost/static/scipost/assets/css/_submissions.scss +++ b/scipost/static/scipost/assets/css/_submissions.scss @@ -16,7 +16,7 @@ table.submission_header { left: 0; box-shadow: 0 1px 0 0 $card-shadow-color; border: 1px solid; - border-color: $card-grey-border-color; + border-color: $card-gray-border-color; z-index: 1; border-radius: $border-radius 0 $border-radius $border-radius; } @@ -29,11 +29,15 @@ table.submission_header { padding: 0.5rem calc(1rem - 3px); background: #f4f4f4; border: 1px solid; - border-color: $card-grey-border-color; + border-color: $card-gray-border-color; border-radius: 0 $border-radius $border-radius 0; box-shadow: 0 1px 0 0 $card-shadow-color; border-left: 0; z-index: 99; + + h3 { + margin: 0.25rem 0; + } } } @@ -56,3 +60,22 @@ table.submission_header { } } } + +.related-publications, +.submission-quick-actions, +.submission-contents { + border-left: 5px solid $scipost-darkblue; + padding: 0 1rem; + margin-bottom: 2rem; +} + +.related-publications { + margin-left: $grid-gutter-width / 2; +} + +.referee-box { + border: 1px solid $scipost-darkblue; + border-left-width: 5px; + padding: 1rem; + margin-bottom: 2rem; +} diff --git a/scipost/static/scipost/assets/css/_typography.scss b/scipost/static/scipost/assets/css/_typography.scss index 94f42554138026073a201dd9dbfdd661abb9bc5c..7d62daefdfa4ca2bc7b1af926362b6004c4dd9de 100644 --- a/scipost/static/scipost/assets/css/_typography.scss +++ b/scipost/static/scipost/assets/css/_typography.scss @@ -7,32 +7,36 @@ a { text-decoration: none; cursor: pointer; outline: none; + + &.muted-link { + color: inherit; + } + + &.active { + font-weight: bold; + } } a:hover { color: $scipost-darkblue; text-decoration: underline; } h1, h2, h3, h4, h5, h6 { - padding-top: 5px; - padding-bottom: 5px; text-shadow: none; + + &.title { + margin-bottom: 0.25rem; + } + + &.sub-title { + color: $gray-500; + margin-bottom: 1.25rem; + } } h1 > a { color: $scipost-darkblue; } -h3, -.h3, -h4, -.h4, -h5, -.h5, -h6 -.h6 { - margin-bottom: 0; -} - .text-black { color: $scipost-darkblue; } @@ -51,26 +55,10 @@ h6 font-weight: 300; } -.container-outside.header { - background-color: $gray-200; - - h1, - h2 { - padding: 15px; - background-color: $gray-200; - border-radius: $card-border-radius; - border: 0; - margin-top: 0; - margin-bottom: 0; - // margin-bottom: 10px; - box-shadow: none; - } -} - .highlight { - background-color: $body-bg; + background-color: $gray-100; border-radius: $card-border-radius; - border: 1px solid #ddd; + border: $card-border-width solid $card-border-color; &.tight { display: inline-block; @@ -98,12 +86,14 @@ h2.highlight-empty { h3.highlight, h3.highlight-empty { - padding: 10px; + padding: 10px 15px; } h5, .h5 { font-weight: 300; + // text-transform: uppercase; + letter-spacing: 0.5px; } hr, @@ -133,6 +123,28 @@ hr.hr12 { } } +hr.sm, +hr.lg { + height: 0; + background: none; + box-shadow: none; + margin: 1rem 0; + box-sizing: content-box; + border-radius: 0; +} + +hr { + &.sm { + border-top: 1px solid rgba(0,0,0,.1); + } + + &.lg { + height: 5px; + background-color: $scipost-darkblue; + border-radius: 3px; + } +} + .text-blue { color: $scipost-lightblue; } @@ -149,7 +161,16 @@ hr.hr12 { font-size: 1.5em; } +p { + line-height: 1.25rem; +} + +a.disabled { + text-decoration: line-through; +} + .active-bold.active { font-weight: bold; text-decoration: underline; + } diff --git a/scipost/static/scipost/assets/css/scipost-physics.scss b/scipost/static/scipost/assets/css/scipost-physics.scss index 9bd7e4a0d5e1a453ff8fefad883589907e3b128d..8a3826d22cac191a62adb91196f2d5fb83368cc8 100644 --- a/scipost/static/scipost/assets/css/scipost-physics.scss +++ b/scipost/static/scipost/assets/css/scipost-physics.scss @@ -19,6 +19,7 @@ padding: 5px; list-style: none; display: flex; + flex-direction: column; margin-bottom: 5px; a { @@ -34,3 +35,11 @@ } } } + +@media screen and (min-width: 768px) { + .journal-sub-head { + .links { + flex-direction: row; + } + } +} diff --git a/scipost/static/scipost/assets/js/dynamic_loading.js b/scipost/static/scipost/assets/js/dynamic_loading.js index 8b7d43de70cc45ce5a726183b757d474e181444b..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/scipost/static/scipost/assets/js/dynamic_loading.js +++ b/scipost/static/scipost/assets/js/dynamic_loading.js @@ -1,29 +0,0 @@ -function dynamic_load_tab( target_tab ) { - var tab = $(target_tab); - var url = tab.attr('sp-dynamic-load'); - if(tab.data('sp-loaded') == 'true') { - // window.history.replaceState('scipost', document.title, url); - return; // Only load once - } - - var target = $(tab.attr('href')); - $(target) - .show() - .html('<div class="loading"><i class="fa fa-spinner fa-pulse fa-3x fa-fw"></i></div>'); - - $.get(url).done(function(data) { - $(target).html(data).promise().done(function() { - tab.data('sp-loaded', 'true'); - }); - - // window.history.replaceState('scipost', document.title, url); - }); -} - -$(function(){ - // Change `tab` GET parameter for page-reload - $('.tab-nav-container.dynamic a[data-toggle="tab"]').on('shown.bs.tab', function (e) { - dynamic_load_tab( e.target ) - }) - $('[data-toggle="tab"][sp-autoload="true"]').tab('show'); -}); diff --git a/scipost/static/scipost/assets/js/newsticker.js b/scipost/static/scipost/assets/js/newsticker.js index c4e090fcf8ef94c50342cf051a7f0be8284a3154..657387df83f55c989a1ae0f6bcfb81937885a743 100644 --- a/scipost/static/scipost/assets/js/newsticker.js +++ b/scipost/static/scipost/assets/js/newsticker.js @@ -36,7 +36,7 @@ NewsTicker = (function() { var self = this if(typeof(this.cached_items[item.id]) == 'undefined') { - $.get(this.options.url + '/' + item.id + '/?format=html') + $.get(this.options.url + item.id + '/?format=html') .done(function(data) { self.cached_items[item.id] = data self.set_item(item.id) diff --git a/scipost/static/scipost/assets/js/notifications.js b/scipost/static/scipost/assets/js/notifications.js index 6d995a57e6942e803a9d3a4e9b4a9ef2a3511d32..d9bb617eecdaeddeca366984e8fd2f136c2c1926 100644 --- a/scipost/static/scipost/assets/js/notifications.js +++ b/scipost/static/scipost/assets/js/notifications.js @@ -1,8 +1,4 @@ -function fetch_api_data(callback, url, args) { - if (!url) { - var url = notify_api_url_count; - } - +function fetch_api_data(url, callback, args) { if (callback) { //only fetch data if a function is setup var r = new XMLHttpRequest(); @@ -17,171 +13,106 @@ function fetch_api_data(callback, url, args) { } } }) - r.open("GET", url + '?max=5', true); + r.open("GET", url, true); r.send(); } } -function update_counter_callback(data, args) { - var counter = data['unread_count']; - var el = $(args['element']); - - if (typeof counter == 'undefined') { - counter = 0; - } +function update_count() { + var el = '#live_notify_badge'; + fetch_api_data("/notifications/api/unread_count/", update_count_callback, {'element': el}); +} - el.html(counter); - if (counter > 0) { - el.parents('.notifications_container').addClass('positive_count') +function update_count_callback(data, args) { + var el = $(args['element']); + var count = data['unread_count']; + if( count > 0 ) { + el.html(count).parent().addClass('positive_count'); } else { - el.parents('.notifications_container').removeClass('positive_count') + el.html(count).parent().removeClass('positive_count'); } } + +function update_list(el) { + var el = $(el); + var offset = typeof el.data('count') == 'undefined' ? 0 : el.data('count'); + $('#load-notifications').addClass('loading'); + fetch_api_data("/notifications/api/list/?mark_as_read=1&offset=" + offset, update_list_callback, {'element': el}); +} + function update_list_callback(data, args) { var items = data['list']; var el = $(args['element']); - + var template = el.find('.template').html(); + var re = { + actor: new RegExp("{actor}","g"), + verb: new RegExp("{verb}","g"), + forward_link: new RegExp("{forward_link}","g"), + target: new RegExp("{target}","g"), + timesince: new RegExp("{timesince}","g"), + unread: new RegExp("{unread}","g"), + slug: new RegExp("{slug}","g"), + }; var messages = items.map(function (item) { // Notification content var message = '', link = ''; - if(typeof item.actor !== 'undefined'){ - message += '<strong>' + item.actor + '</strong>'; - } - if(typeof item.verb !== 'undefined'){ - message += " " + item.verb; - } - if(typeof item.target !== 'undefined'){ - if(typeof item.forward_link !== 'undefined') { - link = item.forward_link; - message += " <a href='" + item.forward_link + "'>" + item.target + "</a>"; - } else { - message += " " + item.target; - } - } - if(typeof item.timesince !== 'undefined'){ - message += "<div class='meta'>"; - if(typeof item.forward_link !== 'undefined') { - message += " <a href='" + item.forward_link + "'>Direct link</a> · "; - } - message += "<span class='text-muted'>" + item.timesince + " ago</span></div>"; - } - - // Notification actions + t = template.replace(re.actor, item.actor); + t = t.replace(re.verb, item.verb); + t = t.replace(re.forward_link, item.forward_link); + t = t.replace(re.target, item.target); + t = t.replace(re.timesince, item.timesince); + t = t.replace(re.slug, item.slug); if(item.unread) { - var mark_toggle = '<a href="javascript:;" class="mark-toggle" data-slug="' + item.slug + '"><i class="fa fa-circle" data-toggle="tooltip" data-placement="auto" title="Mark as read" aria-hidden="true"></i></a>'; + t = t.replace(re.unread, 'unread'); } else { - var mark_toggle = '<a href="javascript:;" class="mark-toggle" data-slug="' + item.slug + '"><i class="fa fa-circle-o" data-toggle="tooltip" data-placement="auto" title="Mark as unread" aria-hidden="true"></i></a>'; + t = t.replace(re.unread, 'read'); } + return t; + }); - if(typeof item.forward_link !== 'undefined') { - mark_toggle += "<br><a href='" + item.forward_link + "' data-toggle='tooltip' data-placement='auto' title='Go to item'><i class='fa fa-share' aria-hidden='true'></i></a>"; - } - - // Complete list html - if(link !== '') { - return '<li href="' + link + '" class="item ' + (item.unread ? ' active' : '') + '"><div>' + message + '</div><div class="actions">' + mark_toggle + '</div></li>'; - } else { - return '<li class="item ' + (item.unread ? ' active' : '') + '"><div>' + message + '</div><div class="actions">' + mark_toggle + '</div></li>'; - } - - }).join(''); - - if (messages == '') { - messages = '<li class="item px-5"><em>You have no new notifications</em></li>' + if(messages == '') { + messages = '<li class="item"><em>You have no new notifications</em></li>' } // Fill DOM - el.find('.live_notify_list').html(messages).parents('body').trigger('refresh_notify_list'); -} - -function update_mark_callback(data, args) { - var el = $(args['element']); - $(el).parents('.item').toggleClass('active'); - trigger_badge(); -} - - -function update_counter(el) { - fetch_api_data(update_counter_callback, "/notifications/api/unread_count/", {'element': el}); + var count = typeof el.data('count') == 'undefined' ? 0 : el.data('count'); + el.append(messages).data('count', count + items.length).trigger('refresh-notifications'); + $('#load-notifications').removeClass('loading'); } -function mark_toggle(el) { - var url = "/notifications/mark-toggle/" + $(el).data('slug') + "?json=1"; - fetch_api_data(update_mark_callback, url, {'element': el}); -} - -function update_list(el) { - fetch_api_data(update_list_callback, "/notifications/api/list/?mark_as_read=1", {'element': el}); -} - -function trigger_badge() { - $('.live_notify_badge').trigger('notification_count_updated'); -} -// Update Badge count every minute -var badge_timer = setInterval(trigger_badge, 60000); +$(function(){ + update_count(); + setInterval(update_count, 10000); -function initiate_popover() { - var template = $('.notifications_container .popover-template').html(); - $('.notifications_container a[data-toggle="popover"]').popover({ - trigger: 'focus', - animation: false, - offset: '0, 10px', - template: template, - delay: { - 'show': 0, - 'hide': 200, - }, - placement: 'bottom', - boundary: 'viewport', - title: 'empty-on-purpose' - }) - .on('inserted.bs.popover', function() { - $('body').trigger('notification_open_list'); - var self = this; - $('.popover').on('click', function() { - $('.notifications_container a[data-toggle="popover"]').focus(); - }); - }) - .on('hide.bs.popover', function() { - // Bug: force removal of tooltip + $('#notification_center').on('show.bs.modal', function(e) { + if( typeof $('#notification_center').data('reload') == 'undefined' ) { + update_list('#notifications-list'); + $('#notification_center').data('reload', 1); + } + }).on('hide.bs.modal', function() { $('body > .tooltip').remove(); }); -} - -$(function(){ - $('body').on('notification_open_list', function() { - update_list(this); - }) - $('.live_notify_badge').on('notification_count_updated', function() { - update_counter(this); - }).trigger('notification_count_updated'); + $('#load-notifications a').on('click', function(e) { + e.preventDefault(); + update_list('#notifications-list'); + }); + $('body').on('refresh-notifications', function(e) { + var list = $(e.target.children).filter('[data-refresh=1]'); - $('body').on('refresh_notify_list', function() { - // Bloody js - var list = $('.live_notify_list'); - list.find('li.item').on('click', function(e) { - e.stopPropagation(); - }) - .filter('[href]').on('click', function(e) { - window.location.href = $(this).attr('href') - }); list.find('[data-toggle="tooltip"]').tooltip({ animation: false, delay: {"show": 500, "hide": 100}, fallbackPlacement: 'clockwise', placement: 'bottom' }); - list.find('.actions a.mark-toggle').on('click', function(e) { - e.stopPropagation(); - mark_toggle(this); - }); - }); - initiate_popover(); + list.removeAttr('data-refresh'); + update_count(); + }); }); diff --git a/scipost/static/scipost/assets/js/scripts.js b/scipost/static/scipost/assets/js/scripts.js index e9b1bdd7d331dd8f4a8a4a6ef08050a5aa9aaf12..e99273c054ee76ad1e79d702f05e95a93b92acb6 100644 --- a/scipost/static/scipost/assets/js/scripts.js +++ b/scipost/static/scipost/assets/js/scripts.js @@ -83,6 +83,30 @@ function init_page() { select_form_table('.table-selectable'); } +function dynamic_load_tab( target_tab ) { + var tab = $(target_tab); + var url = tab.attr('sp-dynamic-load'); + if(tab.data('sp-loaded') == 'true') { + // window.history.replaceState('scipost', document.title, url); + return; // Only load once + } + + var target = $(tab.attr('href')); + $(target) + .show() + .html('<div class="loading"><i class="fa fa-spinner fa-pulse fa-3x fa-fw"></i></div>'); + + $.get(url).done(function(data) { + $(target).html(data).promise().done(function() { + tab.data('sp-loaded', 'true'); + init_page(); + }); + + // window.history.replaceState('scipost', document.title, url); + }); + +} + $(function(){ // Remove all alerts in screen automatically after 15sec. setTimeout(function() {hide_all_alerts()}, 15000); @@ -113,4 +137,11 @@ $(function(){ window.history.replaceState('scipost', document.title, url); }); }); + + + // Change `tab` GET parameter for page-reload + $('.tab-nav-container.dynamic a[data-toggle="tab"]').on('shown.bs.tab', function (e) { + dynamic_load_tab( e.target ) + }) + $('[data-toggle="tab"][sp-autoload="true"]').tab('show'); }); diff --git a/scipost/templates/partials/scipost/notification_center_modal.html b/scipost/templates/partials/scipost/notification_center_modal.html new file mode 100644 index 0000000000000000000000000000000000000000..597f97759154c990c4e241272744981c9d0d3f90 --- /dev/null +++ b/scipost/templates/partials/scipost/notification_center_modal.html @@ -0,0 +1,79 @@ +{% load request_filters %} + +<!-- Modal --> +<div class="modal notification-center" id="notification_center" tabindex="-1" role="dialog" aria-labelledby="notification_center" aria-hidden="true"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + {% if request.user.contributor %} + <h4 class="modal-title">{{ request.user.contributor.get_title_display }} {{ request.user.first_name }} {{ request.user.last_name }}</h4> + {% endif %} + <!-- <h5 class="modal-title" id="exampleModalLongTitle">Modal title</h5> --> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body pb-2"> + <ul class="list-unstyled links mb-0"> + <li><a class="item {% active 'scipost:personal_page' %}" href="{% url 'scipost:personal_page' %}">Personal Page</a></li> + {% if user.partner_contact or perms.scipost.can_read_partner_page %} + <li><a class="item {% active 'partners:dashboard' %}" href="{% url 'partners:dashboard' %}">Partner Page</a></li> + {% endif %} + + {% if perms.scipost.can_view_timesheets %} + <li><a class="item {% active 'finances:finances' %}" href="{% url 'finances:finances' %}">Financial Administration</a></li> + {% endif %} + + {% if perms.scipost.can_view_all_funding_info %} + <li><a class="item {% active 'funders:funders_dashboard' %}" href="{% url 'funders:funders_dashboard' %}">Funders</a></li> + {% endif %} + + {% if perms.scipost.can_view_production %} + <li><a class="item {% active 'production:production' %}" href="{% url 'production:production' %}">Production</a></li> + {% endif %} + + {% if perms.scipost.can_view_pool %} + <li><a class="item {% active 'submissions:pool' %}" href="{% url 'submissions:pool' %}">Submissions Pool</a></li> + {% endif %} + + <li><a class="item" href="{% url 'scipost:logout' %}">Logout</a></li> + </ul> + </div> + <div class="modal-header"> + <h4 class="modal-title">Inbox</h4> + </div> + <div class="modal-body p-2 px-md-3"> + <ul class="list-unstyled notifications" id="notifications-list"> + <div style="display: none;" class="template"> + <li class="{unread} item" data-refresh=1> + <div> + <div> + <strong>{actor}</strong> + <span>{verb}</span> + </div> + <div class="mt-1"> + <span class="text-muted">{timesince}</span> + · + <a href="{forward_link}">See item page →</a> + </div> + </div> + <div class="actions"> + <!-- <a href="javascript:;" class="mark-toggle show-unread" data-slug="{slug}"><i class="fa fa-circle" data-toggle="tooltip" data-placement="auto" title="Mark as read" aria-hidden="true"></i></a> --> + <!-- <a href="javascript:;" class="mark-toggle show-read" data-slug="{slug}"><i class="fa fa-circle-o" data-toggle="tooltip" data-placement="auto" title="Mark as unread" aria-hidden="true"></i></a> --> + <a href="{forward_link}" data-toggle='tooltip' data-placement="auto" title="Go to item"><i class="fa fa-share" aria-hidden="true"></i></a> + </div> + </li> + </div> + </ul> + + <p class="text-center loading-link" id="load-notifications"> + <a href="javascript:;">Load more...</a> + <i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw" id="#loading-notifications"></i> + </p> + </div> + <div class="modal-footer py-2"> + <button type="button" class="btn btn-link" data-dismiss="modal">Close</button> + </div> + </div> + </div> +</div> diff --git a/scipost/templates/partials/scipost/personal_page/account.html b/scipost/templates/partials/scipost/personal_page/account.html index 43e350ba7295fd8f3707fe43756b793d40a18520..f34ec5c0a460ec3a164be7b06d66d7007811bd0d 100644 --- a/scipost/templates/partials/scipost/personal_page/account.html +++ b/scipost/templates/partials/scipost/personal_page/account.html @@ -15,7 +15,7 @@ <div class="row"> <div class="col-12"> - <div class="card card-grey"> + <div class="card bg-light"> <div class="card-body"> <h2 class="card-title mb-0">Your Account</h2> </div> @@ -93,7 +93,7 @@ <h3>You are a SciPost Production Officer.</h3> {% endif %} - {% if contributor.fellowships.exists %} + {% if contributor.fellowships.all %} <h3>Your Fellowships:</h3> <ul class="mb-2"> {% for fellowship in contributor.fellowships.all %} diff --git a/scipost/templates/partials/scipost/personal_page/admin_actions.html b/scipost/templates/partials/scipost/personal_page/admin_actions.html index d0f616fdc56b2e5bf990d54f104056df9b3a3d58..3377ca73701f94b4e8011098329eba22c071b00d 100644 --- a/scipost/templates/partials/scipost/personal_page/admin_actions.html +++ b/scipost/templates/partials/scipost/personal_page/admin_actions.html @@ -2,7 +2,7 @@ {% is_scipost_admin request.user as is_scipost_admin %} <div class="row"> <div class="col-12"> - <div class="card card-grey"> + <div class="card bg-light"> <div class="card-body"> <h2 class="card-title mb-0">Admin Actions</h2> </div> @@ -11,7 +11,7 @@ </div> <div class="row"> - {% if perms.scipost.can_vet_registration_requests or perms.scipost.can_create_registration_invitations or perms.scipost.can_resend_registration_requests %} + {% if perms.scipost.can_vet_registration_requests or perms.scipost.can_create_registration_invitations or perms.scipost.can_resend_registration_requests or perms.scipost.can_manage_news %} <div class="col-md-4"> <h3>Registration actions</h3> <ul> @@ -26,6 +26,13 @@ {% endif %} </ul> + {% if perms.scipost.can_manage_news %} + <h3>News management</h3> + <ul> + <li><a href="{% url 'news:manage' %}">Manage News Items and Newsletters</a></li> + </ul> + {% endif %} + {% if perms.scipost.can_manage_registration_invitations %} <h3>Notifications</h3> <ul> @@ -61,6 +68,11 @@ </ul> {% endif %} + <h3>Ontology</h3> + <ul> + <li><a href="{% url 'ontology:ontology' %}">View/Manage the Ontology</a></li> + </ul> + {% if perms.scipost.can_manage_organizations %} <h3>Organizations</h3> <ul> diff --git a/scipost/templates/partials/scipost/personal_page/author_replies.html b/scipost/templates/partials/scipost/personal_page/author_replies.html index 2149474b1344a93ed81533cd57b93ce41af54ce0..a5068722c3e1149064836afaa894da4d9ebd8604 100644 --- a/scipost/templates/partials/scipost/personal_page/author_replies.html +++ b/scipost/templates/partials/scipost/personal_page/author_replies.html @@ -1,6 +1,6 @@ <div class="row"> <div class="col-12"> - <div class="card card-grey"> + <div class="card bg-light"> <div class="card-body"> <h2 class="card-title mb-0">Your Author Replies</h2> </div> diff --git a/scipost/templates/partials/scipost/personal_page/commentaries.html b/scipost/templates/partials/scipost/personal_page/commentaries.html index 2e50be5535ef30f630b23b89d014ade1d59e8e30..650c2b9a04d7dc7a62d0536a0894f283b729af29 100644 --- a/scipost/templates/partials/scipost/personal_page/commentaries.html +++ b/scipost/templates/partials/scipost/personal_page/commentaries.html @@ -1,6 +1,6 @@ <div class="row"> <div class="col-12"> - <div class="card card-grey"> + <div class="card bg-light"> <div class="card-body"> <h2 class="card-title">Commentaries</h2> <ul class="mb-0"> diff --git a/scipost/templates/partials/scipost/personal_page/comments.html b/scipost/templates/partials/scipost/personal_page/comments.html index f6a5f52db29c21f347f7cc59073eb7ed99940173..afd1f9eca1422294b339fd3ceff48762d8b70a88 100644 --- a/scipost/templates/partials/scipost/personal_page/comments.html +++ b/scipost/templates/partials/scipost/personal_page/comments.html @@ -1,6 +1,6 @@ <div class="row"> <div class="col-12"> - <div class="card card-grey"> + <div class="card bg-light"> <div class="card-body"> <h2 class="card-title mb-0">Your Comments</h2> </div> diff --git a/scipost/templates/partials/scipost/personal_page/editorial_actions.html b/scipost/templates/partials/scipost/personal_page/editorial_actions.html index df5d934427c521ef7fbf03828e29a9bc392a4b11..2271662f10137fe488cd6a4ca5deb2c133bde644 100644 --- a/scipost/templates/partials/scipost/personal_page/editorial_actions.html +++ b/scipost/templates/partials/scipost/personal_page/editorial_actions.html @@ -1,8 +1,9 @@ {% load user_groups %} {% is_scipost_admin request.user as is_scipost_admin %} + <div class="row"> <div class="col-12"> - <div class="card card-grey"> + <div class="card bg-light"> <div class="card-body"> <h2 class="card-title mb-0">Pending Editorial Actions</h2> </div> @@ -14,24 +15,24 @@ <div class="col-md-4"> {% if perms.scipost.can_vet_comments or perms.scipost.can_vet_submitted_reports %} - <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_list' %}">Vet submitted Comments</a> ({{ nr_comments_to_vet }})</li> - {% endif %} - {% if perms.scipost.can_vet_thesislink_requests %} - <li><a href="{% url 'theses:unvetted_thesislinks' %}">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_list' %}">Vet submitted Reports</a> ({{ nr_reports_to_vet }})</li> - {% endif %} - </ul> + <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_list' %}">Vet submitted Comments</a> ({{ nr_comments_to_vet }})</li> + {% endif %} + {% if perms.scipost.can_vet_thesislink_requests %} + <li><a href="{% url 'theses:unvetted_thesislinks' %}">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_list' %}">Vet submitted Reports</a> ({{ nr_reports_to_vet }})</li> + {% endif %} + </ul> {% endif %} {% if perms.scipost.can_oversee_refereeing %} diff --git a/scipost/templates/partials/scipost/personal_page/publications.html b/scipost/templates/partials/scipost/personal_page/publications.html index 4a10e24f855f92dff7dd5befa7e800f8347ff364..c1942c31002534b7251fa69a7f1d151ac3a57404 100644 --- a/scipost/templates/partials/scipost/personal_page/publications.html +++ b/scipost/templates/partials/scipost/personal_page/publications.html @@ -1,6 +1,6 @@ <div class="row"> <div class="col-12"> - <div class="card card-grey"> + <div class="card bg-light"> <div class="card-body"> <h2 class="card-title">Publications</h2> <ul class="mb-0"> @@ -22,14 +22,14 @@ <ul class="list-unstyled"> {% for pub in own_publications %} <li> - <div class="card card-grey card-publication" id="{{pub.doi_label}}"> + <div class="card bg-light card-publication" id="{{pub.doi_label}}"> {% include 'journals/_publication_card_content.html' with publication=pub current_user=request.user %} - {% if request.user == pub.accepted_submission.submitted_by.user %} - {% if not pub.pubfractions_confirmed_by_authors or not pub.pubfractions_sum_to_1 %} + {% if request.user == pub.accepted_submission.submitted_by.user %} + {% if not pub.pubfractions_confirmed_by_authors or not pub.pubfractions_sum_to_1 %} - <h4 class="m-2"><a href="{% url 'journals:allocate_orgpubfractions' doi_label=pub.doi_label %}"><span class="text-danger">Intervention needed:</span> review support fractions</a></h4> - {% endif %} - {% endif %} + <h4 class="m-2"><a href="{% url 'journals:allocate_orgpubfractions' doi_label=pub.doi_label %}"><span class="text-danger">Intervention needed:</span> review support fractions</a></h4> + {% endif %} + {% endif %} </div> </li> {% empty %} diff --git a/scipost/templates/partials/scipost/personal_page/refereeing.html b/scipost/templates/partials/scipost/personal_page/refereeing.html index a36c275b5dc56a32364f0978435c9284de3f87ca..fc650446175572eeed36bbfe6dbfdb4cb85bd31b 100644 --- a/scipost/templates/partials/scipost/personal_page/refereeing.html +++ b/scipost/templates/partials/scipost/personal_page/refereeing.html @@ -1,6 +1,6 @@ <div class="row"> <div class="col-12"> - <div class="card card-grey"> + <div class="card bg-light"> <div class="card-body"> <h2 class="card-title">Refereeing Tasks</h2> <ul class="mb-0"> @@ -14,14 +14,13 @@ {% if contributor.reports.in_draft.all %} <div class="row"> <div class="col-12"> - <h3>Unfinished reports:</h3> - </div> - <div class="col-12"> + <h3 class="highlight">Unfinished reports:</h3> <ul class="list-group list-group-flush"> {% for report in contributor.reports.in_draft.all %} <li class="list-group-item"> <div class="card-body px-0"> - {% include 'partials/submissions/submission_card_content.html' with submission=report.submission %} + {% include 'partials/submissions/submission_li.html' with submission=report.submission %} + <a class="btn btn-outline-primary my-2" href="{% url 'submissions:submit_report' report.submission.preprint.identifier_w_vn_nr %}">Finish report</a> </div> </li> @@ -34,29 +33,83 @@ <div class="row"> <div class="col-12"> - <h3>Pending Refereeing Tasks:</h3> - </div> - <div class="col-12"> - <ul class="list-group list-group-flush"> - {% for task in contributor.referee_invitations.in_process.all %} - <li class="list-group-item"> - <div class="card-body px-0"> - {% include 'partials/submissions/refereeing_invitation_card_content.html' with invitation=task %} - </div> - </li> - {% empty %} - <li class="list-group-item"><em>You do not have any pending refereeing task</em></li> - {% endfor %} - </ul> + <h3 class="highlight">Refereeing Invitations</h3> + {% if contributor.referee_invitations.all %} + <h3 class="mt-4">Pending Refereeing Invitations</h3> + {% if contributor.referee_invitations.in_process.all %} + <ul class="list-group list-group-flush"> + {% for invitation in contributor.referee_invitations.in_process.all %} + <li class="list-group-item py-2"> + {% include 'partials/submissions/submission_li.html' with submission=invitation.submission %} + + <table> + <tr> + <th style='min-width: 100px;'>Due:</th> + <td>{{ invitation.submission.reporting_deadline|date:'d F Y' }}{% if invitation.submission.reporting_deadline_has_passed %} <span class="label label-sm label-danger ml-2 px-3">overdue</span> {% endif %}<td> + </tr> + <tr> + <th>Status:</th> + <td>{{ invitation.get_status_display }}</td> + </tr> + {% if invitation.accepted is not None %} + <tr> + <th>{{ invitation.accepted|yesno:'Accepted,Declined' }}:</th> + <td>{{ invitation.date_responded }}</td> + </tr> + {% endif %} + <tr> + <td colspan="2"> + <a class="d-inline-block" href="{% url 'submissions:submit_report' identifier_w_vn_nr=invitation.submission.preprint.identifier_w_vn_nr %}">Submit your Report</a> <span class="text-blue">|</span> + <a class="d-inline-block" href="{% url 'submissions:communication' identifier_w_vn_nr=invitation.submission.preprint.identifier_w_vn_nr comtype='RtoE' referee_id=request.user.contributor.id %}">Write to the Editor-in-charge</a> + </td> + </tr> + </table> + </li> + {% endfor %} + </ul> + {% else %} + <p><em>You do not have any pending refereeing task</em></p> + {% endif %} + + <br> + <h3><a href="javascript:;" data-toggle="toggle" data-target="#all-invitations">+ See all Refereeing Invitations ({{ contributor.referee_invitations.all|length }})</a></h3> + <ul class="list-group list-group-flush ml-md-4" id="all-invitations" style="display: none;"> + {% for invitation in contributor.referee_invitations.all %} + <li class="list-group-item py-2"> + {% include 'partials/submissions/submission_li.html' with submission=invitation.submission %} + + <table> + <tr> + <th style='min-width: 100px;'>Status:</th> + <td>{{ invitation.get_status_display }}</td> + </tr> + {% if invitation.accepted is not None %} + <tr> + <th>{{ invitation.accepted|yesno:'Accepted,Declined' }}:</th> + <td>{{ invitation.date_responded }}</td> + </tr> + {% endif %} + {% if invitation.related_report %} + <tr> + <th>Report:</th> + <td><a href="{{ invitation.related_report.get_absolute_url }}">{{ invitation.related_report.citation|default:'Link' }}</a></td> + </tr> + {% endif %} + </table> + </li> + {% endfor %} + </ul> + {% else %} + <p><em>You do not have any refereeing invitation</em></p> + {% endif %} </div> </div> {% if contributor.reports.non_draft.all %} <div class="row"> <div class="col-12"> - <h3>Finished reports:</h3> - </div> - <div class="col-12"> + <h3 class="highlight">Finished reports</h3> + <ul class="list-group list-group-flush"> {% for report in contributor.reports.non_draft.all %} <li class="list-group-item"> @@ -72,10 +125,12 @@ <tr> <th>Status:</th><td {% if report.status == 'vetted' %}class="text-success"{% elif report.status == 'unvetted' %}class="text-danger"{% endif %}>{{report.get_status_display}}</td> </tr> - {% if report.doi_label %} - <tr> - <th>DOI:</th><td>{{ report.doi_string }}</td></th> -{% endif %} + {% if report.doi_label %} + <tr> + <th>DOI:</th> + <td>{{ report.doi_string }}</td> + </tr> + {% endif %} <tr> <th>Anonymous:</th><td>{{report.anonymous|yesno:'Yes,No'}}</td>{% if report.anonymous %}<td>You can <a href="{% url 'journals:sign_existing_report' report_id=report.id %}">click here to sign this Report</a> (leads to confirmation page){% endif %}</td> </tr> diff --git a/scipost/templates/partials/scipost/personal_page/submissions.html b/scipost/templates/partials/scipost/personal_page/submissions.html index db08f85fd0caf8f57682878d9259954c83de1033..31d42257039ae868ebc5e9b919cd0ff708fde503 100644 --- a/scipost/templates/partials/scipost/personal_page/submissions.html +++ b/scipost/templates/partials/scipost/personal_page/submissions.html @@ -1,6 +1,6 @@ <div class="row"> <div class="col-12"> - <div class="card card-grey"> + <div class="card bg-light"> <div class="card-body"> <h2 class="card-title">Submissions</h2> <ul class="mb-0"> diff --git a/scipost/templates/partials/scipost/personal_page/theses.html b/scipost/templates/partials/scipost/personal_page/theses.html index de0e22998d920a70b8a3ac95e133ae82c0d9c372..33fdc2a4be19b584994d2009d5953c1444110a96 100644 --- a/scipost/templates/partials/scipost/personal_page/theses.html +++ b/scipost/templates/partials/scipost/personal_page/theses.html @@ -1,6 +1,6 @@ <div class="row"> <div class="col-12"> - <div class="card card-grey"> + <div class="card bg-light"> <div class="card-body"> <h2 class="card-title">Theses</h2> <ul class="mb-0"> diff --git a/scipost/templates/scipost/ExpSustDrive2018.html b/scipost/templates/scipost/ExpSustDrive2018.html index 9b51c95cfa9984463c73befb8b8b577c64a525c7..7f6f085cd0bbe831d23f6d3e51293809a6f3465e 100644 --- a/scipost/templates/scipost/ExpSustDrive2018.html +++ b/scipost/templates/scipost/ExpSustDrive2018.html @@ -23,6 +23,7 @@ <p> We are very thankful for the immense support we have received from the scientific community during our initial phase. In view of this success, the time is now ripe for us to be bold, and unleash the next steps in our implementation plans. Read on to see how you can concretely help us bring forth a new age in publishing. </p> + <p><span class="text-danger">Update [2018-11-13]</span>: see our broader expansion plans at our <a href="{% url 'scipost:PlanSciPost' %}">Plan SciPost</a> page.</p> </div> <div class="col-md-7 col-lg-5"> <div class="embed-responsive embed-responsive-16by9"> @@ -43,7 +44,7 @@ </p> <ul> <li>Expansion of our <a href="{% url 'scipost:about' %}#editorial_college_physics">Editorial College</a>,</li> - <li>Expansion of our <a href="{% url 'partners:partners' %}" target="_blank">Supporting Partners</a> Board.</li> + <li>Expansion of our <a href="{% url 'sponsors:sponsors' %}" target="_blank">Sponsors</a> Board.</li> </ul> <p> The first aims to ensure we can process the increasing editorial workflow. The second aims to ensure that we can expand the support team needed to run the infrastructure underlying all our operations (at the moment this still relies on a large amount of pro bono work from the core team). @@ -52,7 +53,7 @@ <p>By the end of 2018, we wish to have achieved:</p> <ul> <li><strong>Expansion</strong>: <i>to have a minimum of 200 Fellows in the <a href="{% url 'scipost:about' %}#editorial_college_physics">Editorial College (Physics)</a>, with a minimum of 8 in each specialization;</i></li><br/> - <li><strong>Sustainability</strong>: <i>to achieve €200 000 yearly income from <a href="{% url 'partners:partners' %}" target="_blank">Supporting Partners</a> or other benefactors.</i></li> + <li><strong>Sustainability</strong>: <i>to achieve €200 000 yearly income from <a href="{% url 'sponsors:sponsors' %}" target="_blank">Sponsors</a> or other benefactors.</i></li> </ul> <p> Our estimates are that we can fully process around 500 publications per year with this upscaling of our cost-slashing infrastructure. @@ -65,8 +66,8 @@ <li><i>Is your field insufficiently represented in our current <a href="{% url 'scipost:about' %}#editorial_college_physics">Editorial College</a>?</i><br/>We are looking for world-class researchers to become Fellows. Send us your nominations at <a href="mailto:admin@scipost.org">admin@scipost.org</a>.<br/>Are you a professorial-level researcher working as editor for non-<a href="https://jscaux.org/blog/post/2018/05/05/genuine-open-access/" target="_blank">Genuine OA</a>-compliant publishers? Looking to invest your expertise in a more community-friendly alternative? Get in touch.<br/>Please note that in view of our development plans, we also welcome nominations in fields beyond Physics.</li> <br/> <li</li> - <li><i>Is your institution or funding agency not listed on our <a href="{% url 'partners:partners' %}" target="_blank">Partners page</a>?</i><br/>Encourage them (through a librarian, Open Access officer, director, ...) to join by <a href="{% url 'petitions:petition' slug='join-SPB' %}" target="_blank">signing our petition</a>, and by personally emailing them directly using this <a href="mailto:?subject=Petition to support SciPost&body={% autoescape on %}{% include 'petitions/petition_email.html' %}{% endautoescape %}&cc=partners@scipost.org">email template</a>. Experience shows that such personal testimonies and statements of support from active scientists constitute the most persuasive means to convince institutions to support us. - <br/>Funders which have been acknowledged in SciPost publications are listed at <a href="https://scipost.org/funders/" target="_blank">this link</a>; clicking on a funder will show how many publications they are related to, which have been produced at no direct cost to them by our cost-slashing operations. Seeing this might also help convincing them to become Partners.</li> + <li><i>Is your institution or funding agency not listed on our <a href="{% url 'sponsors:sponsors' %}" target="_blank">Sponsors page</a>?</i><br/>Encourage them (through a librarian, Open Access officer, director, ...) to join by personally emailing them directly using this <a href="mailto:?subject=Petition to support SciPost&body={% autoescape on %}{% include 'petitions/petition_email.html' %}{% endautoescape %}&cc=sponsors@scipost.org">email template</a>. Experience shows that such personal testimonies and statements of support from active scientists constitute the most persuasive means to convince institutions to support us. + <br/>Funders which have been acknowledged in SciPost publications are listed at <a href="https://scipost.org/organizations/" target="_blank">this link</a>; clicking on a funder will show how many publications they are related to, which have been produced at no direct cost to them by our cost-slashing operations. Seeing this might also help convincing them to become Sponsors.</li> <br/> <li><i>Are people in your surroundings and social network not yet sufficiently aware of SciPost?</i><br/>You can point them to our <a href="https://youtu.be/Pgvd7EvehCI" target="_blank">intro video</a> and mention this drive on social media using the <a href="https://twitter.com/hashtag/SciPostDrive2018">#SciPostDrive2018</a> and <a href="https://twitter.com/hashtag/SciPost">#SciPost</a> hashtags. </li> diff --git a/scipost/templates/scipost/PlanSciPost.html b/scipost/templates/scipost/PlanSciPost.html index fbd9bfca6255281545097e757bc70d82fbc14520..3b9704f6e5019bd11e049e605a03c912a315ceca 100644 --- a/scipost/templates/scipost/PlanSciPost.html +++ b/scipost/templates/scipost/PlanSciPost.html @@ -11,14 +11,11 @@ {% block content %} -<div class="container border border-danger"> - <span class="text-danger">DRAFT (POOLVIEW ONLY) - <a href="mailto:admin@scipost.org">COMMENTS WELCOME</a></span> - <div class="row"> <div class="col-12"> <h1 class="highlight">Plan Scipost</h1> <p class="ml-2 mr-2">At SciPost, we believe that cleaning up the business of scientific publishing requires building new infrastructure. We also believe that this business is most appropriately left in the hands of scientists themselves. Our achievements so far in the field of Physics have demonstrated the success and further potential of this approach.</p> - <p class="ml-2 mr-2">Increasing numbers of governments, funding agencies, universities and other academic instances have taken position in favour of a faster, larger-scale transition to Open Access. SciPost applauds these encouraging and empowering statements.</p> + <p class="ml-2 mr-2">Increasing numbers of governments, funding agencies, universities and other academic instances have taken position in favour of a faster, larger-scale transition to Open Access (see in particular the recently-announced <a href="https://www.scienceeurope.org/making-open-access-a-reality-by-2020/">Plan S</a>). SciPost applauds these encouraging and empowering statements.</p> <p class="ml-2 mr-2">Our initiative volunteers to provide the missing element in the proposed solutions: <strong>the infrastructure</strong>.</p> <h2 class="highlight">Our vision for the future</h2> @@ -30,7 +27,7 @@ <p class="ml-2 mr-2">The general architecture is illustrated as follows:</p> <p><img style="max-width: 600px;" src="{% static 'scipost/2018_10_SciPost_Journals_plan.png' %}"></img></p> - <p class="ml-2 mr-2">Within a given field, the set of journal titles is designed to respond to and serve field-specific needs (see the example of our family of <a href="{% url 'journals:journals' %}#physics">Physics Journals</a>). Each Journal must however follow our open peer-witnessed refereeing workflow and general Editorial College-based procedures. Moreover, each field is to feature a field-leading title mirroring our pioneering Journal <a href="{% url 'journal:about' 'SciPostPhys' %}">SciPost Physics</a> (and in particular implementing promotion of selected extended abstracts for publication in <a href="{% url 'journals:journals' %}#selections">SciPost Selections</a>).</p> + <p class="ml-2 mr-2">Within a given field, the set of journal titles is designed to respond to and serve field-specific needs (see the example of our family of <a href="{% url 'journals:journals' %}#physics">Physics Journals</a>). Each Journal must however follow our open peer-witnessed refereeing workflow and general Editorial College-based procedures. Moreover, each field is to feature a field-leading title mirroring our pioneering Journal <a href="{% url 'journal:about' 'SciPostPhys' %}">SciPost Physics</a> (and in particular implementing promotion of selected extended abstracts for publication in {#<a href="{% url 'journals:journals' %}#selections">#}SciPost Selections{#</a>#}).</p> <h3>Current situation and triggers for expansion</h3> <p class="ml-2 mr-2 mt-2"> @@ -70,6 +67,4 @@ </div> -</div> - {% endblock content %} diff --git a/scipost/templates/scipost/_personal_page_base.html b/scipost/templates/scipost/_personal_page_base.html index 4dbf56d4114f5b50839a4f9c5a73e4cdc2a523d3..a02abc0a8b9a904705f8503327c05a064bc0dee5 100644 --- a/scipost/templates/scipost/_personal_page_base.html +++ b/scipost/templates/scipost/_personal_page_base.html @@ -1,7 +1,7 @@ {% extends 'scipost/base.html' %} {% block breadcrumb %} - <div class="container-outside header"> + <div class="breadcrumb-container"> <div class="container"> <nav class="breadcrumb hidden-sm-down"> {% block breadcrumb_items %} diff --git a/scipost/templates/scipost/_public_info_as_table.html b/scipost/templates/scipost/_public_info_as_table.html index 1f0f93e93ff44d17a377c2632e39254646ebc0d8..a3b73a41a3193ec35862d22f53d11d96c0e412dc 100644 --- a/scipost/templates/scipost/_public_info_as_table.html +++ b/scipost/templates/scipost/_public_info_as_table.html @@ -15,4 +15,13 @@ </td> </tr> <tr><td>Personal web page: </td><td>{{ contributor.personalwebpage|default:'-' }}</td></tr> + + {% if perms.scipost.can_vet_registration_requests %} + <tr class="text-muted"><td>Username</td><td>{{ contributor.user.username }}</td></tr> + <tr class="text-muted"><td>Email (from User)</td><td>{{ contributor.user.email }}</td></tr> + <tr class="text-muted"><td>Date joined / last login</td><td>{{ contributor.user.date_joined }} / {{ contributor.user.last_login }}</td></tr> + <tr class="text-muted"><td>Status</td><td>{{ contributor.get_status_display }}</td></tr> + <tr class="text-muted"><td>User active?</td><td>{{ contributor.user.is_active }}</td></tr> + <tr class="text-muted"><td>Id</td><td>{{ contributor.id }}{% if contributor.profile %} <a href="{% url 'profiles:profile_detail' pk=contributor.profile.id %}">View Profile <i class="fa fa-arrow-right"></i></a>{% endif %}</td></tr> + {% endif %} </table> diff --git a/scipost/templates/scipost/bare_base.html b/scipost/templates/scipost/bare_base.html index c8ea52e70a122f76ac52a66ff890ab9b74cda163..065eeea771258b69a3a7b9795d88a712f68ae0b6 100644 --- a/scipost/templates/scipost/bare_base.html +++ b/scipost/templates/scipost/bare_base.html @@ -37,6 +37,8 @@ {% block base %}{% endblock base %} + + {% include 'partials/scipost/notification_center_modal.html' %} <script type="text/x-mathjax-config"> MathJax.Hub.Config({ tex2jax: { diff --git a/scipost/templates/scipost/base.html b/scipost/templates/scipost/base.html index af8a7d2c9b2f174f8837cb582b9efbf1034cdb85..be505c1b78c8c17a3e728dd4885c15011a57a5ac 100644 --- a/scipost/templates/scipost/base.html +++ b/scipost/templates/scipost/base.html @@ -2,15 +2,13 @@ {% block base %} - <div class="{% block container_class %}container pb-5{% endblock %}"> - <div class="container-inner"> - {% block page_header %}{% endblock page_header %} + <main class="{% block container_class %}container {% endblock %}"> + {% block page_header %}{% endblock page_header %} - {% block content %}{% endblock content %} + {% block content %}{% endblock content %} - {% block content_footer %}{% endblock content_footer %} - </div> - </div> + {% block content_footer %}{% endblock content_footer %} + </main> {% block secondary_footer %}{% endblock secondary_footer %} diff --git a/scipost/templates/scipost/base_for_sidebar.html b/scipost/templates/scipost/base_for_sidebar.html index 7d1e9d6c69adf2cd9b8a81b8c26e1d4c0916cb89..fee68cf3fa840f5ebd59560d6a4820923f3b218a 100644 --- a/scipost/templates/scipost/base_for_sidebar.html +++ b/scipost/templates/scipost/base_for_sidebar.html @@ -3,30 +3,22 @@ {% block body_class %}{{block.super}} has-sidebar{% endblock %} {% block base %} - <div class="container"> - <div class="content-wrapper"> - <div class="main-panel"> - <div class="container-inner"> - <div class="{% block container_class %}{% endblock %}"> - {% block page_header %}{% endblock page_header %} + <main class="container"> + <div class="row"> + <div class="col-md-8 col-lg-9 pr-lg-3"> - {% block content %}{% endblock content %} + {% block page_header %}{% endblock page_header %} - {% block content_footer %}{% endblock content_footer %} - </div> + {% block content %}{% endblock content %} - {% block secondary_footer %}{% endblock secondary_footer %} - </div> - - </div> + {% block content_footer %}{% endblock content_footer %} + </div> - <div class="sidebar"> - <div class="container-inner"> - {% block sidebar %}{% endblock %} - </div> - </div> + <div class="col-md-4 col-lg-3 sidebar mt-4 mt-md-0"> + {% block sidebar %}{% endblock %} </div> </div> + </main> {% include 'scipost/footer.html' %} {% endblock base %} diff --git a/scipost/templates/scipost/claim_authorships.html b/scipost/templates/scipost/claim_authorships.html index 1f9a0f9819222c5b91cec264572e4e158ce5c7ba..b87730a549c9d4fc22f771106bff240b586dcb57 100644 --- a/scipost/templates/scipost/claim_authorships.html +++ b/scipost/templates/scipost/claim_authorships.html @@ -29,7 +29,7 @@ <div class="row"> <div class="col-12"> {% for pub in publication_authorships_to_claim %} - <div class="card card-grey card-publication" id="{{pub.doi_label}}"> + <div class="card bg-light card-publication" id="{{pub.doi_label}}"> {% include 'journals/_publication_card_content.html' with publication=pub %} <div class="card-footer"> <form class="d-inline-block" action="{% url 'scipost:claim_pub_authorship' publication_id=pub.id claim=1 %}" method="post"> diff --git a/scipost/templates/scipost/comments_block.html b/scipost/templates/scipost/comments_block.html index d3fa22a5a38ba0bf1a51739ff35d9ebc1250258e..0c4ba8c56121e1c74e871ce8b728fabaf7f15eec 100644 --- a/scipost/templates/scipost/comments_block.html +++ b/scipost/templates/scipost/comments_block.html @@ -2,7 +2,7 @@ <div class="row"> <div class="col-12"> - <div class="card card-grey"> + <div class="card bg-light"> <div class="card-body"> <h2 class="card-title mb-0">Comments{% if type_of_object %} on this {{type_of_object}}{% endif %}</h2> <a href="javascript:;" data-toggle="toggle" data-target="#commentslist">Toggle comments view</a> diff --git a/scipost/templates/scipost/contributor_duplicate_list.html b/scipost/templates/scipost/contributor_duplicate_list.html new file mode 100644 index 0000000000000000000000000000000000000000..ae58665e3dc65fff6c90b73f60603e10bf23b2e3 --- /dev/null +++ b/scipost/templates/scipost/contributor_duplicate_list.html @@ -0,0 +1,44 @@ +{% extends 'profiles/base.html' %} + +{% load bootstrap %} + +{% block breadcrumb_items %} + {{ block.super }} +<span class="breadcrumb-item">Contributor duplicates</span> +{% endblock %} + +{% load scipost_extras %} + +{% block pagetitle %}: Contributor duplicates{% endblock pagetitle %} + +{% block content %} +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Potentially duplicate Contributors</h1> + {% if merge_form %} + <form action="{% url 'scipost:contributor_merge' %}" method="get"> + {{ merge_form|bootstrap }} + <input class="btn btn-outline-secondary" type="submit" value="Check"> + </form> + {% endif %} + + <br> + <h3>All found duplicates</h3> + <ul> + {% for contrib_dup in object_list %} + <li>{{ contrib_dup }} (<em>id={{ contrib_dup.id }}</em>)</li> + {% empty %} + <li<em>No duplicates found</em></li> + {% endfor %} + </ul> + + {% if is_paginated %} + <div class="col-12"> + {% include 'partials/pagination.html' with page_obj=page_obj %} + </div> + {% endif %} + + </div> +</div> + +{% endblock content %} diff --git a/scipost/templates/scipost/contributor_info.html b/scipost/templates/scipost/contributor_info.html index d65751ed7db2636235ff7754b3725448eed99fb8..0969da038368d52d0d76039dba9bbc15143e6018 100644 --- a/scipost/templates/scipost/contributor_info.html +++ b/scipost/templates/scipost/contributor_info.html @@ -6,9 +6,16 @@ <h1 class="highlight mb-4">Contributor info: {{ contributor.get_title_display }} {{ contributor.user.first_name }} {{ contributor.user.last_name }}</h1> -{% include "scipost/_public_info_as_table.html" with contributor=contributor %} -<br> +<div class="card"> + <div class="card-header"> + Details + </div> + <div class="card-body"> + {% include "scipost/_public_info_as_table.html" with contributor=contributor %} + </div> +</div> + {% if contributor_publications %} <div class="row"> diff --git a/scipost/templates/scipost/contributor_merge.html b/scipost/templates/scipost/contributor_merge.html new file mode 100644 index 0000000000000000000000000000000000000000..8589170711c51d3a0eeb77853ac0668b7d551a5f --- /dev/null +++ b/scipost/templates/scipost/contributor_merge.html @@ -0,0 +1,50 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block breadcrumb_items %} + {{ block.super }} +<span class="breadcrumb-item"><a href="{% url 'scipost:contributor_duplicates' %}">Duplicates</a></span> +<span class="breadcrumb-item">Merge Contributors {{ contributor_to_merge.id }} and {{ contributor_to_merge_into.id }}</span> +{% endblock %} + +{% load scipost_extras %} + +{% block pagetitle %}: Contributor duplicates: merge{% endblock pagetitle %} + +{% block content %} +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Merge Contributors {{ contributor_to_merge.id }} and {{ contributor_to_merge_into.id }}</h1> + {% if contributor_to_merge.user.is_active and not contributor_to_merge_into.user.is_active %} + <h3 class="text-danger">Warning: the contributor to merge is active, while the one to merge into is not</h3> + <p>Consider <a href="{% url 'scipost:contributor_merge' %}?to_merge={{ contributor_to_merge_into.id }}&to_merge_into={{ contributor_to_merge.id }}" method="get">merging the other way around</a></p> + {% endif %} + </div> +</div> +<div class="row"> + <div class="col-12"> + <h3 class="highlight">Contributor {{ contributor_to_merge.id }}</h3> + {% include "scipost/_public_info_as_table.html" with contributor=contributor_to_merge %} + </div> +</div> +<div class="row"> + <div class="col-12"> + <h3 class="highlight">Contributor {{ contributor_to_merge_into.id }}</h3> + {% include "scipost/_public_info_as_table.html" with contributor=contributor_to_merge_into %} + </div> +</div> + +<div class="row"> + <div class="col-12"> + <h3 class="highlight">Merge:</h3> + <form method="post"> + {% csrf_token %} + {{ merge_form|bootstrap }} + <input class="btn btn-primary" type="submit" value="Confirm merge"> + <a class="text-warning" href="{% url 'scipost:contributor_merge' %}?to_merge={{ contributor_to_merge_into.id }}&to_merge_into={{ contributor_to_merge.id }}" method="get">Merge the other way around</a></p> + </form> + </div> +</div> + +{% endblock content %} diff --git a/scipost/templates/scipost/footer.html b/scipost/templates/scipost/footer.html index a423be0af8357a466e7e9db24fb4d32e32b118f2..a3567ffb2be657a052892a4b91083edc4efa04f7 100644 --- a/scipost/templates/scipost/footer.html +++ b/scipost/templates/scipost/footer.html @@ -11,29 +11,24 @@ <a href="{% url 'scipost:terms_and_conditions' %}">Terms and conditions</a> <table class="mt-2 social-media"> - <tr> - <td> - <a href="//www.facebook.com/scipost" target="_blank" title="Facebook"> - <i class="fa fa-facebook" aria-hidden="true"></i> - </a> + <tr> + <td> + <a href="//twitter.com/scipost_dot_org" target="_blank" title="Twitter"> + <i class="fa fa-twitter" aria-hidden="true"></i> + </a> </td> - <td> - <a href="//twitter.com/scipost_dot_org" target="_blank" title="Twitter"> - <i class="fa fa-twitter" aria-hidden="true"></i> - </a> + <td> + <a href="{% url 'scipost:feeds' %}" title="RSS feeds"> + <i class="fa fa-rss" aria-hidden="true"></i> + </a> </td> - <td> - <a href="{% url 'scipost:feeds' %}" title="RSS feeds"> - <i class="fa fa-rss" aria-hidden="true"></i> - </a> - </td> - </tr> + </tr> </table> </div> <div class="col-md-4 mb-3 mb-md-0"> <a rel="license" href="//creativecommons.org/licenses/by/4.0/" target="_blank"><img alt="Creative Commons License" style="border-width:0" src="//licensebuttons.net/l/by/4.0/80x15.png" /></a><br />Except where otherwise noted, all content on SciPost is licensed under a <a rel="license" href="//creativecommons.org/licenses/by/4.0/" target="_blank">Creative Commons Attribution 4.0 International License</a>. </div> - <div class="col-md-4 mb-3 mb-md-0 text-right"> + <div class="col-md-4 mb-3 mb-md-0 text-md-right"> <a href="{% url 'journals:journals' %}">Journals</a> <br> <a href="{% url 'submissions:submissions' %}">Submissions</a> diff --git a/scipost/templates/scipost/header.html b/scipost/templates/scipost/header.html index be3219ff206a2092147378ca46f7972475790976..45650f3d04032ee8610a78381024b36993107071 100644 --- a/scipost/templates/scipost/header.html +++ b/scipost/templates/scipost/header.html @@ -1,17 +1,89 @@ {% load staticfiles %} - <header> - <div class="container"> - <div class="content"> - <div class="logobox"> - <a href="{% url 'scipost:index' %}"><img src="{% static 'scipost/images/logo_scipost_RGB_HTML_groot.png' %}" alt="SciPost logo" width="240" /></a> - </div> - <div class="blurbbox d-none d-md-block"> - <p> - <i>The complete scientific publication portal</i><br /> - <i>Managed by professional scientists</i><br /> - <i>For open, global and perpetual access to science</i> - </p> - </div> +{% load notifications_tags %} + +<header> + <div class="container"> + <div class="d-md-flex justify-content-between"> + <div class="logobox"> + <a href="{% url 'scipost:index' %}"><img src="{% static 'scipost/images/logo_scipost_RGB_HTML_groot.png' %}" alt="SciPost logo" width="180" /></a> </div> + + <ul class="navbar-nav"> + <li class="nav-item"> + <a class="nav-link" id="header-search-button" href="{% url 'scipost:search' %}"> + <i class="fa fa-search"></i> + Search + </a> + + <div id="header-search-form"{% if not request.GET.q %} style="display: none;"{% endif %}> + <form method="get" action="{% url 'scipost:search' %}"> + <input type="text" name="q" placeholder="Search term" value="{{ request.GET.q }}"> + <div class="btn-group"> + <button type="submit" class="btn btn-link"> + <i class="fa fa-search"></i> + Search + </button> + <a href="javascript:;" class="btn btn-link close-form" id="header-search-close-btn"> + <i class="fa fa-times"></i> + </a> + </div> + </form> + </div> + </li> + <li class="nav-item"> + <span class="separator">·</span> + <a class="nav-link" href="{% url 'scipost:about' %}">About</a> + </li> + <li class="nav-item dropdown"> + <span class="separator">·</span> + <a class="nav-link dropdown-toggle" href="javascript:;" data-toggle="dropdown">Our journals </a> + + <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton"> + {% for journal in journals %} + {% if journal.active %} + <a href="{{ journal.get_absolute_url }}" class="dropdown-item">{{journal}}</a> + {% elif perms.scipost.can_view_pool %} + <a href="{% url 'journal:about' journal.name %}" class="dropdown-item">{{journal}}</a> + {% endif %} + {% endfor %} + </div> + </li> + </ul> </div> - </header> + + <script type="text/javascript"> + function searchHeader() { + document.getElementById("header-search-button").addEventListener("click", function(event){ + if (document.documentElement.clientWidth > 768) { + event.preventDefault(); + var x = document.getElementById("header-search-form"); + if (x.style.display === "none") { + x.style.display = "block"; + } else { + x.style.display = "none"; + } + } + }); + + document.getElementById("header-search-close-btn").addEventListener("click", function(event){ + var x = document.getElementById("header-search-form"); + x.style.display = "none" + }); + + if (document.documentElement.clientWidth <= 768) { + // Force-close if form is prefilled. + x.style.display = "none"; + } + } + searchHeader(); + </script> + + {% comment %} + <div id="search-header"> + <form method="get" action="{% url 'scipost:search' %}"> + <input type="text" name="q" placeholder="Search term"> + </form> + </div> + {% endcomment %} + </div> +</header> diff --git a/scipost/templates/scipost/index.html b/scipost/templates/scipost/index.html index 9f78caf59e23dbdd732cc4b3e8c8b768143f6818..968c25729fd5c21b2887bb0a2f83d319f347d7de 100644 --- a/scipost/templates/scipost/index.html +++ b/scipost/templates/scipost/index.html @@ -3,28 +3,7 @@ {% load render_bundle from webpack_loader %} {% load staticfiles %} -{% block body_class %}{{block.super}} has-breadcrumb-submenu homepage{% endblock %} - -{% block breadcrumb %} - <div class="container-outside sub-nav"> - <div class="container"> - <nav class="navbar sub-nav navbar-expand-lg"> - <ul class="navbar-nav"> - <li class="nav-item"><span class="nav-link">Our Journals:</span></li> - {% for journal in journals %} - <li class="nav-item"> - {% if journal.active %} - <a href="{{journal.get_absolute_url}}" class="nav-link">{{journal}}</a> - {% elif perms.scipost.can_view_pool %} - <a href="{% url 'journal:about' journal.name %}" class="nav-link">{{journal}}</a> - {% endif %} - </li> - {% endfor %} - </div> - </nav> - </div> - </div> -{% endblock %} +{% block body_class %}{{ block.super }} homepage{% endblock %} {% block footer_script %} {% render_bundle 'homepage' 'js' %} @@ -34,165 +13,48 @@ <div class="row"> <div class="col-lg-6"> <!-- Latest publications --> - <div class="card card-grey px-3"> - <div class="card-header px-0"> - <h2 class="mb-0"><a href="{% url 'journals:journals' %}" class="text-black">Latest Publications</a></h2> - </div> - <div class="card-body p-0"> - <ul class="list-group list-group-flush"> - {% for publication in publications %} - <li class="list-group-item"> - <div class="card-body px-0"> - {% include 'partials/journals/publication_li_content.html' with publication=publication %} - </div> - </li> - {% endfor %} - <li class="list-group-item"> - <div class="card-body pt-3 mb-2"> - <a href="{% url 'journals:journals' %}">All Journals</a> - </div> - </li> - </ul> + <div class="card card-publications bg-light px-1 mb-2 scipost-bar"> + <div class="card-body pb-0"> + <h2 class="title mb-3">Latest Publications</h2> + <hr class="sm mb-0 mt-2"> </div> + <ul class="list-group list-group-flush px-3 mb-3"> + {% for publication in publications %} + <li class="list-group-item py-2"> + {% include 'partials/journals/publication_li_content.html' with publication=publication %} + </li> + {% endfor %} + </ul> + <p class="mb-3 px-3"><a href="{% url 'journals:publications' %}">View all Publications.</a></p> </div><!-- End latest publications --> </div> <div class="col-lg-6"> <!-- Latest submissions --> - <div class="card card-grey px-3"> - <div class="card-header px-0"> - <h2 class="mb-0"><a href="{% url 'submissions:submissions' %}" class="text-black">Latest Submissions</a></h2> + <div class="card card-submissions bg-light px-1 mb-2 scipost-bar"> + <div class="card-body pb-0"> + <h2 class="title mb-3">Latest Submissions</h2> + <hr class="sm mb-0 mt-2"> </div> - <div class="card-body p-0"> - <ul class="list-group list-group-flush"> - {% for submission in submissions %} - <li class="list-group-item"> - <div class="card-body px-0"> - {% include 'partials/submissions/submission_card_content_homepage.html' with submission=submission %} - </div> - </li> - {% endfor %} - <li class="list-group-item"> - <div class="card-body pt-3 mb-2"> - <a href="{% url 'submissions:submissions' %}">All submissions</a> - </div> + <ul class="list-group list-group-flush px-3 mb-3"> + {% for submission in submissions %} + <li class="list-group-item py-2"> + {% include 'partials/submissions/submission_card_content_homepage.html' with submission=submission %} </li> - </ul> - </div> + {% endfor %} + </ul> + <p class="mb-3 px-3"><a href="{% url 'submissions:submissions' %}">View all Submissions.</a></p> </div><!-- End latest submissions --> </div> </div> {% endblock %} {% block sidebar %} - <div class="card card-grey"> - <div class="card-body"> - <h2 class="card-title mb-0"><a href="{% url 'scipost:ExpSustDrive2018' %}">Expansion and Sustainability Drive 2018</a></h2> - <p class="m-0">It is time for us to be bold and unleash the next steps in our implementation plans. Read more <a href="{% url 'scipost:ExpSustDrive2018' %}">here</a> on how you can concretely help. Follow developments at <a href="https://twitter.com/hashtag/SciPostDrive2018">#SciPostDrive2018</a>.</p> - </div> - </div> - - {% if not user.is_authenticated %} - <!-- Register --> - <div class="card card-grey"> - <div class="card-body"> - <h2 class="card-title mb-0">Register</h2> - <p class="m-0">Professional scientists (PhD students and above) can become Contributors to SciPost by filling the <a href="{% url 'scipost:register' %}">registration form</a>.</p> - </div> - </div><!-- End Register --> - {% endif %} - - <!-- News --> - <div class="card card-grey" id="news"> - <div class="card-header border-0"> - <h2 class="card-title mb-0"> - News - <a class="floating-rss-icon" href="{% url 'scipost:feeds' %}"> - <img src="{% static 'scipost/images/feed-icon-14x14.png' %}" alt="Feed logo" width="14"> - </a> - </h2> - <h4 class="card-subtitle pb-0 text-muted">Latest news and announcements.</h4> - </div> - <div class="card-body"> - <ul class="list-group list-group-flush"> - {% if latest_newsitem %} - <li class="list-group-item"> - {% include 'news/news_card_content_short.html' with news=latest_newsitem %} - </li> - {% else %} - <li class="list-group-item"> - No current newsitems found. - </li> - {% endif %} - </ul> - </div> - <div class="card-footer"><a href="{% url 'news:news' %}">More news</a></div> - </div><!-- End news --> - - {# {% comment %}#} - <!-- Sponsors --> - <div class="card card-grey"> - <div class="card-body"> - <h2 class="card-title">Sponsors</h2> - - <div> - <p> - SciPost guarantees free online access to all publications in all its Journals and does not charge any article processing fees for publishing. Sponsors provide operating funds to SciPost through a cost-slashing consortial model. - </p> - <p> - We invite <a href="{% url 'organizations:organizations' %}">organizations benefitting from SciPost's activities</a> to join our growing <a href="{% url 'sponsors:sponsors' %}">list of Sponsors</a>. Look at our <a href="{% static 'sponsors/SciPost_Sponsors_Board_Prospectus.pdf' %}">one-page Prospectus</a> and at our full <a href="{% static 'sponsors/SciPost_Sponsorship_Agreement.pdf' %}">Sponsorship Agreement template</a>. - </p> - <p> - <span style="color: red;">Scientists, please help us out:</span> Please petition your local librarian/director/... to consider sponsoring us. You can use this email <a href="mailto:?subject=Petition to support SciPost&body={% autoescape on %}{% include 'sponsors/sponsor_petition_email.html' %}{% endautoescape %}&cc=sponsors@scipost.org">template</a>. - </p> - <p>Do you or somebody you know have the means to make a difference? -<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top"> -<input type="hidden" name="cmd" value="_s-xclick"> -<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----MIIHVwYJKoZIhvcNAQcEoIIHSDCCB0QCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYAOQwI9KUBE9TSaDPZztply0TNFyPHrGreqmNLw9MEgik5QX0xXB3lSg43BKXMdtooft242SCr3wpL9lzO/2Nr5hOCo8CW0baRBXVFyqUMa8ZZQlK4NKiVIHna4RjzeCCS79BNdSJq/QoyQr0VMm0aTRAC9KJ7wCYnPuTDW8f8/tjELMAkGBSsOAwIaBQAwgdQGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI++2+0u117q2AgbAoWU6uSQfGTaN2AY11WYQtP6cFP/0Czt534MrkU5vG8C4tHuass9h2AcVXWLvHlS+s199hZfyS4Q+V6Huja2aflkEFQHHUfUBiQMfZkcuhugOdRZ6n0EyR6PVRfoJYYZiBQCgxN8djQqULY6JVhpR4DpUG6dgVZ13S0SA/4WD4uW6xckGNsravTKJ8fVjFflxfSlIuBenleoxKma16taMKdCPQxOpK2v9aWLWT6gX/xKCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE4MDUyMjE5NDEyMVowIwYJKoZIhvcNAQkEMRYEFEI2dgukf4ETaBLq9x5NYHcI5dFzMA0GCSqGSIb3DQEBAQUABIGABwHfWvsxLKsNb31K8K6b4XPFYBFJ7aGFmx826Jp7kIs3vsf/EtSMT9lB0UHQoA2h9J9AUisJft9QlJqmnCTo6WhvSpSCzNberZXR5kMhARKGd0zufTLqxCd15QgizG8Iz7Zouo5gqetvHH8dsmBbbnkRc+zitLGGFoL9Q+AkmPI=-----END PKCS7----- -"> -<input type="image" src="https://www.paypalobjects.com/en_GB/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="PayPal – The safer, easier way to pay online!"> -<img alt="" border="0" src="https://www.paypalobjects.com/nl_NL/i/scr/pixel.gif" width="1" height="1"> -</form> - </p> - </div> - </div> - </div><!-- End Sponsors --> - {# {% endcomment %}#} + {% include 'scipost/index_sidebar_2.html' %} - <!-- Summarized --> - <div class="card card-grey"> - <div class="card-body"> - <h2 class="card-title"> - <a href="{% url 'journals:journals' %}">Journals</a> - </h2> - <h4 class="card-subtitle pt-0 mb-2 text-muted">SciPost publishes a portfolio of high-quality two-way open access scientific journals.</h4> - <p> - All SciPost Journals implement the stringent <a href="{% url 'scipost:FAQ' %}#pwr">peer-witnessed refereeing</a> principle. - <br> - All Journals are fully managed by professional scientists. - </p> - <p> - <a href="{% url 'scipost:about' %}#editorial_college_physics">Editorial College (Physics)</a> - <br> - <a href="{% url 'submissions:sub_and_ref_procedure' %}">Submission and refereeing procedure</a> - <br> - <a href="{% url 'submissions:author_guidelines' %}">Author guidelines</a> - <br> - <a href="{% url 'submissions:referee_guidelines' %}">Referee guidelines</a> - </p> - <h2 class="card-title"> - <a href="{% url 'commentaries:commentaries' %}">Commentaries</a> - </h2> - <p>SciPost Commentaries allow Contributors to comment and build on all existing literature.</p> - <h2 class="card-title"> - <a href="{% url 'theses:theses' %}">Theses</a> - </h2> - <p>SciPost Theses allow Contributors to find Master's, Ph.D. and Habilitation theses relevant to their work.</p> - </div> - </div><!-- End Summarized --> {% endblock %} -{% block secondary_footer %} +{% block content_footer %} <footer class="container-fluid text-left secondary pt-4 mt-5"> <div class="row"> <div class="col-lg-6"> diff --git a/scipost/templates/scipost/index_sidebar.html b/scipost/templates/scipost/index_sidebar.html new file mode 100644 index 0000000000000000000000000000000000000000..50abc8624592d06e9dac7f13d9d76a151069a62f --- /dev/null +++ b/scipost/templates/scipost/index_sidebar.html @@ -0,0 +1,88 @@ +{% load static %} + +<div class="p-3 mb-3 bg-light scipost-bar border" id="register"> + <h2><a href="{% url 'scipost:ExpSustDrive2018' %}">Expansion and Sustainability Drive 2018</a></h2> + <p class="m-0">It is time for us to be bold and unleash the next steps in our implementation plans. Read more <a href="{% url 'scipost:ExpSustDrive2018' %}">here</a> on how you can concretely help. Follow developments at <a href="https://twitter.com/hashtag/SciPostDrive2018">#SciPostDrive2018</a>.</p> +</div> + +{% if not user.is_authenticated %} + <!-- Register --> + <div class="p-3 my-3 bg-light scipost-bar border" id="register"> + <h2>Register</h2> + <p class="mb-1">Professional scientists (PhD students and above) can become Contributors to SciPost by filling the <a href="{% url 'scipost:register' %}">registration form</a>.</p> + </div><!-- End Register --> +{% endif %} + +<!-- News --> +<div class="p-3 my-3 bg-light" id="news"> + <h2 class="title"> + News + <small><a href="{% url 'scipost:feeds' %}"><i class="fa fa-rss"></i></a></small> + </h2> + <a href="{% url 'news:news' %}">View all news and announcements.</a> + <hr class="sm"> + <ul class="list-unstyled"> + {% if latest_newsitem %} + <li> + {% include 'news/news_card_content_short.html' with news=latest_newsitem %} + </li> + {% else %} + <li> + No current newsitems found. + </li> + {% endif %} + </ul> +</div><!-- End news --> + + +<!-- Partners --> +<div class="p-3 my-3 bg-light" id="partners"> + <h2>Partners</h2> + + <p> + SciPost guarantees free online access to all publications in all its Journals and does not charge any article processing fees for publishing. Supporting Partners provide operating funds to SciPost through a cost-slashing consortial model. + </p> + <p> + Institutions: consider joining our <a href="{% url 'partners:partners' %}">Supporting Partners Board</a>. SciPost cannot exist without your support. Look at our <a href="{% static 'scipost/SPB/SciPost_Supporting_Partners_Board_Prospectus.pdf' %}">one-page Prospectus</a> and at our full <a href="{% static 'scipost/SPB/SciPost_Supporting_Partner_Agreement.pdf' %}">Partner Agreement</a>. + </p> + <p> + <span style="color: red;">Scientists, please help us out:</span> if it is not listed on our <a href="{% url 'partners:partners' %}">Partners page</a>, please encourage your institution (through a librarian, Open Access officer, director, ...) to join by <a href="{% url 'petitions:petition' slug='join-SPB' %}">signing our petition</a>, and by personally emailing them directly using this <a href="mailto:?subject=Petition to support SciPost&body={% autoescape on %}{% include 'petitions/petition_email.html' %}{% endautoescape %}&cc=partners@scipost.org">email template</a>. + </p> + + <p>Do you or somebody you know have the means to make a difference? +<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top"> +<input type="hidden" name="cmd" value="_s-xclick"> +<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----MIIHVwYJKoZIhvcNAQcEoIIHSDCCB0QCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYAOQwI9KUBE9TSaDPZztply0TNFyPHrGreqmNLw9MEgik5QX0xXB3lSg43BKXMdtooft242SCr3wpL9lzO/2Nr5hOCo8CW0baRBXVFyqUMa8ZZQlK4NKiVIHna4RjzeCCS79BNdSJq/QoyQr0VMm0aTRAC9KJ7wCYnPuTDW8f8/tjELMAkGBSsOAwIaBQAwgdQGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI++2+0u117q2AgbAoWU6uSQfGTaN2AY11WYQtP6cFP/0Czt534MrkU5vG8C4tHuass9h2AcVXWLvHlS+s199hZfyS4Q+V6Huja2aflkEFQHHUfUBiQMfZkcuhugOdRZ6n0EyR6PVRfoJYYZiBQCgxN8djQqULY6JVhpR4DpUG6dgVZ13S0SA/4WD4uW6xckGNsravTKJ8fVjFflxfSlIuBenleoxKma16taMKdCPQxOpK2v9aWLWT6gX/xKCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE4MDUyMjE5NDEyMVowIwYJKoZIhvcNAQkEMRYEFEI2dgukf4ETaBLq9x5NYHcI5dFzMA0GCSqGSIb3DQEBAQUABIGABwHfWvsxLKsNb31K8K6b4XPFYBFJ7aGFmx826Jp7kIs3vsf/EtSMT9lB0UHQoA2h9J9AUisJft9QlJqmnCTo6WhvSpSCzNberZXR5kMhARKGd0zufTLqxCd15QgizG8Iz7Zouo5gqetvHH8dsmBbbnkRc+zitLGGFoL9Q+AkmPI=-----END PKCS7----- +"> +<input type="image" src="https://www.paypalobjects.com/en_GB/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="PayPal – The safer, easier way to pay online!"> +<img alt="" border="0" src="https://www.paypalobjects.com/nl_NL/i/scr/pixel.gif" width="1" height="1"> +</form> + </p> + +</div><!-- End Partners --> + +<!-- Summarized --> +<div class="p-3 my-3 bg-light" id="summarized"> + <h2 class="title"><a href="{% url 'journals:journals' %}">Journals</a></h2> + <h5 class="sub-title">SciPost publishes a portfolio of high-quality two-way open access scientific journals.</h5> + <p> + All SciPost Journals implement the stringent <a href="{% url 'scipost:FAQ' %}#pwr">peer-witnessed refereeing</a> principle. + <br> + All Journals are fully managed by professional scientists. + </p> + <p> + <a href="{% url 'scipost:about' %}#editorial_college_physics">Editorial College (Physics)</a> + <br> + <a href="{% url 'submissions:author_guidelines' %}">Author guidelines</a> + <br> + <a href="{% url 'submissions:referee_guidelines' %}">Referee guidelines</a> + <br> + <a href="{% url 'submissions:sub_and_ref_procedure' %}">Submission and refereeing procedure</a> + </p> + + <h2><a href="{% url 'commentaries:commentaries' %}">Commentaries</a></h2> + <p>SciPost Commentaries allow Contributors to comment and build on all existing literature.</p> + + <h2><a href="{% url 'theses:theses' %}">Theses</a></h2> + <p>SciPost Theses allow Contributors to find Master's, Ph.D. and Habilitation theses relevant to their work.</p> +</div> diff --git a/scipost/templates/scipost/index_sidebar_2.html b/scipost/templates/scipost/index_sidebar_2.html new file mode 100644 index 0000000000000000000000000000000000000000..f9957daa263aab97528ccdae0bad968f38fe294c --- /dev/null +++ b/scipost/templates/scipost/index_sidebar_2.html @@ -0,0 +1,102 @@ +{% load static %} + +<div class="teaser-box" id="register"> + <h2><a href="{% url 'scipost:PlanSciPost' %}" class="text-black">Our future: Plan SciPost</a></h2> + <p class="m-0"> + It is time for us to be bold and unleash the next steps in our implementation plans. + <br> + <br> + <a href="{% url 'scipost:PlanSciPost' %}">Read more here</a> + </p> +</div> + +<hr class="lg"> + +{% if not user.is_authenticated %} + <!-- Register --> + <div class="py-3" id="register"> + <h2>Register</h2> + <p class="mb-1">Professional scientists (PhD students and above) can become Contributors to SciPost by filling the <a href="{% url 'scipost:register' %}">registration form</a>.</p> + </div><!-- End Register --> + + <hr class="lg"> +{% endif %} + +<!-- News --> +<div class="py-3" id="news2"> + <h2 class="title"> + News + <small><a href="{% url 'scipost:feeds' %}"><i class="fa fa-rss"></i></a></small> + </h2> + <!-- <a href="{% url 'news:news' %}">View all news and announcements.</a> --> + <ul class="news-list"> + {% for news in news_items %} + <li> + <h4><a href="{% url 'news:news' %}#news_{{news.id}}">{{ news.headline }}</a></h4> + <div class="text-muted">{{ news.date|date:'j F Y' }}</div> + <p> + {{ news.blurb_short }} + <br> + <br> + <a href="{% url 'news:news' %}#news_{{news.id}}" class="my-1">Read more →</a> + </p> + </li> + {% endfor %} + <a href="{% url 'news:news' %}" class="my-1">See complete News list</a> + </ul> +</div><!-- End news --> + +<hr class="lg"> + +<!-- Sponsors --> +<div class="py-3" id="sponsors"> + <h2>Sponsors</h2> + + <p> + SciPost guarantees free online access to all publications in all its Journals and does not charge any article processing fees for publishing. Sponsors provide operating funds to SciPost through a cost-slashing consortial model. + </p> + <p> +We invite <a href="{% url 'organizations:organizations' %}">organizations benefitting from SciPost's activities</a> to join our growing <a href="{% url 'sponsors:sponsors' %}">list of Sponsors</a>. Look at our <a href="{% static 'sponsors/SciPost_Sponsors_Board_Prospectus.pdf' %}">one-page Prospectus</a> and at our full <a href="{% static 'sponsors/SciPost_Sponsorship_Agreement.pdf' %}">Sponsorship Agreement template</a>. + </p> + <p> + <span style="color: red;">Scientists, please help us out:</span> Please petition your local librarian/director/... to consider sponsoring us. You can use this email <a href="mailto:?subject=Petition to support SciPost&body={% autoescape on %}{% include 'sponsors/sponsor_petition_email.html' %}{% endautoescape %}&cc=sponsors@scipost.org">template</a>. + </p> + <p>Do you or somebody you know have the means to make a difference?</p> + <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top"> + <input type="hidden" name="cmd" value="_s-xclick"> + <input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----MIIHVwYJKoZIhvcNAQcEoIIHSDCCB0QCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYAOQwI9KUBE9TSaDPZztply0TNFyPHrGreqmNLw9MEgik5QX0xXB3lSg43BKXMdtooft242SCr3wpL9lzO/2Nr5hOCo8CW0baRBXVFyqUMa8ZZQlK4NKiVIHna4RjzeCCS79BNdSJq/QoyQr0VMm0aTRAC9KJ7wCYnPuTDW8f8/tjELMAkGBSsOAwIaBQAwgdQGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI++2+0u117q2AgbAoWU6uSQfGTaN2AY11WYQtP6cFP/0Czt534MrkU5vG8C4tHuass9h2AcVXWLvHlS+s199hZfyS4Q+V6Huja2aflkEFQHHUfUBiQMfZkcuhugOdRZ6n0EyR6PVRfoJYYZiBQCgxN8djQqULY6JVhpR4DpUG6dgVZ13S0SA/4WD4uW6xckGNsravTKJ8fVjFflxfSlIuBenleoxKma16taMKdCPQxOpK2v9aWLWT6gX/xKCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE4MDUyMjE5NDEyMVowIwYJKoZIhvcNAQkEMRYEFEI2dgukf4ETaBLq9x5NYHcI5dFzMA0GCSqGSIb3DQEBAQUABIGABwHfWvsxLKsNb31K8K6b4XPFYBFJ7aGFmx826Jp7kIs3vsf/EtSMT9lB0UHQoA2h9J9AUisJft9QlJqmnCTo6WhvSpSCzNberZXR5kMhARKGd0zufTLqxCd15QgizG8Iz7Zouo5gqetvHH8dsmBbbnkRc+zitLGGFoL9Q+AkmPI=-----END PKCS7----- + "> + <input type="image" src="//www.paypalobjects.com/en_GB/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="PayPal – The safer, easier way to pay online!"> + <img alt="" border="0" src="//www.paypalobjects.com/nl_NL/i/scr/pixel.gif" width="1" height="1"> + </form> +</div><!-- End Sponsors --> + +<hr class="lg"> + +<!-- Summarized --> +<div class="py-3" id="summarized"> + <h2 class="title"><a href="{% url 'journals:journals' %}" class="text-black">Journals</a></h2> + <p> + SciPost publishes a portfolio of high-quality two-way open access scientific journals. + </p> + <p> + All SciPost Journals implement the stringent <a href="{% url 'scipost:FAQ' %}#pwr">peer-witnessed refereeing</a> principle. + <br> + All Journals are fully managed by professional scientists. + </p> + <p> + <a href="{% url 'scipost:about' %}#editorial_college_physics">Editorial College (Physics)</a> + <br> + <a href="{% url 'submissions:author_guidelines' %}">Author guidelines</a> + <br> + <a href="{% url 'submissions:referee_guidelines' %}">Referee guidelines</a> + <br> + <a href="{% url 'submissions:sub_and_ref_procedure' %}">Submission and refereeing procedure</a> + </p> + + <h2><a href="{% url 'commentaries:commentaries' %}" class="text-black">Commentaries</a></h2> + <p>SciPost Commentaries allow Contributors to comment and build on all existing literature.</p> + + <h2><a href="{% url 'theses:theses' %}" class="text-black">Theses</a></h2> + <p>SciPost Theses allow Contributors to find Master's, Ph.D. and Habilitation theses relevant to their work.</p> +</div> diff --git a/scipost/templates/scipost/layout_2_col.html b/scipost/templates/scipost/layout_2_col.html index fa5547e21b19e21db34474eaa011101c422811b7..dfc586dc39278adc8be26d2b582924d28ef80dd9 100644 --- a/scipost/templates/scipost/layout_2_col.html +++ b/scipost/templates/scipost/layout_2_col.html @@ -3,45 +3,32 @@ {% block body_class %}{{block.super}} has-sidebar layout-2-col{% endblock %} {% block breadcrumb %} - <div class="container-outside header"> + <div class="breadcrumb-container"> <div class="container"> - <nav class="breadcrumb hidden-sm-down"> + <ol class="breadcrumb"> {% block breadcrumb_items %} - <a href="{% url 'scipost:index' %}" class="breadcrumb-item">SciPost</a> + <li class="breadcrumb-item"><a href="{% url 'scipost:index' %}">Home</a></li> {% endblock %} - </nav> + </ol> </div> </div> {% endblock %} {% block base %} - <div class="container mb-4 header"> - {% block page_header %}{% endblock page_header %} - </div> - - <div class="container"> - <div class="content-wrapper"> - <div class="main-panel"> - <div class="container-inner"> - <div class="{% block container_class %}{% endblock %}"> - {% block content %}{% endblock content %} - - {% block content_footer %}{% endblock content_footer %} - </div> - - {% block secondary_footer %}{% endblock secondary_footer %} - </div> - - </div> - - <div class="sidebar {% block sidebar_class %}{% endblock %}"> - <div class="container-inner"> - {% block sidebar %}{% endblock %} - </div> - </div> + <main class="container"> + <div class="row"> + <div class="col-md-4 col-lg-3 sidebar"> + {% block sidebar %}{% endblock %} + </div> + <div class="col-md-8 col-lg-9"> + {% block content %}{% endblock content %} + {% block content_footer %}{% endblock content_footer %} + <hr> + {% block secondary_footer %}{% endblock secondary_footer %} </div> </div> + </main> {% include 'scipost/footer.html' %} {% endblock base %} diff --git a/scipost/templates/scipost/navbar.html b/scipost/templates/scipost/navbar.html index e364be488e929e15aca304d3354d5042b76cfa76..450504c4c8b1755983bb4ade0d92ab2fd570201b 100644 --- a/scipost/templates/scipost/navbar.html +++ b/scipost/templates/scipost/navbar.html @@ -1,84 +1,109 @@ +{% load request_filters %} {% load staticfiles %} {% load notifications_tags %} {% load scipost_extras %} +{% load user_groups %} +{% is_editorial_college request.user as is_editorial_college %} -<div class="container-outside main-nav"> +<nav class="navbar navbar-expand-lg main-nav"> + <!-- <a class="navbar-brand mr-auto mr-lg-0" href="#">Offcanvas navbar</a> --> <div class="container"> - <nav class="navbar main-nav navbar-expand-lg"> - <ul id="menu-navbar" class="navbar-nav"> - <li class="nav-item{% if request.path == '/' %} active{% endif %}"> - <a href="{% url 'scipost:index' %}" class="nav-link">Home</a> - </li> + <button class="navbar-toggler p-0 border-0" type="button" data-toggle="collapse" data-target="#main-navbar"> + <div class="navbar-toggler-icon" onclick="navbarIconToggle(this)"> + <div class="bar1"></div> + <div class="bar2"></div> + <div class="bar3"></div> + </div> + </button> - <li class="nav-item{% if '/journals/' == request.path %} active{% endif %}"> - <a href="{% url 'journals:journals' %}" class="nav-link">Journals</a> - </li> - <li class="nav-item{% if '/journals/publications' in request.path %} active{% endif %}"> - <a href="{% url 'journals:publications' %}" class="nav-link">Publications</a> - </li> - <li class="nav-item{% if '/submissions/' in request.path %} active{% endif %}"> - <a class="nav-link" href="{% url 'submissions:submissions' %}">Submissions</a> - </li> - <li class="nav-item{% if '/commentaries/' in request.path %} active{% endif %}"> - <a class="nav-link" href="{% url 'commentaries:commentaries' %}">Commentaries</a> - </li> - <li class="nav-item{% if '/theses/' in request.path %} active{% endif %}"> - <a class="nav-link" href="{% url 'theses:theses' %}">Theses</a> - </li> - <li class="nav-item{% if '/about' in request.path %} active{% endif %}"> - <a class="nav-link" href="{% url 'scipost:about' %}">About SciPost</a> - </li> + <div class="collapse navbar-collapse" id="main-navbar"> + <ul class="navbar-nav mr-auto"> + <li class="nav-item{% if request.path == '/' %} active{% endif %}"> + <a href="{% url 'scipost:index' %}" class="nav-link">Home</a> + </li> - - {% if user.is_authenticated %} - {% if request.user|is_in_group:'Testers' %} - <li class="nav-item navbar-counter"> - <div class="nav-link notifications_container"> - <a href="javascript:;" class="d-inline-block ml-1 badge_link" id="notifications_badge" data-toggle="popover"> - <span class="user">{% if user.last_name %}{% if user.contributor %}{{ user.contributor.get_title_display }} {% endif %}{{ user.first_name }} {{ user.last_name }}{% else %}{{ user.username }}{% endif %}</span> - <i class="fa fa-inbox" aria-hidden="true" style="min-width: 16px;"></i> - {% live_notify_badge classes="badge badge-pill badge-primary" %} - </a> - {% live_notify_list %} - </div> - + <li class="nav-item{% if '/journals/' == request.path %} active{% endif %}"> + <a href="{% url 'journals:journals' %}" class="nav-link">Journals</a> </li> - {% else %} - <li class="nav-item highlighted"> - <span class="nav-link">Logged in as {{ user.username }}</span> + <li class="nav-item{% if '/journals/publications' in request.path %} active{% endif %}"> + <a href="{% url 'journals:publications' %}" class="nav-link">Publications</a> </li> - {% endif %} - <li class="nav-item"> - <a class="nav-link" href="{% url 'scipost:logout' %}">Logout</a> + <li class="nav-item{% if '/submissions/' in request.path %} active{% endif %}"> + <a class="nav-link" href="{% url 'submissions:submissions' %}">Submissions</a> </li> - {% if perms.scipost.can_view_production %} - <li class="nav-item{% if '/production' in request.path %} active{% endif %}"> - <a class="nav-link" href="{% url 'production:production' %}">Production</a> - </li> - {% endif %} + <li class="nav-item{% if '/commentaries/' in request.path %} active{% endif %}"> + <a class="nav-link" href="{% url 'commentaries:commentaries' %}">Commentaries</a> + </li> + <li class="nav-item{% if '/theses/' in request.path %} active{% endif %}"> + <a class="nav-link" href="{% url 'theses:theses' %}">Theses</a> + </li> + </ul> + + <hr class="lg d-lg-none"> + + <ul class="navbar-nav mr-0"> + {% if user.is_authenticated %} + <li class="nav-item"> + <span class="nav-link text-white"> + Welcome {% if user.last_name %}{% if user.contributor %}{{ user.contributor.get_title_display }} {% endif %}{{ user.first_name }} {{ user.last_name }}{% else %}{{ user.username }}{% endif %} + {% if request.user.contributor and not request.user.contributor.is_currently_available %} + <a href="javascript:;" class="text-warning" data-toggle="tooltip" data-title="You are currently unavailable.<br>Check your availability on your Personal Page if this should not be the case." data-html="true"><i class="fa fa-warning"></i></a> + {% endif %} + </span> + </li> {% if user.contributor %} + {% if is_editorial_college %} + <li class="nav-item{% if '/submissions/pool/' in request.path %} active{% endif %}"> + <span class="separator">·</span> + <a class="nav-link" href="{% url 'submissions:pool' %}">Submissions Pool</a> + </li> + {% endif %} <li class="nav-item{% if '/personal_page' in request.path %} active{% endif %}"> - <a class="nav-link" href="{% url 'scipost:personal_page' %}">Personal Page</a> + <span class="separator">·</span> + <a class="nav-link" href="{% url 'scipost:personal_page' %}">My Personal Page</a> </li> + {% if perms.scipost.can_view_production %} + <li class="nav-item {% active 'production:production' %}"> + <span class="separator">·</span> + <a class="nav-link" href="{% url 'production:production' %}">Production</a> + </li> + {% endif %} {% endif %} {% if user.partner_contact %} <li class="nav-item{% if '/partners/dashboard' in request.path %} active{% endif %}"> - <a class="nav-link" href="{% url 'partners:dashboard' %}">Partner Page</a> + <span class="separator">·</span> + <a class="nav-link" href="{% url 'partners:dashboard' %}">Partner Page →</a> </li> {% endif %} - {% else %} - <li class="nav-item{% if request.path == '/login/' %} active{% endif %}"> - <a class="nav-link" href="{% url 'scipost:login' %}">Login</a> + <li class="nav-item navbar-counter"> + <span class="separator">·</span> + <div class="d-inline-block notifications_container"> + <a href="javascript:;" class="d-inline-block badge_link nav-link" id="notifications_badge" data-toggle="modal" data-target="#notification_center"> + <span class="user d-inline d-lg-none d-xl-inline">Notifications</span> + <i class="fa fa-inbox" aria-hidden="true" style="min-width: 16px;"></i> + {% live_notify_badge classes="badge badge-pill" %} + </a> + {% live_notify_list %} + </div> </li> - {% endif %} - + <li class="nav-item"> + <span class="separator">·</span> + <a class="nav-link" href="{% url 'scipost:logout' %}?next={{ request.path }}">Logout</a> + </li> + {% else %} + <li class="nav-item{% if request.path == '/login/' %} active{% endif %}"> + <a class="nav-link" href="{% url 'scipost:login' %}?next={{request.path}}">Login or register</a> + </li> + {% endif %} </ul> - <form action="{% url 'scipost:search' %}" method="get" class="form-inline search-nav-form"> - <input class="form-control mr-sm-2" id="id_q" maxlength="100" name="q" type="text" aria-label="Search" value="{{ search_query|default:'' }}"> - <button class="btn my-0" type="submit"><i class="fa fa-search"></i> Search</button> - </form> - </nav> + </div> </div> -</div> +</nav> + +<script> + function navbarIconToggle(x) { + x.classList.toggle("change"); + } +</script> diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html index 2f35382ae66abf10bf2a6289f1c83c5a733d0f5c..08696c5878df461cacc4cfe8aa311024ccec5742 100644 --- a/scipost/templates/scipost/personal_page.html +++ b/scipost/templates/scipost/personal_page.html @@ -5,16 +5,12 @@ {% block pagetitle %}: personal page{% endblock pagetitle %} -{% block breadcrumb %} - <div class="container-outside header"> - <div class="container"> - <h1>Welcome to your SciPost Personal Page{% if appellation %}, {{ appellation }}{% endif %}</h1> - </div> - </div> -{% endblock %} +{% block body_class %}{{ block.super }} personal-page{% endblock %} {% block content %} + <h1 class="highlight">Welcome to your SciPost Personal Page{% if appellation %}, {{ appellation }}{% endif %}</h1> + {% if needs_validation %} <div class="row"> @@ -113,7 +109,6 @@ {% endif %} {% if contributor %} - {# If user is contributor #} <!-- Tab: Publications --> <div class="tab-pane" id="publications" role="tabpanel"> </div><!-- End tab --> @@ -145,7 +140,6 @@ <div class="tab-pane" id="author-replies" role="tabpanel"> </div><!-- End tab --> - {# END: If user is contributor #} {% endif %} </div> diff --git a/scipost/templates/scipost/update_personal_data.html b/scipost/templates/scipost/update_personal_data.html index bf638ee1511c87ace3d42a6608f795ce215c41dc..bdc1ccf4e747e9f486be081eb6540e712d966bb6 100644 --- a/scipost/templates/scipost/update_personal_data.html +++ b/scipost/templates/scipost/update_personal_data.html @@ -71,6 +71,9 @@ <div class="text-center"> <input type="submit" class="btn btn-primary btn-lg px-3" value="Save changes" /> + {% if request.user.contributor %} + <a href="{% url 'scipost:personal_page' %}" class="btn btn-link">Back to Personal Page</a> + {% endif %} </div> </form> diff --git a/scipost/templates/scipost/vet_registration_requests.html b/scipost/templates/scipost/vet_registration_requests.html index c854c00524a4de2b089c517798dab61a815f3ed2..6a9b397bb5f44beb374eff21b1b4eee22d51775d 100644 --- a/scipost/templates/scipost/vet_registration_requests.html +++ b/scipost/templates/scipost/vet_registration_requests.html @@ -25,7 +25,7 @@ $(function() { <div class="row"> <div class="col-12"> - <div class="card card-grey"> + <div class="card bg-light"> <div class="card-body"> <h1 class="card-title">SciPost Registration requests to vet:</h1> <p class="card-text mb-0"> diff --git a/scipost/templates/widgets/nocaptcha.html b/scipost/templates/widgets/nocaptcha.html new file mode 100644 index 0000000000000000000000000000000000000000..b9ab0f308d84f9421a03e3ce26a7adbfb3569858 --- /dev/null +++ b/scipost/templates/widgets/nocaptcha.html @@ -0,0 +1,23 @@ +<script src="https://www.recaptcha.net/recaptcha/api.js{% if lang %}?hl={{ lang }}{% endif %}"></script> +<div class="g-recaptcha" data-sitekey="{{ public_key }}" {% for option,value in widget.attrs.items %}data-{{ option }}="{{ value }}" {% endfor %}></div> +<noscript> + <div style="width: 302px; height: 352px;"> + <div style="width: 302px; height: 352px; position: relative;"> + <div style="width: 302px; height: 352px; position: absolute;"> + <iframe src="https://www.recaptcha.net/recaptcha/api/fallback?k={{ public_key }}" + frameborder="0" scrolling="no" + style="width: 302px; height:352px; border-style: none;"> + </iframe> + </div> + <div style="width: 250px; height: 80px; position: absolute; border-style: none; + bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;"> + <textarea id="g-recaptcha-response" name="g-recaptcha-response" + class="recaptcha_challenge_field" + style="width: 250px; height: 80px; border: 1px solid #c1c1c1; + margin: 0px; padding: 0px; resize: none;" value=""> + </textarea> + <input type="hidden" name="recaptcha_response_field" value="manual_challenge" /> + </div> + </div> + </div> +</noscript> diff --git a/scipost/templatetags/bootstrap.py b/scipost/templatetags/bootstrap.py index 15e0199c880c93b9aca691f22f7cfc6fc9e30794..6ba02b0928c1054e6f2c423adb22991f6289d440 100644 --- a/scipost/templatetags/bootstrap.py +++ b/scipost/templatetags/bootstrap.py @@ -71,6 +71,16 @@ def add_input_classes(field, extra_classes=''): field.field.widget.attrs['class'] = field_classes +@register.filter +def add_css_class(field, extra_class): + """Add additional CSS classes to a field in the template.""" + if not is_checkbox(field) and not is_multiple_checkbox(field) \ + and not is_radio(field) and not is_file(field): + field_classes = field.field.widget.attrs.get('class', '') + field_classes += ' ' + extra_class + field.field.widget.attrs['class'] = field_classes + return '' + def render(element, markup_classes): element_type = element.__class__.__name__.lower() diff --git a/scipost/templatetags/user_groups.py b/scipost/templatetags/user_groups.py index 21fdecf8a109e12616e830c28fe843c320dea1ec..5cc738412825fe49dfcf7cec3f04767f7de854ea 100644 --- a/scipost/templatetags/user_groups.py +++ b/scipost/templatetags/user_groups.py @@ -38,15 +38,14 @@ def is_financial_admin(user): def is_editorial_college(user): """ Assign template variable (boolean) to check if user is member of Editorial College group. - - !!! - This filter should actually be dynamic, not checking the permissions group but the current - active Fellowship instances for the user. - !!! - - This assignment is limited to a certain context block! """ - return user.groups.filter(name='Editorial College').exists() or user.is_superuser + if not hasattr(user, 'contributor'): + return False + if user.is_superuser: + return True + elif user.groups.filter(name='Editorial College').exists(): + return True + return user.contributor.fellowships.exists() @register.simple_tag diff --git a/scipost/urls.py b/scipost/urls.py index 250f1309f18c906d99bf6d48f53a18401a7551eb..bf8b31a84836303c92a879faac47137e18af328e 100644 --- a/scipost/urls.py +++ b/scipost/urls.py @@ -37,8 +37,7 @@ urlpatterns = [ ), url( r'^PlanSciPost$', - permission_required('scipost.can_attend_VGMs')( - TemplateView.as_view(template_name='scipost/PlanSciPost.html')), + TemplateView.as_view(template_name='scipost/PlanSciPost.html'), name='PlanSciPost' ), url(r'^foundation$', TemplateView.as_view(template_name='scipost/foundation.html'), @@ -110,7 +109,7 @@ urlpatterns = [ # Authentication url(r'^login/$', views.login_view, name='login'), - url(r'^logout$', views.logout_view, name='logout'), + url(r'^logout$', views.SciPostLogoutView.as_view(), name='logout'), url(r'^change_password$', views.change_password, name='change_password'), url(r'^reset_password_confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$', views.reset_password_confirm, name='reset_password_confirm'), @@ -159,6 +158,13 @@ urlpatterns = [ url(r'^vet_authorship_claim/(?P<claim_id>[0-9]+)/(?P<claim>[0-1])$', views.vet_authorship_claim, name='vet_authorship_claim'), + # Potential duplicates + url(r'contributor_duplicates/$', + views.ContributorDuplicateListView.as_view(), + name='contributor_duplicates'), + url(r'contributor_merge/$', + views.contributor_merge, + name='contributor_merge'), #################### # Email facilities # diff --git a/scipost/views.py b/scipost/views.py index f399522d10c023b8f955b3a33d7947329d5575f4..9159ae59caf960e4acff15f65e27c1b910cda34a 100644 --- a/scipost/views.py +++ b/scipost/views.py @@ -8,19 +8,21 @@ from django.utils import timezone from django.shortcuts import get_object_or_404, render from django.conf import settings from django.contrib import messages -from django.contrib.auth import login, logout, update_session_auth_hash +from django.contrib.auth import login, update_session_auth_hash from django.contrib.auth.decorators import login_required from django.contrib.auth.models import Group -from django.contrib.auth.views import password_reset, password_reset_confirm +from django.contrib.auth.views import password_reset, password_reset_confirm, LogoutView from django.core import mail from django.core.exceptions import PermissionDenied from django.core.mail import EmailMessage, EmailMultiAlternatives from django.core.paginator import Paginator -from django.core.urlresolvers import reverse -from django.db.models import Prefetch +from django.core.urlresolvers import reverse, reverse_lazy +from django.db import transaction from django.http import Http404 from django.shortcuts import redirect from django.template import Context, Template +from django.utils.decorators import method_decorator +from django.views.decorators.cache import never_cache from django.views.decorators.http import require_POST from django.views.generic.list import ListView from django.views.debug import cleanse_setting @@ -33,13 +35,13 @@ from .constants import ( SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS, subject_areas_raw_dict, SciPost_from_addresses_dict, NORMAL_CONTRIBUTOR) from .decorators import has_contributor, is_contributor_user -from .models import ( - Contributor, UnavailabilityPeriod, AuthorshipClaim, EditorialCollege, - EditorialCollegeFellowship) +from .models import Contributor, UnavailabilityPeriod, AuthorshipClaim, EditorialCollege from .forms import ( AuthenticationForm, UnavailabilityPeriodForm, RegistrationForm, AuthorshipClaimForm, SearchForm, VetRegistrationForm, reg_ref_dict, UpdatePersonalDataForm, UpdateUserDataForm, - PasswordChangeForm, EmailGroupMembersForm, EmailParticularForm, SendPrecookedEmailForm) + PasswordChangeForm, ContributorMergeForm, + EmailGroupMembersForm, EmailParticularForm, SendPrecookedEmailForm) +from .mixins import PermissionsMixin, PaginationMixin from .utils import Utils, EMAIL_FOOTER, SCIPOST_SUMMARY_FOOTER, SCIPOST_SUMMARY_FOOTER_HTML from affiliations.forms import AffiliationsFormset @@ -49,7 +51,7 @@ from commentaries.models import Commentary from comments.models import Comment from invitations.constants import STATUS_REGISTERED from invitations.models import RegistrationInvitation -from journals.models import Publication, Journal, PublicationAuthorsTable +from journals.models import Publication, PublicationAuthorsTable from news.models import NewsItem from organizations.models import Organization from partners.models import MembershipAgreement @@ -116,9 +118,10 @@ class SearchView(SearchView): def index(request): """Homepage view of SciPost.""" context = { + 'news_items': NewsItem.objects.homepage().order_by('-date')[:4], 'latest_newsitem': NewsItem.objects.homepage().order_by('-date').first(), 'submissions': Submission.objects.public().order_by('-submission_date')[:3], - 'journals': Journal.objects.order_by('name'), + # 'journals': Journal.objects.order_by('name'), 'publications': Publication.objects.published().order_by('-publication_date', '-paper_nr')[:3], 'current_sponsors': (Organization.objects.with_subsidy_above_and_up_to(5000, 1000000000) @@ -154,6 +157,7 @@ def feeds(request): # Contributors: ################ +@transaction.atomic def register(request): """ Contributor registration form page. @@ -404,12 +408,18 @@ def login_view(request): return render(request, 'scipost/login.html', context) -def logout_view(request): - """Logout form page.""" - logout(request) - messages.success(request, ('<h3>Keep contributing!</h3>' - 'You are now logged out of SciPost.')) - return redirect(reverse('scipost:index')) +class SciPostLogoutView(LogoutView): + """Logout processing page.""" + + next_page = reverse_lazy('scipost:index') + redirect_field_name = 'next' + + @method_decorator(never_cache) + def dispatch(self, request, *args, **kwargs): + """Add message to request after logout.""" + response = super().dispatch(request, *args, **kwargs) + messages.success(request, '<h3>Keep contributing!</h3> You are now logged out of SciPost.') + return response @login_required @@ -488,6 +498,7 @@ def _personal_page_editorial_actions(request): 'Editorial College', 'Vetting Editors', 'Junior Ambassadors']).exists() or request.user.is_superuser + permission = permission or request.user.contributor.is_MEC() if not permission: raise PermissionDenied @@ -958,6 +969,86 @@ def contributor_info(request, contributor_id): return render(request, 'scipost/contributor_info.html', context) +class ContributorDuplicateListView(PermissionsMixin, PaginationMixin, ListView): + """ + List Contributors with potential (not yet handled) duplicates. + Two sources of duplicates are separately considered: + - duplicate full names (last name + first name) + - duplicate email addresses. + + """ + permission_required = 'scipost.can_vet_registration_requests' + model = Contributor + template_name = 'scipost/contributor_duplicate_list.html' + + def get_queryset(self): + queryset = Contributor.objects.all() + if self.request.GET.get('kind') == 'names': + queryset = queryset.with_duplicate_names() + elif self.request.GET.get('kind') == 'emails': + queryset = queryset.with_duplicate_emails() + else: + queryset = queryset.with_duplicate_names() + return queryset + + def get_context_data(self, *args, **kwargs): + context = super().get_context_data(*args, **kwargs) + + if len(context['object_list']) > 1: + initial = { + 'to_merge': context['object_list'][0].id, + 'to_merge_into': context['object_list'][1].id + } + context['merge_form'] = ContributorMergeForm(initial=initial) + return context + + +@transaction.atomic +@permission_required('scipost.can_vet_registration_requests') +def contributor_merge(request): + """ + Handles the merging of data from one Contributor instance to another, + to solve one person - multiple registrations issues. + + Both instances are preserved, but the merge_from instance's + status is set to DOUBLE_ACCOUNT and its User is set to inactive. + + If both Contributor instances were active, then the account owner + is emailed with information about the merge. + """ + merge_form = ContributorMergeForm(request.POST or None, initial=request.GET) + context = {'merge_form': merge_form} + + if request.method == 'POST': + if merge_form.is_valid(): + contributor = merge_form.save() + messages.success(request, 'Contributors merged') + return redirect(reverse('scipost:contributor_duplicates')) + else: + try: + context.update({ + 'contributor_to_merge': get_object_or_404( + Contributor, pk=merge_form.cleaned_data['to_merge'].id), + 'contributor_to_merge_into': get_object_or_404( + Contributor, pk=merge_form.cleaned_data['to_merge_into'].id) + }) + except ValueError: + raise Http404 + + elif request.method == 'GET': + try: + context.update({ + 'contributor_to_merge': get_object_or_404(Contributor, + pk=int(request.GET['to_merge'])), + 'contributor_to_merge_into': get_object_or_404(Contributor, + pk=int(request.GET['to_merge_into'])), + }) + except ValueError: + raise Http404 + + return render(request, 'scipost/contributor_merge.html', context) + + #################### # Email facilities # #################### diff --git a/scipost/widgets.py b/scipost/widgets.py index ee2eadc4fceefe78194bcb77fb28de22f730662d..45cc00630bc22ebc04ca4a7533e69a9c8d054be3 100644 --- a/scipost/widgets.py +++ b/scipost/widgets.py @@ -1,9 +1,49 @@ __copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" __license__ = "AGPL v3" +import json -from django.forms.widgets import CheckboxSelectMultiple +from django.forms.widgets import CheckboxSelectMultiple, Widget +from django.utils.safestring import mark_safe class SelectButtonWidget(CheckboxSelectMultiple): template_name = 'widgets/checkbox_as_btn.html' + + +class ReCaptcha(Widget): + recaptcha_response_name = 'g-recaptcha-response' + recaptcha_challenge_name = 'g-recaptcha-response' + template_name = 'widgets/nocaptcha.html' + + def __init__(self, public_key, *args, **kwargs): + super().__init__(*args, **kwargs) + self.public_key = public_key + + def value_from_datadict(self, data, files, name): + return [ + data.get(self.recaptcha_challenge_name, None), + data.get(self.recaptcha_response_name, None) + ] + + def get_context(self, name, value, attrs): + try: + lang = attrs['lang'] + except KeyError: + # Get the generic language code + lang = 'en' + + try: + context = super().get_context(name, value, attrs) + except AttributeError: + context = { + "widget": { + "attrs": self.build_attrs(attrs) + } + } + context.update({ + 'public_key': self.public_key, + 'lang': lang, + 'options': mark_safe(json.dumps(self.attrs, indent=2)), + }) + return context diff --git a/start_celery.sh b/start_celery.sh index c15d6a49669f34215474d1aba3cec705a9f73513..7c66323c4fb8632539f60fcc33e2cb5319df4929 100755 --- a/start_celery.sh +++ b/start_celery.sh @@ -1,5 +1,7 @@ #!/bin/bash +cd /Users/jorrandewit/Documents/Develop/SciPost/scipost_v1 && source venv/bin/activate + mkdir -p ./local_files/logs touch ./local_files/logs/celery_worker.log touch ./local_files/logs/celery_beat.log diff --git a/stats/views.py b/stats/views.py index 1d79d329cb3af2b82dd410468b3d5b6d354e3e88..f9ccf9ceead3cfba8c012f0e5080756e62fe7f72 100644 --- a/stats/views.py +++ b/stats/views.py @@ -24,7 +24,7 @@ def statistics(request, journal_doi_label=None, volume_nr=None, issue_nr=None, y context['year'] = year context['citedby_impact_factor'] = journal.citedby_impact_factor(year) submissions = Submission.objects.filter( - submitted_to_journal=journal_doi_label).originally_submitted( + submitted_to__doi_label=journal_doi_label).originally_submitted( datetime.date(int(year), 1, 1), datetime.date(int(year), 12, 31)) context['submissions'] = submissions nr_ref_inv = 0 @@ -36,12 +36,12 @@ def statistics(request, journal_doi_label=None, volume_nr=None, issue_nr=None, y nr_rep_obt_con = 0 for submission in submissions: nr_ref_inv += submission.referee_invitations.count() - nr_acc += submission.count_accepted_invitations() - nr_dec += submission.count_declined_invitations() - nr_pen += submission.count_pending_invitations() - nr_rep_obt += submission.count_obtained_reports() - nr_rep_obt_inv += submission.count_invited_reports() - nr_rep_obt_con += submission.count_contrib_reports() + nr_acc += submission.referee_invitations.accepted().count() + nr_dec += submission.referee_invitations.declined().count() + nr_pen += submission.referee_invitations.awaiting_response().count() + nr_rep_obt += submission.reports.accepted().count() + nr_rep_obt_inv += submission.reports.accepted().invited().count() + nr_rep_obt_con += submission.reports.acccepted().contributed().count() context['nr_ref_inv'] = nr_ref_inv context['nr_acc'] = nr_acc context['nr_dec'] = nr_dec diff --git a/submissions/admin.py b/submissions/admin.py index fa235416b9ffe5ce731bd4bc62072f579a356ef0..43a8cf5f2693715798f60c5fa9be8f2c55555fa7 100644 --- a/submissions/admin.py +++ b/submissions/admin.py @@ -7,7 +7,6 @@ from django import forms from guardian.admin import GuardedModelAdmin -from preprints.models import Preprint from submissions.models import ( Submission, EditorialAssignment, RefereeInvitation, Report, EditorialCommunication, EICRecommendation, SubmissionEvent, iThenticateReport) @@ -18,7 +17,12 @@ def submission_short_title(obj): return obj.submission.title[:30] -admin.site.register(iThenticateReport) +class iThenticateReportAdmin(admin.ModelAdmin): + list_display = ['doc_id', 'to_submission', 'status'] + list_filter = ['status'] + + +admin.site.register(iThenticateReport, iThenticateReportAdmin) class SubmissionAdminForm(forms.ModelForm): @@ -31,6 +35,8 @@ class SubmissionAdminForm(forms.ModelForm): authors_false_claims = forms.ModelMultipleChoiceField( required=False, queryset=Contributor.objects.order_by('user__last_name')) + is_resubmission_of = forms.ModelChoiceField( + queryset=Submission.objects.order_by('-preprint__identifier_w_vn_nr')) class Meta: model = Submission @@ -40,8 +46,9 @@ class SubmissionAdminForm(forms.ModelForm): class SubmissionAdmin(GuardedModelAdmin): date_hierarchy = 'submission_date' form = SubmissionAdminForm - list_display = ('title', 'author_list', 'status', 'submission_date', 'publication') - list_filter = ('status', 'discipline', 'submission_type', 'submitted_to_journal') + list_display = ('title', 'author_list', 'submitted_to', + 'status', 'submission_date', 'publication') + list_filter = ('status', 'discipline', 'submission_type', 'submitted_to') search_fields = ['submitted_by__user__last_name', 'title', 'author_list', 'abstract'] raw_id_fields = ('editor_in_charge', 'submitted_by') readonly_fields = ('publication',) @@ -49,7 +56,7 @@ class SubmissionAdmin(GuardedModelAdmin): # Admin fields should be added in the fieldsets radio_fields = { "discipline": admin.VERTICAL, - "submitted_to_journal": admin.VERTICAL, + "submitted_to": admin.VERTICAL, "refereeing_cycle": admin.HORIZONTAL, "submission_type": admin.VERTICAL } @@ -64,8 +71,10 @@ class SubmissionAdmin(GuardedModelAdmin): }), ('Versioning', { 'fields': ( + 'thread_hash', 'is_current', - 'is_resubmission', + '_is_resubmission', + 'is_resubmission_of', 'list_of_changes'), }), ('Submission details', { @@ -100,7 +109,7 @@ class SubmissionAdmin(GuardedModelAdmin): 'referees_flagged', 'referees_suggested', 'remarks_for_editors', - 'submitted_to_journal', + 'submitted_to', 'proceedings', 'pdf_refereeing_pack', 'plagiarism_report', @@ -109,7 +118,7 @@ class SubmissionAdmin(GuardedModelAdmin): }), ('Meta', { 'classes': ('collapse',), - 'fields': ('metadata', 'submission_date'), + 'fields': ('metadata', 'submission_date', 'needs_conflicts_update'), }), ) @@ -132,7 +141,6 @@ class EditorialAssignmentAdmin(admin.ModelAdmin): list_display = ( 'to', submission_short_title, 'status', 'date_created', 'date_invited', 'invitation_order') date_hierarchy = 'date_created' - # list_filter = ('accepted', 'deprecated', 'completed', ) list_filter = ('status',) form = EditorialAssignmentAdminForm diff --git a/submissions/apps.py b/submissions/apps.py index 1c00c7f7852f5ad4bbff43763dbec6d7652f862a..52b874cf9bddd0f8c11f7f17a8ce3dac699cdcab 100644 --- a/submissions/apps.py +++ b/submissions/apps.py @@ -21,3 +21,5 @@ class SubmissionsConfig(AppConfig): sender=models.EditorialAssignment) post_save.connect(signals.notify_new_referee_invitation, sender=models.RefereeInvitation) + post_save.connect(signals.notify_new_communication, + sender=models.EditorialCommunication) diff --git a/submissions/constants.py b/submissions/constants.py index d32c9d654fc4079dac178a3d6c4ca503c04f69b2..3845028dc260c38a292a4555a0f4b1c64b54081d 100644 --- a/submissions/constants.py +++ b/submissions/constants.py @@ -46,6 +46,7 @@ NO_REQUIRED_ACTION_STATUSES = [ ] SUBMISSION_TYPE = ( + # ('', None), ('Letter', 'Letter (broad-interest breakthrough results)'), ('Article', 'Article (in-depth reports on specialized research)'), ('Review', 'Review (candid snapshot of current research in a given area)'), @@ -203,11 +204,24 @@ EIC_REC_STATUSES = ( (DEPRECATED, 'Editorial Recommendation deprecated'), ) +# Plagiarism Report statuses +STATUS_WAITING = 'waiting' +STATUS_SENT, STATUS_RECEIVED = 'sent', 'received' +STATUS_FAILED_DOWNLOAD, STATUS_FAILED_UPLOAD = 'fail_down', 'fail_up' + +PLAGIARISM_STATUSES = ( + (STATUS_WAITING, 'Awaiting action'), + (STATUS_SENT, 'Sent succesfully, awaiting report'), + (STATUS_RECEIVED, 'Report received'), + (STATUS_FAILED_DOWNLOAD, 'Failed (downloading failed)'), + (STATUS_FAILED_UPLOAD, 'Failed (uploading failed)'), +) + # Define regexes arxiv_regex_wo_vn = '[0-9]{4,}.[0-9]{4,}' arxiv_regex_w_vn = '[0-9]{4,}.[0-9]{4,}v[0-9]{1,2}' -scipost_regex_wo_vn = 'scipost_[0-9]{4,}.[0-9]{4,}' -scipost_regex_w_vn = 'scipost_[0-9]{4,}.[0-9]{4,}v[0-9]{1,2}' +scipost_regex_wo_vn = 'scipost_[0-9]{4,}_[0-9]{4,}' +scipost_regex_w_vn = 'scipost_[0-9]{4,}_[0-9]{4,}v[0-9]{1,2}' SUBMISSIONS_NO_VN_REGEX = '(?P<identifier_wo_vn_nr>(%s|%s))' % (arxiv_regex_wo_vn, scipost_regex_wo_vn) SUBMISSIONS_COMPLETE_REGEX = '(?P<identifier_w_vn_nr>(%s|%s))' % (arxiv_regex_w_vn, scipost_regex_w_vn) SCIPOST_PREPRINT_W_VN_REGEX = '(?P<identifier_w_vn_nr>%s)' % scipost_regex_w_vn diff --git a/submissions/factories.py b/submissions/factories.py index 647bfd22ecfe89d1fdf09bba1d77c6288dc1623f..64afff7b448b0890b0d840f4765bd93851bc8fc2 100644 --- a/submissions/factories.py +++ b/submissions/factories.py @@ -26,7 +26,7 @@ class SubmissionFactory(factory.django.DjangoModelFactory): author_list = factory.Faker('name') submitted_by = factory.Iterator(Contributor.objects.all()) submission_type = factory.Iterator(SUBMISSION_TYPE, getter=lambda c: c[0]) - submitted_to_journal = factory.Sequence(lambda n: random_scipost_journal()) + submitted_to = factory.Sequence(lambda n: Journal.objects.get(doi_label=random_scipost_journal())) title = factory.Faker('sentence') abstract = factory.Faker('paragraph', nb_sentences=10) identifier_wo_vn_nr = factory.Sequence( @@ -153,7 +153,7 @@ class ResubmittedSubmissionFactory(EICassignedSubmissionFactory): self.submitted_by = submission.submitted_by self.editor_in_charge = submission.editor_in_charge self.submission_type = submission.submission_type - self.submitted_to_journal = submission.submitted_to_journal + self.submitted_to = submission.submitted_to self.title = submission.title self.subject_area = submission.subject_area self.domain = submission.domain @@ -207,7 +207,7 @@ class ResubmissionFactory(EICassignedSubmissionFactory): self.submitted_by = submission.submitted_by self.editor_in_charge = submission.editor_in_charge self.submission_type = submission.submission_type - self.submitted_to_journal = submission.submitted_to_journal + self.submitted_to = submission.submitted_to self.title = submission.title self.subject_area = submission.subject_area self.domain = submission.domain @@ -232,7 +232,7 @@ class PublishedSubmissionFactory(EICassignedSubmissionFactory): if create and extracted is not False: from journals.factories import PublicationFactory PublicationFactory( - journal=self.submitted_to_journal, + journal=self.submitted_to.doi_label, accepted_submission=self, title=self.title, author_list=self.author_list) @factory.post_generation diff --git a/submissions/forms.py b/submissions/forms.py index 760caedcc777698782d07ac08c64c4c3f944f579..d1409ffe6239699749a970d5d2c2e31b68663d8c 100644 --- a/submissions/forms.py +++ b/submissions/forms.py @@ -27,16 +27,14 @@ from .models import ( iThenticateReport, EditorialCommunication) from .signals import notify_manuscript_accepted -from common.helpers import get_new_secrets_key from colleges.models import Fellowship -from invitations.models import RegistrationInvitation +from journals.models import Journal from journals.constants import SCIPOST_JOURNAL_PHYSICS_PROC, SCIPOST_JOURNAL_PHYSICS from mails.utils import DirectMailUtil -from preprints.helpers import generate_new_scipost_identifier, format_scipost_identifier +from preprints.helpers import generate_new_scipost_identifier from preprints.models import Preprint from production.utils import get_or_create_production_stream -from profiles.models import Profile -from scipost.constants import SCIPOST_SUBJECT_AREAS, INVITATION_REFEREEING +from scipost.constants import SCIPOST_SUBJECT_AREAS from scipost.services import ArxivCaller from scipost.models import Contributor, Remark import strings @@ -97,182 +95,241 @@ class SubmissionPoolFilterForm(forms.Form): # Submission and resubmission # ############################### -class SubmissionChecks: - """Mixin with checks run at least the Submission creation form.""" +class SubmissionService: + """ + Object to run checks for prefiller and submit manuscript forms. + """ - use_arxiv_preprint = True - arxiv_data = {} - is_resubmission = False - last_submission = None + metadata = {} - def __init__(self, *args, **kwargs): - self.requested_by = kwargs.pop('requested_by', None) - super().__init__(*args, **kwargs) - # Prefill `is_resubmission` property if data is coming from initial data - if kwargs.get('initial', None): - if kwargs['initial'].get('is_resubmission', None): - self.is_resubmission = kwargs['initial']['is_resubmission'] in ('True', True) - - # `is_resubmission` property if data is coming from (POST) request - if kwargs.get('data', None): - if kwargs['data'].get('is_resubmission', None): - self.is_resubmission = kwargs['data']['is_resubmission'] in ('True', True) - - def _submission_already_exists(self, identifier): - if Submission.objects.filter(preprint__identifier_w_vn_nr=identifier).exists(): - error_message = 'This preprint version has already been submitted to SciPost.' - raise forms.ValidationError(error_message, code='duplicate') + def __init__(self, requested_by, preprint_server, identifier=None, resubmission_of_id=None): + self.requested_by = requested_by + self.preprint_server = preprint_server + self.identifier = identifier + self.resubmission_of_id = resubmission_of_id + self._arxiv_data = None + + @property + def latest_submission(self): + """ + Return latest version of preprint series or None. + """ + if hasattr(self, '_latest_submission'): + return self._latest_submission + + if self.identifier: + # Check if is resubmission when identifier data is submitted. + identifier = self.identifier.rpartition('v')[0] + self._latest_submission = Submission.objects.filter( + preprint__identifier_wo_vn_nr=identifier).order_by( + '-preprint__vn_nr').first() + elif self.resubmission_of_id: + # Resubmission (submission id) is selected by user. + try: + self._latest_submission = Submission.objects.filter( + id=int(self.resubmission_of_id)).order_by('-preprint__vn_nr').first() + except ValueError: + self._latest_submission = None + else: + self._latest_submission = None + return self._latest_submission + + @property + def arxiv_data(self): + if self._arxiv_data is None: + self._call_arxiv() + return self._arxiv_data + + def run_checks(self): + """ + Do several pre-checks (using the arXiv API if needed). + + This is needed for both the prefill and submission forms. + """ + self._submission_already_exists() + self._submission_previous_version_is_valid_for_submission() + + if self.preprint_server == 'arxiv': + self._submission_is_already_published() + + def _call_arxiv(self): + """ + Retrieve all data from the ArXiv database for `identifier`. + """ + if self.preprint_server != 'arxiv': + # Do the call here to prevent multiple calls to the arXiv API in one request. + self._arxiv_data = {} + return + if not self.identifier: + print('crap', self.identifier) + return + + caller = ArxivCaller(self.identifier) - def _call_arxiv(self, identifier): - caller = ArxivCaller(identifier) if caller.is_valid: - self.arxiv_data = caller.data + self._arxiv_data = caller.data self.metadata = caller.metadata else: error_message = 'A preprint associated to this identifier does not exist.' raise forms.ValidationError(error_message) - def _submission_is_already_published(self, identifier): - published_id = None - if 'arxiv_doi' in self.arxiv_data: - published_id = self.arxiv_data['arxiv_doi'] - elif 'arxiv_journal_ref' in self.arxiv_data: - published_id = self.arxiv_data['arxiv_journal_ref'] + def get_latest_submission_data(self): + """ + Return initial form data originating from earlier Submission. + """ + if self.is_resubmission(): + return { + 'title': self.latest_submission.title, + 'abstract': self.latest_submission.abstract, + 'author_list': self.latest_submission.author_list, + 'discipline': self.latest_submission.discipline, + 'domain': self.latest_submission.domain, + 'referees_flagged': self.latest_submission.referees_flagged, + 'referees_suggested': self.latest_submission.referees_suggested, + 'secondary_areas': self.latest_submission.secondary_areas, + 'subject_area': self.latest_submission.subject_area, + 'submitted_to': self.latest_submission.submitted_to, + 'submission_type': self.latest_submission.submission_type, + } + return {} - if published_id: - error_message = ('This paper has been published under DOI %(published_id)s' - '. Please comment on the published version.'), - raise forms.ValidationError(error_message, code='published', - params={'published_id': published_id}) + def is_resubmission(self): + """ + Check if Submission is a SciPost or arXiv resubmission. + """ + return self.latest_submission is not None - def _submission_previous_version_is_valid_for_submission(self, identifier): - """Check if previous submitted versions have the appropriate status.""" - identifiers = self.identifier_into_parts(identifier) - submission = (Submission.objects - .filter(preprint__identifier_wo_vn_nr=identifiers['identifier_wo_vn_nr']) - .order_by('preprint__vn_nr').last()) - - # If submissions are found; check their statuses - if submission: - self.last_submission = submission - if submission.open_for_resubmission: - self.is_resubmission = True - if self.requested_by.contributor not in submission.authors.all(): - error_message = ('There exists a preprint with this arXiv identifier ' - 'but an earlier version number. Resubmission is only possible' - ' if you are a registered author of this manuscript.') - raise forms.ValidationError(error_message) - elif submission.status == STATUS_REJECTED: - error_message = ('This arXiv preprint has previously undergone refereeing ' - 'and has been rejected. Resubmission is only possible ' - 'if the manuscript has been substantially reworked into ' - 'a new arXiv submission with distinct identifier.') - raise forms.ValidationError(error_message) - else: - error_message = ('There exists a preprint with this arXiv identifier ' - 'but an earlier version number, which is still undergoing ' - 'peer refereeing. ' - 'A resubmission can only be performed after request ' - 'from the Editor-in-charge. Please wait until the ' - 'closing of the previous refereeing round and ' - 'formulation of the Editorial Recommendation ' - 'before proceeding with a resubmission.') - raise forms.ValidationError(error_message) + def identifier_matches_regex(self, journal_code): + """ + Check if identifier is valid for the Journal submitting to. + """ + if self.preprint_server != 'arxiv': + # Only check arXiv identifiers + return - def identifier_matches_regex(self, identifier, journal_code): - """Check if arXiv identifier is valid for the Journal submitting to.""" if journal_code in EXPLICIT_REGEX_MANUSCRIPT_CONSTRAINTS.keys(): regex = EXPLICIT_REGEX_MANUSCRIPT_CONSTRAINTS[journal_code] else: regex = EXPLICIT_REGEX_MANUSCRIPT_CONSTRAINTS['default'] pattern = re.compile(regex) - if not pattern.match(identifier): + if not pattern.match(self.identifier): # No match object returned, identifier is invalid error_message = ('The journal you want to submit to does not allow for this' - ' arXiv identifier. Please contact SciPost if you have' + ' identifier. Please contact SciPost if you have' ' any further questions.') - raise forms.ValidationError(error_message, code='submitted_to_journal') - - def submission_is_resubmission(self): - """Check if the Submission is a resubmission.""" - return self.is_resubmission - - def identifier_into_parts(self, identifier): - """Split the preprint identifier into parts.""" - data = { - 'identifier_w_vn_nr': identifier, - 'identifier_wo_vn_nr': identifier.rpartition('v')[0], - 'vn_nr': int(identifier.rpartition('v')[2]) - } - return data + raise forms.ValidationError(error_message, code='submitted_to') - def do_pre_checks(self, identifier): - """Group call of different checks.""" - self._submission_already_exists(identifier) - if self.use_arxiv_preprint: - self._call_arxiv(identifier) - self._submission_is_already_published(identifier) - self._submission_previous_version_is_valid_for_submission(identifier) + def process_resubmission_procedure(self, submission): + """ + Update all fields for new and old Submission and EditorialAssignments to comply with + the resubmission procedures. + -- submission: the new version of the Submission series. + """ + if not self.latest_submission: + raise Submission.DoesNotExist -class SubmissionIdentifierForm(SubmissionChecks, forms.Form): - """Prefill SubmissionForm using this form that takes an arXiv ID only.""" + # Close last submission + Submission.objects.filter(id=self.latest_submission.id).update( + is_current=False, open_for_reporting=False, status=STATUS_RESUBMITTED) - IDENTIFIER_PLACEHOLDER = 'new style (with version nr) ####.####(#)v#(#)' + # Copy Topics + submission.topics.add(*self.latest_submission.topics.all()) - identifier_w_vn_nr = forms.RegexField( - regex=IDENTIFIER_PATTERN_NEW, strip=True, - error_messages={'invalid': strings.arxiv_query_invalid}, - widget=forms.TextInput({'placeholder': IDENTIFIER_PLACEHOLDER})) + # Open for comment and reporting and copy EIC info + Submission.objects.filter(id=submission.id).update( + open_for_reporting=True, + open_for_commenting=True, + is_resubmission_of=self.latest_submission, + visible_pool=True, + refereeing_cycle=CYCLE_UNDETERMINED, + editor_in_charge=self.latest_submission.editor_in_charge, + status=STATUS_EIC_ASSIGNED, + thread_hash=self.latest_submission.thread_hash) - def clean_identifier_w_vn_nr(self): - """Do basic prechecks based on the arXiv ID only.""" - identifier = self.cleaned_data['identifier_w_vn_nr'] - self.do_pre_checks(identifier) - return identifier + # Add author(s) (claim) fields + submission.authors.add(*self.latest_submission.authors.all()) + submission.authors_claims.add(*self.latest_submission.authors_claims.all()) + submission.authors_false_claims.add(*self.latest_submission.authors_false_claims.all()) - def _gather_data_from_last_submission(self): - """Return dictionary with data coming from previous submission version.""" - if self.submission_is_resubmission(): - data = { - 'is_resubmission': True, - 'discipline': self.last_submission.discipline, - 'domain': self.last_submission.domain, - 'referees_flagged': self.last_submission.referees_flagged, - 'referees_suggested': self.last_submission.referees_suggested, - 'secondary_areas': self.last_submission.secondary_areas, - 'subject_area': self.last_submission.subject_area, - 'submitted_to_journal': self.last_submission.submitted_to_journal, - 'submission_type': self.last_submission.submission_type, - } - return data or {} + # Create new EditorialAssigment for the current Editor-in-Charge + EditorialAssignment.objects.create( + submission=submission, + to=self.latest_submission.editor_in_charge, + status=STATUS_ACCEPTED) - def request_arxiv_preprint_form_prefill_data(self): - """Return dictionary to prefill `RequestSubmissionForm`.""" - form_data = self.arxiv_data - form_data['identifier_w_vn_nr'] = self.cleaned_data['identifier_w_vn_nr'] - if self.submission_is_resubmission(): - form_data.update(self._gather_data_from_last_submission()) - return form_data + def _submission_already_exists(self): + """ + Check if preprint has already been submitted before. + """ + if Submission.objects.filter(preprint__identifier_w_vn_nr=self.identifier).exists(): + error_message = 'This preprint version has already been submitted to SciPost.' + raise forms.ValidationError(error_message, code='duplicate') + def _submission_previous_version_is_valid_for_submission(self): + """ + Check if previous submitted versions have the appropriate status. + """ -class RequestSubmissionForm(SubmissionChecks, forms.ModelForm): - """Form to submit a new Submission.""" + if self.latest_submission: + if self.latest_submission.status == STATUS_REJECTED: + # Explicitly give rejected status warning. + error_message = ('This preprint has previously undergone refereeing ' + 'and has been rejected. Resubmission is only possible ' + 'if the manuscript has been substantially reworked into ' + 'a new submission with distinct identifier.') + raise forms.ValidationError(error_message) + elif self.latest_submission.open_for_resubmission: + # Check if verified author list contains current user. + if self.requested_by.contributor not in self.latest_submission.authors.all(): + error_message = ('There exists a preprint with this identifier ' + 'but an earlier version number. Resubmission is only possible' + ' if you are a registered author of this manuscript.') + raise forms.ValidationError(error_message) + else: + # Submission has not an appropriate status for resubmission. + error_message = ('There exists a preprint with this identifier ' + 'but an earlier version number, which is still undergoing ' + 'peer refereeing. ' + 'A resubmission can only be performed after request ' + 'from the Editor-in-charge. Please wait until the ' + 'closing of the previous refereeing round and ' + 'formulation of the Editorial Recommendation ' + 'before proceeding with a resubmission.') + raise forms.ValidationError(error_message) - scipost_identifier = None + def _submission_is_already_published(self): + """ + Check if preprint number is already registered with a DOI in the *ArXiv* database. + """ + published_id = None + if 'arxiv_doi' in self.arxiv_data: + published_id = self.arxiv_data['arxiv_doi'] + elif 'arxiv_journal_ref' in self.arxiv_data: + published_id = self.arxiv_data['arxiv_journal_ref'] + + if published_id: + error_message = ('This paper has been published under DOI %(published_id)s' + '. Please comment on the published version.'), + raise forms.ValidationError(error_message, code='published', + params={'published_id': published_id}) + + +class SubmissionForm(forms.ModelForm): + """ + Form to submit a new (re)Submission. + """ identifier_w_vn_nr = forms.CharField(widget=forms.HiddenInput()) - arxiv_link = forms.URLField( - widget=forms.TextInput(attrs={'placeholder': 'ex.: arxiv.org/abs/1234.56789v1'})) preprint_file = forms.FileField() class Meta: model = Submission fields = [ - 'is_resubmission', + 'is_resubmission_of', 'discipline', - 'submitted_to_journal', + 'submitted_to', 'proceedings', 'submission_type', 'domain', @@ -285,138 +342,139 @@ class RequestSubmissionForm(SubmissionChecks, forms.ModelForm): 'list_of_changes', 'remarks_for_editors', 'referees_suggested', - 'referees_flagged' + 'referees_flagged', + 'arxiv_link', ] widgets = { - 'is_resubmission': forms.HiddenInput(), + 'is_resubmission_of': forms.HiddenInput(), 'secondary_areas': forms.SelectMultiple(choices=SCIPOST_SUBJECT_AREAS), - 'remarks_for_editors': forms.TextInput( - attrs={'placeholder': 'Any private remarks (for the editors only)', 'rows': 3}), - 'referees_suggested': forms.TextInput( - attrs={'placeholder': 'Optional: names of suggested referees', 'rows': 3}), - 'referees_flagged': forms.TextInput( - attrs={'placeholder': 'Optional: names of referees whose reports should be treated with caution (+ short reason)', 'rows': 3}), + 'arxiv_link': forms.TextInput( + attrs={'placeholder': 'ex.: arxiv.org/abs/1234.56789v1'}), + 'remarks_for_editors': forms.Textarea( + attrs={'placeholder': 'Any private remarks (for the editors only)', 'rows': 5}), + 'referees_suggested': forms.Textarea( + attrs={'placeholder': 'Optional: names of suggested referees', 'rows': 5}), + 'referees_flagged': forms.Textarea( + attrs={ + 'placeholder': 'Optional: names of referees whose reports should be treated with caution (+ short reason)', + 'rows': 5 + }), + 'author_comments': forms.Textarea( + attrs={'placeholder': 'Your resubmission letter (will be viewable online)'}), + 'list_of_changes': forms.Textarea( + attrs={'placeholder': 'Give a point-by-point list of changes (will be viewable online)'}), } def __init__(self, *args, **kwargs): - self.use_arxiv_preprint = kwargs.pop('use_arxiv_preprint', True) + self.requested_by = kwargs.pop('requested_by') + self.preprint_server = kwargs.pop('preprint_server', 'arxiv') + self.resubmission_preprint = kwargs['initial'].get('resubmission', False) + + data = args[0] if len(args) > 1 else kwargs.get('data', {}) + identifier = kwargs['initial'].get('identifier_w_vn_nr', None) or data.get('identifier_w_vn_nr') + + self.service = SubmissionService( + self.requested_by, self.preprint_server, + identifier=identifier, + resubmission_of_id=self.resubmission_preprint) + if self.preprint_server == 'scipost': + kwargs['initial'] = self.service.get_latest_submission_data() super().__init__(*args, **kwargs) - # Alter resubmission-dependent fields - if not self.submission_is_resubmission(): - # These fields are only available for resubmissions + if not self.preprint_server == 'arxiv': + # No arXiv-specific data required. + del self.fields['identifier_w_vn_nr'] + del self.fields['arxiv_link'] + elif not self.preprint_server == 'scipost': + # No need for a file upload if user is not using the SciPost preprint server. + del self.fields['preprint_file'] + + # Find all submission allowed to be resubmitted by current user. + self.fields['is_resubmission_of'].queryset = Submission.objects.candidate_for_resubmission( + self.requested_by) + + # Fill resubmission-dependent fields + if self.is_resubmission(): + self.fields['is_resubmission_of'].initial = self.service.latest_submission + else: + # These fields are only available for resubmissions. del self.fields['author_comments'] del self.fields['list_of_changes'] - else: - self.fields['author_comments'].widget.attrs.update({ - 'placeholder': 'Your resubmission letter (will be viewable online)', }) - self.fields['list_of_changes'].widget.attrs.update({ - 'placeholder': 'Give a point-by-point list of changes (will be viewable online)'}) - # ArXiv or SciPost preprint fields - if self.use_arxiv_preprint: - del self.fields['preprint_file'] - else: - del self.fields['arxiv_link'] - del self.fields['identifier_w_vn_nr'] + if not self.fields['is_resubmission_of'].initial: + # No intial nor submitted data found. + del self.fields['is_resubmission_of'] + + # Select Journal instances. + self.fields['submitted_to'].queryset = Journal.objects.active() + self.fields['submitted_to'].label = 'Journal: submit to' # Proceedings submission fields qs = self.fields['proceedings'].queryset.open_for_submission() self.fields['proceedings'].queryset = qs self.fields['proceedings'].empty_label = None if not qs.exists(): - # Open the proceedings Journal for submission - def filter_proceedings(item): - return item[0] != SCIPOST_JOURNAL_PHYSICS_PROC - - self.fields['submitted_to_journal'].choices = filter( - filter_proceedings, self.fields['submitted_to_journal'].choices) + # No proceedings issue to submit to, so adapt the form fields + self.fields['submitted_to'].queryset = self.fields['submitted_to'].queryset.exclude( + doi_label=SCIPOST_JOURNAL_PHYSICS_PROC) del self.fields['proceedings'] - # Submission type is optional - self.fields['submission_type'].required = False + def is_resubmission(self): + return self.service.is_resubmission() def clean(self, *args, **kwargs): - """Do all prechecks which are also done in the prefiller.""" + """ + Do all general checks for Submission. + """ cleaned_data = super().clean(*args, **kwargs) + + # SciPost preprints are auto-generated here. + self.scipost_identifier = None if 'identifier_w_vn_nr' not in cleaned_data: - # New series of SciPost preprints - identifier_str, self.scipost_identifier = generate_new_scipost_identifier() - cleaned_data['identifier_w_vn_nr'] = format_scipost_identifier(identifier_str) + self.service.identifier, self.scipost_identifier = generate_new_scipost_identifier( + cleaned_data.get('is_resubmission_of', None)) + # Also copy to the form data + self.cleaned_data['identifier_w_vn_nr'] = self.service.identifier - self.do_pre_checks(cleaned_data['identifier_w_vn_nr']) - self.identifier_matches_regex( - cleaned_data['identifier_w_vn_nr'], cleaned_data['submitted_to_journal']) + # Run checks again to clean any possible human intervention and run checks again + # with possibly newly generated identifier. + self.service.run_checks() + self.service.identifier_matches_regex(cleaned_data['submitted_to'].doi_label) - if self.cleaned_data['submitted_to_journal'] != SCIPOST_JOURNAL_PHYSICS_PROC: + if self.cleaned_data['submitted_to'].doi_label != SCIPOST_JOURNAL_PHYSICS_PROC: try: del self.cleaned_data['proceedings'] except KeyError: # No proceedings returned to data return cleaned_data - return cleaned_data def clean_author_list(self): - """Check if author list matches the Contributor submitting. - - The submitting user must be an author of the submission. - Also possibly may be extended to check permissions and give ultimate submission - power to certain user groups. + """ + Check if author list matches the Contributor submitting. """ author_list = self.cleaned_data['author_list'] - if not self.use_arxiv_preprint: - # Using SciPost preprints, there is nothing to check with. - return author_list - if not self.requested_by.last_name.lower() in author_list.lower(): error_message = ('Your name does not match that of any of the authors. ' 'You are not authorized to submit this preprint.') - raise forms.ValidationError(error_message, code='not_an_author') + self.add_error('author_list', error_message) return author_list def clean_submission_type(self): - """Validate Submission type. - - The SciPost Physics journal requires a Submission type to be specified. + """ + Validate Submission type for the SciPost Physics journal. """ submission_type = self.cleaned_data['submission_type'] - journal = self.cleaned_data['submitted_to_journal'] - if journal == SCIPOST_JOURNAL_PHYSICS and not submission_type: + journal_doi_label = self.cleaned_data['submitted_to'].doi_label + if journal_doi_label == SCIPOST_JOURNAL_PHYSICS and not submission_type: self.add_error('submission_type', 'Please specify the submission type.') return submission_type - @transaction.atomic - def copy_and_save_data_from_resubmission(self, submission): - """Fill given Submission with data coming from last_submission.""" - if not self.last_submission: - raise Submission.DoesNotExist - - # Close last submission - Submission.objects.filter(id=self.last_submission.id).update( - is_current=False, open_for_reporting=False, status=STATUS_RESUBMITTED) - - # Open for comment and reporting and copy EIC info - Submission.objects.filter(id=submission.id).update( - open_for_reporting=True, - open_for_commenting=True, - is_resubmission=True, - visible_pool=True, - editor_in_charge=self.last_submission.editor_in_charge, - status=STATUS_EIC_ASSIGNED) - - # Add author(s) (claim) fields - submission.authors.add(*self.last_submission.authors.all()) - submission.authors_claims.add(*self.last_submission.authors_claims.all()) - submission.authors_false_claims.add(*self.last_submission.authors_false_claims.all()) - - # Create new EditorialAssigment for the current Editor-in-Charge - EditorialAssignment.objects.create( - submission=submission, to=self.last_submission.editor_in_charge, status=STATUS_ACCEPTED) - def set_pool(self, submission): - """Set the default set of (guest) Fellows for this Submission.""" + """ + Set the default set of (guest) Fellows for this Submission. + """ qs = Fellowship.objects.active() fellows = qs.regular().filter( contributor__discipline=submission.discipline).return_active_for_submission(submission) @@ -430,46 +488,85 @@ class RequestSubmissionForm(SubmissionChecks, forms.ModelForm): @transaction.atomic def save(self): - """Fill, create and transfer data to the new Submission. - - Prefill instance before save. - Because of the ManyToManyField on `authors`, commit=False for this form - is disabled. Saving the form without the database call may loose `authors` - data without notice. + """ + Create the new Submission and Preprint instances. """ submission = super().save(commit=False) submission.submitted_by = self.requested_by.contributor # Save identifiers - identifiers = self.identifier_into_parts(self.cleaned_data['identifier_w_vn_nr']) + identifiers = self.cleaned_data['identifier_w_vn_nr'].rpartition('v') preprint, __ = Preprint.objects.get_or_create( - identifier_w_vn_nr=identifiers['identifier_w_vn_nr'], - identifier_wo_vn_nr=identifiers['identifier_wo_vn_nr'], - vn_nr=identifiers['vn_nr'], + identifier_w_vn_nr=self.cleaned_data['identifier_w_vn_nr'], + identifier_wo_vn_nr=identifiers[0], + vn_nr=identifiers[2], url=self.cleaned_data.get('arxiv_link', ''), scipost_preprint_identifier=self.scipost_identifier, _file=self.cleaned_data.get('preprint_file', None), ) # Save metadata directly from ArXiv call without possible user interception - submission.metadata = self.metadata if hasattr(self, 'metadata') else {} + submission.metadata = self.service.metadata submission.preprint = preprint - if self.submission_is_resubmission(): - # Reset Refereeing Cycle. EIC needs to pick a cycle on resubmission. - submission.refereeing_cycle = CYCLE_UNDETERMINED - submission.save() # Save before filling from old Submission. - - self.copy_and_save_data_from_resubmission(submission) - else: - # Save! - submission.save() + submission.save() + if self.is_resubmission(): + self.service.process_resubmission_procedure(submission) # Gather first known author and Fellows. submission.authors.add(self.requested_by.contributor) self.set_pool(submission) # Return latest version of the Submission. It could be outdated by now. - return Submission.objects.get(id=submission.id) + submission.refresh_from_db() + return submission + + +class SubmissionIdentifierForm(forms.Form): + """ + Prefill SubmissionForm using this form that takes an arXiv ID only. + """ + + IDENTIFIER_PLACEHOLDER = 'new style (with version nr) ####.####(#)v#(#)' + + identifier_w_vn_nr = forms.RegexField( + label='arXiv identifier with version number', + regex=IDENTIFIER_PATTERN_NEW, strip=True, + error_messages={'invalid': strings.arxiv_query_invalid}, + widget=forms.TextInput({'placeholder': IDENTIFIER_PLACEHOLDER})) + + def __init__(self, *args, **kwargs): + self.requested_by = kwargs.pop('requested_by') + return super().__init__(*args, **kwargs) + + + def clean_identifier_w_vn_nr(self): + """ + Do basic prechecks based on the arXiv ID only. + """ + identifier = self.cleaned_data.get('identifier_w_vn_nr', None) + + self.service = SubmissionService(self.requested_by, 'arxiv', identifier=identifier) + self.service.run_checks() + return identifier + + def get_initial_submission_data(self): + """ + Return dictionary to prefill `SubmissionForm`. + """ + form_data = self.service.arxiv_data + form_data['identifier_w_vn_nr'] = self.cleaned_data['identifier_w_vn_nr'] + if self.service.is_resubmission(): + form_data.update({ + 'discipline': self.service.latest_submission.discipline, + 'domain': self.service.latest_submission.domain, + 'referees_flagged': self.service.latest_submission.referees_flagged, + 'referees_suggested': self.service.latest_submission.referees_suggested, + 'secondary_areas': self.service.latest_submission.secondary_areas, + 'subject_area': self.service.latest_submission.subject_area, + 'submitted_to': self.service.latest_submission.submitted_to, + 'submission_type': self.service.latest_submission.submission_type, + }) + return form_data class SubmissionReportsForm(forms.ModelForm): @@ -757,9 +854,7 @@ class EditorialAssignmentForm(forms.ModelForm): # Update related Submission. if self.is_normal_cycle(): # Default Refereeing process - deadline = timezone.now() + datetime.timedelta(days=28) - if assignment.submission.submitted_to_journal == 'SciPostPhysLectNotes': - deadline += datetime.timedelta(days=28) + deadline = timezone.now() + self.instance.submission.submitted_to.refereeing_period # Update related Submission. Submission.objects.filter(id=self.submission.id).update( @@ -806,80 +901,9 @@ class ConsiderAssignmentForm(forms.Form): refusal_reason = forms.ChoiceField(choices=ASSIGNMENT_REFUSAL_REASONS, required=False) -class RefereeSelectForm(forms.Form): - """Pre-fill form to get the last name of the requested referee.""" - +class RefereeSearchForm(forms.Form): last_name = forms.CharField(widget=forms.TextInput({ - 'placeholder': 'Search in contributors database'})) - - -class RefereeRecruitmentForm(forms.ModelForm): - """Invite non-registered scientist to register and referee a Submission.""" - - class Meta: - model = RefereeInvitation - fields = [ - 'profile', - 'title', - 'first_name', - 'last_name', - 'email_address', - 'auto_reminders_allowed', - 'invitation_key'] - widgets = { - 'profile': forms.HiddenInput(), - 'invitation_key': forms.HiddenInput() - } - - def __init__(self, *args, **kwargs): - self.request = kwargs.pop('request', None) - self.submission = kwargs.pop('submission', None) - - initial = kwargs.pop('initial', {}) - initial['invitation_key'] = get_new_secrets_key() - kwargs['initial'] = initial - super().__init__(*args, **kwargs) - - def clean_email_address(self): - email = self.cleaned_data['email_address'] - if Contributor.objects.filter(user__email=email).exists(): - contr = Contributor.objects.get(user__email=email) - msg = ( - 'This email address is already registered. ' - 'Invite {title} {last_name} using the link above.') - self.add_error('email_address', msg.format( - title=contr.get_title_display(), last_name=contr.user.last_name)) - return email - - def save(self, commit=True): - if not self.request or not self.submission: - raise forms.ValidationError('No request or Submission given.') - - # Try to associate an existing Profile to ref/reg invitations: - profile = Profile.objects.get_unique_from_email_or_None( - email=self.cleaned_data['email_address']) - self.instance.profile = profile - - self.instance.submission = self.submission - self.instance.invited_by = self.request.user.contributor - referee_invitation = super().save(commit=False) - - registration_invitation = RegistrationInvitation( - profile=profile, - title=referee_invitation.title, - first_name=referee_invitation.first_name, - last_name=referee_invitation.last_name, - email=referee_invitation.email_address, - invitation_type=INVITATION_REFEREEING, - created_by=self.request.user, - invited_by=self.request.user, - invitation_key=referee_invitation.invitation_key, - key_expires=timezone.now() + datetime.timedelta(days=365)) - - if commit: - referee_invitation.save() - registration_invitation.save() - return (referee_invitation, registration_invitation) + 'placeholder': 'Search for a referee in the SciPost Profiles database'})) class ConsiderRefereeInvitationForm(forms.Form): @@ -889,7 +913,11 @@ class ConsiderRefereeInvitationForm(forms.Form): class SetRefereeingDeadlineForm(forms.Form): - deadline = forms.DateField(required=False, label='', widget=forms.SelectDateWidget) + deadline = forms.DateField( + required=False, label='', widget=forms.SelectDateWidget( + years=[timezone.now().year + i for i in range(2)], + empty_label=("Year", "Month", "Day"), + )) def clean_deadline(self): if not self.cleaned_data.get('deadline'): @@ -912,11 +940,15 @@ class VotingEligibilityForm(forms.ModelForm): def __init__(self, *args, **kwargs): """Get queryset of Contributors eligibile for voting.""" super().__init__(*args, **kwargs) + secondary_areas = self.instance.submission.secondary_areas + if not secondary_areas: + secondary_areas = [] + self.fields['eligible_fellows'].queryset = Contributor.objects.filter( fellowships__pool=self.instance.submission).filter( Q(EIC=self.instance.submission) | Q(expertises__contains=[self.instance.submission.subject_area]) | - Q(expertises__contains=self.instance.submission.secondary_areas)).order_by( + Q(expertises__contains=secondary_areas)).order_by( 'user__last_name').distinct() def save(self, commit=True): @@ -994,7 +1026,7 @@ class ReportForm(forms.ModelForm): required_fields_label = ['report', 'recommendation', 'qualification'] # If the Report is not a followup: Explicitly assign more fields as being required! - if not self.instance.is_followup_report: + if not self.instance.is_followup_report and self.submission.submitted_to.name != SCIPOST_JOURNAL_PHYSICS_PROC: required_fields_label += [ 'strengths', 'weaknesses', @@ -1067,11 +1099,10 @@ class VetReportForm(forms.Form): refusal_reason = forms.ChoiceField(choices=REPORT_REFUSAL_CHOICES, required=False) email_response_field = forms.CharField(widget=forms.Textarea(), label='Justification (optional)', required=False) - report = forms.ModelChoiceField(queryset=Report.objects.awaiting_vetting(), required=True, - widget=forms.HiddenInput()) def __init__(self, *args, **kwargs): - super(VetReportForm, self).__init__(*args, **kwargs) + self.report = kwargs.pop('report', None) + super().__init__(*args, **kwargs) self.fields['email_response_field'].widget.attrs.update({ 'placeholder': ('Optional: give a textual justification ' '(will be included in the email to the Report\'s author)'), @@ -1088,20 +1119,19 @@ class VetReportForm(forms.Form): def process_vetting(self, current_contributor): """Set the right report status and update submission fields if needed.""" - report = self.cleaned_data['report'] - report.vetted_by = current_contributor + self.report.vetted_by = current_contributor if self.cleaned_data['action_option'] == REPORT_ACTION_ACCEPT: # Accept the report as is - report.status = STATUS_VETTED - report.submission.latest_activity = timezone.now() - report.submission.save() + self.report.status = STATUS_VETTED + self.report.submission.latest_activity = timezone.now() + self.report.submission.save() elif self.cleaned_data['action_option'] == REPORT_ACTION_REFUSE: # The report is rejected - report.status = self.cleaned_data['refusal_reason'] + self.report.status = self.cleaned_data['refusal_reason'] else: raise exceptions.InvalidReportVettingValue(self.cleaned_data['action_option']) - report.save() - return report + self.report.save() + return self.report ################### @@ -1129,7 +1159,7 @@ class EICRecommendationForm(forms.ModelForm): DAYS_TO_VOTE = 7 assignment = None - earlier_recommendations = None + earlier_recommendations = [] class Meta: model = EICRecommendation @@ -1162,8 +1192,9 @@ class EICRecommendationForm(forms.ModelForm): """ self.submission = kwargs.pop('submission') self.reformulate = kwargs.pop('reformulate', False) + self.load_earlier_recommendations() + if self.reformulate: - self.load_earlier_recommendations() latest_recommendation = self.earlier_recommendations.first() if latest_recommendation: kwargs['initial'] = { @@ -1181,9 +1212,9 @@ class EICRecommendationForm(forms.ModelForm): recommendation = super().save(commit=False) recommendation.submission = self.submission recommendation.voting_deadline += datetime.timedelta(days=self.DAYS_TO_VOTE) # Test this + recommendation.version = len(self.earlier_recommendations) + 1 + if self.reformulate: - # Increment version number - recommendation.version = len(self.earlier_recommendations) + 1 event_text = 'The Editorial Recommendation has been reformulated: {}.' else: event_text = 'An Editorial Recommendation has been formulated: {}.' @@ -1191,7 +1222,15 @@ class EICRecommendationForm(forms.ModelForm): if recommendation.recommendation in [REPORT_MINOR_REV, REPORT_MAJOR_REV]: # Minor/Major revision: return to Author; ask to resubmit recommendation.status = DECISION_FIXED - Submission.objects.filter(id=self.submission.id).update(open_for_reporting=False) + Submission.objects.filter(id=self.submission.id).update( + open_for_reporting=False, + open_for_commenting=False, + reporting_deadline=timezone.now()) + + if self.assignment: + # The EIC has fulfilled this editorial assignment. + self.assignment.status = STATUS_COMPLETED + self.assignment.save() # Add SubmissionEvents for both Author and EIC self.submission.add_general_event(event_text.format( @@ -1211,10 +1250,6 @@ class EICRecommendationForm(forms.ModelForm): recommendation.save() - if self.assignment: - # The EIC has fulfilled this editorial assignment. - self.assignment.status = STATUS_COMPLETED - self.assignment.save() return recommendation def revision_requested(self): @@ -1432,7 +1467,8 @@ class FixCollegeDecisionForm(forms.ModelForm): def fix_decision(self, recommendation): """Fix decision of EICRecommendation.""" - EICRecommendation.objects.filter(id=recommendation.id).update(status=DECISION_FIXED) + EICRecommendation.objects.filter(id=recommendation.id).update( + status=DECISION_FIXED) submission = recommendation.submission if recommendation.recommendation in [REPORT_PUBLISH_1, REPORT_PUBLISH_2, REPORT_PUBLISH_3]: # Publish as Tier I, II or III @@ -1453,6 +1489,15 @@ class FixCollegeDecisionForm(forms.ModelForm): status=STATUS_REJECTED, latest_activity=timezone.now()) submission.get_other_versions().update(visible_public=False) + # Force-close the refereeing round for new referees. + Submission.objects.filter(id=submission.id).update( + open_for_reporting=False, + open_for_commenting=False) + + # Update Editorial Assignment statuses. + EditorialAssignment.objects.filter( + submission=submission, to=submission.editor_in_charge).update(status=STATUS_COMPLETED) + # Add SubmissionEvent for authors submission.add_event_for_author( 'The Editorial Recommendation has been formulated: {0}.'.format( diff --git a/submissions/management/commands/remind_fellows_to_submit_report.py b/submissions/management/commands/remind_fellows_to_submit_report.py index 9623097fd38df3c51894c0bb041bbc03bddc5bb5..c2a56364569360ebe41d99bc5e34e10122edb217 100644 --- a/submissions/management/commands/remind_fellows_to_submit_report.py +++ b/submissions/management/commands/remind_fellows_to_submit_report.py @@ -11,6 +11,10 @@ from ...signals import notify_invitation_approaching_deadline, notify_invitation class Command(BaseCommand): def handle(self, *args, **options): + return + + # Deprecated. + # Moving to Celery: Use tasks.py instead. for invitation in RefereeInvitation.objects.approaching_deadline(): notify_invitation_approaching_deadline(RefereeInvitation, invitation, False) for invitation in RefereeInvitation.objects.overdue(): diff --git a/submissions/managers.py b/submissions/managers.py index b723bdb6d61f8c9e28df9581f5a3f98c00d63d83..7c5a7cbf0f8ccec92840c4122bd2fb315fbb1976 100644 --- a/submissions/managers.py +++ b/submissions/managers.py @@ -17,6 +17,8 @@ now = timezone.now() class SubmissionQuerySet(models.QuerySet): def _newest_version_only(self, queryset): """ + TODO: Make more efficient... with agregation or whatever. + The current Queryset should return only the latest version of the Arxiv submissions known to SciPost. @@ -157,7 +159,7 @@ class SubmissionQuerySet(models.QuerySet): (including subsequent resubmissions, even if those came in later). """ identifiers = [] - for sub in self.filter(is_resubmission=False, + for sub in self.filter(is_resubmission_of__isnull=True, submission_date__range=(from_date, until_date)): identifiers.append(sub.preprint.identifier_wo_vn_nr) return self.filter(preprint__identifier_wo_vn_nr__in=identifiers) @@ -177,6 +179,10 @@ class SubmissionQuerySet(models.QuerySet): """Return published Submissions.""" return self.filter(status=constants.STATUS_PUBLISHED) + def unpublished(self): + """Return unpublished Submissions.""" + return self.exclude(status=constants.STATUS_PUBLISHED) + def assignment_failed(self): """Return Submissions which have failed assignment.""" return self.filter(status=constants.STATUS_ASSIGNMENT_FAILED) @@ -205,6 +211,19 @@ class SubmissionQuerySet(models.QuerySet): """Return Submissions that have EditorialAssignments that still need to be sent.""" return self.filter(editorial_assignments__status=constants.STATUS_PREASSIGNED) + def candidate_for_resubmission(self, user): + """ + Return all Submissions that are open for resubmission specialised for a certain User. + """ + if not hasattr(user, 'contributor'): + return self.none() + + return self.filter(is_current=True, status__in=[ + constants.STATUS_INCOMING, + constants.STATUS_UNASSIGNED, + constants.STATUS_EIC_ASSIGNED, + ], submitted_by=user.contributor) + class SubmissionEventQuerySet(models.QuerySet): def for_author(self): @@ -219,6 +238,16 @@ class SubmissionEventQuerySet(models.QuerySet): """Return all events of the last `hours` hours.""" return self.filter(created__gte=timezone.now() - datetime.timedelta(hours=hours)) + def plagiarism_report_to_be_uploaded(self): + """Return Submissions that has not been sent to iThenticate for their plagiarism check.""" + return self.filter( + models.Q(plagiarism_report__isnull=True) | + models.Q(plagiarism_report__status=constants.STATUS_WAITING)).distinct() + + def plagiarism_report_to_be_updated(self): + """Return Submissions for which their iThenticateReport has not received a report yet.""" + return self.filter(plagiarism_report__status=constants.STATUS_SENT) + class EditorialAssignmentQuerySet(models.QuerySet): def get_for_user_in_pool(self, user): @@ -340,50 +369,85 @@ class EICRecommendationQuerySet(models.QuerySet): class ReportQuerySet(models.QuerySet): + """QuerySet for the Report model.""" + def accepted(self): + """Return the subset of vetted Reports.""" return self.filter(status=constants.STATUS_VETTED) def awaiting_vetting(self): + """Return the subset of unvetted Reports.""" return self.filter(status=constants.STATUS_UNVETTED) def rejected(self): + """Return the subset of rejected Reports.""" return self.filter(status__in=[ constants.STATUS_UNCLEAR, constants.STATUS_INCORRECT, constants.STATUS_NOT_USEFUL, constants.STATUS_NOT_ACADEMIC]) def in_draft(self): + """Return the subset of Reports in draft.""" return self.filter(status=constants.STATUS_DRAFT) def non_draft(self): + """Return the subset of unvetted, vetted and rejected Reports.""" return self.exclude(status=constants.STATUS_DRAFT) def contributed(self): + """Return the subset of contributed Reports.""" return self.filter(invited=False) def invited(self): + """Return the subset of invited Reports.""" return self.filter(invited=True) class RefereeInvitationQuerySet(models.QuerySet): + """Queryset for RefereeInvitation model.""" + def awaiting_response(self): - return self.pending().open() + """Filter invitations awaiting response by referee.""" + return self.filter(accepted=None, cancelled=False) def pending(self): - return self.filter(accepted=None, cancelled=False) + """DEPRECATED.""" + return self.awaiting_response() + + def open(self): + """DEPRECATED.""" + return self.awaiting_response() def accepted(self): + """Filter invitations (non-cancelled) accepted by referee.""" return self.filter(accepted=True, cancelled=False) def declined(self): + """Filter invitations declined by referee.""" return self.filter(accepted=False) - def open(self): - return self.pending().filter(cancelled=False) - def in_process(self): + """Filter invitations (non-cancelled) accepted by referee that are not fulfilled.""" return self.accepted().filter(fulfilled=False, cancelled=False) + def non_cancelled(self): + """Return invitations awaiting reponse, accepted or fulfilled.""" + return self.filter(cancelled=False) + + def needs_attention(self): + """Filter invitations that needs attention. + + The following is defined as `needs attention`: + 1. not responded to invite in more than 3 days. + 2. not fulfilled (but accepted) with deadline within 7 days. + """ + compare_3_days = timezone.now() + datetime.timedelta(days=3) + compare_7_days = timezone.now() + datetime.timedelta(days=7) + return self.filter(cancelled=False, fulfilled=False).filter( + Q(accepted=None, date_last_reminded__lt=compare_3_days) | + Q(accepted=True, submission__reporting_deadline__lt=compare_7_days)).distinct() + def approaching_deadline(self, days=2): + """Filter non-fulfilled invitations for which the deadline is within `days` days.""" qs = self.in_process() pseudo_deadline = now + datetime.timedelta(days) deadline = datetime.datetime.now() @@ -392,9 +456,15 @@ class RefereeInvitationQuerySet(models.QuerySet): return qs def overdue(self): + """Filter non-fulfilled invitations that are overdue.""" return self.in_process().filter(submission__reporting_deadline__lte=now) class EditorialCommunicationQueryset(models.QuerySet): def for_referees(self): + """Only return communication between Referees and Editors.""" return self.filter(comtype__in=['EtoR', 'RtoE']) + + def for_authors(self): + """Only return communication between Authors and Editors.""" + return self.filter(comtype__in=['EtoA', 'AtoE']) diff --git a/submissions/migrations/0033_auto_20180819_1343.py b/submissions/migrations/0033_auto_20180819_1343.py new file mode 100644 index 0000000000000000000000000000000000000000..0b1087fe841bdfae03099377e7576f02f21177bc --- /dev/null +++ b/submissions/migrations/0033_auto_20180819_1343.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-08-19 11:43 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0032_merge_20180806_1236'), + ] + + operations = [ + migrations.AddField( + model_name='report', + name='created', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='report', + name='modified', + field=models.DateTimeField(auto_now=True), + ), + migrations.AlterField( + model_name='submission', + name='needs_conflicts_update', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='submission', + name='status', + field=models.CharField(choices=[('incoming', 'Submission incoming, undergoing pre-screening'), ('unassigned', 'Unassigned, awaiting editor assignment'), ('failed_pre', 'Failed pre-screening'), ('assigned', 'Editor-in-charge assigned'), ('assignment_failed', 'Failed to assign Editor-in-charge; manuscript rejected'), ('resubmitted', 'Has been resubmitted'), ('accepted', 'Publication decision taken: accept'), ('rejected', 'Publication decision taken: reject'), ('withdrawn', 'Withdrawn by the Authors'), ('published', 'Published')], default='incoming', max_length=30), + ), + ] diff --git a/submissions/migrations/0034_auto_20180820_1511.py b/submissions/migrations/0034_auto_20180820_1511.py new file mode 100644 index 0000000000000000000000000000000000000000..98f5ff4e0704781e534ce91e7c267bcc27b73283 --- /dev/null +++ b/submissions/migrations/0034_auto_20180820_1511.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-08-20 13:11 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0033_auto_20180819_1343'), + ] + + operations = [ + migrations.AlterModelOptions( + name='refereeinvitation', + options={'ordering': ['cancelled', 'date_invited']}, + ), + ] diff --git a/submissions/migrations/0035_merge_20180915_1337.py b/submissions/migrations/0035_merge_20180915_1337.py new file mode 100644 index 0000000000000000000000000000000000000000..8fa8484c2f277e17b8672d6ba60d487d5ca7e0a8 --- /dev/null +++ b/submissions/migrations/0035_merge_20180915_1337.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-09-15 11:37 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0034_auto_20180820_1511'), + ('submissions', '0034_auto_20180913_2114'), + ] + + operations = [ + ] diff --git a/submissions/migrations/0037_merge_20181009_2050.py b/submissions/migrations/0037_merge_20181009_2050.py new file mode 100644 index 0000000000000000000000000000000000000000..7941a4fddc41f4f46b45aa09fd0a2de67053a6ab --- /dev/null +++ b/submissions/migrations/0037_merge_20181009_2050.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-10-09 18:50 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0035_merge_20180915_1337'), + ('submissions', '0036_merge_20181002_1358'), + ] + + operations = [ + ] diff --git a/submissions/migrations/0038_ithenticatereport_status.py b/submissions/migrations/0038_ithenticatereport_status.py new file mode 100644 index 0000000000000000000000000000000000000000..19720df34494cd221409e178e942eba693f0ffe6 --- /dev/null +++ b/submissions/migrations/0038_ithenticatereport_status.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-10-11 05:46 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0037_merge_20181009_2050'), + ] + + operations = [ + migrations.AddField( + model_name='ithenticatereport', + name='status', + field=models.CharField(choices=[('waiting', 'Awaiting action'), ('sent', 'Sent succesfully, awaiting report'), ('received', 'Report received'), ('fail_down', 'Failed (downloading failed)'), ('fail_up', 'Failed (uploading failed)')], default='waiting', max_length=16), + ), + ] diff --git a/submissions/migrations/0039_auto_20181011_0746.py b/submissions/migrations/0039_auto_20181011_0746.py new file mode 100644 index 0000000000000000000000000000000000000000..27022e383d2ac3f751dd927c156d0ad58b715224 --- /dev/null +++ b/submissions/migrations/0039_auto_20181011_0746.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-10-11 05:46 +from __future__ import unicode_literals + +from django.db import migrations + + +def upload_current_plagiarism_statuses(apps, schema_editor): + iThenticateReport = apps.get_model('submissions', 'iThenticateReport') + iThenticateReport.objects.all().update(status='received') + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0038_ithenticatereport_status'), + ] + + operations = [ + migrations.RunPython(upload_current_plagiarism_statuses), + ] diff --git a/submissions/migrations/0039_submission_topics.py b/submissions/migrations/0039_submission_topics.py new file mode 100644 index 0000000000000000000000000000000000000000..1435362c0ba7c3e7adb885a006f3c871eb756b9e --- /dev/null +++ b/submissions/migrations/0039_submission_topics.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-10-29 19:04 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ontology', '0005_auto_20181028_2038'), + ('submissions', '0038_auto_20181027_1807'), + ] + + operations = [ + migrations.AddField( + model_name='submission', + name='topics', + field=models.ManyToManyField(blank=True, to='ontology.Topic'), + ), + ] diff --git a/submissions/migrations/0040_auto_20181102_1332.py b/submissions/migrations/0040_auto_20181102_1332.py new file mode 100644 index 0000000000000000000000000000000000000000..935c27f62231134c28d3d23b058c3aac0776add5 --- /dev/null +++ b/submissions/migrations/0040_auto_20181102_1332.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-02 12:32 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0039_submission_topics'), + ] + + operations = [ + migrations.AlterField( + model_name='submission', + name='author_list', + field=models.CharField(max_length=10000, verbose_name='author list'), + ), + ] diff --git a/submissions/migrations/0041_submission_submitted_to.py b/submissions/migrations/0041_submission_submitted_to.py new file mode 100644 index 0000000000000000000000000000000000000000..c60bc2f7f8aa93b207a5a72f7dffb2e682b96421 --- /dev/null +++ b/submissions/migrations/0041_submission_submitted_to.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-10 07:32 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0052_journal_refereeing_period'), + ('submissions', '0040_auto_20181102_1332'), + ] + + operations = [ + migrations.AddField( + model_name='submission', + name='submitted_to', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='journals.Journal'), + ), + ] diff --git a/submissions/migrations/0042_populate_submitted_to.py b/submissions/migrations/0042_populate_submitted_to.py new file mode 100644 index 0000000000000000000000000000000000000000..a4889cbd9a1f75a702ef12a33d4521540597a698 --- /dev/null +++ b/submissions/migrations/0042_populate_submitted_to.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-10 07:33 +from __future__ import unicode_literals + +from django.db import migrations + + +def populate_submitted_to_from_submitted_to_journal(apps, schema_editor): + Journal = apps.get_model('journals', 'Journal') + Submission = apps.get_model('submissions', 'Submission') + + for journal in Journal.objects.all(): + Submission.objects.filter(submitted_to_journal=journal.name).update( + submitted_to=journal) + + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0041_submission_submitted_to'), + ] + + operations = [ + migrations.RunPython(populate_submitted_to_from_submitted_to_journal, + reverse_code=migrations.RunPython.noop), + ] diff --git a/submissions/migrations/0043_remove_submission_submitted_to_journal.py b/submissions/migrations/0043_remove_submission_submitted_to_journal.py new file mode 100644 index 0000000000000000000000000000000000000000..c96d3314ce9ffe7f8713187f5d74541836c466ba --- /dev/null +++ b/submissions/migrations/0043_remove_submission_submitted_to_journal.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-10 12:51 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0042_populate_submitted_to'), + ] + + operations = [ + migrations.RemoveField( + model_name='submission', + name='submitted_to_journal', + ), + ] diff --git a/submissions/migrations/0044_auto_20181115_1009.py b/submissions/migrations/0044_auto_20181115_1009.py new file mode 100644 index 0000000000000000000000000000000000000000..7139126d8fb0582482a918c15959c8d1ddf28bc8 --- /dev/null +++ b/submissions/migrations/0044_auto_20181115_1009.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-15 09:09 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0043_remove_submission_submitted_to_journal'), + ] + + operations = [ + migrations.AlterField( + model_name='submission', + name='submitted_to', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='journals.Journal'), + ), + ] diff --git a/submissions/migrations/0045_submission_is_resubmission_of.py b/submissions/migrations/0045_submission_is_resubmission_of.py new file mode 100644 index 0000000000000000000000000000000000000000..a0ea4d2ebea14f5bcd7a740717c053fc4624bb36 --- /dev/null +++ b/submissions/migrations/0045_submission_is_resubmission_of.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-30 09:13 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0044_auto_20181115_1009'), + ] + + operations = [ + migrations.AddField( + model_name='submission', + name='is_resubmission_of', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='successor', to='submissions.Submission'), + ), + ] diff --git a/submissions/migrations/0046_auto_20181130_1013.py b/submissions/migrations/0046_auto_20181130_1013.py new file mode 100644 index 0000000000000000000000000000000000000000..f865766022750d3a8e850b6da93a1b2882e5e124 --- /dev/null +++ b/submissions/migrations/0046_auto_20181130_1013.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-11-30 09:13 +from __future__ import unicode_literals + +from django.db import migrations + + +def populate_explicit_resubmission_links(apps, schema_editor): + Submission = apps.get_model('submissions', 'Submission') + + for resubmission in Submission.objects.filter(preprint__vn_nr__gt=1): + resub_of = Submission.objects.filter( + preprint__identifier_wo_vn_nr=resubmission.preprint.identifier_wo_vn_nr, + preprint__vn_nr__lt=resubmission.preprint.vn_nr).order_by( + '-preprint__vn_nr').exclude(id=resubmission.id).first() + Submission.objects.filter(id=resubmission.id).update(is_resubmission_of=resub_of) + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0045_submission_is_resubmission_of'), + ] + + operations = [ + migrations.RunPython(populate_explicit_resubmission_links, reverse_code=migrations.RunPython.noop), + ] diff --git a/submissions/migrations/0047_auto_20181204_2011.py b/submissions/migrations/0047_auto_20181204_2011.py new file mode 100644 index 0000000000000000000000000000000000000000..15fe0d2cb0de419eb07beb1d6f8954efdf8a3bfc --- /dev/null +++ b/submissions/migrations/0047_auto_20181204_2011.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-12-04 19:11 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0046_auto_20181130_1013'), + ] + + operations = [ + migrations.RenameField( + model_name='submission', + old_name='is_resubmission', + new_name='_is_resubmission', + ), + migrations.AlterField( + model_name='submission', + name='submission_type', + field=models.CharField(blank=True, choices=[('Letter', 'Letter (broad-interest breakthrough results)'), ('Article', 'Article (in-depth reports on specialized research)'), ('Review', 'Review (candid snapshot of current research in a given area)')], max_length=10), + ), + ] diff --git a/submissions/migrations/0048_submission_thread_hash.py b/submissions/migrations/0048_submission_thread_hash.py new file mode 100644 index 0000000000000000000000000000000000000000..ddf50c99d378afb379eedca6278da7bf875a29b6 --- /dev/null +++ b/submissions/migrations/0048_submission_thread_hash.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-12-04 19:37 +from __future__ import unicode_literals + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0047_auto_20181204_2011'), + ] + + operations = [ + migrations.AddField( + model_name='submission', + name='thread_hash', + field=models.UUIDField(default=uuid.uuid4), + ), + ] diff --git a/submissions/migrations/0049_auto_20181204_2040.py b/submissions/migrations/0049_auto_20181204_2040.py new file mode 100644 index 0000000000000000000000000000000000000000..de5091d216afdce2fb398d7c36e78ac64ded3b89 --- /dev/null +++ b/submissions/migrations/0049_auto_20181204_2040.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-12-04 19:40 +from __future__ import unicode_literals + +import uuid + +from django.db import migrations + + +def get_thread_ids(parent, submissions_list=[]): + successor = parent.successor.first() + if not successor: + return submissions_list + + submissions_list.append(successor.id) + return get_thread_ids(successor, submissions_list) + + +def populate_thread_hashes(apps, schema_editor): + Submission = apps.get_model('submissions', 'Submission') + + for original_submission in Submission.objects.filter(is_resubmission_of__isnull=True): + children_ids = get_thread_ids(original_submission, [original_submission.id]) + Submission.objects.filter(id__in=children_ids).update(thread_hash=original_submission.thread_hash) + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0048_submission_thread_hash'), + ] + + operations = [ + migrations.RunPython(populate_thread_hashes, reverse_code=migrations.RunPython.noop), + ] diff --git a/submissions/migrations/0050_merge_20181207_1008.py b/submissions/migrations/0050_merge_20181207_1008.py new file mode 100644 index 0000000000000000000000000000000000000000..5e58ef5c897e1dc4cca7a545186ca71eb8906315 --- /dev/null +++ b/submissions/migrations/0050_merge_20181207_1008.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-12-07 09:08 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0039_auto_20181011_0746'), + ('submissions', '0049_auto_20181204_2040'), + ] + + operations = [ + ] diff --git a/submissions/models.py b/submissions/models.py index a365f886549706d85c698b4ea4fd545dc17023c9..426281dfa2868ac8cd1312331284abb9896a9fe6 100644 --- a/submissions/models.py +++ b/submissions/models.py @@ -4,6 +4,7 @@ __license__ = "AGPL v3" import datetime import feedparser +import uuid from django.contrib.postgres.fields import JSONField from django.contrib.contenttypes.fields import GenericRelation @@ -16,16 +17,19 @@ from django.utils.functional import cached_property from .behaviors import SubmissionRelatedObjectMixin from .constants import ( ASSIGNMENT_REFUSAL_REASONS, ASSIGNMENT_NULLBOOL, SUBMISSION_TYPE, STATUS_PREASSIGNED, - ED_COMM_CHOICES, REFEREE_QUALIFICATION, QUALITY_SPEC, RANKING_CHOICES, REPORT_REC, + ED_COMM_CHOICES, REFEREE_QUALIFICATION, QUALITY_SPEC, RANKING_CHOICES, STATUS_INVITED, SUBMISSION_STATUS, REPORT_STATUSES, STATUS_UNVETTED, STATUS_INCOMING, STATUS_EIC_ASSIGNED, SUBMISSION_CYCLES, CYCLE_DEFAULT, CYCLE_SHORT, DECISION_FIXED, ASSIGNMENT_STATUSES, CYCLE_DIRECT_REC, EVENT_GENERAL, EVENT_TYPES, EVENT_FOR_AUTHOR, EVENT_FOR_EIC, REPORT_TYPES, REPORT_NORMAL, STATUS_DRAFT, STATUS_VETTED, EIC_REC_STATUSES, VOTING_IN_PREP, STATUS_UNASSIGNED, STATUS_INCORRECT, STATUS_UNCLEAR, STATUS_NOT_USEFUL, STATUS_NOT_ACADEMIC, DEPRECATED, - STATUS_INVITED, STATUS_REPLACED, STATUS_ACCEPTED, STATUS_DEPRECATED, STATUS_COMPLETED) + STATUS_FAILED_PRESCREENING, STATUS_RESUBMITTED, STATUS_REJECTED, STATUS_WITHDRAWN, REPORT_REC, + STATUS_PUBLISHED, STATUS_REPLACED, STATUS_ACCEPTED, STATUS_DEPRECATED, STATUS_COMPLETED, + PLAGIARISM_STATUSES, STATUS_WAITING) from .managers import ( SubmissionQuerySet, EditorialAssignmentQuerySet, EICRecommendationQuerySet, ReportQuerySet, SubmissionEventQuerySet, RefereeInvitationQuerySet, EditorialCommunicationQueryset) +from .refereeing_cycles import RegularCycle from .utils import ( ShortSubmissionCycle, DirectRecommendationSubmissionCycle, GeneralSubmissionCycle) @@ -36,7 +40,7 @@ from scipost.constants import TITLE_CHOICES from scipost.constants import SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS from scipost.fields import ChoiceArrayField from scipost.storage import SecureFileStorage -from journals.constants import SCIPOST_JOURNALS_SUBMIT, SCIPOST_JOURNALS_DOMAINS +from journals.constants import SCIPOST_JOURNALS_DOMAINS from journals.models import Publication from mails.utils import DirectMailUtil @@ -53,7 +57,7 @@ class Submission(models.Model): preprint = models.OneToOneField('preprints.Preprint', related_name='submission') author_comments = models.TextField(blank=True) - author_list = models.CharField(max_length=1000, verbose_name="author list") + author_list = models.CharField(max_length=10000, verbose_name="author list") discipline = models.CharField(max_length=20, choices=SCIPOST_DISCIPLINES, default='physics') domain = models.CharField(max_length=3, choices=SCIPOST_JOURNALS_DOMAINS) editor_in_charge = models.ForeignKey('scipost.Contributor', related_name='EIC', blank=True, @@ -75,7 +79,10 @@ class Submission(models.Model): is_current = models.BooleanField(default=True) visible_public = models.BooleanField("Is publicly visible", default=False) visible_pool = models.BooleanField("Is visible in the Pool", default=False) - is_resubmission = models.BooleanField(default=False) + is_resubmission_of = models.ForeignKey( + 'self', blank=True, null=True, related_name='successor') + thread_hash = models.UUIDField(default=uuid.uuid4) + _is_resubmission = models.BooleanField(default=False) refereeing_cycle = models.CharField( max_length=30, choices=SUBMISSION_CYCLES, default=CYCLE_DEFAULT, blank=True) @@ -84,15 +91,13 @@ class Submission(models.Model): subject_area = models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS, verbose_name='Primary subject area', default='Phys:QP') - submission_type = models.CharField(max_length=10, choices=SUBMISSION_TYPE) + submission_type = models.CharField(max_length=10, choices=SUBMISSION_TYPE, blank=True) submitted_by = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE, related_name='submitted_submissions') voting_fellows = models.ManyToManyField('colleges.Fellowship', blank=True, related_name='voting_pool') - # Replace this by foreignkey? - submitted_to_journal = models.CharField(max_length=30, choices=SCIPOST_JOURNALS_SUBMIT, - verbose_name="Journal to be submitted to") + submitted_to = models.ForeignKey('journals.Journal', on_delete=models.CASCADE) proceedings = models.ForeignKey('proceedings.Proceedings', null=True, blank=True, related_name='submissions') title = models.CharField(max_length=300) @@ -129,6 +134,9 @@ class Submission(models.Model): acceptance_date = models.DateField(verbose_name='acceptance date', null=True, blank=True) latest_activity = models.DateTimeField(auto_now=True) + # Topics for semantic linking + topics = models.ManyToManyField('ontology.Topic', blank=True) + objects = SubmissionQuerySet.as_manager() # Temporary @@ -140,7 +148,6 @@ class Submission(models.Model): def save(self, *args, **kwargs): """Prefill some fields before saving.""" - obj = super().save(*args, **kwargs) if hasattr(self, 'cycle'): self.set_cycle() @@ -174,6 +181,13 @@ class Submission(models.Model): @property def cycle(self): + """Get cycle object that's relevant for the Submission.""" + if not hasattr(self, '_cycle'): + self._cycle = RegularCycle(self) + return self._cycle + + @property + def cycle_old(self): """Get cycle object that's relevant for the Submission.""" if not hasattr(self, '__cycle'): self.set_cycle() @@ -193,6 +207,16 @@ class Submission(models.Model): """Return url of the Submission detail page.""" return reverse('submissions:submission', args=(self.preprint.identifier_w_vn_nr,)) + def get_notification_url(self, url_code): + """Return url related to the Submission by the `url_code` meant for Notifications.""" + if url_code == 'editorial_page': + return reverse('submissions:editorial_page', args=(self.preprint.identifier_w_vn_nr,)) + return self.get_absolute_url() + + @property + def is_resubmission(self): + return self.is_resubmission_of is not None + @property def notification_name(self): """Return string representation of this Submission as shown in Notifications.""" @@ -218,8 +242,23 @@ class Submission(models.Model): @property def reporting_deadline_has_passed(self): """Check if Submission has passed it's reporting deadline.""" + if self.status in [STATUS_INCOMING, STATUS_UNASSIGNED]: + # These statuses do not have a deadline + return False + return timezone.now() > self.reporting_deadline + @property + def reporting_deadline_approaching(self): + """Check if reporting deadline is within 7 days from now but not passed yet.""" + if self.status in [STATUS_INCOMING, STATUS_UNASSIGNED]: + # These statuses do not have a deadline + return False + + if self.reporting_deadline_has_passed: + return False + return timezone.now() > self.reporting_deadline - datetime.timedelta(days=7) + @property def is_open_for_reporting(self): """Check if Submission is open for reporting and within deadlines.""" @@ -229,19 +268,53 @@ class Submission(models.Model): def original_submission_date(self): """Return the submission_date of the first Submission in the thread.""" return Submission.objects.filter( - preprint__identifier_wo_vn_nr=self.preprint.identifier_wo_vn_nr).first().submission_date + thread_hash=self.thread_hash, is_resubmission_of__isnull=True).first().submission_date + + @property + def in_refereeing_phase(self): + """Check if Submission is in active refereeing phase. + + This is not meant for functional logic, rather for explanatory functionality to the user. + """ + if self.eicrecommendations.active().exists(): + # Editorial Recommendation is formulated! + return False + + if self.refereeing_cycle == CYCLE_DIRECT_REC: + # There's no refereeing in this cycle at all. + return False + + if self.referee_invitations.in_process().exists(): + # Some unfinished invitations exist still. + return True + + if self.referee_invitations.awaiting_response().exists(): + # Some invitations have been sent out without a response. + return True + + # Maybe: Check for unvetted Reports? + return self.status == STATUS_EIC_ASSIGNED and self.is_open_for_reporting + + @property + def can_reset_reporting_deadline(self): + """Check if reporting deadline is allowed to be reset.""" + blocked_statuses = [ + STATUS_FAILED_PRESCREENING, STATUS_RESUBMITTED, STATUS_ACCEPTED, + STATUS_REJECTED, STATUS_WITHDRAWN, STATUS_PUBLISHED] + if self.status in blocked_statuses: + return False + + if self.refereeing_cycle == CYCLE_DIRECT_REC: + # This cycle doesn't have a formal refereeing round. + return False + + return self.editor_in_charge is not None @property def thread(self): """Return all (public) Submissions in the database in this ArXiv identifier series.""" - return Submission.objects.public().filter( - preprint__identifier_wo_vn_nr=self.preprint.identifier_wo_vn_nr).order_by( - '-preprint__vn_nr') - - @cached_property - def other_versions_public(self): - """Return other (public) Submissions in the database in this ArXiv identifier series.""" - return self.get_other_versions().order_by('-preprint__vn_nr') + return Submission.objects.public().filter(thread_hash=self.thread_hash).order_by( + '-preprint__vn_nr', '-submission_date') @cached_property def other_versions(self): @@ -250,32 +323,11 @@ class Submission(models.Model): def get_other_versions(self): """Return queryset of other Submissions with this ArXiv identifier series.""" - return Submission.objects.filter( - preprint__identifier_wo_vn_nr=self.preprint.identifier_wo_vn_nr).exclude(pk=self.id) - - def count_accepted_invitations(self): - """Count number of accepted RefereeInvitations for this Submission.""" - return self.referee_invitations.filter(accepted=True).count() - - def count_declined_invitations(self): - """Count number of declined RefereeInvitations for this Submission.""" - return self.referee_invitations.filter(accepted=False).count() + return Submission.objects.filter(thread_hash=self.thread_hash).exclude(pk=self.id) - def count_pending_invitations(self): - """Count number of RefereeInvitations awaiting response for this Submission.""" - return self.referee_invitations.filter(accepted=None).count() - - def count_invited_reports(self): - """Count number of invited Reports for this Submission.""" - return self.reports.accepted().filter(invited=True).count() - - def count_contrib_reports(self): - """Count number of contributed Reports for this Submission.""" - return self.reports.accepted().filter(invited=False).count() - - def count_obtained_reports(self): - """Count total number of Reports for this Submission.""" - return self.reports.accepted().filter(invited__isnull=False).count() + def get_latest_version(self): + """Return the latest known version in the thread of this Submission.""" + return self.thread.first() def add_general_event(self, message): """Generate message meant for EIC and authors.""" @@ -439,12 +491,13 @@ class EditorialAssignment(SubmissionRelatedObjectMixin, models.Model): # Only send if status is appropriate to prevent double sending return False - EditorialAssignment.objects.filter( - id=self.id).update(date_invited=timezone.now(), status=STATUS_INVITED) - # Send mail mail_sender = DirectMailUtil(mail_code='eic/assignment_request', instance=self) mail_sender.send() + + EditorialAssignment.objects.filter( + id=self.id).update(date_invited=timezone.now(), status=STATUS_INVITED) + return True @@ -483,6 +536,9 @@ class RefereeInvitation(SubmissionRelatedObjectMixin, models.Model): objects = RefereeInvitationQuerySet.as_manager() + class Meta: + ordering = ['cancelled', 'date_invited'] + def __str__(self): """Summerize the RefereeInvitation's basic information.""" return (self.first_name + ' ' + self.last_name + ' to referee ' + @@ -508,7 +564,55 @@ class RefereeInvitation(SubmissionRelatedObjectMixin, models.Model): @property def related_report(self): """Return the Report that's been created for this invitation.""" - return self.submission.reports.filter(author=self.referee).first() + return self.submission.reports.filter(author=self.referee).last() + + @property + def needs_response(self): + """Check if invitation has no response in more than three days.""" + if not self.cancelled and self.accepted is None: + if self.date_last_reminded: + # No reponse in over three days since last reminder + return timezone.now() - self.date_last_reminded > datetime.timedelta(days=3) + + # No reponse in over three days since original invite + return timezone.now() - self.date_invited > datetime.timedelta(days=3) + + return False + + @property + def needs_fulfillment_reminder(self): + """Check if isn't fullfilled but deadline is closing in.""" + if self.accepted and not self.cancelled and not self.fulfilled: + # Refereeing deadline closing in/overdue, but invitation isn't fulfilled yet. + return (self.submission.reporting_deadline - timezone.now()).days < 7 + return False + + @property + def is_overdue(self): + """Check if isn't fullfilled but deadline has expired.""" + if self.accepted and not self.cancelled and not self.fulfilled: + # Refereeing deadline closing in/overdue, but invitation isn't fulfilled yet. + return (self.submission.reporting_deadline - timezone.now()).days < 0 + return False + + @property + def needs_attention(self): + """Check if invitation needs attention by the editor.""" + return self.needs_response or self.needs_fulfillment_reminder + + @property + def get_status_display(self): + """Get status: a combination between different boolean fields.""" + if self.cancelled: + return 'Cancelled' + if self.fulfilled: + return 'Fulfilled' + if self.accepted is None: + return 'Awaiting response' + elif self.accepted: + return 'Accepted' + else: + return 'Declined ({})'.format(self.get_refusal_reason_display()) def reset_content(self): """Reset the invitation's information as a new invitation.""" @@ -549,7 +653,6 @@ class Report(SubmissionRelatedObjectMixin, models.Model): # `flagged' if author of report has been flagged by submission authors (surname check only) flagged = models.BooleanField(default=False) - date_submitted = models.DateTimeField('date submitted') author = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE, related_name='reports') qualification = models.PositiveSmallIntegerField( @@ -590,6 +693,10 @@ class Report(SubmissionRelatedObjectMixin, models.Model): anonymous = models.BooleanField(default=True, verbose_name='Publish anonymously') pdf_report = models.FileField(upload_to='UPLOADS/REPORTS/%Y/%m/', max_length=200, blank=True) + date_submitted = models.DateTimeField('date submitted') + created = models.DateTimeField(auto_now_add=True) + modified = models.DateTimeField(auto_now=True) + # Attachment file_attachment = models.FileField( upload_to='uploads/reports/%Y/%m/%d/', blank=True, @@ -624,6 +731,16 @@ class Report(SubmissionRelatedObjectMixin, models.Model): """Return url of the Report on the Submission detail page.""" return self.submission.get_absolute_url() + '#report_' + str(self.report_nr) + def get_notification_url(self, url_code): + """Return url related to the Report by the `url_code` meant for Notifications.""" + if url_code == 'report_form': + return reverse( + 'submissions:submit_report', args=(self.submission.preprint.identifier_w_vn_nr,)) + elif url_code == 'editorial_page': + return reverse( + 'submissions:editorial_page', args=(self.submission.preprint.identifier_w_vn_nr,)) + return self.get_absolute_url() + def get_attachment_url(self): """Return url of the Report its attachment if exists.""" return reverse('submissions:report_attachment', kwargs={ @@ -774,9 +891,21 @@ class EditorialCommunication(SubmissionRelatedObjectMixin, models.Model): authors=self.submission.author_list[:30]) return output + def get_absolute_url(self): + """Return url of the related Submission detail page.""" + return self.submission.get_absolute_url() + + def get_notification_url(self, url_code): + """Return url related to the Communication by the `url_code` meant for Notifications.""" + if url_code == 'editorial_page': + return reverse( + 'submissions:editorial_page', args=(self.submission.preprint.identifier_w_vn_nr,)) + return self.get_absolute_url() + class EICRecommendation(SubmissionRelatedObjectMixin, models.Model): - """The recommendation formulated for a specific Submission, formulated by the EIC. + """ + The recommendation formulated for a specific Submission, formulated by the EIC. The EICRecommendation is the recommendation of a Submission written by the Editor-in-charge formulated at the end of the refereeing cycle. It can be voted for by a subset of Fellows and @@ -795,6 +924,7 @@ class EICRecommendation(SubmissionRelatedObjectMixin, models.Model): status = models.CharField(max_length=32, choices=EIC_REC_STATUSES, default=VOTING_IN_PREP) version = models.SmallIntegerField(default=1) active = models.BooleanField(default=True) + # status = models.CharField(default='', max_length=180) # Editorial Fellows who have assessed this recommendation: eligible_to_vote = models.ManyToManyField('scipost.Contributor', blank=True, @@ -857,7 +987,7 @@ class EICRecommendation(SubmissionRelatedObjectMixin, models.Model): @property def may_be_reformulated(self): """Check if this EICRecommdation is allowed to be reformulated in a new version.""" - if not self.status == DEPRECATED: + if self.status == DEPRECATED: # Already reformulated before; please use the latest version return self.submission.eicrecommendations.last() == self return self.status != DECISION_FIXED @@ -886,6 +1016,7 @@ class iThenticateReport(TimeStampedModel): doc_id = models.IntegerField(primary_key=True) part_id = models.IntegerField(null=True, blank=True) percent_match = models.IntegerField(null=True, blank=True) + status = models.CharField(max_length=16, choices=PLAGIARISM_STATUSES, default=STATUS_WAITING) class Meta: verbose_name = 'iThenticate Report' diff --git a/submissions/plagiarism.py b/submissions/plagiarism.py index f09bab2c44e210a0af433c59f85e00d4601ad175..c4f0bcfffbb9e3f0c5a06ca2c8175640daa42305 100644 --- a/submissions/plagiarism.py +++ b/submissions/plagiarism.py @@ -49,7 +49,7 @@ class iThenticate: Generates a new folder and id if needed. """ - group_re = '{journal}_submissions'.format(journal=submission.submitted_to_journal) + group_re = '{journal}_submissions'.format(journal=submission.submitted_to.doi_label) folder_re = '{year}_{month}'.format( year=submission.submission_date.year, month=submission.submission_date.month diff --git a/submissions/refereeing_cycles.py b/submissions/refereeing_cycles.py new file mode 100644 index 0000000000000000000000000000000000000000..9feeb5fdef1dc101689bdc556026b0fcd1c81692 --- /dev/null +++ b/submissions/refereeing_cycles.py @@ -0,0 +1,304 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + +import datetime + +from django.core.urlresolvers import reverse +from django.utils import timezone +from django.utils.html import format_html + +from . import constants +from .utils import RequiredActionsDict + + +class BaseAction: + """An item in the RequiredActionsDict dictionary for the Submission refereeing cycle.""" + txt = '' + url = '#' + url2 = '#' + submission = None + + def __init__(self, object=None, **kwargs): + self._objects = [] + self.add(object) + + self.id = object.__class__.__name__ if object else self.__class__.__name__ + + def add(self, object=None): + if object: + self._objects.append(object) + + def _format_text(self, text, obj=None): + if obj is None and self._objects: + obj = self._objects[0] + + timedelta = datetime.timedelta() + deadline = datetime.timedelta() + + if hasattr(obj, 'date_invited'): + timedelta = timezone.now() - obj.date_invited + if hasattr(obj, 'submission'): + deadline = obj.submission.reporting_deadline - timezone.now() + + return text.format( + count=len(self._objects), + object=obj.__class__.__name__, + author=obj.author.formal_str if hasattr(obj, 'author') and obj.author else '', + referee=obj.referee_str if hasattr(obj, 'referee_str') else '', + days=timedelta.days, + deadline=deadline.days, + deadline_min=-deadline.days, + url=self.url, + url2=self.url2) + + def as_text(self): + return ' '.join([e for e in self]) + + def __iter__(self): + if self._objects: + for obj in self._objects: + yield format_html(self._format_text(self.txt)) + else: + yield format_html(self._format_text(self.txt)) + + def __str__(self): + return self.as_text() + + +class VettingAction(BaseAction): + txt = '{author} has delivered a {object}. <a href="{url}">Please vet it</a>.' + + @property + def url(self): + return '{}#current-contributions'.format(reverse( + 'submissions:editorial_page', args=(self.submission.preprint.identifier_w_vn_nr,))) + + +class NoRefereeResponseAction(BaseAction): + txt = ( + 'Referee {referee} has not responded for {days} days.' + ' Consider sending a reminder or cancelling the invitation.') + + +class DeadlineAction(BaseAction): + txt = ( + 'Referee {referee} has accepted to send a Report ' + '(with {deadline} days left), but not yet delivered it. Consider sending a reminder.') + + +class OverdueAction(BaseAction): + txt = ( + 'Referee {referee} has accepted to send a Report ' + '({deadline_min} days overdue), but not yet delivered it. Consider sending a reminder.') + + +class ChoiceCycleAction(BaseAction): + txt = 'Choose the submission cycle to proceed with.' + + +class NoEICRecommendationAction(BaseAction): + @property + def txt(self): + if not self.submission.reports.non_draft().exists(): + txt = ( + 'The refereeing deadline has passed and you have received no Reports yet.' + ' Please <a href="{url}">extend the reporting deadline</a> ' + 'and consider sending a reminder to your referees.') + else: + txt = ( + 'The refereeing deadline has passed. Please either ' + '<a href="{url}">extend it</a>, ' + 'or <a href="{url2}">formulate your Editorial Recommendation</a>.') + return txt + + @property + def url(self): + return '{}#reporting-deadline'.format(reverse( + 'submissions:editorial_page', args=(self.submission.preprint.identifier_w_vn_nr,))) + + @property + def url2(self): + return reverse( + 'submissions:eic_recommendation',args=(self.submission.preprint.identifier_w_vn_nr,)) + + +class NeedRefereesAction(BaseAction): + def __init__(self, object=None, **kwargs): + self.minimum_number_of_referees = kwargs.pop('minimum_number_of_referees') + self.current_number_of_referees = kwargs.pop('current_number_of_referees') + super().__init__(object, **kwargs) + + @property + def txt(self): + if self.current_number_of_referees == 0: + text = 'No Referees have yet been invited.' + elif self.current_number_of_referees == 1: + text = 'Only 1 Referee has yet been invited.' + else: + text = 'Only %i Referees have yet been invited.' % self.current_number_of_referees + text += ' At least {minimum} should be. <a href="{url}">Invite a referee here</a>.'.format( + minimum=self.minimum_number_of_referees, + url=reverse( + 'submissions:select_referee', args=(self.submission.preprint.identifier_w_vn_nr,))) + return text + + +class BaseCycle: + """A base blueprint for the Submission refereeing cycle. + + The refereeing process may be defined differently for every cycle class by its own + specific properties. The cycle class will then take care of the required actions, + permissions and the overall refereeing process. + """ + + days_for_refereeing = 28 + minimum_number_of_referees = 3 + can_invite_referees = True + + def __init__(self, submission, current_user=None): + self._submission = submission + self._required_actions = None + self._current_user = current_user + + @property + def required_actions(self): + if self._required_actions is None: + self.update_required_actions() + return self._required_actions + + def has_required_actions(self): + return bool(self.required_actions) + + def add_action(self, action): + if action not in self.required_actions: + self._required_actions[action] = action + else: + self._required_actions[action].add(action) + self._required_actions[action].submission = self._submission + + def update_required_actions(self): + """Gather the required actions list and populate self._required_actions.""" + self._required_actions = RequiredActionsDict() + + if not self._submission.refereeing_cycle: + # Submission is a resubmission: EIC has to determine which cycle to proceed with. + self.add_action(ChoiceCycleAction()) + return # If no cycle is chosen. Make this a first priority! + + # The EIC is late with formulating a Recommendation. + if self._submission.eic_recommendation_required: + if self._submission.reporting_deadline_has_passed: + self.add_action(NoEICRecommendationAction()) + + # Submission is a resubmission: EIC has to determine which cycle to proceed with. + comments_to_vet = self._submission.comments.awaiting_vetting().values_list('id') + for comment in comments_to_vet: + self.add_action(VettingAction(comment)) + + reports_to_vet = self._submission.reports.awaiting_vetting() + for report in reports_to_vet: + self.add_action(VettingAction(report)) + + if self.can_invite_referees and self._submission.in_refereeing_phase: + # Referees required in this cycle. + referee_invitations_count = self._submission.referee_invitations.non_cancelled().count() + + # The current number of referees does not meet the minimum number of referees yet. + if referee_invitations_count < self.minimum_number_of_referees: + self.add_action(NeedRefereesAction( + current_number_of_referees=referee_invitations_count, + minimum_number_of_referees=self.minimum_number_of_referees)) + + referee_invitations = self._submission.referee_invitations.needs_attention() + for referee_invitation in referee_invitations: + if referee_invitation.needs_response: + # Invited, but no response + self.add_action(NoRefereeResponseAction(referee_invitation)) + elif referee_invitation.is_overdue: + self.add_action(OverdueAction(referee_invitation)) + elif referee_invitation.needs_fulfillment_reminder: + self.add_action(DeadlineAction(referee_invitation)) + + def as_text(self): + """Return a *short* description of the current status of the submission cycle.""" + texts = [] + + recommendation = self._submission.eicrecommendations.active().last() + if recommendation: + texts.append('Thank you for formulating your Editorial Recommendation.') + if recommendation.status == constants.VOTING_IN_PREP: + texts.append('The Editorial Administration is now preparing it for voting.') + elif recommendation.status == constants.PUT_TO_VOTING: + texts.append('It is now put to voting in the college.') + + if self._submission.status == constants.STATUS_RESUBMITTED: + new_sub_id = self._submission.get_latest_version().preprint.identifier_w_vn_nr + txt = ( + 'The Submission has been resubmitted as {identifier},' + ' <a href="{url}">go to its editorial page</a>.') + texts.append(txt.format( + url=reverse('submissions:editorial_page', args=(new_sub_id,)), + identifier=new_sub_id)) + elif self._submission.status == constants.STATUS_ACCEPTED: + texts.append('The SciPost production team is working on the proofs for publication.') + elif self._submission.status == constants.STATUS_REJECTED: + texts.append('The Submission is rejected for publication.') + elif self._submission.status == constants.STATUS_WITHDRAWN: + texts.append('The authors have withdrawn the Submission.') + elif self._submission.status == constants.STATUS_PUBLISHED: + texts.append('The Submission has been published as <a href="{url}">{doi}</a>.'.format( + url=self._submission.publication.get_absolute_url(), + doi=self._submission.publication.doi_label)) + elif self._submission.status == constants.STATUS_EIC_ASSIGNED: + if not self._submission.in_refereeing_phase: + if recommendation: + texts.append('The refereeing round is closed.') + else: + texts.append(( + 'The refereeing round is closed ' + 'and you have not formulated an Editorial Recommendation.')) + + if not self.required_actions and not texts: + texts.append('No action required.') + elif self.required_actions: + texts.append( + '<strong>Please see your required actions below.</strong>') + return format_html(' '.join(texts)) + + def __str__(self): + return self.as_text() + + def reset_refereeing_round(self): + """Set the Submission status to EIC_ASSIGNED and reset the reporting deadline.""" + from .models import Submission # Prevent circular import errors + if self._submission.status in [constants.STATUS_INCOMING, constants.STATUS_UNASSIGNED]: + Submission.objects.filter(id=self._submission.id).update( + status=constants.STATUS_EIC_ASSIGNED) + + deadline = timezone.now() + datetime.timedelta(days=self.days_for_refereeing) + Submission.objects.filter(id=self._submission.id).update(reporting_deadline=deadline) + + def reinvite_referees(self, referees, request): + """Duplicate and reset RefereeInvitations and send `reinvite` mail. + + referees - (list of) RefereeInvitation instances + """ + from mails.utils import DirectMailUtil + SubmissionUtils.load({'submission': self._submission}) + for referee in referees: + invitation = referee + invitation.pk = None # Duplicate, do not remove the old invitation + invitation.submission = self._submission + invitation.reset_content() + invitation.date_invited = timezone.now() + invitation.save() + mail_sender = DirectMailUtil( + mail_code='referees/reinvite_contributor_to_referee', instance=invitation) + mail_sender.send() + + # SubmissionUtils.load({'invitation': invitation}, request) + # SubmissionUtils.reinvite_referees_email() + + +class RegularCycle(BaseCycle): + pass diff --git a/submissions/services.py b/submissions/services.py new file mode 100644 index 0000000000000000000000000000000000000000..5fe3f68c04a028f76c465eaa9171fc0a5b6dabc9 --- /dev/null +++ b/submissions/services.py @@ -0,0 +1,182 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + +from django.conf import settings + +from .helpers import retrieve_pdf_from_arxiv +from .models import iThenticateReport +from .plagiarism import iThenticate + + +class iThenticateCaller: + def __init__(self, submission, document_id=None): + self.document_id = document_id + self.submission = submission + + def _get_client(self): + client = iThenticate.API.Client( + settings.ITHENTICATE_USERNAME, settings.ITHENTICATE_PASSWORD) + if client.login(): + return client + self.add_error(None, "Failed to login to iThenticate.") # To do: wrong + return None + + def update_status(self): + if not self.document_id: + return False + + # ... + + def upload_document(self, document=None): + if self.document_id: + # Wrong action? + return None + + if not document: + # Expect: ArxivPDFNotFound exception + document = retrieve_pdf_from_arxiv(self.submission.preprint.identifier_w_vn_nr) + + client = self._get_client() + if not client: + return None + + try: + plagiarism = iThenticate() + data = plagiarism.upload_submission(document, self.submission) + + # Give feedback to the user + if not data: + self.add_error(None, "Updating failed. iThenticate didn't return valid data [3]") # To do: . wrong + for msg in plagiarism.get_messages(): + self.add_error(None, msg) # To do: wrong. + return None + except AttributeError: + # To do: all wrong... + if not self.fields.get('file'): + # The document is invalid. + self.add_error(None, ('A valid pdf could not be found at arXiv.' + ' Please upload the pdf manually.')) + else: + self.add_error(None, ('The uploaded file is not valid.' + ' Please upload a valid pdf.')) + self.fields['file'] = forms.FileField() + + return data + +# class iThenticateReportForm(forms.ModelForm): +# class Meta: +# model = iThenticateReport +# fields = [] +# +# def __init__(self, submission, *args, **kwargs): +# self.submission = submission +# super().__init__(*args, **kwargs) +# +# if kwargs.get('files', {}).get('file'): +# # Add file field if file data is coming in! +# self.fields['file'] = forms.FileField() +# +# def clean(self): +# cleaned_data = super().clean() +# doc_id = self.instance.doc_id +# if not doc_id and not self.fields.get('file'): +# try: +# cleaned_data['document'] = helpers.retrieve_pdf_from_arxiv( +# self.submission.preprint.identifier_w_vn_nr) +# except exceptions.ArxivPDFNotFound: +# self.add_error( +# None, 'The pdf could not be found at arXiv. Please upload the pdf manually.') +# self.fields['file'] = forms.FileField() +# elif not doc_id and cleaned_data.get('file'): +# cleaned_data['document'] = cleaned_data['file'].read() +# elif doc_id: +# self.document_id = doc_id +# +# # Login client to append login-check to form +# self.client = self.get_client() +# +# if not self.client: +# return None +# +# # Document (id) is found +# if cleaned_data.get('document'): +# self.document = cleaned_data['document'] +# try: +# self.response = self.call_ithenticate() +# except AttributeError: +# if not self.fields.get('file'): +# # The document is invalid. +# self.add_error(None, ('A valid pdf could not be found at arXiv.' +# ' Please upload the pdf manually.')) +# else: +# self.add_error(None, ('The uploaded file is not valid.' +# ' Please upload a valid pdf.')) +# self.fields['file'] = forms.FileField() +# elif hasattr(self, 'document_id'): +# self.response = self.call_ithenticate() +# +# if hasattr(self, 'response') and self.response: +# return cleaned_data +# +# # Don't return anything as someone submitted invalid data for the form at this point! +# return None +# +# def save(self, *args, **kwargs): +# data = self.response +# +# report, created = iThenticateReport.objects.get_or_create(doc_id=data['id']) +# +# if not created: +# try: +# iThenticateReport.objects.filter(doc_id=data['id']).update( +# uploaded_time=data['uploaded_time'], +# processed_time=data['processed_time'], +# percent_match=data['percent_match'], +# part_id=data.get('parts', [{}])[0].get('id') +# ) +# except KeyError: +# pass +# else: +# report.save() +# Submission.objects.filter(id=self.submission.id).update(plagiarism_report=report) +# return report +# +# def call_ithenticate(self): +# if hasattr(self, 'document_id'): +# # Update iThenticate status +# return self.update_status() +# elif hasattr(self, 'document'): +# # Upload iThenticate document first time +# return self.upload_document() +# +# def get_client(self): +# client = iThenticate.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 update_status(self): +# client = self.client +# response = client.documents.get(self.document_id) +# if response['status'] == 200: +# return response.get('data')[0].get('documents')[0] +# self.add_error(None, "Updating failed. iThenticate didn't return valid data [1]") +# +# for msg in client.messages: +# self.add_error(None, msg) +# return None +# +# def upload_document(self): +# from .plagiarism import iThenticate +# plagiarism = iThenticate() +# data = plagiarism.upload_submission(self.document, self.submission) +# +# # Give feedback to the user +# if not data: +# self.add_error(None, "Updating failed. iThenticate didn't return valid data [3]") +# for msg in plagiarism.get_messages(): +# self.add_error(None, msg) +# return None +# return data diff --git a/submissions/signals.py b/submissions/signals.py index e8e3b3fbe4852883e43ff62e596f768874aa7129..40ef1c43f3b48fdf5971583416140038e6669630 100644 --- a/submissions/signals.py +++ b/submissions/signals.py @@ -4,38 +4,68 @@ __license__ = "AGPL v3" from django.contrib.auth.models import User, Group -from notifications.constants import NOTIFICATION_REFEREE_DEADLINE, NOTIFICATION_REFEREE_OVERDUE -from notifications.models import Notification +from notifications.constants import ( + NOTIFICATION_REFEREE_DEADLINE, NOTIFICATION_REFEREE_OVERDUE, NOTIFICATION_REPORT_UNFINISHED) +from notifications.models import Notification, FakeActors from notifications.signals import notify def notify_new_manuscript_submitted(sender, instance, created, **kwargs): - """ - Notify the Editorial Administration about a new Submission submitted. - """ + """Notify the Editorial Administration about a new Submission submitted.""" if created: administrators = User.objects.filter(groups__name='Editorial Administrators') for user in administrators: - notify.send(sender=sender, recipient=user, actor=instance.submitted_by, - verb=' submitted a new manuscript.', target=instance) + notify.send( + sender=sender, recipient=user, actor=instance.submitted_by, + verb=' submitted a new manuscript.', target=instance, + url_code='editorial_page') def notify_new_editorial_recommendation(sender, instance, created, **kwargs): - """ - Notify the Editorial Recommendation about a new Submission submitted. - """ + """Notify the Editorial Recommendation about a new Submission submitted.""" if created: administrators = User.objects.filter(groups__name='Editorial Administrators') editor_in_charge = instance.submission.editor_in_charge for user in administrators: - notify.send(sender=sender, recipient=user, actor=editor_in_charge, - verb=' formulated a new Editorial Recommendation.', target=instance) + notify.send( + sender=sender, recipient=user, actor=editor_in_charge, + verb=' formulated a new Editorial Recommendation.', target=instance, + url_code='editorial_page') + + +def notify_eic_new_report(sender, instance, created, **kwargs): + """Notify the Editor-in-charge about a new submitted Report.""" + editor_in_charge = instance.submission.editor_in_charge + if editor_in_charge: + notify.send( + sender=sender, actor=instance.author, recipient=editor_in_charge.user, + verb=' delivered a Report. Please vet it.', target=instance, + url_code='editorial_page') + + +def notify_report_vetted(sender, instance, created, **kwargs): + """Notify author that its Report has been vetted.""" + if instance.vetted_by == instance.submission.editor_in_charge: + actor, __ = FakeActors.objects.get_or_create(name='Editor-in-charge') + else: + actor, __ = FakeActors.objects.get_or_create(name='Editorial Administration') + + txt = ' vetted your Report: %s.' % instance.get_status_display() + notify.send( + sender=sender, actor=actor, recipient=instance.author.user, + verb=txt, target=instance) + + +def notify_submission_author_new_report(sender, instance, created, **kwargs): + """Notify the Editor-in-charge about a new submitted Report.""" + actor, __ = FakeActors.objects.get_or_create(name='') # Silence. + notify.send( + sender=sender, actor=instance.author, recipient=instance.submission.submitted_by.user, + verb='A new Report has been delivered to your Submission.', target=instance) def notify_new_editorial_assignment(sender, instance, created, **kwargs): - """ - Notify a College Fellow about a new EIC invitation. - """ + """Notify a College Fellow about a new EIC invitation.""" if created: administration = Group.objects.get(name='Editorial Administrators') if instance.accepted: @@ -43,63 +73,157 @@ def notify_new_editorial_assignment(sender, instance, created, **kwargs): text = ' assigned you Editor-in-charge.' else: text = ' invited you to become Editor-in-charge.' - notify.send(sender=sender, recipient=instance.to.user, actor=administration, - verb=text, target=instance) + notify.send( + sender=sender, recipient=instance.to.user, + actor=administration, verb=text, target=instance) + + +def notify_editor_assigned(sender, instance, created, **kwargs): + """Notify Editorial Administration about a new assignment.""" + if instance.to: + recipients = User.objects.filter(groups__name='Editorial Administrators') + for recipient in recipients: + # TO DO: Not filtered for possible admin-author conflict. + notify.send( + sender=sender, recipient=recipient, actor=instance.to, url_code='editorial_page', + verb=' has accepted to act as Editor-in-charge.', target=instance.submission) def notify_new_referee_invitation(sender, instance, created, **kwargs): - """ - Notify a Referee about a new refereeing invitation. - """ + """Notify a Referee about a new refereeing invitation.""" if created and instance.referee: notify.send(sender=sender, recipient=instance.referee.user, actor=instance.submission.editor_in_charge, verb=' would like to invite you to referee a Submission.', target=instance) +def notify_invitation_cancelled(sender, instance, created, **kwargs): + """Notify a Referee its invitation got cancelled.""" + if instance.referee: + eic = instance.submission.editor_in_charge + if eic and sender == eic.user: + actor, __ = FakeActors.objects.get_or_create(name='Editor-in-charge') + else: + actor, __ = FakeActors.objects.get_or_create(name='Editorial Administration') + notify.send( + sender=sender, recipient=instance.referee.user, actor=actor, + verb=' cancelled your referee invitation.', target=instance.submission) + + +def notify_eic_invitation_reponse(sender, instance, created, **kwargs): + """Notify the EIC that a referee has responded to a RefInv.""" + eic = instance.submission.editor_in_charge + if eic: + txt = ' %s the refereeing invitation.' % ('accepted' if instance.accepted else 'declined') + notify.send( + sender=sender, recipient=eic.user, actor=eic.user, verb=txt, + target=instance.submission, url_code='editorial_page') + + +def notify_new_communication(sender, instance, created, **kwargs): + """Notify the receiver of the new Communication.""" + if not created: + return + + if instance.comtype in ['AtoE', 'RtoE', 'StoE']: + if instance.submission.editor_in_charge: + recipients = [instance.submission.editor_in_charge.user] + else: + # No editor assigned yet. + return + if instance.comtype == 'StoE': + actor, __ = FakeActors.objects.get_or_create(name='Editorial Administration') + elif instance.comtype == 'AtoE': + actor = instance.submission.submitted_by + elif instance.comtype == 'RtoE': + actor = instance.referee + text = ' has sent a communication regarding a Submission you are Editor-in-charge for.' + url_code = 'editorial_page' + elif instance.comtype == 'EtoS': + # To Editorial Administration + recipients = User.objects.filter(groups__name='Editorial Administrators') + actor, __ = FakeActors.objects.get_or_create(name='Editor-in-charge') + text = ' has sent a communication to the Editorial Administration.' + url_code = 'editorial_page' + elif instance.comtype == 'EtoA': + # Submitting author + recipients = [instance.submission.submitted_by.user] + actor, __ = FakeActors.objects.get_or_create(name='Editor-in-charge') + text = ' has sent a communication regarding your Submission.' + url_code = '' + elif instance.comtype == 'EtoR': + # To referee + recipients = [instance.referee.user] + actor, __ = FakeActors.objects.get_or_create(name='Editor-in-charge') + text = ' has sent a communication regarding a Submission you have been invited to referee.' + url_code = '' + else: + # Weird. + return + + for recipient in recipients: + notify.send( + sender=sender, recipient=recipient, actor=actor, verb=text, + target=instance, url_code=url_code) + + +def notify_unfinished_report(sender, instance, created, **kwargs): + """Notify Referee he has an unfinished Report.""" + if not instance.author: + return + + send_new = not Notification.objects.filter( + recipient=instance.author.user, internal_type=NOTIFICATION_REPORT_UNFINISHED).unread_or_today().exists() + if send_new: + # User doesn't already have a notification to remind him. + administration = Group.objects.get(name='Editorial Administrators') + notify.send( + sender=sender, recipient=instance.author.user, actor=administration, + verb=' would like to remind you that you have an unfinished Report.', + target=instance.submission, type=NOTIFICATION_REPORT_UNFINISHED, + url_code='report_form') + + def notify_invitation_approaching_deadline(sender, instance, created, **kwargs): - """ - Notify Referee its unfinished duty is approaching the deadline. - """ + """Notify Referee its unfinished duty is approaching the deadline.""" if instance.referee: notifications = Notification.objects.filter( - recipient=instance.referee.user, internal_type=NOTIFICATION_REFEREE_DEADLINE).unread() + recipient=instance.referee.user, internal_type=NOTIFICATION_REFEREE_DEADLINE).unread_or_today() if not notifications.exists(): # User doesn't already have a notification to remind him. administration = Group.objects.get(name='Editorial Administrators') - notify.send(sender=sender, recipient=instance.referee.user, - actor=administration, - verb=(' would like to remind you that your Refereeing Task is ' - 'approaching its deadline, please submit your Report'), - target=instance.submission, type=NOTIFICATION_REFEREE_DEADLINE) + notify.send( + sender=sender, recipient=instance.referee.user, actor=administration, + verb=( + ' would like to remind you that your Refereeing Task is approaching' + ' its deadline, please submit your Report'), + target=instance.submission, type=NOTIFICATION_REFEREE_DEADLINE) def notify_invitation_overdue(sender, instance, created, **kwargs): - """ - Notify Referee its unfinished duty is overdue. - """ + """Notify Referee its unfinished duty is overdue.""" if instance.referee: notifications = Notification.objects.filter( - recipient=instance.referee.user, internal_type=NOTIFICATION_REFEREE_OVERDUE).unread() + recipient=instance.referee.user, internal_type=NOTIFICATION_REFEREE_OVERDUE).unread_or_today() if not notifications.exists(): # User doesn't already have a notification to remind him. administration = Group.objects.get(name='Editorial Administrators') - notify.send(sender=sender, recipient=instance.referee.user, - actor=administration, - verb=(' would like to remind you that your Refereeing Task is overdue, ' - 'please submit your Report'), - target=instance.submission, type=NOTIFICATION_REFEREE_OVERDUE) + notify.send( + sender=sender, recipient=instance.referee.user, actor=administration, + verb=( + ' would like to remind you that your Refereeing Task is overdue, ' + 'please submit your Report'), + target=instance.submission, type=NOTIFICATION_REFEREE_OVERDUE) def notify_manuscript_accepted(sender, instance, created, **kwargs): - """ - Notify authors about their manuscript decision. + """Notify authors about their manuscript decision. instance --- Submission """ college = Group.objects.get(name='Editorial College') authors = User.objects.filter(contributor__submissions=instance) for user in authors: - notify.send(sender=sender, recipient=user, actor=college, - verb=' has accepted your manuscript for publication.', - target=instance) + notify.send( + sender=sender, recipient=user, actor=college, + verb=' has accepted your manuscript for publication.', target=instance) diff --git a/submissions/tasks.py b/submissions/tasks.py index 6343b72305458ec06853a748f772276b0ee27c60..e3113cf3d8cf892e1915e4db2875e699eada2372 100644 --- a/submissions/tasks.py +++ b/submissions/tasks.py @@ -1,9 +1,14 @@ __copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" __license__ = "AGPL v3" +from datetime import timedelta +from django.utils import timezone + from SciPost_v1.celery import app -from .models import Submission, EditorialAssignment +from .models import Submission, EditorialAssignment, RefereeInvitation, Report +from .signals import ( + notify_invitation_approaching_deadline, notify_invitation_overdue, notify_unfinished_report) @app.task(bind=True) @@ -27,3 +32,36 @@ def send_editorial_assignment_invitations(self): if ed_assignment.send_invitation(): count += 1 return {'new_invites': count} + + +@app.task(bind=True) +def remind_referees_to_fulfill_to_invitation(self): + """Remind Referees with unfilfilled RefereeInvitations to submit a Report.""" + for invitation in RefereeInvitation.objects.approaching_deadline(): + notify_invitation_approaching_deadline(RefereeInvitation, invitation, False) + for invitation in RefereeInvitation.objects.overdue(): + notify_invitation_overdue(RefereeInvitation, invitation, False) + + +@app.task(bind=True) +def remind_referees_to_submit_report(self): + """Remind Referees with unfinished Report finish their Report.""" + compare_dt = timezone.now() - timedelta(days=2) + + for report in Report.objects.in_draft().filter(modified__lt=compare_dt): + notify_unfinished_report(Report, report, False) + + +@app.task(bind=True) +def submit_submission_document_for_plagiarism(self): + """Upload a new Submission document to iThenticate.""" + submissions_to_upload = Submission.objects.plagiarism_report_to_be_uploaded() + submission_to_update = Submission.objects.plagiarism_report_to_be_updated() + + for submission in submissions_to_upload: + report, __ = iThenticate.objects.get_or_create(to_submission=submission) + # do it... + + for submission in submission_to_update: + report = submission.plagiarism_report + # do it... diff --git a/submissions/templates/partials/submissions/communication_thread.html b/submissions/templates/partials/submissions/communication_thread.html new file mode 100644 index 0000000000000000000000000000000000000000..f743b97546af1e7ff63a3f1d080720698e151261 --- /dev/null +++ b/submissions/templates/partials/submissions/communication_thread.html @@ -0,0 +1,45 @@ +<ul class="mt-2 communications {{ css_class }}"> + {% for comm in communication %} + <li class="date"><span>{{ comm.timestamp|date:'d F Y' }}</span></li> + <li class="comm comm-{{ comm.comtype }}"> + <span class="datetime">{{ comm.timestamp|date:'d F Y G:i' }}</span> + <span class="time">{{ comm.timestamp|date:'G:i' }}</span> + <div class="content"> + <span class="header"> + {% if comm.comtype == 'RtoE' %} + From {{ comm.referee.user.first_name }} {{ comm.referee.user.last_name }} to {{ reader_is_editor|yesno:'you,Editor-in-charge' }} + {% elif comm.comtype == 'EtoR' %} + From {{ reader_is_editor|yesno:'you,Editor-in-charge' }} to + {% if comm.referee %} + {{ comm.referee.user.first_name }} {{ comm.referee.user.last_name }} + {% else %} + referee (?) + {% endif %} + {% elif comm.comtype == 'AtoE' %} + From + {% if comm.referee %} + {{ comm.referee.user.first_name }} {{ comm.referee.user.last_name }} + {% else %} + author (?) + {% endif %} + to {{ reader_is_editor|yesno:'you,Editor-in-charge' }} + {% elif comm.comtype == 'EtoA' %} + From {{ reader_is_editor|yesno:'you,Editor-in-charge' }} to + {% if comm.referee %} + {{ comm.referee.user.first_name }} {{ comm.referee.user.last_name }} + {% else %} + author (?) + {% endif %} + {% elif comm.comtype == 'StoE' %} + From Editorial Administration to {{ reader_is_editor|yesno:'you,Editor-in-charge' }} + {% elif comm.comtype == 'EtoS' %} + From {{ reader_is_editor|yesno:'you,Editor-in-charge' }} to Editorial Administration + {% endif %} + </span> + <p class="comm-text">{{ comm.text|linebreaksbr }}</p> + </div> + </li> + {% empty %} + <li>There have been no communications.</li> + {% endfor %} +</ul> diff --git a/submissions/templates/partials/submissions/pool/referee_invitations.html b/submissions/templates/partials/submissions/pool/referee_invitations.html index cd81b0c66743ca5b4850ce70f56dba9e803977a0..f1fa19637e8b5db8dd708d2a2ef624963486e91e 100644 --- a/submissions/templates/partials/submissions/pool/referee_invitations.html +++ b/submissions/templates/partials/submissions/pool/referee_invitations.html @@ -1,20 +1,38 @@ -<table class="table bg-light table-hover v-center"> - <thead> +{% load submissions_extras %} + +<table class="table table-light table-hover v-center"> + <thead class="thead-light"> <tr> + <th></th> <th>Referee</th> <th>Invitation date</th> <th>Task status</th> - <th>Auto reminders {% include 'partials/submissions/refinv_auto_reminders_tooltip.html' %}</th> + <th>Auto reminders <small>{% include 'partials/submissions/refinv_auto_reminders_tooltip.html' %}</small></th> <th colspan="4">Actions</th> </tr> </thead> <tbody> {% for invitation in invitations %} - <tr> - <td>{{ invitation.get_title_display }} {{invitation.first_name}} {{invitation.last_name}}</td> + <tr{% if invitation.needs_attention %} class="table-warning"{% endif %}> <td> - invited <br> - {{invitation.date_invited}} + {% if invitation.needs_response %} + <div class="text-center" data-toggle="tooltip" data-title="This referee has not responded in over three days.<br>Consider sending a reminder or cancelling the invitation." data-html="true"> + <i class="fa fa-info"></i> + <i class="fa fa-long-arrow-right"></i> + </div> + {% elif invitation.needs_fulfillment_reminder %} + <div class="text-center" data-toggle="tooltip" data-title="This referee has accepted to send a Report, but not yet delivered it.<br>Consider sending a reminder." data-html="true"> + <i class="fa fa-info"></i> + <i class="fa fa-long-arrow-right"></i> + </div> + {% endif %} + {% if invitation.is_overdue %} + <div class="badge badge-danger">overdue</div> + {% endif %} + </td> + <td class="py-3">{{ invitation.get_title_display }} {{invitation.first_name}} {{invitation.last_name}}</td> + <td> + {{ invitation.date_invited }} </td> <td> {% if invitation.fulfilled %} @@ -27,7 +45,7 @@ {% else %} <strong class="text-danger">task declined</strong> {% endif %} - <div>{{invitation.date_responded}}</div> + <div>{{ invitation.date_responded }}</div> {% else %} response pending {% endif %} @@ -36,20 +54,20 @@ {% if not invitation.accepted == False and not invitation.cancelled %} <td> <div> - {% if invitation.auto_reminders_allowed %} - <div><span class="label-outline-success">On</span></div> - <div>(<a href="{% url 'submissions:set_refinv_auto_reminder' invitation_id=invitation.id auto_reminders=0 %}">turn off</a>)</div> - {% else %} - <div><span class="label-outline-danger">Off</span></div> - <div>(<a href="{% url 'submissions:set_refinv_auto_reminder' invitation_id=invitation.id auto_reminders=1 %}">turn on</a>)</div> - {% endif %} + {% if invitation.auto_reminders_allowed %} + <div><span class="label-outline-success">On</span></div> + <div>(<a href="{% url 'submissions:set_refinv_auto_reminder' invitation_id=invitation.id auto_reminders=0 %}">turn off</a>)</div> + {% else %} + <div><span class="label-outline-danger">Off</span></div> + <div>(<a href="{% url 'submissions:set_refinv_auto_reminder' invitation_id=invitation.id auto_reminders=1 %}">turn on</a>)</div> + {% endif %} </div> </td> <td> {% if not invitation.fulfilled %} <a href="{% url 'submissions:ref_invitation_reminder' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr invitation_id=invitation.id %}">Send reminder email manually</a> {% else %} - <strong style="color: green">Report has been delivered</strong> + <strong class="text-success">Report has been delivered</strong> {% endif %} </td> <td> @@ -61,6 +79,12 @@ <td> {% if invitation.referee %} <a href="{% url 'submissions:communication' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr comtype='EtoR' referee_id=invitation.referee.id %}">Write a communication</a> + {% if invitation.referee.editorial_communications|filter_for_submission:submission %} + <br> + <a href="javascript:;" data-toggle="toggle" data-target="#comm-row-{{ invitation.id }}"> + Show communication ({{ invitation.referee.editorial_communications|filter_for_submission:submission|length }}) + </a> + {% endif %} {% else %} (not yet registered) {% endif %} @@ -71,13 +95,43 @@ {% endif %} </td> {% else %} - <td colspan="5"></td> + <td colspan="3"></td> + <td colspan="2"> + {% if invitation.referee %} + <a href="javascript:;" data-toggle="toggle" data-target="#comm-row-{{ invitation.id }}"> + Show communication ({{ invitation.referee.editorial_communications|filter_for_submission:submission|length }}) + </a> + {% endif %} + </td> {% endif %} </tr> + {% if invitation.referee %} + <tr style="display: none;" class="pt-1 table-info" id="comm-row-{{ invitation.id }}"> + <td></td> + <td colspan="8"> + {% include 'partials/submissions/communication_thread.html' with communication=invitation.referee.editorial_communications|filter_for_submission:submission css_class='wide' %} + <a href="javascript:;" class="d-inline-block mb-2" data-toggle="toggle" data-target="#comm-row-{{ invitation.id }}">Hide communication</a> + </td> + </tr> + {% endif %} {% empty %} <tr> - <td class="text-center py-3" colspan="7">You have not invited any referees yet.</td> + <td class="text-center py-3" colspan="9">You do not have any referees.</td> </tr> {% endfor %} + + {# {% if submission.refereeing_cycle != 'direct_rec' and submission.is_open_for_reporting %} #} + {% if submission.in_refereeing_phase %} + <tr class="bg-white"> + <td class="text-center py-3" colspan="9"> + {% if invitations %} + <h4><a href="{% url 'submissions:select_referee' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">+ Invite an additional referee</a></h4> + {% else %} + <i class="fa fa-exclamation-circle text-danger"></i> + You have not invited any referees yet. <a href="{% url 'submissions:select_referee' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Invite the first referee here</a>. + {% endif %} + </td> + </tr> + {% endif %} </tbody> </table> diff --git a/submissions/templates/partials/submissions/pool/referee_invitations_status.html b/submissions/templates/partials/submissions/pool/referee_invitations_status.html index c7b8d4d44f5514e5cde8967022781b00a8ee454c..95bfb5f3c8c3dd7fac698a1b7e6ab5ba926ac2db 100644 --- a/submissions/templates/partials/submissions/pool/referee_invitations_status.html +++ b/submissions/templates/partials/submissions/pool/referee_invitations_status.html @@ -4,13 +4,13 @@ <tr> <td>Nr referees invited:</td> <td> - {{submission.referee_invitations.count}} <span>[{{submission.count_accepted_invitations}} acccepted / {{submission.count_declined_invitations}} declined / {{submission.count_pending_invitations}} response pending]</span> + {{ submission.referee_invitations.count }} <span>[{{ submission.referee_invitations.accepted.count }} acccepted / {{ submission.referee_invitations.declined.count }} declined / {{ submission.referee_invitations.awaiting_response.count }} response pending]</span> </td> </tr> <tr> <td>Nr reports obtained:</td> <td> - {{submission.count_obtained_reports}} [{{submission.count_invited_reports}} invited / {{submission.count_contrib_reports}} contributed], nr refused: {{submission.reports.rejected.count}}, nr awaiting vetting: {{submission.reports.awaiting_vetting.count}} + {{ submission.reports.accepted.count }} [{{ submission.reports.accepted.invited.count }} invited / {{ submission.reports.contributed.accepted.count }} contributed], nr refused: {{submission.reports.rejected.count}}, nr awaiting vetting: {{submission.reports.awaiting_vetting.count}} </td> </tr> </tbody> diff --git a/submissions/templates/partials/submissions/pool/required_actions_block.html b/submissions/templates/partials/submissions/pool/required_actions_block.html index 05066fa33f44cbe804c9f70240f1e428dd57799f..7b82edb3c133143bfd807bc3cdf5273d7c151b08 100644 --- a/submissions/templates/partials/submissions/pool/required_actions_block.html +++ b/submissions/templates/partials/submissions/pool/required_actions_block.html @@ -1,11 +1,11 @@ {% if submission.cycle.has_required_actions %} - <div class="card {% if submission.cycle.get_required_actions %}bg-danger text-white{% else %}bg-white border-success text-success{% endif %}"> - <div class="card-header py-1 {% if not submission.cycle.get_required_actions %}bg-transparent border-0 pb-0{% endif %}"> - <h3>Required actions:</h3> + <div class="card bg-danger text-white mb-3"> + <div class="card-header py-1"> + <h3 class="my-1">Required actions:</h3> </div> <div class="card-body"> <ul class="mb-0"> - {% for action in submission.cycle.get_required_actions %} + {% for action in submission.cycle.required_actions %} <li>{{action.1}}</li> {% empty %} <li>No actions required</li> diff --git a/submissions/templates/partials/submissions/pool/required_actions_tooltip.html b/submissions/templates/partials/submissions/pool/required_actions_tooltip.html index 1e9ce2a7234240d0a5ef96e121cb45c37f694b06..4b42c758a933bfe49aefcc6b88a2efbfc293f5b5 100644 --- a/submissions/templates/partials/submissions/pool/required_actions_tooltip.html +++ b/submissions/templates/partials/submissions/pool/required_actions_tooltip.html @@ -1,9 +1,9 @@ -{% if submission.cycle.has_required_actions and submission.cycle.get_required_actions %} +{% if submission.cycle.has_required_actions %} <i class="fa fa-exclamation-circle {{ classes }}" data-toggle="tooltip" data-html="true" title=" Required Actions: <ul class='mb-0 pl-3 text-left'> - {% for action in submission.cycle.get_required_actions %} - <li>{{action.1}}</li> + {% for action in submission.cycle.required_actions %} + <li>{{ action.1 }}</li> {% endfor %} </ul> "></i> diff --git a/submissions/templates/partials/submissions/pool/submission_comments_summary_table.html b/submissions/templates/partials/submissions/pool/submission_comments_summary_table.html index 68ed6a2d1b9f2fb9855fbcbffc2a47637d14594d..a2f13f51cf1e468aec9b209ba9769901c7ce644a 100644 --- a/submissions/templates/partials/submissions/pool/submission_comments_summary_table.html +++ b/submissions/templates/partials/submissions/pool/submission_comments_summary_table.html @@ -1,16 +1,25 @@ -<table class="table bg-light table-hover v-center"> - <thead> +<table class="table table-light table-hover v-center"> + <thead class="thead-light"> <tr> - <th>Referee</th> + <th></th> + <th>Author</th> <th>Status</th> - <th>Recommendation</th> + <th>Text</th> <th>Type</th> <th>Date</th> </tr> </thead> <tbody> - {% for comment in submission.comments.all %} - <tr> + {% for comment in submission.comments_set_complete %} + <tr{% if comment.is_unvetted %} class="table-warning"{% endif %}> + <td class="text-center"> + {% if comment.is_unvetted %} + <div class="text-center" data-toggle="tooltip" data-title="This Comment has not yet been vetted." data-html="true"> + <i class="fa fa-info"></i> + <i class="fa fa-long-arrow-right" ></i> + </div> + {% endif %} + </td> <td> {{ comment.author }} {% if comment.anonymous %} @@ -23,6 +32,8 @@ <i class="fa fa-check-circle text-success"></i> {% elif comment.is_rejected %} <i class="fa fa-times-circle text-danger"></i> + {% elif comment.is_unvetted %} + <i class="fa fa-exclamation-triangle text-warning"></i> {% endif %} {{ comment.get_status_display }} {% if comment.is_unvetted %} @@ -39,7 +50,7 @@ </tr> {% empty %} <tr> - <td class="text-center py-3" colspan="5">There are no Comments yet.</td> + <td class="text-center py-3" colspan="6">There are no Comments yet.</td> </tr> {% endfor %} </tbody> diff --git a/submissions/templates/partials/submissions/pool/submission_info_table.html b/submissions/templates/partials/submissions/pool/submission_info_table.html index 642b35736cc326ba28c0263aac5e1b08c9db143e..3594325cb2603b6fa8e0a1f976bce104afc02848 100644 --- a/submissions/templates/partials/submissions/pool/submission_info_table.html +++ b/submissions/templates/partials/submissions/pool/submission_info_table.html @@ -20,7 +20,7 @@ {% endif %} <tr> <td>Submitted</td> - <td>{{submission.submission_date}} to {{submission.get_submitted_to_journal_display}}</td> + <td>{{submission.submission_date}} to {{submission.submitted_to}}</td> </tr> {% if submission.acceptance_date %} diff --git a/submissions/templates/partials/submissions/pool/submission_li.html b/submissions/templates/partials/submissions/pool/submission_li.html index 470fe0eb8bcf2bf18b784242212a86af99fce99d..708bfc31b328b65af23c9f6808eb7ea4a2b03952 100644 --- a/submissions/templates/partials/submissions/pool/submission_li.html +++ b/submissions/templates/partials/submissions/pool/submission_li.html @@ -54,7 +54,7 @@ </div> </div> - {% if submission.cycle.has_required_actions and submission.cycle.get_required_actions %} + {% if submission.cycle.has_required_actions %} <div class="card-text bg-danger text-white mt-1 py-1 px-2"> This Submission contains required actions, <a href="{% url 'submissions:pool' submission.preprint.identifier_w_vn_nr %}" class="text-white" data-toggle="dynamic" data-target="#container_{{ submission.id }}">click to see details.</a> {% include 'partials/submissions/pool/required_actions_tooltip.html' with submission=submission classes='text-white' %} </div> diff --git a/submissions/templates/partials/submissions/pool/submission_reports_summary_table.html b/submissions/templates/partials/submissions/pool/submission_reports_summary_table.html index d30352c127972003112e2033b07c0a515f49fa25..be733be294f8a265ac1360a58b639d33822bc3c4 100644 --- a/submissions/templates/partials/submissions/pool/submission_reports_summary_table.html +++ b/submissions/templates/partials/submissions/pool/submission_reports_summary_table.html @@ -1,6 +1,7 @@ -<table class="table bg-light table-hover v-center"> - <thead> +<table class="table table-light table-hover v-center"> + <thead class="thead-light"> <tr> + <th></th> <th>Referee</th> <th>Status</th> <th>Recommendation</th> @@ -10,7 +11,15 @@ </thead> <tbody> {% for report in submission.reports.all %} - <tr> + <tr{% if report.is_unvetted %} class="table-warning"{% endif %}> + <td class="text-center"> + {% if report.is_unvetted %} + <div class="text-center" data-toggle="tooltip" data-title="This Report has not yet been vetted." data-html="true"> + <i class="fa fa-info"></i> + <i class="fa fa-long-arrow-right" ></i> + </div> + {% endif %} + </td> <td> {{ report.author }} {% if report.anonymous %} @@ -23,6 +32,8 @@ <i class="fa fa-check-circle text-success"></i> {% elif report.is_rejected %} <i class="fa fa-times-circle text-danger"></i> + {% elif report.is_unvetted %} + <i class="fa fa-exclamation-triangle text-warning"></i> {% endif %} {{ report.get_status_display }} {% if report.is_unvetted %} @@ -39,7 +50,7 @@ </tr> {% empty %} <tr> - <td class="text-center py-3" colspan="5">There are no Reports yet.</td> + <td class="text-center py-3" colspan="6">There are no Reports yet.</td> </tr> {% endfor %} </tbody> diff --git a/submissions/templates/partials/submissions/recommendation_author_content.html b/submissions/templates/partials/submissions/recommendation_author_content.html index 82897d217492568033d8be50cf0df7ad7329c50c..8bc7f42d9f00ca624abb72c193bc4d7b42aeb5e4 100644 --- a/submissions/templates/partials/submissions/recommendation_author_content.html +++ b/submissions/templates/partials/submissions/recommendation_author_content.html @@ -1,32 +1,43 @@ -<div class="border p-3 my-3"> - <h2 class="mb-0">Editorial Recommendation</h2> - {% block recommendation_header %} - <h4 class="text-muted mb-2">Date {{recommendation.date_submitted}}</h4> - {% endblock %} +<div class="border px-3 py-2 my-3" id="rec-{{ recommendation.id }}"> + <div {% if not recommendation.active %}style="display: none;"{% endif %} class="mb-4 {% if not recommendation.active %}pt-2{% endif %}"> + {% if recommendation.is_deprecated %}<h4 class="mb-0"><i class="fa fa-exclamation-circle text-danger"></i> Deprecated</h4>{% endif %} + <h2 class="my-2">Editorial Recommendation</h2> + {% block recommendation_header %} + <h4 class="text-muted mb-2">Date {{recommendation.date_submitted}}</h4> + {% endblock %} + <br> + + <table class="mb-2"> + <tr> + <td class="pr-2">Version:</td> + <td>{{ recommendation.version }}</td> + </tr> + <tr> + <td class="pr-2">Recommendation:</td> + <td>{{ recommendation.get_recommendation_display }}</td> + </tr> + <tr> + <td class="pr-2">Status:</td> + <td><span class="label label-secondary">{{ recommendation.get_status_display }}</span></td> + </tr> + </table> - <table class="mb-3"> - <tr> - <td class="pr-2">Version:</td> - <td>{{ recommendation.version }}</td> - </tr> - <tr> - <td class="pr-2">Recommendation:</td> - <td>{{ recommendation.get_recommendation_display }}</td> - </tr> - <tr> - <td class="pr-2">Status:</td> - <td><span class="label label-secondary">{{ recommendation.get_status_display }}</span></td> - </tr> - </table> + <h3 class="pb-0">Remarks for authors</h3> + <p class="pl-md-3">{{recommendation.remarks_for_authors|default:'-'}}</p> - <h3 class="pb-0">Remarks for authors</h3> - <p class="pl-md-3">{{recommendation.remarks_for_authors|default:'-'}}</p> + <h3 class="pb-0">Requested changes</h3> + <p class="pl-md-3">{{recommendation.requested_changes|default:'-'}}</p> - <h3 class="pb-0">Requested changes</h3> - <p class="pl-md-3">{{recommendation.requested_changes|default:'-'}}</p> + {% block recommendation_before_recommendation %}{% endblock %} - {% block recommendation_before_recommendation %}{% endblock %} + <h3 class="pb-0">Recommendation</h3> + <p class="pl-md-3 mb-0">{{recommendation.get_recommendation_display}}</p> + </div> - <h3 class="pb-0">Recommendation</h3> - <p class="pl-md-3 mb-0">{{recommendation.get_recommendation_display}}</p> + {% if not recommendation.active %} + <a href="javascript:;" data-toggle="toggle" data-target="#rec-{{ recommendation.id }} > div" class="d-block my-2">Show/hide Editorial Recommendation {{ recommendation.version }} (status: {{ recommendation.get_status_display }}) →</a> + {% endif %} + + {% block recommendation_footer %} + {% endblock %} </div> diff --git a/submissions/templates/partials/submissions/recommendation_fellow_content.html b/submissions/templates/partials/submissions/recommendation_fellow_content.html index 5a3bc709c8afd08f00f6cd5bcaa724a7b37dfa1e..6f1efc95ca33c4caa99d135385a1a5585b182ef9 100644 --- a/submissions/templates/partials/submissions/recommendation_fellow_content.html +++ b/submissions/templates/partials/submissions/recommendation_fellow_content.html @@ -8,3 +8,9 @@ <h3 class="pb-0">Remarks for Editorial College</h3> <p class="pl-md-3">{{recommendation.remarks_for_editorial_college|default:'-'}}</p> {% endblock %} + +{% block recommendation_footer %} + {% if recommendation.may_be_reformulated %} + <a href="{% url 'submissions:reformulate_eic_recommendation' recommendation.submission.preprint.identifier_w_vn_nr %}" class="d-block my-2">Reformulate Editorial Recommendation</a> + {% endif %} +{% endblock %} diff --git a/submissions/templates/partials/submissions/refereeing_invitation_card_content.html b/submissions/templates/partials/submissions/refereeing_invitation_card_content.html deleted file mode 100644 index 2fd6cf0053292a5a8e700e615b0ecbbff7ebe33f..0000000000000000000000000000000000000000 --- a/submissions/templates/partials/submissions/refereeing_invitation_card_content.html +++ /dev/null @@ -1,8 +0,0 @@ - -<h3 class="card-title">{% if invitation.submission.reporting_deadline_has_passed %}<span class="label label-sm label-danger mr-2">overdue</span> {% endif %}{{ invitation.submission }}</h3> -<h4 class="card-subtitle text-muted">due: {{ invitation.submission.reporting_deadline }}</h4> - -<div class="d-block"> - <a class="d-inline-block" href="{% url 'submissions:submit_report' identifier_w_vn_nr=invitation.submission.preprint.identifier_w_vn_nr %}">Submit your Report</a> <span class="text-blue">|</span> - <a class="d-inline-block" href="{% url 'submissions:communication' identifier_w_vn_nr=invitation.submission.preprint.identifier_w_vn_nr comtype='RtoE' referee_id=request.user.contributor.id %}">Write to the Editor-in-charge</a>. -</div> diff --git a/submissions/templates/partials/submissions/refereeing_pack_tex_template.html b/submissions/templates/partials/submissions/refereeing_pack_tex_template.html index fdfb3e290c3879e4c9950c2005819755fd762e03..c53d18f5d9ba24592d7953f9a412a68a168d62c8 100644 --- a/submissions/templates/partials/submissions/refereeing_pack_tex_template.html +++ b/submissions/templates/partials/submissions/refereeing_pack_tex_template.html @@ -49,7 +49,7 @@ Refereeing Package of\href{https://scipost.org{{submission.get_absolute_url|safe %%%%%%%%%% DATES \small{\ \\Received {{submission.submission_date|date:'d-m-Y'}}\newline {% if submission.acceptance_date %}Accepted {{submission.acceptance_date|date:'d-m-Y'}} \newline{% endif %} -Submitted to {{submission.get_submitted_to_journal_display}} +Submitted to {{submission.submitted_to}} } diff --git a/submissions/templates/partials/submissions/refereeing_status_card.html b/submissions/templates/partials/submissions/refereeing_status_card.html index ba4175750b607cc4d62b64c957959bac82c6b24d..f1b87143c3b5b2800bd2bac34d2e28715b8bf6a4 100644 --- a/submissions/templates/partials/submissions/refereeing_status_card.html +++ b/submissions/templates/partials/submissions/refereeing_status_card.html @@ -1,73 +1,51 @@ -<div class="card border-warning"> - <div class="card-body"> - {% if invitation.accepted is None %} - <h3 class="card-title">Referee Invitation response pending</h3> - <p class="card-text"> - In view of your expertise and on behalf of the Editor-in-charge we would like to invite you to referee this Submission. Please accept or decline this invitation. - We would be extremely grateful for your contribution, and thank you in advance for your consideration. - </p> - <a href="{% url 'submissions:accept_or_decline_ref_invitations' invitation.id %}" class="card-link">Accept or decline here</a> - {% elif invitation.accepted %} - <h3 class="card-title">Referee Invitation</h3> - <p class="card-text">Thank you for agreeing to referee this Submission. The following checklist will guide you through the steps needed to complete your refereeing task.</p> - <ul class="fa-ul"> - <li><i class="fa-li fa fa-check-square-o"></i>Accepted Invitation on {{ invitation.date_responded }}.</li> +<div class="referee-box"> + {% if invitation.accepted is None %} + <h3>Your Referee Invitation is pending response</h3> + <p> + In view of your expertise and on behalf of the Editor-in-charge we would like to invite you to referee this Submission. Please accept or decline this invitation. + We would be extremely grateful for your contribution, and thank you in advance for your consideration. + </p> + <a href="{% url 'submissions:accept_or_decline_ref_invitations' invitation.id %}" class="card-link">Accept or decline here</a> + {% elif invitation.accepted %} + <h3>Your Referee Invitation</h3> + <p>Thank you for agreeing to referee this Submission. The following checklist will guide you through the steps needed to complete your refereeing task.</p> + <ul class="fa-ul"> + <li><i class="fa-li fa fa-check-square-o"></i>Accepted Invitation on {{ invitation.date_responded }}.</li> - <li> - {% if invitation.fulfilled %} - Thank you! - <i class="fa-li fa fa-check-square-o"></i>Report submitted on {{ invitation.related_report.date_submitted }}. - <ul class="list-style-none"> - <li>Status: <span class="{% if invitation.related_report.status == 'vetted' %}text-success{% elif invitation.related_report.status == 'unvetted' %}text-danger{% endif %}">{{ invitation.related_report.get_status_display }}</span></li> - <li>Anonymous: {{ invitation.related_report.anonymous|yesno:'Yes,No' }}</li> - {% if invitation.related_report.doi_label %}<li>DOI: {{ invitation.related_report.doi_string }}</li>{% endif %} - </ul> + <li> + {% if invitation.fulfilled %} + Thank you! + <i class="fa-li fa fa-check-square-o"></i>Report submitted on {{ invitation.related_report.date_submitted }}. + <ul class="list-style-none"> + <li>Status: <span class="{% if invitation.related_report.status == 'vetted' %}text-success{% elif invitation.related_report.status == 'unvetted' %}text-danger{% endif %}">{{ invitation.related_report.get_status_display }}</span></li> + <li>Anonymous: {{ invitation.related_report.anonymous|yesno:'Yes,No' }}</li> + {% if invitation.related_report.doi_label %}<li>DOI: {{ invitation.related_report.doi_string }}</li>{% endif %} + </ul> + {% else %} + <i class="fa-li fa fa-square-o"></i> + {% if invitation.related_report.is_in_draft %} + You have a Report in draft, <a href="{% url 'submissions:submit_report' invitation.submission.preprint.identifier_w_vn_nr %}">finish your Report</a>. {% else %} - <i class="fa-li fa fa-square-o"></i> - {% if invitation.related_report.is_in_draft %} - You have a Report in draft, <a href="{% url 'submissions:submit_report' invitation.submission.preprint.identifier_w_vn_nr %}">finish your Report</a>. - {% else %} - <a href="{% url 'submissions:submit_report' invitation.submission.preprint.identifier_w_vn_nr %}">Submit your Report</a> due on {{ invitation.submission.reporting_deadline }}. - {% endif %} + <a href="{% url 'submissions:submit_report' invitation.submission.preprint.identifier_w_vn_nr %}">Submit your Report</a> due on {{ invitation.submission.reporting_deadline }}. {% endif %} - </li> - </ul> + {% endif %} + </li> + </ul> - {% if invitation.related_report.anonymous %} - <p class="card-text">You have submitted your Report anonymously. <a href="{% url 'journals:sign_existing_report' report_id=invitation.related_report.id %}">You can click here to sign this Report</a> (leads to confirmation page).</p> - {% endif %} - - {% if invitation.submission.editor_in_charge %} - <h3 class="card-title">Communications</h3> - <a class="card-link" href="{% url 'submissions:communication' invitation.submission.preprint.identifier_w_vn_nr 'RtoE' %}">Write to the Editor-in-charge</a> + {% if invitation.related_report.anonymous and not invitation.related_report.is_in_draft %} + <p>You have submitted your Report anonymously. <a href="{% url 'journals:sign_existing_report' report_id=invitation.related_report.id %}">You can click here to sign this Report</a> (leads to confirmation page).</p> + {% endif %} - <ul class="pl-4 mt-2"> - {% for comm in communication %} - <li> - <span class="font-weight-bold"> - {% if comm.comtype == 'RtoE' %} - From {{ comm.referee.user.first_name }} {{ comm.referee.user.last_name }} to Editor-in-charge - {% elif comm.comtype == 'EtoR' %} - From Editor-in-charge to - {% if comm.referee %} - {{ comm.referee.user.first_name }} {{ comm.referee.user.last_name }} - {% endif %} - {% endif %} - </span> - <small class="d-inline-block text-muted text-sm">on {{ comm.timestamp }}</small> + {% if invitation.submission.editor_in_charge %} + <h4>Communications</h4> + <a href="{% url 'submissions:communication' invitation.submission.preprint.identifier_w_vn_nr 'RtoE' %}">Write to the Editor-in-charge</a> - <p class="card-text">{{ comm.text }}</p> - </li> - {% empty %} - <li>There have been no communications for this Submission.</li> - {% endfor %} - </ul> - {% endif %} - {% else %} - <h3 class="card-title">Referee Invitation</h3> - <p class="card-text">You have declined to contribute a Report. Nonetheless, we thank you very much for considering this refereeing invitation.</p> - <p class="card-text">Reason: {{ invitation.get_refusal_reason_display }}</p> + {% include 'partials/submissions/communication_thread.html' with communication=communication %} {% endif %} - </div> + {% elif not invitation.accepted %} + <h3>Your Referee Invitation</h3> + <p>You have declined to contribute a Report. Nonetheless, we thank you very much for considering this refereeing invitation.</p> + <p>Reason: {{ invitation.get_refusal_reason_display }}</p> + {% endif %} </div> diff --git a/submissions/templates/partials/submissions/report_public_without_comments.html b/submissions/templates/partials/submissions/report_public_without_comments.html index b28a6331aff811ca5bb371c073be917103480d7c..8dca5fbea432a3908995b9c68aeb804cb19ff09c 100644 --- a/submissions/templates/partials/submissions/report_public_without_comments.html +++ b/submissions/templates/partials/submissions/report_public_without_comments.html @@ -8,11 +8,21 @@ <div class="reportid"> <h3> - {% if report.anonymous %}(chose public anonymity) {% endif %}<a href="{{report.author.get_absolute_url}}">{{ report.author.user.first_name }} {{ report.author.user.last_name }}</a> + {% if report.anonymous %} + (chose public anonymity) + {% endif %} + + Report {{report.report_nr}} by <a href="{{report.author.get_absolute_url}}">{{ report.author.user.first_name }} {{ report.author.user.last_name }}</a> on {{ report.date_submitted|date:'Y-n-j' }} {% if report.report_type == 'report_post_edrec' %} <small><label class="label label-outline-primary ml-2">Post-Editorial Recommendation Report</label> <i class="fa fa-question-circle-o text-primary" data-toggle="tooltip" data-placement="auto" data-html="true" title="Post-Editorial Reports are submitted after the Editorial Recommendation has been formulated.<br>Hence, they have not been part of the College's decision to Publish the Submission." aria-hidden="true"></i></small> {% endif %} + + {% if report.invited %} + <span class="report-label">Invited Report</span> + {% else %} + <span class="report-label">Contributed Report</span> + {% endif %} </h3> {% if report.doi_string or report.pdf_report %} <ul class="clickables"> @@ -27,8 +37,8 @@ </ul> {% endif %} {% if perms.scipost.can_manage_reports %} - <h3 class="mt-2">Administration</h3> - <ul> + <h3 class="mt-3">Administration</h3> + <ul class="mb-0"> <li><a href="{% url 'submissions:report_pdf_compile' report.id %}">Update/Compile the Report pdf</a></li> <li>Mark DOI as <a href="{% url 'journals:mark_report_doi_needed' report_id=report.id needed=1 %}">needed</a> / <a href="{% url 'journals:mark_report_doi_needed' report_id=report.id needed=0 %}">not needed</a></li> <li><a href="{% url 'journals:generic_metadata_xml_deposit' type_of_object='report' object_id=report.id %}">Create the metadata and deposit it to Crossref</a></li> @@ -49,7 +59,7 @@ {% include 'partials/submissions/report_content.html' with report=report %} - <div class="row"> + <div class="row mt-3"> <div class="col-12"> <h3>Remarks for editors</h3> <div class="pl-md-4">{{ report.remarks_for_editors|default:'-' }}</div> @@ -65,11 +75,22 @@ {% else %} <div class="reportid"> <h3> - {% if report.anonymous %}Anonymous Report {{report.report_nr}}{% else %}<a href="{{report.author.get_absolute_url}}">{{ report.author.user.first_name }} {{ report.author.user.last_name }}</a>{% endif %} + {% if report.anonymous %} + Anonymous Report {{report.report_nr}} + {% else %} + Report {{report.report_nr}} by <a href="{{report.author.get_absolute_url}}">{{ report.author.user.first_name }} {{ report.author.user.last_name }}</a> + {% endif %} + on {{ report.date_submitted|date:'Y-n-j' }} {% if report.report_type == 'report_post_edrec' %} <small><label class="label label-outline-primary ml-2">Post-Editorial Recommendation Report</label> <i class="fa fa-question-circle-o text-primary" data-toggle="tooltip" data-placement="auto" data-html="true" title="Post-Editorial Reports are submitted after the Editorial Recommendation has been formulated.<br>Hence, they have not been part of the College's decision to Publish the Submission." aria-hidden="true"></i></small> {% endif %} + + {% if report.invited %} + <span class="report-label">Invited Report</span> + {% else %} + <span class="report-label">Contributed Report</span> + {% endif %} </h3> {% if report.doi_string or report.pdf_report %} <ul class="clickables"> diff --git a/submissions/templates/partials/submissions/submission_author_information.html b/submissions/templates/partials/submissions/submission_author_information.html index f5e7d4d4ace297fb38ee0d2aa8aaeae8e6d64d9e..f56b1892695c310b65a44ec471b56f9f34278314 100644 --- a/submissions/templates/partials/submissions/submission_author_information.html +++ b/submissions/templates/partials/submissions/submission_author_information.html @@ -1,11 +1,26 @@ {% load bootstrap %} -<h3 class="highlight">Author information <small><small class="text-muted"><i class="fa fa-question-circle-o" data-toggle="tooltip" data-title="You see this information because you are a verified author of this Submission."></i></small></small></h3> -<a href="javascript:;" class="btn btn-default mb-2" data-toggle="toggle" data-target="#authorinformation">Show/hide author information</a> +<h3 class="highlight"> + Author information + <small><a href="javascript:;" class="text-muted" data-toggle="tooltip" data-title="You see this information because you are a verified author of this Submission.">?</a></small> + <br> + <small><a href="javascript:;" class="mt-2 d-inline-block text-sm" data-toggle="toggle" data-target="#authorinformation">Show/hide author information</a></small> +</h3> <div id="authorinformation" class="mt-2"> <div class="mb-4"> - <h3>Status summary:</h3> + <h3 class="mt-3">Status summary</h3> + <p> + {% if not submission.editor_in_charge %} + No Editor-in-charge is assigned yet. The SciPost administration will inform you as soon as one is assigned. + {% elif submission.in_refereeing_phase %} + The refereeing round has started. The current deadline is set to {{ submission.reporting_deadline|date:'j F Y' }}. + {% elif submission.eicrecommendations.active.first %} + An Editorial Recommendation has been formulated. See its details below. + {% elif not submission.open_for_reporting %} + The refereeing round is closed, but no Editorial Recommendation is formulated yet. The editor will formulate the Editorial Recommendation after all Reports have been received. + {% endif %} + </p> <table class="table table-borderless"> <tr> <td>Submission status:</td> @@ -50,17 +65,29 @@ </table> </div> + {% if submission.eicrecommendations.active %} + <div class="mb-4"> + <h3 class="mb-2">Editorial Recommendation:</h3> + {% for recommendation in submission.eicrecommendations.active %} + {% include 'partials/submissions/recommendation_author_content.html' with recommendation=recommendation %} + {% empty %} + No Editorial Recommendation is formulated yet. The editor will formulate the Editorial Recommendation after all Reports have been received. + {% endfor %} + </div> + {% endif %} + <div class="mb-4"> - <h3 class="mb-2">Editorial Recommendation:</h3> - {% for recommendation in submission.eicrecommendations.active %} - {% include 'partials/submissions/recommendation_author_content.html' with recommendation=recommendation %} - {% empty %} - No Editorial Recommendation is formulated yet. - {% endfor %} + <h3>Communication</h3> + <div id="communications"> + {% if submission.editor_in_charge %} + <a href="{% url 'submissions:communication' submission.preprint.identifier_w_vn_nr 'AtoE' %}">Write to the Editor-in-charge</a> + {% endif %} + {% include 'partials/submissions/communication_thread.html' with communication=submission.editorial_communications.for_authors css_class='wide' %} + </div> </div> <div class="mb-4"> - <h3>Events:</h3> + <h3>Events</h3> <div id="eventslist"> {% include 'partials/submissions/submission_events.html' with events=submission.events.for_author %} </div> @@ -68,7 +95,7 @@ <div class="mb-4" id="proofsslist"> - <h3>Proofs:</h3> + <h3>Proofs</h3> <ul class="list-group list-group-flush events-list"> {% for proofs in submission.production_stream.proofs.for_authors %} <li> @@ -91,5 +118,3 @@ </ul> </div> </div> - -<hr class="divider"> diff --git a/submissions/templates/partials/submissions/submission_card_content.html b/submissions/templates/partials/submissions/submission_card_content.html index 61269a9c316c6a9440d8a325b4d1b803d9bf4843..6a5f938774c555f3e3c1afa685c5f1786ce21a2b 100644 --- a/submissions/templates/partials/submissions/submission_card_content.html +++ b/submissions/templates/partials/submissions/submission_card_content.html @@ -7,7 +7,7 @@ {% if submission.publication and submission.publication.is_published %} Published as <a href="{{ submission.publication.get_absolute_url }}">{{ submission.publication.get_journal.abbreviation_citation }}{% if submission.publication.in_issue.in_volume %} <strong>{{ submission.publication.in_issue.in_volume.number }}</strong>,{% endif %} {{ submission.publication.get_paper_nr }} ({{ submission.publication.publication_date|date:'Y' }})</a> {% else %} - Submitted {{ submission.submission_date }} to {{ submission.get_submitted_to_journal_display }} + Submitted {{ submission.submission_date }} to {{ submission.submitted_to }} {% endif %} · latest activity: {{ submission.latest_activity }} </p> {% endblock %} diff --git a/submissions/templates/partials/submissions/submission_card_content_homepage.html b/submissions/templates/partials/submissions/submission_card_content_homepage.html index 18dac36ba2e7bd46f4829e9c6e25cf2bac0540c8..5f63d272edc3f1bb3789cdf471c7ea093b19272c 100644 --- a/submissions/templates/partials/submissions/submission_card_content_homepage.html +++ b/submissions/templates/partials/submissions/submission_card_content_homepage.html @@ -2,6 +2,6 @@ {% block card_footer %} <div class="meta"> - Submitted {{submission.submission_date}} to {{submission.get_submitted_to_journal_display}}</span> + Submitted {{submission.submission_date}} to {{submission.submitted_to}}</span> </div> {% endblock %} diff --git a/submissions/templates/partials/submissions/submission_editorial_information.html b/submissions/templates/partials/submissions/submission_editorial_information.html index cf56804716fb9d1dfcfe0abe3ac06206d87c8171..0808c075f8ddeb6a855c465b7e49ac75f0922f5a 100644 --- a/submissions/templates/partials/submissions/submission_editorial_information.html +++ b/submissions/templates/partials/submissions/submission_editorial_information.html @@ -109,5 +109,3 @@ </ul> </div> </div> - -<hr class="divider"> diff --git a/submissions/templates/partials/submissions/submission_events.html b/submissions/templates/partials/submissions/submission_events.html index 8194ea235122e48368e462ea0a23b32b21cadc72..4c747f15dad7aa1a5945af5f569c51d258c9beb0 100644 --- a/submissions/templates/partials/submissions/submission_events.html +++ b/submissions/templates/partials/submissions/submission_events.html @@ -4,7 +4,7 @@ {% for event in events %} <li class="list-group-item"> <div class="d-flex flex-nowrap justify-content-start"> - <div class="time text-muted pr-2">{{event.created}}</div> + <div class="time text-muted pr-2">{{ event.created|date:'d F Y G:i' }}</div> <div class="content">{{event.text}}</div> </div> </li> diff --git a/submissions/templates/partials/submissions/submission_events_explicit.html b/submissions/templates/partials/submissions/submission_events_explicit.html index fa8f0e87bb968fd2fa158ee2184e46cbbaf21403..4bd614de2c7820138c1e261d2bbaed3ec315df71 100644 --- a/submissions/templates/partials/submissions/submission_events_explicit.html +++ b/submissions/templates/partials/submissions/submission_events_explicit.html @@ -4,7 +4,7 @@ {% for event in events %} <li class="list-group-item"> <div class="d-flex flex-nowrap justify-content-start"> - <div class="time text-muted pr-2">{{event.created}}</div> + <div class="time text-muted pr-2">{{ event.created|date:'d F Y G:i' }}</div> <div class="content"> <strong>{{event.text}}</strong><br> Submission: <a href="{{event.submission.get_absolute_url}}">{{event.submission.title}} ({{ event.submission.preprint.identifier_w_vn_nr }})</a><br> diff --git a/submissions/templates/partials/submissions/submission_li.html b/submissions/templates/partials/submissions/submission_li.html index c0202ef2b1bb890db63491bf6f117dbe91c77d1f..038204a7b7db8b1acfa83b63d6e7baaf0e5a583c 100644 --- a/submissions/templates/partials/submissions/submission_li.html +++ b/submissions/templates/partials/submissions/submission_li.html @@ -1,5 +1,5 @@ <div class="li submission"> - <h5 class="subject">{{ submission.get_subject_area_display }}</h5> + <h5 class="subject"><a href="{% url 'submissions:submissions' %}?subject_area={{ submission.subject_area }}" class="muted-link">{{ submission.get_subject_area_display }}</h5> <h3 class="title"><a href="{{ submission.get_absolute_url }}">{{ submission.title }}</a></h3> <p class="authors">by {{ submission.author_list }}</p> {% block card_footer %}{% endblock %} diff --git a/submissions/templates/partials/submissions/submission_quick_actions.html b/submissions/templates/partials/submissions/submission_quick_actions.html new file mode 100644 index 0000000000000000000000000000000000000000..e2d04737d44f7662b87aaff5e34ad2f4bb6277a4 --- /dev/null +++ b/submissions/templates/partials/submissions/submission_quick_actions.html @@ -0,0 +1,72 @@ +{% if perms.scipost.can_submit_comments %} + <div class="submission-quick-actions"> + <h3>Actions</h3> + <ul class="my-2 pl-4"> + {% if submission.is_open_for_reporting and perms.scipost.can_referee %} + {% if not is_author and not is_author_unchecked %} + <li> + <h4 class="mb-0"> + <a href="{% url 'submissions:submit_report' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">{% if unfinished_report_for_user %}Finish your report{% else %}Contribute a Report{% endif %}</a> + </h4> + <div class="text-danger mt-1 mb-3">Deadline for reporting: {{ submission.reporting_deadline|date:"Y-m-d" }}</div> + </li> + <li> + <h4> + <a href="mailto:?subject=Contribute a Report on a Submission to SciPost?&body={% autoescape on %}{% include 'submissions/contributor_referee_invitation_email.html' %}{% endautoescape %}&cc=edadmin@scipost.org">Invite an expert you know to contribute a Report</a> + </h4> + </li> + {% elif is_author_unchecked %} + <li> + <h4> + <a href="javascript:;" class="disabled">Contribute a Report</a> + <small class="text-danger">[deactivated]</small> + </h4> + <div class="border bg-light p-2 mb-2"> + The system flagged you as a potential author of this Submission. Please <a href="{% url 'scipost:claim_authorships' %}">clarify this here</a>. + You are not allowed to contributor a Report until your authorship has been verified. + </div> + </li> + {% elif is_author %} + <li> + <h4> + <a href="javascript:;" class="disabled">Contribute a Report</a> + <small class="text-danger">[deactivated]</small> + </h4> + <div class="border bg-white p-2 mb-2"> + You are a verified author. Therefore, you can not submit a Report.</span> + </div> + </li> + {% endif %} + {% elif unfinished_report_for_user %} + <li><i class="fa fa-exclamation"></i> You have an unfinished report for this submission. You can <a href="{% url 'submissions:submit_report' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">finish your report here</a>.</li> + {% else %} + <li class="py-1">Reporting for this Submission is closed.</li> + {% endif %} + + {% if submission.open_for_commenting %} + {% if perms.scipost.can_submit_comments %} + <li class="pt-1"> + <h4><a href="#contribute_comment">Contribute a Comment</a></h4> + </li> + {% endif %} + {% else %} + <li class="py-1">Commenting on this Submission is closed.</li> + {% endif %} + + {% if submission.editor_in_charge == request.user.contributor %} + <li class="pt-1"> + <h4><a href="{% url 'submissions:editorial_page' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Go to the Editorial Page</a></h4> + </li> + {% endif %} + </ul> + + {% if perms.scipost.can_manage_reports %} + <h3 class="mt-4">Administrative actions</h3> + <ul class="pl-4"> + <li> + <a href="{% url 'submissions:treated_submission_pdf_compile' submission.preprint.identifier_w_vn_nr %}">Update the Refereeing Package pdf</a> + </li> + </ul> + {% endif %} + </div> +{% endif %} diff --git a/submissions/templates/partials/submissions/submission_refereeing_history.html b/submissions/templates/partials/submissions/submission_refereeing_history.html index 35491b589860a864cee4ecd1e80a8dd08535dbd4..236903d4901432a85924dee43a6bc7e445c6ae8a 100644 --- a/submissions/templates/partials/submissions/submission_refereeing_history.html +++ b/submissions/templates/partials/submissions/submission_refereeing_history.html @@ -1,20 +1,20 @@ {% load request_filters %} -<div class="card"> - <div class="card-body"> - <h3 class="card-title">Submission & Refereeing History</h3> - {% for sibling in submission.thread %} - <div class="my-2"> - {% if forloop.last %}Submission{% else %}Resubmission{% endif %} <a href="{{ sibling.get_absolute_url }}" class="pubtitleli"{% if target_blank %} target="_blank"{% endif %}>{{ sibling.preprint.identifier_w_vn_nr }}</a> ({{ sibling.submission_date|date:'j F Y' }}) - </div> - <ul class="m-0 pl-4"> - {% for report in sibling.reports.accepted %} - <li><a href="{{ report.get_absolute_url }}"{% if target_blank %} target="_blank"{% endif %}>Report {{ report.report_nr }} submitted on {{ report.date_submitted }} by {% if report.anonymous %}<em>Anonymous</em>{% else %}{{ report.author.get_title_display }} {{ report.author.user.last_name }}{% endif %}</a></li> - {% include 'partials/comments/comments_list.html' with comments=report.comments.vetted css_class='my-1 pl-4' target_blank=target_blank %} - {% endfor %} - </ul> +<div class="submission-contents"> + <h3>Submission & Refereeing History</h3> + {% for sibling in submission.thread.public %} + <div class="mt-3 mb-1"> + {% if forloop.last %}Submission{% else %}Resubmission{% endif %} <a href="{{ sibling.get_absolute_url }}" class="pubtitleli"{% if target_blank %} target="_blank"{% endif %}>{{ sibling.preprint.identifier_w_vn_nr }}</a> on {{ sibling.submission_date|date:'j F Y' }} + </div> + <ul class="my-2 pl-4"> + {% for report in sibling.reports.accepted %} + <li><a href="{{ report.get_absolute_url }}"{% if target_blank %} target="_blank"{% endif %}>Report {{ report.report_nr }} submitted on {{ report.date_submitted }} by {% if report.anonymous %}<em>Anonymous</em>{% else %}{{ report.author.get_title_display }} {{ report.author.user.last_name }}{% endif %}</a></li> + {% include 'partials/comments/comments_list.html' with comments=report.comments.vetted css_class='my-1 pl-4' target_blank=target_blank %} + {% endfor %} + </ul> - {% include 'partials/comments/comments_list.html' with comments=sibling.comments.vetted css_class='my-1 pl-4' target_blank=target_blank %} - {% endfor %} - </div> + {% include 'partials/comments/comments_list.html' with comments=sibling.comments.vetted css_class='my-2 pl-4' target_blank=target_blank %} + {% empty %} + <h5 class="mt-4">There are no publicly visible links available yet.</h5> + {% endfor %} </div> diff --git a/submissions/templates/partials/submissions/submission_status.html b/submissions/templates/partials/submissions/submission_status.html index 2f3e16e7fbf6782fdb84023c6dbfb5c10fccc770..3497b1b68428c19fa76ed0b684e09db1ca9e158f 100644 --- a/submissions/templates/partials/submissions/submission_status.html +++ b/submissions/templates/partials/submissions/submission_status.html @@ -3,13 +3,13 @@ <div class="status"> <span class="label label-secondary">{{submission.get_status_display}}</span> {% if submission.publication and submission.publication.is_published %} - as <a href="{{submission.publication.get_absolute_url}}"> - {% if submission.publication.in_issue %} - {{submission.publication.in_issue.in_volume.in_journal.abbreviation_citation}} <strong>{{submission.publication.in_issue.in_volume.number}}</strong>, {{submission.publication.get_paper_nr}} - {% else %} - {{submission.publication.in_journal.abbreviation_citation}}, {{submission.publication.paper_nr}} - {% endif %} - ({{submission.publication.publication_date|date:'Y'}})</a> + as + <a href="{{submission.publication.get_absolute_url}}"> + {{ submission.publication.get_journal.abbreviation_citation }} + {% if submission.publication.in_issue.in_volume %} <strong>{{ submission.publication.in_issue.in_volume.number }}</strong>,{% endif %} + {{ submission.publication.paper_nr }} + ({{ submission.publication.publication_date|date:'Y' }}) + </a> {% endif %} </div> </div> diff --git a/submissions/templates/partials/submissions/submission_summary.html b/submissions/templates/partials/submissions/submission_summary.html index 99e0db0235f17ee1aca40e8a5bb2dce50e9517c6..b75a9ae12c54edc45721b19ab973fc99d3ff9de1 100644 --- a/submissions/templates/partials/submissions/submission_summary.html +++ b/submissions/templates/partials/submissions/submission_summary.html @@ -50,7 +50,7 @@ </tr> <tr> <td>Submitted to:</td> - <td>{{submission.get_submitted_to_journal_display}}</td> + <td>{{submission.submitted_to}}</td> </tr> <tr> <td>Domain(s):</td> @@ -68,6 +68,6 @@ <a href="{% url 'submissions:refereeing_package_pdf' submission.preprint.identifier_w_vn_nr %}" target="_blank" class="btn btn-outline-primary">Download Refereeing Package</a> </p> {% endif %} - <h3 class="mt-3">Abstract</h3> + <h3 class="mt-4">Abstract</h3> <p>{{submission.abstract}}</p> {% endif %} diff --git a/submissions/templates/partials/submissions/submission_topics.html b/submissions/templates/partials/submissions/submission_topics.html new file mode 100644 index 0000000000000000000000000000000000000000..6f4d50c6d18a3c263514b9bd07207db05840fddd --- /dev/null +++ b/submissions/templates/partials/submissions/submission_topics.html @@ -0,0 +1,35 @@ +{% load user_groups %} + +{% is_scipost_admin request.user as is_scipost_admin %} +{% is_edcol_admin request.user as is_edcol_admin %} + +{% if submission.topics.all or is_scipost_admin or is_edcol_admin %} + <h3 class="mt-4">Ontology / Topics</h3> + See full <a href="{% url 'ontology:ontology' %}">Ontology</a> or <a href="{% url 'ontology:topics' %}">Topics</a> database. + <br> + <br> + + <div> + {% for topic in submission.topics.all %} + <span class="label label-secondary"><a href="{% url 'ontology:topic_details' slug=topic.slug %}">{{ topic }}</a>{% if perms.scipost.can_manage_ontology %} <a href="{% url 'submissions:submission_remove_topic' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr slug=topic.slug %}"><i class="fa fa-times-circle text-danger"></i></a>{% endif %}</span> + {% empty %} + <div>No Topic has yet been associated to this Submission</div> + {% endfor %} + </div> + {% if perms.scipost.can_manage_ontology %} + + <br> + <ul class="list-inline"> + <li class="list-inline-item"> + <form class="form-inline" action="{% url 'submissions:submission_add_topic' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}" method="post"> + <ul class="list-inline"> + <li class="list-inline-item">Add an existing Topic:</li> + <li class="list-inline-item">{% csrf_token %}{{ select_topic_form }}</li> + <li class="list-inline-item"><input class="btn btn-outline-secondary" type="submit" value="Link"></li> + </ul> + </form> + </li> + <li class="list-inline-item p-2">Can't find the Topic you need? <a href="{% url 'ontology:topic_create' %}" target="_blank">Create it</a> (opens in new window)</li> + </ul> + {% endif %} +{% endif %} diff --git a/submissions/templates/partials/submissions/submission_topics_card.html b/submissions/templates/partials/submissions/submission_topics_card.html new file mode 100644 index 0000000000000000000000000000000000000000..0a9309ea82e56d1982dc3e5e5cfbd8020b587859 --- /dev/null +++ b/submissions/templates/partials/submissions/submission_topics_card.html @@ -0,0 +1,35 @@ +{% load user_groups %} + +{% is_scipost_admin request.user as is_scipost_admin %} +{% is_edcol_admin request.user as is_edcol_admin %} + +{% if submission.topics.all or is_scipost_admin or is_edcol_admin %} +<div class="card"> + <div class="card-header"> + <a href="{% url 'ontology:ontology' %}">Ontology</a>/<a href="{% url 'ontology:topics' %}">Topics</a> + </div> + <div class="card-body"> + <ul class="list-inline mb-0"> + {% for topic in submission.topics.all %} + <li class="list-inline-item p-1"><a href="{% url 'ontology:topic_details' slug=topic.slug %}">{{ topic }}</a>{% if perms.scipost.can_manage_ontology %} <a href="{% url 'submissions:submission_remove_topic' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr slug=topic.slug %}"><i class="fa fa-times-circle text-danger"></i></a>{% endif %}</li> + {% empty %} + <li class="list-inline-item">No Topic has yet been associated to this Submission</li> + {% endfor %} + </ul> + {% if perms.scipost.can_manage_ontology %} + <ul class="list-inline"> + <li class="list-inline-item"> + <form class="form-inline" action="{% url 'submissions:submission_add_topic' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}" method="post"> + <ul class="list-inline"> + <li class="list-inline-item">Add an existing Topic:</li> + <li class="list-inline-item">{% csrf_token %}{{ select_topic_form }}</li> + <li class="list-inline-item"><input class="btn btn-outline-secondary" type="submit" value="Link"></li> + </ul> + </form> + </li> + <li class="list-inline-item p-2">Can't find the Topic you need? <a href="{% url 'ontology:topic_create' %}" target="_blank">Create it</a> (opens in new window)</li> + </ul> + {% endif %} + </div> +</div> +{% endif %} diff --git a/submissions/templates/submissions/admin/base.html b/submissions/templates/submissions/admin/base.html index ca0bf72fa20c07d57038adaa363c02932d6be87a..e0ffc6d68d35aeb0d4295eea1c7daf1e162df7a5 100644 --- a/submissions/templates/submissions/admin/base.html +++ b/submissions/templates/submissions/admin/base.html @@ -1,7 +1,7 @@ {% extends 'scipost/base.html' %} {% block breadcrumb %} - <div class="container-outside header"> + <div class="breadcrumb-container"> <div class="container"> <nav class="breadcrumb hidden-sm-down"> {% block breadcrumb_items %} diff --git a/submissions/templates/submissions/admin/submission_presassign_editors.html b/submissions/templates/submissions/admin/submission_presassign_editors.html index c6637bdee4c1cb41ceb93ada0132c18cb7077367..27c28a5891bc3c57d31900cee5bcc588cc45c25c 100644 --- a/submissions/templates/submissions/admin/submission_presassign_editors.html +++ b/submissions/templates/submissions/admin/submission_presassign_editors.html @@ -29,14 +29,15 @@ <br> -<li><a href="{% url 'submissions:editorial_page' submission.preprint.identifier_w_vn_nr %}">Go to editorial page</a></li> -{% if submission.status == 'incoming' %} - <li><a href="{% url 'submissions:do_prescreening' submission.preprint.identifier_w_vn_nr %}">Go to pre-screening page</a></li> -{% else %} - <li><a href="{% url 'submissions:assignment_failed' submission.preprint.identifier_w_vn_nr %}">Close pre-screening: failure to find EIC</a></li> -{% endif %} +<ul> + <li><a href="{% url 'submissions:editorial_page' submission.preprint.identifier_w_vn_nr %}">Go to editorial page</a></li> + {% if submission.status == 'incoming' %} + <li><a href="{% url 'submissions:do_prescreening' submission.preprint.identifier_w_vn_nr %}">Go to pre-screening page</a></li> + {% else %} + <li><a href="{% url 'submissions:assignment_failed' submission.preprint.identifier_w_vn_nr %}">Close pre-screening: failure to find EIC</a></li> + {% endif %} +</ul> -<br><br> <h3 class="highlight">Current invitations</h3> <table class="submission" id="current-status"> @@ -183,8 +184,8 @@ {% if conflict_groups %} {{ conflict_groups|length }} potential conflict{{ conflict_groups|length|pluralize }} found <br> - <a href="javascript:;" data-toggle="toggle" data-target="#fellow-conflicts-{{ form.instance.id }}">Show/hide conflicts</a> - <div id="fellow-conflicts-{{ form.instance.id }}"> + <a href="javascript:;" data-toggle="toggle" data-target="#fellow-conflicts-{{ forloop.counter }}">Show/hide conflicts</a> + <div id="fellow-conflicts-{{ forloop.counter }}"> {% for conflict_group in conflict_groups %} <div class="my-1"> <h4 class="mb-1"><a href="{{ conflict_group.url }}" target="_blank">{{ conflict_group.title }}</a></h4> diff --git a/submissions/templates/submissions/author_guidelines.html b/submissions/templates/submissions/author_guidelines.html index b06cc6a6a9c3679018370e39ddf0b8e82d24cf2a..a891b5b75d75c9ef6261fd330cf2f9d05d561484 100644 --- a/submissions/templates/submissions/author_guidelines.html +++ b/submissions/templates/submissions/author_guidelines.html @@ -1,18 +1,12 @@ -{% extends 'scipost/base.html' %} +{% extends 'submissions/base.html' %} {% block pagetitle %}: author guidelines{% endblock pagetitle %} {% load staticfiles %} -{% block breadcrumb %} - <div class="container-outside header"> - <div class="container"> - <nav class="breadcrumb hidden-sm-down"> - <a href="{% url 'submissions:submissions' %}" class="breadcrumb-item">Submissions</a> - <span class="breadcrumb-item">Author guidelines</span> - </nav> - </div> - </div> +{% block breadcrumb_items %} + {{ block.super }} + <span class="breadcrumb-item">Author guidelines</span> {% endblock %} {% block content %} diff --git a/submissions/templates/submissions/base.html b/submissions/templates/submissions/base.html new file mode 100644 index 0000000000000000000000000000000000000000..bcc86208266d5f531ec23d30b607620f7644c183 --- /dev/null +++ b/submissions/templates/submissions/base.html @@ -0,0 +1,15 @@ +{% extends 'scipost/base.html' %} + +{% block body_class %}{{ block.super }} Submissions{% endblock %} + +{% block breadcrumb %} + <div class="breadcrumb-container"> + <div class="container"> + <nav class="breadcrumb"> + {% block breadcrumb_items %} + <a href="{% url 'submissions:submissions' %}" class="breadcrumb-item">Submissions</a> + {% endblock %} + </nav> + </div> + </div> +{% endblock %} diff --git a/submissions/templates/submissions/communication.html b/submissions/templates/submissions/communication.html index c3d12bb2b619c0e6fcf728fd7bad5646193c2348..657a2fb073c4865ea3d9134c1eb7005cc0fac868 100644 --- a/submissions/templates/submissions/communication.html +++ b/submissions/templates/submissions/communication.html @@ -4,27 +4,36 @@ {% load scipost_extras %} {% load bootstrap %} +{% load user_groups %} + +{% is_edcol_admin request.user as is_editorial_admin %} {% block breadcrumb_items %} - {{block.super}} - <a href="{% url 'submissions:editorial_page' submission.preprint.identifier_w_vn_nr %}" class="breadcrumb-item">Editorial Page ({{submission.preprint.identifier_w_vn_nr}})</a> + + {% if request.user.contributor == submissions.editor_in_charge or is_edcol_admin %} + {{block.super}} + <a href="{% url 'submissions:editorial_page' submission.preprint.identifier_w_vn_nr %}" class="breadcrumb-item">Editorial Page ({{submission.preprint.identifier_w_vn_nr}})</a> + {% else %} + <a href="{% url 'submissions:submissions' %}" class="breadcrumb-item">Submissions</a> + <a href="{{ submission.get_absolute_url }}" class="breadcrumb-item">{{ submission.preprint.identifier_w_vn_nr }}</a> + {% endif %} <span class="breadcrumb-item">Communication</span> {% endblock %} {% block content %} -<div class="card card-grey"> +<div class="card bg-light mb-3"> <div class="card-body"> <h1 class="pb-0">Send a Communication</h1> {% if comtype == 'EtoA' %} - <h3>to the submitting Author of Submission</h3> + <h3 class="mb-0">to the submitting Author of Submission</h3> {% elif comtype == 'AtoE' or comtype == 'RtoE' or comtype == 'StoE' %} - <h3>to the Editor-in-charge of Submission</h3> + <h3 class="mb-0">to the Editor-in-charge of Submission</h3> {% elif comtype == 'EtoR' %} - <h3>to Referee of Submission</h3> + <h3 class="mb-0">to Referee of Submission</h3> {% elif comtype == 'EtoS' %} - <h3>to SciPost Editorial Administrators</h3> + <h3 class="mb-0">to SciPost Editorial Administrators</h3> {% endif %} </div> </div> diff --git a/submissions/templates/submissions/editorial_workflow.html b/submissions/templates/submissions/editorial_workflow.html index 963be2593edb390ac98a7ee0fa1f3852f46133b8..7f6dedf8fabbbabb72a63fd05f78739bf7b02c54 100644 --- a/submissions/templates/submissions/editorial_workflow.html +++ b/submissions/templates/submissions/editorial_workflow.html @@ -1,4 +1,4 @@ -{% extends 'scipost/_personal_page_base.html' %} +{% extends 'submissions/base.html' %} {% block breadcrumb_items %} {{block.super}} diff --git a/submissions/templates/submissions/pool/assignment_request.html b/submissions/templates/submissions/pool/assignment_request.html index 358d9711e69c5a40a4027235ce7eba41e3bea8b0..12c246fcbe7091a97ee0410fe9c8e052da25c0f8 100644 --- a/submissions/templates/submissions/pool/assignment_request.html +++ b/submissions/templates/submissions/pool/assignment_request.html @@ -14,7 +14,7 @@ {% block content %} -<div class="card card-grey"> +<div class="card card-gray"> <div class="card-body"> <h1>Assignment request</h1> <h3 class="pt-0">Can you act as Editor-in-charge? (see below to accept/decline)</h3> diff --git a/submissions/templates/submissions/pool/base.html b/submissions/templates/submissions/pool/base.html index 98873d2bc3e474dc6b401947511b4841931f125d..85820d8f18305a9c442fa777b5bb54f8f80a20a7 100644 --- a/submissions/templates/submissions/pool/base.html +++ b/submissions/templates/submissions/pool/base.html @@ -3,7 +3,7 @@ {% block body_class %}{{ block.super }} pool{% endblock %} {% block breadcrumb %} - <div class="container-outside header"> + <div class="breadcrumb-container"> <div class="container"> <nav class="breadcrumb hidden-sm-down"> {% block breadcrumb_items %} diff --git a/submissions/templates/submissions/pool/editorial_page.html b/submissions/templates/submissions/pool/editorial_page.html index a7fb099f684c13d459263481ea31ccb1b1946b50..8838426481a95ffc161e402f03de1f9736c63f6f 100644 --- a/submissions/templates/submissions/pool/editorial_page.html +++ b/submissions/templates/submissions/pool/editorial_page.html @@ -21,9 +21,7 @@ <h1 class="text-primary">{{submission.title}}</h1> <h3>by {{submission.author_list}}</h3> - <div class="ml-2 mt-2"> - <h3>- Go to the <a href="{% url 'submissions:submission' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Submission Page</a> to view Reports and Comments</h3> - </div> + <h4 class="ml-2 mt-4">- <a href="{% url 'submissions:submission' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Go to the Submission Page</a> to view Reports and Comments</h4> <h3 class="mt-4">Submission summary</h3> {% include 'partials/submissions/submission_summary.html' with submission=submission hide_title=1 %} @@ -54,268 +52,351 @@ <div class="pl-md-4">{{ submission.referees_flagged }}</div> {% endif %} - + <br> + <h3 id="editorial-recommendation">Editorial Recommendation</h3> {% for recommendation in submission.eicrecommendations.all %} - {% include 'partials/submissions/recommendation_author_content.html' with recommendation=recommendation %} - {% if recommendation.may_be_reformulated %} - <a href="{% url 'submissions:reformulate_eic_recommendation' submission.preprint.identifier_w_vn_nr %}">Reformulate this Editorial Recommendation</a> - {% endif %} + {% include 'partials/submissions/recommendation_fellow_content.html' with recommendation=recommendation %} {% endfor %} + + {% if submission.eic_recommendation_required %} + <div class="mb-4"> + {% if not submission.eicrecommendations.all %} + No Editorial Recommendation has been formulated yet. + {% endif %} + {% if submission.in_refereeing_phase %} + Refereeing is still in progress, you may <a href="{% url 'submissions:eic_recommendation' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">formulate an Editorial Recommendation</a> when the refereeing round is closed. + {% elif not submission.reports.accepted %} + Please make sure you have at least one vetted Report before you <a href="{% url 'submissions:eic_recommendation' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">formulate an Editorial Recommendation</a>. + {% else %} + When refereeing has finished, you may <a href="{% url 'submissions:eic_recommendation' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">formulate an Editorial Recommendation here</a>. + {% endif %} + </div> + {% endif %} </div> <div class="col-md-4"> {% include 'partials/submissions/submission_refereeing_history.html' with submission=submission %} + + <div class="submission-contents"> + <h3>On this Editorial Page:</h3> + <ul class="my-2 pl-4"> + <li><a href="#editorial-recommendation">Editorial Recommendation</a></li> + <li> + <a href="#editorial-status">Editorial status</a> + <ul> + <li><a href="#required-actions">Required actions</a> ({{ submission.cycle.required_actions|length }})</li> + <li><a href="#referee-details">Refereeing invitations</a></li> + </ul> + </li> + <li> + <a href="#current-contributions">Current contributions</a> + <ul> + <li><a href="#reports-summary">Reports</a></li> + <li><a href="#comments-summary">Comments</a></li> + </ul> + </li> + <li> + <a href="#communications">Communications</a> + </li> + <li> + <a href="#events">Events</a> + </li> + </ul> + </div> </div> </div> +<hr class="lg"> + <div class="py-2 mb-2"> <h2 class="highlight">Editorial Workflow</h2> <a href="{% url 'submissions:editorial_workflow' %}">How-to guide: summary of the editorial workflow</a> </div> -<div class="row"><!-- Status --> - <div class="col"> - <h3>Editorial status:</h3> - <table class="table table-borderless"> - <tr> - <td>Submission status:</td> - <td><span class="label label-secondary">{{ submission.get_status_display }}</span></td> - </tr> - <tr> - <td>Recommendation status:</td> - <td> - {% if submission.eicrecommendations.active.first %} - <span class="label label-secondary">{{ submission.eicrecommendations.active.first.get_status_display }}</span> - {% else %} - <span class="label label-secondary mb-1">No Editorial Recommendation is formulated yet.</span> - {% if submission.eic_recommendation_required %} - <br><a href="{% url 'submissions:eic_recommendation' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Formulate an Editorial Recommendation here.</a> - {% endif %} - {% endif %} - </td> - </tr> - <tr> - <td>Refereeing cycle:</td> - <td>{{ submission.get_refereeing_cycle_display }}</td> - </tr> + + + <h2 id="editorial-status">Editorial status</h2> + <p>{{ submission.cycle }}</p> + <table class="table table-borderless"> + <tr> + <td>Submission status:</td> + <td><span class="label label-secondary">{{ submission.get_status_display }}</span></td> + </tr> + <tr> + <td>Recommendation status:</td> + <td> + {% if submission.eicrecommendations.active.first %} + <span class="label label-secondary">{{ submission.eicrecommendations.active.first.get_status_display }}</span> + {% else %} + <span class="label label-secondary mb-1">No Editorial Recommendation is formulated yet.</span> + {% endif %} + </td> + </tr> + <tr> + <td>Editor-in-charge:</td> + <td> + {% if submission.editor_in_charge %} + {{ submission.editor_in_charge.formal_str }} + {% else %} + Unassigned{% if is_editorial_admin %}, <a href="{% url 'submissions:editor_invitations' submission.preprint.identifier_w_vn_nr %}">see editor invitations</a>.{% endif %} + {% endif %} + </td> + </tr> + <tr> + <td>Refereeing cycle:</td> + <td>{{ submission.get_refereeing_cycle_display }}</td> + </tr> + <tr> + <td>Publicly available:</td> + <td> + {% if submission.visible_public %} + <i class="fa fa-check-circle text-success" aria-hidden="true"></i> + <span class="text-muted">Available in public pages and search results.</span> + {% else %} + <i class="fa fa-times-circle text-danger" aria-hidden="true"></i> + <span class="text-muted">Only available for editors and authors.</span> + {% endif %} + </td> + </tr> + {% if submission.plagiarism_report or perms.scipost.can_do_plagiarism_checks %} <tr> - <td>Publicly available:</td> + <td>Plagiarism report:</td> <td> - {% if submission.visible_public %} - <i class="fa fa-check-circle text-success" aria-hidden="true"></i> - <span class="text-muted">Available in public pages and search results.</span> - {% else %} - <i class="fa fa-times-circle text-danger" aria-hidden="true"></i> - <span class="text-muted">Only available for editors and authors.</span> - {% endif %} - </td> - </tr> - {% if submission.plagiarism_report or perms.scipost.can_do_plagiarism_checks %} - <tr> - <td>Plagiarism report:</td> - <td> - {% if submission.plagiarism_report %} - {% if submission.plagiarism_report.percent_match %} - <b>{{ submission.plagiarism_report.percent_match }}%</b> - {% else %} - <em>Scan in progress</em> - {% if perms.scipost.can_do_plagiarism_checks %} - <br> - <a href="{% url 'submissions:plagiarism' submission.preprint.identifier_w_vn_nr %}">Update plagiarism score</a> - {% endif %} + {% if submission.plagiarism_report %} + {% if submission.plagiarism_report.percent_match %} + <b>{{ submission.plagiarism_report.percent_match }}%</b> + {% else %} + <em>Scan in progress</em> + {% if perms.scipost.can_do_plagiarism_checks %} + <br> + <a href="{% url 'submissions:plagiarism' submission.preprint.identifier_w_vn_nr %}">Update plagiarism score</a> {% endif %} - {% elif perms.scipost.can_do_plagiarism_checks %} - <em>No plagiarism report found.</em> - <br> - <a href="{% url 'submissions:plagiarism' submission.preprint.identifier_w_vn_nr %}">Run plagiarism check</a> {% endif %} - </td> - </tr> - {% endif %} - <tr> - <td>Open for commenting:</td> - <td> - {% if submission.open_for_commenting %} - <i class="fa fa-check-circle text-success" aria-hidden="true"></i> - <span class="text-muted">Open for commenting.</span> - {% else %} - <i class="fa fa-times-circle text-danger" aria-hidden="true"></i> - <span class="text-muted">Commenting closed.</span> + {% elif perms.scipost.can_do_plagiarism_checks %} + <em>No plagiarism report found.</em> + <br> + <a href="{% url 'submissions:plagiarism' submission.preprint.identifier_w_vn_nr %}">Run plagiarism check</a> {% endif %} </td> </tr> - <tr> - <td>Open for refereeing:</td> - <td> + {% endif %} + <tr> + <td>Open for commenting:</td> + <td> + {% if submission.open_for_commenting %} + <i class="fa fa-check-circle text-success" aria-hidden="true"></i> + <span class="text-muted">Open for commenting.</span> + {% else %} + <i class="fa fa-times-circle text-danger" aria-hidden="true"></i> + <span class="text-muted">Commenting closed.</span> + {% endif %} + </td> + </tr> + <tr id="reporting-deadline"> + <td>Open for refereeing:</td> + <td> + {% if submission.is_open_for_reporting %} + <i class="fa fa-check-circle text-success" aria-hidden="true"></i> + <span class="text-muted">Open for refereeing. Deadline: {{ submission.reporting_deadline|date:"SHORT_DATE_FORMAT" }}.</span> + {% else %} + <i class="fa fa-times-circle text-danger" aria-hidden="true"></i> + <span class="text-muted"> + Refereeing closed. + <br> + <em>Invited referees may still submit a Report, as long as their invitation is not finished nor cancelled.</em> + </span> + {% endif %} + {% if submission.in_refereeing_phase %} + {% if submission.reporting_deadline_has_passed %} + <div class="mt-2 p-2 border"> + <i class="fa fa-exclamation-triangle text-danger"></i> + <strong>The reporting deadline has passed.</strong> + </div> + {% elif submission.reporting_deadline_approaching %} + <div class="mt-2 p-2 border"> + <i class="fa fa-exclamation-triangle text-warning"></i> + The reporting deadline is in {{ submission.reporting_deadline|timeuntil }}. + </div> + {% endif %} + {% endif %} + + {% if submission.can_reset_reporting_deadline %} {% if submission.is_open_for_reporting %} - <i class="fa fa-check-circle text-success" aria-hidden="true"></i> - <span class="text-muted">Open for refereeing. Deadline: {{ submission.reporting_deadline|date:"SHORT_DATE_FORMAT" }}</span> + <div class="my-1"> + You may extend the refereeing deadline by + <a href="{% url 'submissions:extend_refereeing_deadline' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr days=2 %}">2 days</a>, + <a href="{% url 'submissions:extend_refereeing_deadline' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr days=7 %}">1 week</a> or + <a href="{% url 'submissions:extend_refereeing_deadline' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr days=14 %}">2 weeks</a>, + + or set a refereeing deadline: + <form class="form-inline d-inline-block" action="{% url 'submissions:set_refereeing_deadline' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}" method="post"> + {% csrf_token %} + <div class="d-inline-block mx-2"> + {% for field in set_deadline_form.visible_fields %} + {{ field|add_css_class:'form-control' }} + {{ field }} + {% endfor %} + </div> + + <input class="btn btn-outline-secondary" type="submit" value="Set deadline"/> + </form>. + </div> {% else %} - <i class="fa fa-times-circle text-danger" aria-hidden="true"></i> - <span class="text-muted"> - Refereeing closed. - <br> - <em>Invited referees may still submit a Report, as long as their invitation is not finished nor cancelled.</em> - </span> + <div class="mt-1"> + Open the refereeing round by setting a refereeing deadline: + <form class="form-inline d-inline-block" action="{% url 'submissions:set_refereeing_deadline' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}" method="post"> + {% csrf_token %} + <div class="d-inline-block mx-2"> + {% for field in set_deadline_form.visible_fields %} + {{ field|add_css_class:'form-control' }} + {{ field }} + {% endfor %} + </div> + + <input class="btn btn-outline-secondary" type="submit" value="Set deadline"/> + </form> + </div> {% endif %} - </td> - </tr> - </table> - </div> - {% if full_access %} - <div class="col-12"> - {% include 'partials/submissions/pool/required_actions_block.html' with submission=submission %} - </div> - {% endif %} -</div><!-- End status --> + {% endif %} + + {% if submission.eic_recommendation_required %} + <div class="px-3 py-2 mt-1 mb-4 border"> + <a href="{% url 'submissions:eic_recommendation' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Formulate an Editorial Recommendation</a>. + <br> + If you recommend revisions, this will be communicated directly to the Authors, who will be asked to resubmit. + <br> + If you recommend acceptance or rejection, this will be put to the Editorial College for ratification. + </div> + {% endif %} + </td> + </tr> + <tr> + <td>Number of referees invited:</td> + <td> + {{ submission.referee_invitations.count }} <span>[{{ submission.referee_invitations.accepted.count }} acccepted / {{ submission.referee_invitations.declined.count }} declined / {{ submission.referee_invitations.awaiting_response.count }} response pending]</span> + </td> + </tr> + <tr> + <td>Number of reports obtained:</td> + <td> + {{ submission.reports.accepted.count }} [{{ submission.reports.accepted.invited.count }} invited / {{ submission.reports.accepted.contributed.count }} contributed]; {{ submission.reports.rejected.count }} refused, {{ submission.reports.awaiting_vetting.count }} awaiting vetting + </td> + </tr> + </table> -{% if not submission.refereeing_cycle %} {% if full_access %} - <div class="row"> - <div class="col-12"> - {% include 'partials/submissions/pool/submission_cycle_choice_form.html' with form=cycle_choice_form submission=submission %} - </div> - </div> - {% else %} - <div class="row"> - <div class="col-12"> - <h3 class="text-center">The Editor-in-charge first has to decide which refereeing cycle to use. Please check this page again at a later moment.</h3> - </div> + <div class="my-5 p-3 border{% if submission.cycle.has_required_actions %} border-danger{% endif %}" id="required-actions" {% if submission.cycle.has_required_actions %}style="border-width: 2px !important;"{% endif %}> + <h3> + {% if submission.cycle.has_required_actions %} + <i class="fa fa-exclamation-triangle text-danger"></i> + {% else %} + <i class="fa fa-check-circle"></i> + {% endif %} + Required actions + </h3> + {{ submission.cycle.required_actions }} </div> {% endif %} -{% else %} - {% if submission.refereeing_cycle != 'direct_rec' %} - <div class="row"> - <div class="col-12"> - <h3>Refereeing status summary:</h3> - - {% include 'partials/submissions/pool/referee_invitations_status.html' with submission=submission %} - <a href="#reports-summary">View Reports and Comments on this Submission</a> - </div> - </div> + {% if not submission.refereeing_cycle %} {% if full_access %} <div class="row"> <div class="col-12"> - <h3 class="mb-2">Detail of refereeing invitations:</h3> - {% include 'partials/submissions/pool/referee_invitations.html' with submission=submission invitations=submission.referee_invitations.all %} + {% include 'partials/submissions/pool/submission_cycle_choice_form.html' with form=cycle_choice_form submission=submission %} </div> </div> - {% endif %} - {% endif %} - - - {% if full_access %} - <hr> - - {% if not submission.is_current %} + {% else %} <div class="row"> <div class="col-12"> - <div class="card mt-3 border-warning bg-light"> - <div class="card-body"> - <h3 class="mb-3"><strong>BEWARE: This is not the editorial page for the current version!</strong></h3> - <p class="mb-0"> - The tools here are thus available only for exceptional circumstances (e.g. vetting a late report on a deprecated version). - <br>Please go to the current version's page using the link at the top. - </p> - </div> - </div> + <h3 class="text-center">The Editor-in-charge first has to decide which refereeing cycle to use. Please check this page again at a later moment.</h3> </div> </div> {% endif %} + {% else %} + {% if full_access %} - <div class="row"> - <div class="col-12"> - <h2 class="mt-3">Actions</h2> - <ul> - {% if submission.refereeing_cycle != 'direct_rec' %} - <li> - <a href="{% url 'submissions:select_referee' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Select an additional referee</a> (bear in mind flagged referees if any) - </li> - <li>Extend the refereeing deadline (currently {{ submission.reporting_deadline|date:'Y-m-d' }}) by - <a href="{% url 'submissions:extend_refereeing_deadline' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr days=2 %}">2 days</a>, - <a href="{% url 'submissions:extend_refereeing_deadline' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr days=7 %}">1 week</a> or - <a href="{% url 'submissions:extend_refereeing_deadline' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr days=14 %}">2 weeks</a> - {% if submission.reporting_deadline_has_passed %} - <span class="ml-1 label label-sm label-outline-danger">THE REPORTING DEADLINE HAS PASSED</span> - {% endif %} - </li> - <li> - Set refereeing deadline: - <form class="form-inline" action="{% url 'submissions:set_refereeing_deadline' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}" method="post"> - {% csrf_token %} - {{ set_deadline_form|bootstrap_inline:'0,12' }} - <div class="ml-2 form-group row"> - <div class="col-12"> - <input class="btn btn-outline-secondary" type="submit" value="Set deadline"/> - </div> - </div> - </form> - </li> - - {% if submission.is_open_for_reporting %} - <li><a href="{% url 'submissions:close_refereeing_round' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Close the refereeing round</a> (deactivates submission of new Reports and Comments)</li> - {% endif %} + {% if submission.refereeing_cycle != 'direct_rec' %} + <h3 class="mt-3" id="referee-details">Refereeing invitations</h3> + {% if not submission.referee_invitations.exists %} + <p>You have not yet invited referees. <a href="{% url 'submissions:select_referee' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Please invite at least {{ submission.cycle.minimum_number_of_referees }} referees</a>.</p> + {% elif submission.eicrecommendations.active %} + <p>Thank you for formulating your Editorial Recommendation. The statuses of the referee invitations might still change, see details below.</p> + {% elif submission.referee_invitations.non_cancelled|length < submission.cycle.minimum_number_of_referees %} + {% if submission.referee_invitations.non_cancelled|length == 1 %} + <p>You have 1 referee who {% if submission.referee_invitations.non_cancelled.first.fulfilled %}submitted a Report{% elif submission.referee_invitations.non_cancelled.first.accepted %}has accepted to referee{% else %} is invited{% endif %}. + {% else %} + <p>You have {{ submission.referee_invitations.non_cancelled|length }} referee{{ submission.referee_invitations.non_cancelled|length|pluralize }} who are either invited, have accepted or submitted a Report. {% endif %} - {% with submission.reports.awaiting_vetting as reports %} - {% if reports %} - <li> - Vet submitted Report{{reports|pluralize}}: - <ul class="mb-1"> - {% for report in reports %} - <li><a href="{% url 'submissions:vet_submitted_report' report.id %}">{{report}}</a></li> - {% endfor %} - </ul> - </li> - {% else %} - <li>All Reports have been vetted.</li> - {% endif %} - {% endwith %} - - {% with submission.comments_set_complete.awaiting_vetting as comments %} - {% if comments %} - <li> - Vet submitted Comment{{comments|pluralize}}: - <ul class="mb-1"> - {% for comment in comments %} - <li><a href="{% url 'comments:vet_submitted_comment' comment.id %}">{{comment}}</a></li> - {% endfor %} - </ul> - </li> + <a href="{% url 'submissions:select_referee' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Please invite at least {{ submission.cycle.minimum_number_of_referees }} referees</a>.</p> + {% else %} + <p>Currently, you have {{ submission.referee_invitations.non_cancelled|length }} referees who are either invited, have accepted or submitted a Report. See details below.</p> + {% endif %} + + {% if submission.referee_invitations.needs_attention.count %} + <p><strong><i class="fa fa-exclamation-triangle text-warning"></i> {{ submission.referee_invitations.needs_attention.count }} refereeing invitation{{ submission.referee_invitations.needs_attention.count|pluralize }} need{{ submission.referee_invitations.needs_attention.count|pluralize:'s,' }} attention. See below.</strong></p> + {% endif %} + + {% if submission.eic_recommendation_required %} + <p> + {% if submission.in_refereeing_phase %} + Refereeing is still in progress, you may <a href="{% url 'submissions:eic_recommendation' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">formulate an Editorial Recommendation here</a> when the refereeing round is closed. + {% elif not submission.reports.accepted %} + Please make sure you have at least one vetted Report before you <a href="{% url 'submissions:eic_recommendation' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">formulate an Editorial Recommendation</a>. {% else %} - <li>All Comments have been vetted.</li> - {% endif %} - {% endwith %} - {% if submission.eic_recommendation_required %} - <li> - {% if submission.eicrecommendations.last %} - <a href="{% url 'submissions:reformulate_eic_recommendation' submission.preprint.identifier_w_vn_nr %}">Reformulate Editorial Recommendation</a> - {% else %} - <a href="{% url 'submissions:eic_recommendation' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Formulate an Editorial Recommendation.</a> - {% endif %} - <p> - If you recommend revisions, this will be communicated directly to the Authors, who will be asked to resubmit. - <br> - If you recommend acceptance or rejection, this will be put to the Editorial College for ratification. - </p> - </li> - {% elif submission.eicrecommendations.last %} - {% if submission.eicrecommendations.last.may_be_reformulated %} - <li><a href="{% url 'submissions:reformulate_eic_recommendation' submission.preprint.identifier_w_vn_nr %}">Reformulate Editorial Recommendation</a></li> + When refereeing has finished, you may <a href="{% url 'submissions:eic_recommendation' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">formulate an Editorial Recommendation here</a>. {% endif %} - {% endif %} - </ul> - </div> - </div> + </p> + {% endif %} + + + {% include 'partials/submissions/pool/referee_invitations.html' with submission=submission invitations=submission.referee_invitations.all submission=submission %} + {% endif %} + + <hr class="lg my-5"> + + <h2 id="current-contributions">Current contributions</h2> + <h3 class="mt-3 mb-2" id="reports-summary">Reports</h3> + + {% if submission.reports.awaiting_vetting %} + <p>{{ submission.reports.awaiting_vetting|pluralize:'A new Report has,New Reports have' }} been delivered. Please vet {{ submission.comments_set_complete.awaiting_vetting|pluralize:'it,them' }} below.</p> + {% elif submission.reports.all %} + {% if submission.reporting_deadline_has_passed and submission.eic_recommendation_required %} + <p> + The refereeing deadline has passed and you have received {{ submission.reports.all|length }} Report{{ submission.reports.all|pluralize }}. Please either <a href="#reporting-deadline">extend the reporting deadline</a>, or <a href="{% url 'submissions:eic_recommendation' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">formulate your Editorial Recommendation</a>. + </p> + {% else %} + <p>No action required. All Reports are processed.</p> + {% endif %} + {% else %} + {% if submission.reporting_deadline_has_passed and submission.eic_recommendation_required %} + <p> + <i class="fa fa-exclamation-triangle text-danger"></i> + <strong>The refereeing deadline has passed and you have received no Reports yet.</strong> + Please <a href="#reporting-deadline">extend the reporting deadline</a> and consider sending a reminder to your referees. + </p> + {% else %} + <p>There are no Reports yet. When a Report is submitted, you can take further action from here.</p> + {% endif %} + {% endif %} + + {% include 'partials/submissions/pool/submission_reports_summary_table.html' with submission=submission %} + + + <h3 class="mt-4 mb-2" id="comments-summary">Comments</h3> + + {% if submission.comments_set_complete.awaiting_vetting %} + <p>{{ submission.comments_set_complete.awaiting_vetting|pluralize:'A new Comment has,New Comments have' }} been delivered. Please vet {{ submission.comments_set_complete.awaiting_vetting|pluralize:'it,them' }} below.</p> + {% elif submission.comments_set_complete %} + <p>No action required. All Comments are processed.</p> + {% else %} + <p>There are no Comments yet. When a Comment is submitted, you can take further action from here.</p> + {% endif %} + {% include 'partials/submissions/pool/submission_comments_summary_table.html' with submission=submission %} + {% endif %} {% endif %} -{% endif %} {% if full_access %} - <h3 class="mt-3 mb-2" id="reports-summary">Reports</h3> - {% include 'partials/submissions/pool/submission_reports_summary_table.html' with submission=submission %} - - <h3 class="mt-3 mb-2">Comments</h3> - {% include 'partials/submissions/pool/submission_comments_summary_table.html' with submission=submission %} + <hr class="lg my-5"> - <h3 class="mt-3">Communications</h3> + <h2 id="communications">Communications</h2> <ul> {% if submission.editor_in_charge == request.user.contributor %} <li><a href="{% url 'submissions:communication' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr comtype='EtoA' %}">Draft and send a communication with the submitting Author</a></li> @@ -326,21 +407,13 @@ {% endif %} </ul> - <div class="row"> - <div class="col-12"> - <ul class="list-group list-group-flush"> - {% for comm in submission.editorial_communications.all %} - <li class="list-group-item"> - {% include 'partials/submissions/pool/editorial_communication_content.html' with communication=comm %} - </li> - {% empty %} - <li class="list-group-item">There have been no communications for this Submission.</li> - {% endfor %} - </ul> - </div> - </div> + {% if submission.editor_in_charge == request.user.contributor %} + {% include 'partials/submissions/communication_thread.html' with communication=submission.editorial_communications.all css_class='wide' reader_is_editor=1 %} + {% else %} + {% include 'partials/submissions/communication_thread.html' with communication=submission.editorial_communications.all css_class='wide' reader_is_editor=0 %} + {% endif %} - <h3 class="mt-3">Events</h3> + <h2 class="mt-3" id="events">Events</h2> {% include 'partials/submissions/submission_events.html' with events=submission.events.for_eic %} {% endif %} diff --git a/submissions/templates/submissions/pool/recommendation_formulate.html b/submissions/templates/submissions/pool/recommendation_formulate.html index efd677361590ae2de4d057a38ccd2693f01bf6aa..d21a15d7919b559a6b7018c8954cd8675b082a69 100644 --- a/submissions/templates/submissions/pool/recommendation_formulate.html +++ b/submissions/templates/submissions/pool/recommendation_formulate.html @@ -19,32 +19,19 @@ {% include 'partials/submissions/submission_summary.html' with submission=submission show_abstract=1 %} <br> -<div class="card card-grey"> - <div class="card-body"> - <h2 class="card-title">Your Editorial Recommendation</h2> - <p class="card-text">You recommendation will be processed by the Editorial Administration.</p> - <ul class="mb-0"> - <li>acceptance or rejection: forwarded to the Editorial College for ratification</li> - <li>request for revision: sent directly to the authors</li> - </ul> - </div> +<div class="border bg-light p-3 mb-3"> + <h2>Your Editorial Recommendation</h2> + <p>You recommendation will be processed by the Editorial Administration.</p> + <ul class="mb-0"> + <li>acceptance or rejection: forwarded to the Editorial College for ratification</li> + <li>request for revision: sent directly to the authors</li> + </ul> </div> {% if submission.editor_in_charge != request.user.contributor %} - <div class="row"> - <div class="col-12"> - <div class="card border-danger"> - <div class="card-body d-flex flex-row"> - <div class="p-2"> - <i class="fa fa-2x fa-exclamation-triangle text-warning" aria-hidden="true"></i> - </div> - <div class="px-2"> - You are not assigned as Editor in charge. However, you can formulate an Editorial Recommendation because you are Editorial Administrator. <strong>This Editorial Recommendation will still be signed by the Editor-in-charge.</strong> - </div> - - </div> - </div> - </div> + <div class="border border-danger p-3"> + <i class="fa fa-exclamation-triangle text-warning" aria-hidden="true"></i> + You are not assigned as Editor in charge. However, you can formulate an Editorial Recommendation because you are Editorial Administrator. <strong>This Editorial Recommendation will still be signed by the Editor-in-charge.</strong> </div> {% endif %} diff --git a/submissions/templates/submissions/pool/recommendation_formulate_rewrite.html b/submissions/templates/submissions/pool/recommendation_formulate_rewrite.html index 7e2272dffd8fc965630f1418fc8a5020ea37827f..c859626340e205164f29e6fac3ad7290b4534589 100644 --- a/submissions/templates/submissions/pool/recommendation_formulate_rewrite.html +++ b/submissions/templates/submissions/pool/recommendation_formulate_rewrite.html @@ -18,37 +18,24 @@ <br> {% include 'partials/submissions/submission_summary.html' with submission=submission show_abstract=1 %} -<br> -<div class="card card-grey"> - <div class="card-body"> - <h2 class="card-title">Reformulate Editorial Recommendation</h2> - <p class="card-text">You recommendation will be processed by the Editorial Administration.</p> - <ul> - <li>acceptance or rejection: forwarded to the Editorial College for ratification</li> - <li>request for revision: sent directly to the authors</li> - </ul> - <p class="card-text"> - This recommendation will be saved with a new version number and any other Editorial Recommendation will be deactived. <b>All obtained votes will be lost.</b> - </p> - </div> +<br> +<div class="border bg-light p-3 mb-3"> + <h2>Reformulate your Editorial Recommendation</h2> + <p>You recommendation will be processed by the Editorial Administration.</p> + <ul> + <li>acceptance or rejection: forwarded to the Editorial College for ratification</li> + <li>request for revision: sent directly to the authors</li> + </ul> + <p class="mb-0"> + This recommendation will be saved with a new version number and any other Editorial Recommendation will be deactivated. + </p> </div> {% if submission.editor_in_charge != request.user.contributor %} - <div class="row"> - <div class="col-12"> - <div class="card border-danger"> - <div class="card-body d-flex flex-row"> - <div class="p-2"> - <i class="fa fa-2x fa-exclamation-triangle text-warning" aria-hidden="true"></i> - </div> - <div class="px-2"> - You are not assigned as Editor in charge. However, you can formulate an Editorial Recommendation because you are Editorial Administrator. <strong>This Editorial Recommendation will still be signed by the Editor-in-charge.</strong> - </div> - - </div> - </div> - </div> + <div class="border border-danger p-3"> + <i class="fa fa-exclamation-triangle text-warning" aria-hidden="true"></i> + You are not assigned as Editor in charge. However, you can formulate an Editorial Recommendation because you are Editorial Administrator. <strong>This Editorial Recommendation will still be signed by the Editor-in-charge.</strong> </div> {% endif %} diff --git a/submissions/templates/submissions/referee_form.html b/submissions/templates/submissions/referee_form.html deleted file mode 100644 index 28b12a4dbaa38d702aca9f91d0f3ddf326ee8bb3..0000000000000000000000000000000000000000 --- a/submissions/templates/submissions/referee_form.html +++ /dev/null @@ -1,109 +0,0 @@ -{% extends 'submissions/pool/base.html' %} - -{% block pagetitle %}: select referee for submission{% endblock pagetitle %} - -{% load scipost_extras %} -{% load bootstrap %} - -{% block breadcrumb_items %} - {{block.super}} - <a href="{% url 'submissions:editorial_page' submission.preprint.identifier_w_vn_nr %}" class="breadcrumb-item">Editorial Page ({{submission.preprint.identifier_w_vn_nr}})</a> - <span class="breadcrumb-item">Select Referee</span> -{% endblock %} - -{% block content %} - -<div class="row"> - <div class="col-12"> - <div class="card card-grey"> - <div class="card-body"> - <h1 class="card-text">Referee Selection Page for Submission</h1> - <p class="card-text">(go to the <a href="{% url 'submissions:submission' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Submissions Page</a> to view Reports and Comments)</p> - <p class="card-text">(go back to the <a href="{% url 'submissions:editorial_page' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Editorial Page</a> to take editorial actions)</p> - </div> - </div> - </div> -</div> - -<div class="row"> - <div class="col-12"> - <h2>Submission:</h2> - {% include 'partials/submissions/submission_summary.html' with submission=submission show_abstract=1 %} - - {% if submission.referees_flagged %} - <h3>Referees flagged upon submission (treat reports with caution):</h3> - <p>{{ submission.referees_flagged }}</p> - {% endif %} - </div> -</div> - -<div class="row"> - <div class="col-12"> - <h2 class="highlight" id="form">Select an additional Referee</h2> - - <form action="{% url 'submissions:select_referee' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}#form" method="post"> - {% csrf_token %} - {{ ref_search_form|bootstrap }} - <input class="btn btn-primary" type="submit" value="Find referee"> - </form> - </div> -</div> - -{% if queryresults.entries %} -<div class="row"> - <div class="col-12"> - <div class="card border-danger"> - <div class="card-body"> - <h3 class="card-title text-danger">The system identified the following potential coauthorships (from arXiv database)</h3> - <p class="card-text text-danger">(only up to 5 most recent shown; if within the last 3 years, referee is disqualified):</p> - </div> - <div class="card-body"> - <ul class="list-group list-group-flush px-0"> - {% for entry in queryresults.entries %} - <li class="list-group-item"> - {% include 'partials/submissions/arxiv_queryresult.html' with item=entry id=forloop.counter id2=0 %} - </li> - {% endfor %} - </ul> - </div> - </div> - </div> -</div> -{% endif %} - -<div class="row"> - <div class="col-12"> - {% if contributors_found %} - <h3>Identified as contributor:</h3> - <table class="table"> - {% for contributor in contributors_found %} - <tr> - <td>{{ contributor.user.first_name }} {{ contributor.user.last_name }}</td> - <td> - <a href="{% url 'submissions:send_refereeing_invitation' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr contributor_id=contributor.id auto_reminders_allowed=1 %}">Send refereeing invitation with</a> or <a href="{% url 'submissions:send_refereeing_invitation' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr contributor_id=contributor.id auto_reminders_allowed=0 %}">without</a> auto reminders - {% include 'partials/submissions/refinv_auto_reminders_tooltip.html' %} - </td> - </tr> - {% endfor %} - </table> - {% elif ref_search_form.has_changed %} - <p>No Contributor with this last name could be identified.</p> - {% endif %} - </div> -</div> - -{% if ref_recruit_form %} - <div class="row"> - <div class="col-12"> - <h3 class="mb-2">If the referee you were looking for was not found by using the method above, you can send a registration and refereeing invitation by filling this form:</h3> - <form action="{% url 'submissions:recruit_referee' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}" method="post"> - {% csrf_token %} - {{ ref_recruit_form|bootstrap }} - <input type="submit" name="submit" value="Send invitation" class="btn btn-primary"> - </form> - </div> - </div> -{% endif %} - - -{% endblock %} diff --git a/submissions/templates/submissions/referee_guidelines.html b/submissions/templates/submissions/referee_guidelines.html index 8484e36f4851f3b7ba6b7e4f2fc7714eb9e13394..b6704d628103efd0d7c942c70e32eddfa5fd7356 100644 --- a/submissions/templates/submissions/referee_guidelines.html +++ b/submissions/templates/submissions/referee_guidelines.html @@ -1,24 +1,18 @@ -{% extends 'scipost/base.html' %} +{% extends 'submissions/base.html' %} {% load staticfiles %} {% block pagetitle %}: refereeing guide{% endblock pagetitle %} - -{% block breadcrumb %} - <div class="container-outside header"> - <div class="container"> - <nav class="breadcrumb hidden-sm-down"> - <span class="breadcrumb-item">Refereeing guide</span> - </nav> - </div> - </div> +{% block breadcrumb_items %} + {{block.super}} + <span class="breadcrumb-item">Refereeing guide</span> {% endblock %} {% block content %} -<h1 class="highlight-x">Refereeing at SciPost</h1> +<h1 class="highlight">Refereeing at SciPost</h1> <h2 class="highlight-x">A guide for referees and authors</h2> <div class="row"> diff --git a/submissions/templates/submissions/report_form.html b/submissions/templates/submissions/report_form.html index 729a99feecb8b0b02172b0f61bd7e9e512273485..3f945bfcff85084c8223054871e265b4d09e5fdc 100644 --- a/submissions/templates/submissions/report_form.html +++ b/submissions/templates/submissions/report_form.html @@ -1,4 +1,4 @@ -{% extends 'scipost/_personal_page_base.html' %} +{% extends 'submissions/base.html' %} {% block breadcrumb_items %} {{block.super}} @@ -28,7 +28,7 @@ <hr class="divider"> <div class="row"> <div class="col-12"> - <div class="card card-grey"> + <div class="card bg-light"> <div class="card-body"> <h2>Your {% if form.instance.is_followup_report %}followup {% endif %}report:</h2> <p>A preview of text areas will appear below as you type (you can use $\LaTeX$ \$...\$ for in-text equations or \ [ ... \ ] for on-line equations).</p> @@ -72,6 +72,7 @@ </form> </div> <div class="col-md-6"> + <br> {% include 'partials/submissions/report_preview.html' %} </div> </div> diff --git a/submissions/templates/submissions/select_referee.html b/submissions/templates/submissions/select_referee.html new file mode 100644 index 0000000000000000000000000000000000000000..b647ba97475092a231d5e1f178569cf327c5fbd4 --- /dev/null +++ b/submissions/templates/submissions/select_referee.html @@ -0,0 +1,129 @@ +{% extends 'submissions/pool/base.html' %} + +{% block pagetitle %}: select referee for submission{% endblock pagetitle %} + +{% load scipost_extras %} +{% load bootstrap %} + +{% block breadcrumb_items %} + {{block.super}} + <a href="{% url 'submissions:editorial_page' submission.preprint.identifier_w_vn_nr %}" class="breadcrumb-item">Editorial Page ({{submission.preprint.identifier_w_vn_nr}})</a> + <span class="breadcrumb-item">Select Referee</span> +{% endblock %} + +{% block content %} + +<div class="row"> + <div class="col-12"> + <div class="card bg-light"> + <div class="card-body"> + <h1 class="card-text">Referee Selection Page for Submission</h1> + <p class="card-text">(go to the <a href="{% url 'submissions:submission' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Submissions Page</a> to view Reports and Comments)</p> + <p class="card-text">(go back to the <a href="{% url 'submissions:editorial_page' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Editorial Page</a> to take editorial actions)</p> + </div> + </div> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <h2>Submission:</h2> + {% include 'partials/submissions/submission_summary.html' with submission=submission show_abstract=1 %} + + {% if submission.referees_flagged %} + <h3>Referees flagged upon submission (treat reports with caution):</h3> + <p>{{ submission.referees_flagged }}</p> + {% endif %} + </div> +</div> + +<div class="row"> + <div class="col-12"> + <h2 class="highlight" id="form">Select an additional Referee</h2> + + <form action="{% url 'submissions:select_referee' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}" method="get"> + {{ referee_search_form|bootstrap }} + <input class="btn btn-primary" type="submit" value="Find referee"> + </form> + </div> +</div> + +{% if queryresults.entries %} +<div class="row"> + <div class="col-12"> + <div class="card border-danger"> + <div class="card-body"> + <h3 class="card-title text-danger">The system identified the following potential coauthorships (from arXiv database)</h3> + <p class="card-text text-danger">(only up to 5 most recent shown; if within the last 3 years, referee is disqualified):</p> + </div> + <div class="card-body"> + <ul class="list-group list-group-flush px-0"> + {% for entry in queryresults.entries %} + <li class="list-group-item"> + {% include 'partials/submissions/arxiv_queryresult.html' with item=entry id=forloop.counter id2=0 %} + </li> + {% endfor %} + </ul> + </div> + </div> + </div> +</div> +{% endif %} + +<div class="row"> + <div class="col-12"> + {% if workdays_left_to_report < 15 %} + <h4 class="highlight p-1"><strong class="text-danger">Warning: there are {{ workdays_left_to_report }} working days left before the refereeing deadline.</strong><br/><br/><span class="text-muted">Standard refereeing period for {{ submission.submitted_to }}: <em>{{ submission.submitted_to.refereeing_period.days }} days</em></span><br/><br/>Consider resetting the refereeing deadline at the <a href="{% url 'submissions:editorial_page' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Editorial Page</a> before inviting a referee.</h4> + {% endif %} + {% if profiles_found %} + <h3>Matching people in our database:</h3> + <table class="table"> + <tr> + <th>Name</th> + <th>Registered<br/>Contributor?</th> + <th>Email<br/>known?</th> + <th>Action<br/><span class="text-muted"><small>(Unregistered people will also automatically receive a registration invitation)</small></span></th> + </tr> + {% for profile in profiles_found %} + <tr> + <td>{{ profile }}</td> + <td>{% if profile.contributor %}<i class="fa fa-check-circle text-success"></i>{% else %}<i class="fa fa-times-circle text-danger"></i>{% endif %}</td> + <td>{% if profile.email %}<i class="fa fa-check-circle text-success"></i>{% else %}<i class="fa fa-times-circle text-danger"></i>{% endif %}</td> + <td>{% if profile.email %}Send refereeing invitation <a href="{% url 'submissions:invite_referee' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr profile_id=profile.id auto_reminders_allowed=1 %}">with</a> or <a href="{% url 'submissions:invite_referee' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr profile_id=profile.id auto_reminders_allowed=0 %}">without</a> auto-reminders {% include 'partials/submissions/refinv_auto_reminders_tooltip.html' %}{% else %}<span class="text-danger">Cannot send an invitation without an email</span> <i class="fa fa-arrow-right"></i> Add one: + <form class="form-inline" action="{% url 'profiles:add_profile_email' profile_id=profile.id %}" method="post"> + {% csrf_token %} + {{ profile_email_form|bootstrap }} + <input type="hidden" name="next" value="{{ request.get_full_path }}"> + <input class="btn btn-outline-secondary" type="submit" value="Add"> + </form> + {% endif %} + </td> + </tr> + {% empty %} + <tr> + <td>No Profiles found</td> + <td></td> + </tr> + {% endfor %} + </table> + {% endif %} + + </div> +</div> + +{% if profile_form %} +<div class="row"> + <div class="col-12"> + <h3 class="mb-2">Not found? Then add to our database by filling this form:</h3> + <form action="{% url 'submissions:add_referee_profile' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}" method="post"> + {% csrf_token %} + {{ profile_form|bootstrap }} + <input type="submit" name="submit" value="Add to database" class="btn btn-primary"> + </form> + <h4>(does not send invitation yet: you will return to this page, from which you can then invite this referee)</h4> + </div> +</div> +{% endif %} + + +{% endblock %} diff --git a/submissions/templates/submissions/sub_and_ref_procedure.html b/submissions/templates/submissions/sub_and_ref_procedure.html index 2928a8c120e086a5984ddb41807c55752b92f41e..20e4ab4edc79ffc38776d423e8eb58a7ff64f9a6 100644 --- a/submissions/templates/submissions/sub_and_ref_procedure.html +++ b/submissions/templates/submissions/sub_and_ref_procedure.html @@ -1,16 +1,10 @@ -{% extends 'scipost/base.html' %} +{% extends 'submissions/base.html' %} {% block pagetitle %}: submission and refereeing procedure{% endblock pagetitle %} -{% block breadcrumb %} - <div class="container-outside header"> - <div class="container"> - <nav class="breadcrumb hidden-sm-down"> - <a href="{% url 'submissions:submissions' %}" class="breadcrumb-item">Submissions</a> - <span class="breadcrumb-item">Submission and refereeing procedure</span> - </nav> - </div> - </div> +{% block breadcrumb_items %} + {{ block.super }} + <span class="breadcrumb-item">Submission and refereeing procedure</span> {% endblock %} {% block content %} diff --git a/submissions/templates/submissions/submission_detail.html b/submissions/templates/submissions/submission_detail.html index f1cd3d7542af3e47e71033667d9d623087d25305..fbd21cc19685b64abd1568c4b40ff666dec4991b 100644 --- a/submissions/templates/submissions/submission_detail.html +++ b/submissions/templates/submissions/submission_detail.html @@ -1,4 +1,4 @@ -{% extends 'scipost/base.html' %} +{% extends 'submissions/base.html' %} {% load scipost_extras %} {% load submissions_extras %} @@ -6,15 +6,9 @@ {% block pagetitle %} Submission: {{ submission.title|truncatechars:40 }}{% endblock pagetitle %} -{% block breadcrumb %} - <div class="container-outside breadcrumb-nav"> - <div class="container"> - <nav class="breadcrumb"> - <a href="{% url 'submissions:submissions' %}" class="breadcrumb-item">Submissions</a> - <span class="breadcrumb-item">{{submission.preprint.identifier_w_vn_nr}}</span> - </nav> - </div> - </div> +{% block breadcrumb_items %} + {{ block.super }} + <span class="breadcrumb-item">{{ submission.preprint.identifier_w_vn_nr }}</span> {% endblock %} {% block content %} @@ -28,46 +22,76 @@ </div> </div> {% endif %} - {% if unfinished_report_for_user %} - <div class="col-12"> - <div class="border border-warning py-2 px-3 mb-3"> - <i class="fa fa-exclamation-circle text-warning"></i> You have an unfinished report for this submission, <a href="{% url 'submissions:submit_report' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">finish your report here.</a></h3> + + <div class="col-lg-8"> + <!-- Notifications --> + {% if unfinished_report_for_user %} + <div class="w-100"> + <div class="border border-warning py-2 px-3 mb-3"> + <i class="fa fa-exclamation-circle"></i> You have an unfinished report for this submission, <a href="{% url 'submissions:submit_report' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">finish your report here.</a></h3> + </div> </div> - </div> - {% endif %} - <div class="col-md-8"> + {% endif %} + {% if is_author %} + <div class="w-100"> + <div class="border py-2 px-3 mb-3"> + <i class="fa fa-check-circle"></i> You are a verified author. + </div> + </div> + {% elif is_author_unchecked %} + <div class="w-100"> + <div class="border border-warning py-2 px-3 mb-3"> + <i class="fa fa-question-circle"></i> The system flagged you as a potential author of this Submission. Please <a href="{% url 'scipost:claim_authorships' %}">clarify this here</a>. + </div> + </div> + {% endif %} + <!-- End notifications --> + <h2>SciPost Submission Page</h2> <h1 class="text-primary">{{submission.title}}</h1> <h3 class="mb-3">by {{submission.author_list}}</h3> <div class="pl-2 mb-1"> {% if submission.publication and submission.publication.is_published %} - <h3>- Published as <a href="{{submission.publication.get_absolute_url}}"> - {% if submission.publication.in_issue %} - {{submission.publication.in_issue.in_volume.in_journal.abbreviation_citation}} <strong>{{submission.publication.in_issue.in_volume.number}}</strong>, {{submission.publication.get_paper_nr}} - {% else %} - {{submission.publication.in_journal.abbreviation_citation}}, {{submission.publication.paper_nr}} - {% endif %} - ({{submission.publication.publication_date|date:'Y'}})</a></h3> + <h4> + - Published as + <a href="{{submission.publication.get_absolute_url}}"> + {{ submission.publication.get_journal.abbreviation_citation }} + {% if submission.publication.in_issue.in_volume %} <strong>{{ submission.publication.in_issue.in_volume.number }}</strong>,{% endif %} + {{ submission.publication.paper_nr }} + ({{ submission.publication.publication_date|date:'Y' }}) + </a> + </h4> {% endif %} - - {% if submission.editor_in_charge and request.user.contributor == submission.editor_in_charge %} - <h3>- You are the Editor-in-charge, go to the <a href="{% url 'submissions:editorial_page' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Editorial Page</a> to take editorial actions</h3> + {% if unfinished_report_for_user %} + <h4> + <i class="fa fa-exclamation-circle text-danger"></i> + You have an unfinished report for this submission, <a href="{% url 'submissions:submit_report' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">finish your report here.</a> + </h4> {% endif %} {% if not submission.is_current %} - <h3><span class="text-danger">- This is not the current version.</span></h3> + <h4 class="text-danger"> + <i class="fa fa-exclamation-circle text-danger"></i> + This is not the current version. + </h4> + {% endif %} + + {% if submission.editor_in_charge and request.user.contributor == submission.editor_in_charge %} + <h4 class="my-4">- You are the Editor-in-charge, <a href="{% url 'submissions:editorial_page' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">go to the Editorial Page</a> to take editorial actions.</h4> {% endif %} + </div> - <h3 class="mt-2">Submission summary</h3> + <h3 class="mt-4">Submission summary</h3> {% include 'partials/submissions/submission_summary.html' with submission=submission hide_title=1 show_abstract=1 %} {% include 'partials/submissions/submission_status.html' with submission=submission %} - <br> - <br> + {% include 'partials/submissions/submission_topics.html' with submission=submission %} + <br/> + {% if submission.author_comments %} <h3>Author comments upon resubmission</h3> <div class="blockquote"> @@ -81,113 +105,130 @@ {% endif %} </div> - <div class="col-md-4"> + <div class="col-lg-4"> {% for invitation in invitations %} {% include 'partials/submissions/refereeing_status_card.html' with invitation=invitation %} {% endfor %} {% include 'partials/submissions/submission_refereeing_history.html' with submission=submission %} + + {% include 'partials/submissions/submission_quick_actions.html' with submission=submission %} </div> </div> {% if is_author %} {% include 'partials/submissions/submission_author_information.html' with submission=submission %} -{% endif %} - -{% if can_read_editorial_information %} +{% elif can_read_editorial_information %} {% include 'partials/submissions/submission_editorial_information.html' with submission=submission %} {% endif %} -{% if perms.scipost.can_submit_comments %} -<div class="row"> - <div class="col-12"> - <h3 class="highlight">Actions</h3> - <ul> - {% if submission.is_open_for_reporting or 1 and perms.scipost.can_referee %} - {% if not is_author and not is_author_unchecked %} - <li> - <h3><a href="{% url 'submissions:submit_report' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">{% if unfinished_report_for_user %}Finish your report{% else %}Contribute a Report{% endif %}</a></h3> - <div class="text-danger">Deadline for reporting: {{ submission.reporting_deadline|date:"Y-m-d" }}</div> - </li> - <li> - <h4><a href="mailto:?subject=Contribute a Report on a Submission to SciPost?&body={% autoescape on %}{% include 'submissions/contributor_referee_invitation_email.html' %}{% endautoescape %}&cc=edadmin@scipost.org">Invite an expert you know to contribute a Report</a></h4> - </li> - {% elif is_author_unchecked %} - <li> - <h3><a href="javascript:;">Contribute a Report</a> <small><span class="text-danger">[deactivated]</span></small></h3> - <div class="border border-warning py-2 px-3 mb-2"> - The system flagged you as a potential author of this Submission. Please <a href="{% url 'scipost:claim_authorships' %}">clarify this here</a>. You are not allowed to contributor a Report until your authorship has been verified. - </div> - </li> - {% elif is_author %} +{% comment %} + {% if perms.scipost.can_submit_comments %} + <div class="row"> + <div class="col-12"> + <h3 class="highlight">Actions</h3> + <ul> + {% if submission.is_open_for_reporting or 1 and perms.scipost.can_referee %} + {% if not is_author and not is_author_unchecked %} + <li> + <h3><a href="{% url 'submissions:submit_report' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">{% if unfinished_report_for_user %}Finish your report{% else %}Contribute a Report{% endif %}</a></h3> + <div class="text-danger">Deadline for reporting: {{ submission.reporting_deadline|date:"Y-m-d" }}</div> + </li> + <li> + <h4><a href="mailto:?subject=Contribute a Report on a Submission to SciPost?&body={% autoescape on %}{% include 'submissions/contributor_referee_invitation_email.html' %}{% endautoescape %}&cc=edadmin@scipost.org">Invite an expert you know to contribute a Report</a></h4> + </li> + {% elif is_author_unchecked %} + <li> + <h3><a href="javascript:;">Contribute a Report</a> <small><span class="text-danger">[deactivated]</span></small></h3> + <div class="border border-warning py-2 px-3 mb-2"> + The system flagged you as a potential author of this Submission. Please <a href="{% url 'scipost:claim_authorships' %}">clarify this here</a>. You are not allowed to contributor a Report until your authorship has been verified. + </div> + </li> + {% elif is_author %} + <li> + <a href="javascript:;" disabled>Contribute a Report</a> <br><span class="text-danger">You are a verified author. Therefore, you can not submit a Report.</span>. + </li> + {% endif %} + {% elif unfinished_report_for_user %} + <li><i class="fa fa-exclamation"></i> You have an unfinished report for this submission. You can <a href="{% url 'submissions:submit_report' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">finish your report here</a>.</li> + {% else %} + <li>Reporting for this Submission is closed.</li> + {% endif %} + {% 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 %} + {% if submission.editor_in_charge == request.user.contributor %} + <li><a href="{% url 'submissions:editorial_page' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Go to the Editorial Page</a></li> + {% endif %} + {% if perms.scipost.can_manage_reports %} <li> - <a href="javascript:;" disabled>Contribute a Report</a> <br><span class="text-danger">You are a verified author. Therefore, you can not submit a Report.</span>. + <a href="{% url 'submissions:treated_submission_pdf_compile' submission.preprint.identifier_w_vn_nr %}">Update the Refereeing Package pdf</a> </li> {% endif %} - {% elif unfinished_report_for_user %} - <li><i class="fa fa-exclamation"></i> You have an unfinished report for this submission. You can <a href="{% url 'submissions:submit_report' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">finish your report here</a>.</li> - {% else %} - <li>Reporting for this Submission is closed.</li> - {% endif %} - {% 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 %} - {% if submission.editor_in_charge == request.user.contributor %} - <li><a href="{% url 'submissions:editorial_page' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr %}">Go to the Editorial Page</a></li> - {% endif %} - {% if perms.scipost.can_manage_reports %} - <li> - <a href="{% url 'submissions:treated_submission_pdf_compile' submission.preprint.identifier_w_vn_nr %}">Update the Refereeing Package pdf</a> - </li> - {% endif %} - <ul> + <ul> + </div> </div> -</div> -{% endif %} - -{% if invited_reports %} + {% endif %} +{% endcomment %} -<hr class="divider"> +{% if submission.reports.accepted %} + <hr class="lg my-4"> -<div class="row"> - <div class="col-12"> - <div class="card card-grey"> - <div class="card-body"> - <h2 class="card-title mb-0">Invited Reports on this Submission</h2> - <a href="javascript:;" data-toggle="toggle" data-target="#invitedreportslist">Toggle invited reports view</a> + <div class="row"> + <div class="col-12"> + <div class="mb-3"> + <h2 class="highlight">Reports on this Submission</h2> + <a href="javascript:;" data-toggle="toggle" data-target="#reports">Show/hide Reports view</a> </div> </div> </div> -</div> - -<div id="invitedreportslist"> - {% for report in invited_reports %} - {% include 'partials/submissions/report_public.html' with report=report user=request.user perms=perms %} - {% endfor %} -</div> + <div id="reports"> + {% for report in submission.reports.accepted %} + {% include 'partials/submissions/report_public.html' with report=report user=request.user perms=perms %} + {% endfor %} + </div> {% endif %} -{% if contributed_reports %} -<hr class="divider"> +{% comment %} + {% if invited_reports %} + <hr class="lg my-4"> + <div class="row"> + <div class="col-12"> + <div class="mb-3"> + <h2 class="highlight">Invited Reports on this Submission</h2> + <a href="javascript:;" data-toggle="toggle" data-target="#invitedreportslist">Show/hide invited reports view</a> + </div> + </div> + </div> -<div class="mb-3"> - <h2 class="highlight">Contributed Reports on this Submission</h2> - <a href="javascript:;" data-toggle="toggle" data-target="#contributedreportslist">Show/hide contributed reports</a> -</div> -<div id="contributedreportslist"> - {% for report in contributed_reports %} - {% include 'partials/submissions/report_public.html' with report=report user=request.user perms=perms %} - {% endfor %} -</div> + <div id="invitedreportslist"> + {% for report in invited_reports %} + {% include 'partials/submissions/report_public.html' with report=report user=request.user perms=perms %} + {% endfor %} + </div> + {% endif %} -{% endif %} + {% if contributed_reports %} + <hr class="lg my-4"> + + <div class="mb-3"> + <h2 class="highlight">Contributed Reports on this Submission</h2> + <a href="javascript:;" data-toggle="toggle" data-target="#contributedreportslist">Show/hide contributed reports</a> + </div> + <div id="contributedreportslist"> + {% for report in contributed_reports %} + {% include 'partials/submissions/report_public.html' with report=report user=request.user perms=perms %} + {% endfor %} + </div> + {% endif %} +{% endcomment %} {% if not user.is_authenticated %} {% if submission.comments.vetted.exists %} @@ -198,7 +239,7 @@ {% endif %} {% if submission.comments.vetted %} - <hr class="divider"> + <hr class="lg"> {% include 'scipost/comments_block.html' with comments=submission.comments.vetted %} {% endif %} @@ -213,3 +254,9 @@ {% endif %} {% endblock content %} + + +{% block footer_script %} +{{ block.super }} +{{ select_topic_form.media }} +{% endblock footer_script %} diff --git a/submissions/templates/submissions/submission_form.html b/submissions/templates/submissions/submission_form.html index 394498a0b90bbb88d2163dc492259f5f1936c61e..8d4c879e1e0a988d2488bd69a3c46e2148cf2994 100644 --- a/submissions/templates/submissions/submission_form.html +++ b/submissions/templates/submissions/submission_form.html @@ -1,21 +1,28 @@ -{% extends 'scipost/base.html' %} +{% extends 'submissions/base.html' %} {% block pagetitle %}: submit manuscript{% endblock pagetitle %} {% load bootstrap %} +{% block breadcrumb_items %} + {{ block.super }} + <span class="breadcrumb-item">Submit a manuscript</span> +{% endblock %} + {% block footer_script %} <script> - $(document).ready(function(){ - $('select#id_submitted_to_journal').on('change', function (){ + + $(document).ready(function(){ + + $('select#id_submitted_to').on('change', function (){ var selection = $(this).val(); $("#id_proceedings, #id_submission_type").parents('.form-group').hide() switch(selection){ - case "SciPostPhys": + case "{{ id_SciPostPhys }}": $("#id_submission_type").parents('.form-group').show() break; - case "SciPostPhysProc": + case "{{ id_SciPostPhysProc }}": $("#id_proceedings").parents('.form-group').show() break; } @@ -25,52 +32,48 @@ {% endblock %} {% block content %} + <div class="row"> + <div class="col-12"> + <h1 class="highlight">Submit a manuscript to SciPost + {% if form.identifier_w_vn_nr.value %} <span class="my-1 py-0 text-blue">{{form.identifier_w_vn_nr.value}}{% if form.submission_is_resubmission %} <small>(resubmission)</small>{% endif %}</span>{% endif %} + </h1> + </div> - -<div class="row"> - <div class="col-12"> - <h1 class="highlight">Submit a manuscript to SciPost - {% if form.identifier_w_vn_nr.value %} <span class="my-1 py-0 text-blue">{{form.identifier_w_vn_nr.value}}{% if form.submission_is_resubmission %} <small>(resubmission)</small>{% endif %}</span>{% endif %} - </h1> + <div class="col-12"> + <p class="mb-1"> + Before submitting, make sure you agree with the <a href="{% url 'journals:journals_terms_and_conditions' %}">SciPost Journals Terms and Conditions</a>. + </p> + <p class="mb-1"> + You should also make sure you understand the <a href="{% url 'submissions:sub_and_ref_procedure' %}#pwr">refereeing procedure</a> and its open aspect. + </p> + <p class="mb-1"> + In particular, make sure you are familiar with the <a href="{% url 'journals:journals_terms_and_conditions' %}#license_and_copyright_agreement">license and copyright agreement</a> 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> + </div> </div> - <div class="col-12"> - <p class="mb-1"> - Before submitting, make sure you agree with the <a href="{% url 'journals:journals_terms_and_conditions' %}">SciPost Journals Terms and Conditions</a>. - </p> - <p class="mb-1"> - You should also make sure you understand the <a href="{% url 'submissions:sub_and_ref_procedure' %}#pwr">refereeing procedure</a> and its open aspect. - </p> - <p class="mb-1"> - In particular, make sure you are familiar with the <a href="{% url 'journals:journals_terms_and_conditions' %}#license_and_copyright_agreement">license and copyright agreement</a> 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> - </div> -</div> + <div class="row"> + <div class="col-12"> + {% if perms.scipost.can_submit_manuscript %} -<div class="row"> - <div class="col-12"> - {% if perms.scipost.can_submit_manuscript %} + {% if form %} + <form id="full_submission_form" method="post" enctype="multipart/form-data"> + {% csrf_token %} + {{ form|bootstrap }} + <p> + By clicking on Submit, you state that you have read and agree with the <a href="{% url 'journals:journals_terms_and_conditions' %}">SciPost Journals Terms and Conditions</a>, the <a href="{% url 'journals:journals_terms_and_conditions' %}#license_and_copyright_agreement">license and copyright agreement</a> and the <a href="{% url 'journals:journals_terms_and_conditions' %}#author_obligations">author obligations</a>. + </p> + <input type="submit" class="btn btn-primary" value="Submit manuscript"/> + </form> + {% endif %} - {% if form %} - <form id="full_submission_form" method="post" enctype="multipart/form-data"> - {% csrf_token %} - {{ form|bootstrap }} - <p> - By clicking on Submit, you state that you have read and agree with the <a href="{% url 'journals:journals_terms_and_conditions' %}">SciPost Journals Terms and Conditions</a>, the <a href="{% url 'journals:journals_terms_and_conditions' %}#license_and_copyright_agreement">license and copyright agreement</a> and the <a href="{% url 'journals:journals_terms_and_conditions' %}#author_obligations">author obligations</a>. - </p> - <input type="submit" class="btn btn-primary" value="Submit manuscript"/> - </form> + {% else %} + <h3>You are currently not allowed to submit a manuscript.</h3> {% endif %} - - {% else %} - <h3>You are currently not allowed to submit a manuscript.</h3> - {% endif %} + </div> </div> -</div> - - {% endblock content %} diff --git a/submissions/templates/submissions/submission_list.html b/submissions/templates/submissions/submission_list.html index 9f745e999a607451e5236e766b522fecc20cb993..2ca6e4d34e3ba157d83a3846e2696ec06ece849c 100644 --- a/submissions/templates/submissions/submission_list.html +++ b/submissions/templates/submissions/submission_list.html @@ -1,4 +1,4 @@ -{% extends 'scipost/base.html' %} +{% extends 'submissions/base.html' %} {% load bootstrap %} {% load submissions_extras %} @@ -6,22 +6,32 @@ {% block pagetitle %}: Submissions{% endblock pagetitle %} +{% block breadcrumb_items %} + <a href="{% url 'scipost:index' %}" class="breadcrumb-item">Homepage</a> + <span class="breadcrumb-item">Submissions</span> +{% endblock %} + {% block content %} -<div class="row"> - <div class="col-md-4"> - <div class="card card-grey"> - <div class="card-body min-height-190"> - <h1 class="card-title">SciPost Submissions</h1> - <h3><a href="{% url 'submissions:sub_and_ref_procedure' %}">Submission and refereeing procedure</a></h3> - <h3><a href="{% url 'submissions:author_guidelines' %}">Author guidelines</a></h3> - <h3><a href="{% url 'submissions:referee_guidelines' %}">Referee guidelines</a></h3> - <h3><a href="{% url 'submissions:submit_manuscript' %}">Submit a manuscript to SciPost</a></h3> + <div class="row"> + <div class="col-md-4"> + <div class="p-3 mb-3 bg-light scipost-bar border min-height-190"> + <h1 class="mb-3">SciPost Submissions</h1> + <ul> + <li> + <a href="{% url 'submissions:sub_and_ref_procedure' %}">Submission and refereeing procedure</a> + </li> + <li> + <a href="{% url 'submissions:author_guidelines' %}">Author guidelines</a> + </li> + <li> + <a href="{% url 'submissions:referee_guidelines' %}">Referee guidelines</a> + </li> + </ul> + <h4><a href="{% url 'submissions:submit_manuscript' %}">Submit a manuscript to SciPost</a></h4> </div> </div> - </div> - <div class="col-md-4"> - <div class="card card-grey"> - <div class="card-body min-height-190"> + <div class="col-md-4"> + <div class="p-3 mb-3 bg-light scipost-bar border min-height-190"> <h2 class="card-title">Search SciPost Submissions:</h2> <form action="{% url 'submissions:submissions' %}" class="small" method="get"> {{ form|bootstrap:'4,8,sm' }} @@ -29,10 +39,8 @@ </form> </div> </div> - </div> - <div class="col-md-4"> - <div class="card card-grey"> - <div class="card-body min-height-190"> + <div class="col-md-4"> + <div class="p-3 mb-3 bg-light scipost-bar border min-height-190"> <h2>View SciPost Submissions</h2> <ul> <li>Physics: last <a href="{% url 'submissions:browse' discipline='physics' nrweeksback=1 %}">week</a> <a href="{% url 'submissions:browse' discipline='physics' nrweeksback=4 %}">month</a> <a href="{% url 'submissions:browse' discipline='physics' nrweeksback=52 %}">year</a></li> @@ -40,42 +48,41 @@ </div> </div> </div> -</div> -<hr> -<div class="row"> - <div class="col-12"> - {% if recent %} - <h2>Recent Submissions{% if to_journal %} to {{ to_journal }}{% endif %}:</h2> - {% elif browse %} - <h2>Submissions in {{ discipline }} in the last {{ nrweeksback }} week{% if nrweeksback == '1' %}{% else %}s{% endif %}:</h2> - {% else %} - <h2>Search results:</h3> - {% endif %} - </div> - {% if is_paginated %} + <hr> + <div class="row"> <div class="col-12"> - {% include 'partials/pagination.html' with page_obj=page_obj %} + {% if recent %} + <h2>Recent Submissions{% if to_journal %} to {{ to_journal }}{% endif %}:</h2> + {% elif browse %} + <h2>Submissions in {{ discipline }} in the last {{ nrweeksback }} week{% if nrweeksback == '1' %}{% else %}s{% endif %}:</h2> + {% else %} + <h2>Search results:</h3> + {% endif %} </div> - {% endif %} - <div class="col-12"> - <ul class="list-group list-group-flush"> - {% for submission in object_list %} - <li class="list-group-item"> - <div class="card-body px-0"> - {% include 'partials/submissions/submission_card_content.html' with submission=submission %} - </div> - </li> - {% empty %} - <h3><em>No match found for your search query.</em></h3> - {% endfor %} - </ul> - </div> - {% if is_paginated %} + {% if is_paginated %} + <div class="col-12"> + {% include 'partials/pagination.html' with page_obj=page_obj %} + </div> + {% endif %} <div class="col-12"> - {% include 'partials/pagination.html' with page_obj=page_obj %} + <ul class="list-group list-group-flush"> + {% for submission in object_list %} + <li class="list-group-item"> + <div class="card-body px-0"> + {% include 'partials/submissions/submission_card_content.html' with submission=submission %} + </div> + </li> + {% empty %} + <h3><em>No match found for your search query.</em></h3> + {% endfor %} + </ul> </div> - {% endif %} -</div> + {% if is_paginated %} + <div class="col-12"> + {% include 'partials/pagination.html' with page_obj=page_obj %} + </div> + {% endif %} + </div> {% endblock content %} diff --git a/submissions/templates/submissions/submission_prefill_form.html b/submissions/templates/submissions/submission_prefill_form.html index 0b9cdb911805cff65781fe9c9b3d0c56e5b3573d..238eaab6d4dd5dd57e81bcde55ac24d97e5d4994 100644 --- a/submissions/templates/submissions/submission_prefill_form.html +++ b/submissions/templates/submissions/submission_prefill_form.html @@ -1,31 +1,35 @@ -{% extends 'scipost/base.html' %} +{% extends 'submissions/base.html' %} {% load bootstrap %} {% block pagetitle %}: submit manuscript{% endblock pagetitle %} -{% block content %} +{% block breadcrumb_items %} + {{ block.super }} + <span class="breadcrumb-item">Submit a manuscript</span> +{% endblock %} -<div class="row"> - <div class="col-12"> - <h1 class="highlight">Submit a manuscript to SciPost</h1> - </div> +{% block content %} + <div class="row"> + <div class="col-12"> + <h1 class="highlight">Submit a manuscript to SciPost</h1> + </div> - <div class="col-12"> - <p class="mb-1"> - Before submitting, make sure you agree with the <a href="{% url 'journals:journals_terms_and_conditions' %}">SciPost Journals Terms and Conditions</a>. - </p> - <p class="mb-1"> - You should also make sure you understand the <a href="{% url 'submissions:sub_and_ref_procedure' %}#pwr">refereeing procedure</a> and its open aspect. - </p> - <p class="mb-1"> - In particular, make sure you are familiar with the <a href="{% url 'journals:journals_terms_and_conditions' %}#license_and_copyright_agreement">license and copyright agreement</a> 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> + <div class="col-12"> + <p class="mb-1"> + Before submitting, make sure you agree with the <a href="{% url 'journals:journals_terms_and_conditions' %}">SciPost Journals Terms and Conditions</a>. + </p> + <p class="mb-1"> + You should also make sure you understand the <a href="{% url 'submissions:sub_and_ref_procedure' %}#pwr">refereeing procedure</a> and its open aspect. + </p> + <p class="mb-1"> + In particular, make sure you are familiar with the <a href="{% url 'journals:journals_terms_and_conditions' %}#license_and_copyright_agreement">license and copyright agreement</a> 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> + </div> </div> -</div> {% if perms.scipost.can_submit_manuscript %} @@ -35,7 +39,8 @@ <div class='card-body'> <h3>Please provide the arXiv identifier for your Submission</h3> <p><em>(give the identifier without prefix but with version number, as per the placeholder)</em></p> - <form action="{% url 'submissions:submit_manuscript_arxiv' %}" method="get"> + <form action="{% url 'submissions:prefill_using_identifier' %}" method="post"> + {% csrf_token %} {{ form|bootstrap }} <input type="submit" class="btn btn-outline-secondary" value="Query arXiv"/> <br> @@ -44,12 +49,7 @@ </form> </div> </div> - </div> -</div> - -{% else %} - <h3>You are currently not allowed to submit a manuscript.</h3> -{% endif %} - - + {% else %} + <h3>You are currently not allowed to submit a manuscript.</h3> + {% endif %} {% endblock content %} diff --git a/submissions/templates/submissions/submission_resubmission_candidates.html b/submissions/templates/submissions/submission_resubmission_candidates.html new file mode 100644 index 0000000000000000000000000000000000000000..0a26172c5ff5943c7571244b373f03f51e7cd184 --- /dev/null +++ b/submissions/templates/submissions/submission_resubmission_candidates.html @@ -0,0 +1,39 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: submit manuscript{% endblock pagetitle %} + +{% block content %} + +<div class="row"> + <div class="col-12"> + <form method="post"> + {% csrf_token %} + <h1 class="highlight">Possible Resubmissions</h1> + <p>The system has found {{ submissions|length|pluralize:'a Submission,Submissions' }} for which you are a verified author. If you wish to submit a new version for {{ submissions|length|pluralize:'this Submission,one of these Submissions' }}, please use the "Resubmit this Submission" buttton below.</p> + <ul> + {% for submission in submissions %} + <li class="py-2"> + <strong>{{ submission.title }}</strong> + <br> + {{ submission.author_list }} + <br> + Preprint number: {{ submission.preprint.identifier_w_vn_nr }} + <br> + {% if not submission.open_for_resubmission %} + <strong class="text-danger">This submission is still undergoing peer refereeing. A resubmission can only be performed after request from the Editor-in-charge. Please wait until the closing of the previous refereeing round and formulation of the Editorial Recommendation before proceeding with a resubmission.</strong> + {% else %} + <button type="submit" name="submission" value="{{ submission.id }}"class="btn btn-primary py-1 mt-1">Resubmit this Submission</button> + {% endif %} + </li> + {% endfor %} + </ul> + <p> + If you wish to submit a new Submission, please <button type="submit" name="submission" value="new" class="btn btn-primary py-1 mr-1">submit a new Submission here</button>. + </p> + </form> + </div> +</div> + +{% endblock content %} diff --git a/submissions/templates/submissions/treated_submission_list.html b/submissions/templates/submissions/treated_submission_list.html index caf36359132c26b6c7a6e72348ecfac1da4461fc..f918e43d5adf4fdc8ea0b2a693b108436171b8de 100644 --- a/submissions/templates/submissions/treated_submission_list.html +++ b/submissions/templates/submissions/treated_submission_list.html @@ -19,7 +19,7 @@ <div class="row"> <div class="col-12"> - <table class="table"> + <table class="table table-hover"> <thead> <tr> <th>Submission</th> @@ -32,7 +32,7 @@ </thead> <tbody> {% for submission in submissions %} - <tr{% if not submission.pdf_refereeing_pack %} class="table-danger"{% endif %}> + <tr{% if not submission.pdf_refereeing_pack %} class="table-warning"{% endif %}> <td><a href="{{submission.get_absolute_url}}">{{submission.preprint.identifier_w_vn_nr}}</a></td> <td>{{submission.get_status_display}}</td> <td>{{submission.acceptance_date|default_if_none:'Date unknown'}}</td> diff --git a/submissions/templates/submissions/treated_submission_pdf_compile.html b/submissions/templates/submissions/treated_submission_pdf_compile.html index b6038f713782ec69f5c3a1f1bc5256ffb8078e89..d29f338285326183a9afdc8b129b072bb5fdd015 100644 --- a/submissions/templates/submissions/treated_submission_pdf_compile.html +++ b/submissions/templates/submissions/treated_submission_pdf_compile.html @@ -19,7 +19,8 @@ <p class="my-2"><a href="{{submission.get_absolute_url}}">Go to Submission page</a></p> </div> </div> - <hr> + + <hr class="lg"> <div class="row"> <div class="col-12"> @@ -33,7 +34,7 @@ Number of Reports: {{submission.reports.accepted.count}}<br> Number of Comments <small>(nested comments not counted)</small>: {{submission.comments.vetted.count}} </p> - <pre class="clickfocus" style="max-height: 200px;"><code>{% include 'partials/submissions/refereeing_pack_tex_template.html' with submission=submission %}</code></pre> + <pre class="clickfocus border" style="max-height: 200px;"><code>{% include 'partials/submissions/refereeing_pack_tex_template.html' with submission=submission %}</code></pre> </div> </div> @@ -43,7 +44,7 @@ <form method="post" enctype="multipart/form-data"> {% csrf_token %} {{ form|bootstrap }} - <input class="btn btn-outline-secondary" type="submit" value="Upload"/> + <input class="btn btn-primary" type="submit" value="Upload"/> </form> </div> </div> diff --git a/submissions/templates/submissions/vet_submitted_report.html b/submissions/templates/submissions/vet_submitted_report.html index eafd26b8ed3116b2e6f694ae274bc3bfe3739767..5d1d5dbc5da036c1107f6fdcd5a4eca83a8d17a2 100644 --- a/submissions/templates/submissions/vet_submitted_report.html +++ b/submissions/templates/submissions/vet_submitted_report.html @@ -5,25 +5,23 @@ {% load bootstrap %} {% block headsup %} - -<script> -$(document).ready(function(){ - $('[name="action_option"]').on('change', function() { - if ($('[name="action_option"][value="refuse"]').is(':checked')) { - $('#refusal').show(); - } - else { - $('#refusal').hide(); - } - }).trigger('change'); -}); -</script> - + <script> + $(document).ready(function(){ + $('[name="action_option"]').on('change', function() { + if ($('[name="action_option"][value="refuse"]').is(':checked')) { + $('#refusal').show(); + } + else { + $('#refusal').hide(); + } + }).trigger('change'); + }); + </script> {% endblock headsup %} {% block breadcrumb_items %} {{block.super}} - <span class="breadcrumb-item">Vet Report {{report.report_nr}}</span> + <span class="breadcrumb-item">Vet Report</span> {% endblock %} {% block content %} @@ -34,24 +32,20 @@ $(document).ready(function(){ <h1>There are no Reports for you to vet.</h1> <p>Go back to my <a href="{% url 'scipost:personal_page' %}">personal page</a>.</p> {% else %} - <h1 class="highlight">SciPost Report to vet:</h1> + <h1 class="highlight">SciPost Report to vet</h1> - <h2 class="mb-2">Submission associated to Report:</h2> - <div class="row"> - <div class="col-12"> - {% include 'partials/submissions/submission_summary.html' with submission=report_to_vet.submission %} - </div> - </div> - - <h2 class="mb-2">Report to vet:</h2> + <h3 class="mt-4">Submission associated to Report</h3> + {% include 'partials/submissions/submission_summary.html' with submission=report_to_vet.submission %} + <h3 class="mt-4">Report to vet</h3> {% include 'partials/submissions/report_public_without_comments.html' with submission=report_to_vet.submission report=report_to_vet %} - <hr class="small"> + <hr class="lg"> + <h2>Please vet this Report:</h2> <form action="{% url 'submissions:vet_submitted_report' report_to_vet.id %}" method="post"> {% csrf_token %} - {{ form.report }} + {{ form.errors }} {{ form.action_option|bootstrap }} <div class="col-md-6" id="refusal"> {{ form.refusal_reason|bootstrap }} diff --git a/submissions/templates/submissions/vet_submitted_reports_list.html b/submissions/templates/submissions/vet_submitted_reports_list.html index 186a9ec8a66236d3ff4283a3aa54c8341fa8e9b1..121f8cf830fa1febe4d2140fb1baf1a2cfe2f641 100644 --- a/submissions/templates/submissions/vet_submitted_reports_list.html +++ b/submissions/templates/submissions/vet_submitted_reports_list.html @@ -9,7 +9,7 @@ {% block content %} -<h1>SciPost Reports to vet</h1> +<h1 class="highlight">SciPost Reports to vet</h1> {% if reports_to_vet %} <table class="table mt-4"> diff --git a/submissions/templatetags/submissions_extras.py b/submissions/templatetags/submissions_extras.py index b8a8e4f51014572b335673b8af2d13fe2428e135..760bcb91e475664937523ab63f79130b63e48316 100644 --- a/submissions/templatetags/submissions_extras.py +++ b/submissions/templatetags/submissions_extras.py @@ -10,6 +10,12 @@ from ..models import Submission, EICRecommendation register = template.Library() +@register.filter +def filter_for_submission(qs, submission): + """Filter (any) query with the given Submission.""" + return qs.filter(submission=submission) + + @register.filter def is_possible_author_of_submission(user, submission): """Check if User may be related to the Submission as author.""" diff --git a/submissions/test_views.py b/submissions/test_views.py index 6b6bf63e7c3ed4ce9d2b90355fceb4505048ffbd..c2bf1dba5b4cb2cc410c96699a10c3925e4a2f40 100644 --- a/submissions/test_views.py +++ b/submissions/test_views.py @@ -15,9 +15,11 @@ from .factories import UnassignedSubmissionFactory, EICassignedSubmissionFactory ResubmittedSubmissionFactory, ResubmissionFactory,\ PublishedSubmissionFactory, DraftReportFactory,\ AcceptedRefereeInvitationFactory -from .forms import RequestSubmissionForm, SubmissionIdentifierForm, ReportForm +from .forms import SubmissionIdentifierForm, ReportForm. SubmissionForm from .models import Submission, Report, RefereeInvitation +from journals.models import Journal + from faker import Faker @@ -94,7 +96,7 @@ class PrefillUsingIdentifierTest(BaseContributorTestCase): {'identifier': TEST_SUBMISSION['identifier_w_vn_nr']}) self.assertEqual(response.status_code, 200) - self.assertIsInstance(response.context['form'], RequestSubmissionForm) + # self.assertIsInstance(response.context['form'], SubmissionForm) # Explicitly compare fields instead of assertDictEqual as metadata field may be outdated # self.assertEqual(TEST_SUBMISSION['is_resubmission'], @@ -139,7 +141,7 @@ class SubmitManuscriptTest(BaseContributorTestCase): params.update({ 'discipline': 'physics', 'subject_area': 'Phys:MP', - 'submitted_to_journal': 'SciPostPhys', + 'submitted_to': Journal.objects.filter(doi_label='SciPostPhys'), 'submission_type': 'Article', 'domain': 'T' }) @@ -179,7 +181,7 @@ class SubmitManuscriptTest(BaseContributorTestCase): params.update({ 'discipline': 'physics', 'subject_area': 'Phys:MP', - 'submitted_to_journal': 'SciPostPhys', + 'submitted_to': Journal.objects.get(doi_label='SciPostPhys'), 'submission_type': 'Article', 'domain': 'T' }) @@ -187,7 +189,7 @@ class SubmitManuscriptTest(BaseContributorTestCase): # Submit new Submission form response = client.post(reverse('submissions:submit_manuscript'), params) self.assertEqual(response.status_code, 200) - self.assertIsInstance(response.context['form'], RequestSubmissionForm) + self.assertIsInstance(response.context['form'], SubmissionForm) self.assertFalse(response.context['form'].is_valid()) self.assertIn('author_list', response.context['form'].errors.keys()) diff --git a/submissions/urls.py b/submissions/urls.py index 4c4d6c387a56a7c7d51b8cf2344439f648361700..5591c3ff0cb1661be5172ecc0563d6057c21da2e 100644 --- a/submissions/urls.py +++ b/submissions/urls.py @@ -33,6 +33,14 @@ urlpatterns = [ url(r'^{regex}/reports/pdf$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.submission_refereeing_package_pdf, name='refereeing_package_pdf'), + # Topics + url(r'^submission_add_topic/{regex}/'.format(regex=SUBMISSIONS_COMPLETE_REGEX), + views.submission_add_topic, + name='submission_add_topic'), + url(r'^submission_remove_topic/{regex}/(?P<slug>[-\w]+)/'.format(regex=SUBMISSIONS_COMPLETE_REGEX), + views.submission_remove_topic, + name='submission_remove_topic'), + # Editorial Administration url(r'^admin/treated$', views.treated_submissions_list, name='treated_submissions_list'), url(r'^admin/{regex}/prescreening$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), @@ -60,6 +68,7 @@ urlpatterns = [ url(r'^admin/reports/(?P<report_id>[0-9]+)/compile$', views.report_pdf_compile, name='report_pdf_compile'), + url(r'^resubmit_manuscript$', views.resubmit_manuscript, name='resubmit_manuscript'), url(r'^submit_manuscript$', views.prefill_using_arxiv_identifier, name='submit_manuscript'), url(r'^submit_manuscript/scipost$', views.RequestSubmissionUsingSciPostView.as_view(), name='submit_manuscript_scipost'), @@ -94,12 +103,12 @@ urlpatterns = [ views.editorial_page, name='editorial_page'), url(r'^select_referee/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), views.select_referee, name='select_referee'), - url(r'^recruit_referee/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), - views.recruit_referee, name='recruit_referee'), - url(r'^send_refereeing_invitation/{regex}/(?P<contributor_id>[0-9]+)' + url(r'^add_referee_profile/{regex}$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), + views.add_referee_profile, name='add_referee_profile'), + url(r'^invite_referee/{regex}/(?P<profile_id>[0-9]+)' '/(?P<auto_reminders_allowed>[0-1])$'.format( regex=SUBMISSIONS_COMPLETE_REGEX), - views.send_refereeing_invitation, name='send_refereeing_invitation'), + views.invite_referee, name='invite_referee'), url(r'^set_refinv_auto_reminder/(?P<invitation_id>[0-9]+)/(?P<auto_reminders>[0-1])$', views.set_refinv_auto_reminder, name='set_refinv_auto_reminder'), url(r'^accept_or_decline_ref_invitations/$', diff --git a/submissions/utils.py b/submissions/utils.py index d0251fe227607b0a514752f5f867d67f62319c2d..8855acd84ab628be25cbe6ac1bc550a682f2c3bd 100644 --- a/submissions/utils.py +++ b/submissions/utils.py @@ -3,10 +3,12 @@ __license__ = "AGPL v3" import datetime +import json from django.core.mail import EmailMessage, EmailMultiAlternatives from django.template import Context, Template from django.utils import timezone +from django.utils.html import format_html, format_html_join, html_safe from .constants import ( NO_REQUIRED_ACTION_STATUSES, STATUS_VETTED, STATUS_UNCLEAR, STATUS_INCORRECT, STATUS_INCOMING, @@ -16,6 +18,49 @@ from scipost.utils import EMAIL_FOOTER from common.utils import BaseMailUtil +@html_safe +class RequiredActionsDict(dict): + """A collection of required actions. + + The required action, meant for the editors-in-charge know how to display itself in + various formats. Dictionary keys are the action-codes, the values are the texts + to present to the user. + """ + + def as_data(self): + return {f: e.as_data() for f, e in self.items()} + + def as_list_text(self): + return [e.__str__() for e in self.values()] + + def get_json_data(self, escape_html=False): + return {f: e.get_json_data(escape_html) for f, e in self.items()} + + def as_json(self, escape_html=False): + return json.dumps(self.get_json_data(escape_html)) + + def as_ul(self): + if not self: + return '<div class="no-actions-msg">No required actions.</div>' + return format_html( + '<ul class="actions-list">{}</ul>', format_html_join('', '<li>{}</li>', self.values())) + + def as_text(self): + return ' '.join([action.as_text() for action in self.values()]) + + def __getitem__(self, action): + return super().__getitem__(action.id) + + def __setitem__(self, action, val): + super().__setitem__(action.id, val) + + def __contains__(self, value): + return value.id in list(self.keys()) + + def __str__(self): + return self.as_ul() + + class BaseSubmissionCycle: """ The submission cycle may take different approaches. All steps within a specific @@ -49,12 +94,6 @@ class BaseSubmissionCycle: # Editor-in-charge has requested revision. return False - # if not self.submission.plagiarism_report: - # # No plagiarism report is known yet. - # self.required_actions.append(( - # 'plagiarism_report', - # 'No plagiarism report found. Please run the plagiarism check.')) - if self.submission.eicrecommendations.active().exists(): # An Editorial Recommendation has already been submitted. Cycle done. return False @@ -279,7 +318,8 @@ class SubmissionUtils(BaseMailUtil): """ Requires loading 'submission' attribute. """ email_text = ('Dear ' + cls.submission.submitted_by.get_title_display() + ' ' + cls.submission.submitted_by.user.last_name + - ', \n\nWe have received your Submission to SciPost,\n\n' + + ', \n\nWe have received your Submission to ' + + str(cls.submission.submitted_to) + ',\n\n' + cls.submission.title + ' by ' + cls.submission.author_list + '.' + '\n\nWe will update you on the results of the pre-screening process ' 'within the next 5 working days.' @@ -289,7 +329,7 @@ class SubmissionUtils(BaseMailUtil): '\n\nThe SciPost Team.') email_text_html = ( '<p>Dear {{ title }} {{ last_name }},</p>' - '<p>We have received your Submission to SciPost,</p>' + '<p>We have received your Submission to {{ submitted_to }},</p>' '<p>{{ sub_title }}</p>' '\n<p>by {{ author_list }}.</p>' '\n<p>We will update you on the results of the pre-screening process ' @@ -302,6 +342,7 @@ class SubmissionUtils(BaseMailUtil): 'title': cls.submission.submitted_by.get_title_display(), 'last_name': cls.submission.submitted_by.user.last_name, 'sub_title': cls.submission.title, + 'submitted_to': str(cls.submission.submitted_to), 'author_list': cls.submission.author_list, } email_text_html += '<br/>' + EMAIL_FOOTER @@ -1134,7 +1175,7 @@ class SubmissionUtils(BaseMailUtil): or cls.recommendation.recommendation == 3): email_text += ('We are pleased to inform you that your Submission ' 'has been accepted for publication in ' - + cls.submission.get_submitted_to_journal_display()) + + str(cls.submission.submitted_to)) email_text_html += ( '<p>We are pleased to inform you that your Submission ' 'has been accepted for publication in <strong>{{ journal }}</strong>') @@ -1191,7 +1232,7 @@ class SubmissionUtils(BaseMailUtil): 'sub_title': cls.submission.title, 'author_list': cls.submission.author_list, 'identifier_w_vn_nr': cls.submission.preprint.identifier_w_vn_nr, - 'journal': cls.submission.get_submitted_to_journal_display(), + 'journal': str(cls.submission.submitted_to), } email_text_html += '<br/>' + EMAIL_FOOTER html_template = Template(email_text_html) diff --git a/submissions/views.py b/submissions/views.py index ad850a1aadde0a76911ff8294ed57a3930c3f38d..65f82868b4005d024527aa5bed8a5125d7531f8d 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -1,7 +1,6 @@ __copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" __license__ = "AGPL v3" - import datetime import feedparser import strings @@ -31,46 +30,98 @@ from .models import ( Submission, EICRecommendation, EditorialAssignment, RefereeInvitation, Report, SubmissionEvent) from .mixins import SubmissionAdminViewMixin from .forms import ( - SubmissionIdentifierForm, RequestSubmissionForm, SubmissionSearchForm, RecommendationVoteForm, + SubmissionIdentifierForm, SubmissionForm, SubmissionSearchForm, RecommendationVoteForm, ConsiderAssignmentForm, InviteEditorialAssignmentForm, EditorialAssignmentForm, VetReportForm, - SetRefereeingDeadlineForm, RefereeSelectForm, iThenticateReportForm, VotingEligibilityForm, - RefereeRecruitmentForm, ConsiderRefereeInvitationForm, EditorialCommunicationForm, ReportForm, + SetRefereeingDeadlineForm, RefereeSearchForm, #RefereeSelectForm, + iThenticateReportForm, VotingEligibilityForm, + #RefereeRecruitmentForm, + ConsiderRefereeInvitationForm, EditorialCommunicationForm, ReportForm, SubmissionCycleChoiceForm, ReportPDFForm, SubmissionReportsForm, EICRecommendationForm, SubmissionPoolFilterForm, FixCollegeDecisionForm, SubmissionPrescreeningForm, PreassignEditorsFormSet, SubmissionReassignmentForm) +from .signals import ( + notify_editor_assigned, notify_eic_new_report, notify_invitation_cancelled, + notify_submission_author_new_report, notify_eic_invitation_reponse, notify_report_vetted) from .utils import SubmissionUtils from colleges.permissions import fellowship_required, fellowship_or_admin_required from comments.forms import CommentForm +from common.helpers import get_new_secrets_key +from common.utils import workdays_between +from invitations.constants import STATUS_SENT +from invitations.models import RegistrationInvitation from journals.models import Journal from mails.views import MailEditingSubView +from ontology.models import Topic +from ontology.forms import SelectTopicForm from production.forms import ProofsDecisionForm from profiles.models import Profile +from profiles.forms import SimpleProfileForm, ProfileEmailForm +from scipost.constants import INVITATION_REFEREEING +from scipost.decorators import is_contributor_user from scipost.forms import RemarkForm from scipost.mixins import PaginationMixin from scipost.models import Contributor, Remark -# from notifications.views import is_test_user # Temporarily until release - ############### # SUBMISSIONS: ############### +@login_required +@is_contributor_user() +def resubmit_manuscript(request): + """ + Choose which Submission to resubmit if Submission is available. + + On POST, redirect to submit page. + """ + submissions = get_list_or_404( + Submission.objects.candidate_for_resubmission(request.user)) + if request.POST and request.POST.get('submission'): + if request.POST['submission'] == 'new': + return redirect(reverse('submissions:submit_manuscript_scipost') + '?resubmission=false') + + last_submission = Submission.objects.candidate_for_resubmission(request.user).filter( + id=request.POST['submission']).first() + + if last_submission: + if last_submission.preprint.scipost_preprint_identifier: + # Determine right preprint-view. + extra_param = '?resubmission={}'.format(request.POST['submission']) + return redirect(reverse('submissions:submit_manuscript_scipost') + extra_param) + else: + extra_param = '?identifier_w_vn_nr={}'.format( + last_submission.preprint.identifier_w_vn_nr) + return redirect(reverse('submissions:submit_manuscript') + extra_param) + else: + # POST request invalid. Try again with GET request. + return redirect('submissions:resubmit_manuscript') + context = { + 'submissions': submissions, + } + return render(request, 'submissions/submission_resubmission_candidates.html', context) + class RequestSubmissionView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): """Formview to submit a new manuscript (Submission).""" permission_required = 'scipost.can_submit_manuscript' success_url = reverse_lazy('scipost:personal_page') - form_class = RequestSubmissionForm + form_class = SubmissionForm template_name = 'submissions/submission_form.html' + def get_context_data(self, *args, **kwargs): + context = super().get_context_data(*args, **kwargs) + context['id_SciPostPhys'] = get_object_or_404(Journal, doi_label='SciPostPhys').id + context['id_SciPostPhysProc'] = get_object_or_404(Journal, doi_label='SciPostPhysProc').id + return context + def get_form_kwargs(self): """Form requires extra kwargs.""" kwargs = super().get_form_kwargs() kwargs['requested_by'] = self.request.user - if hasattr(self, 'initial_data'): - kwargs['initial'] = self.initial_data + kwargs['initial'] = getattr(self, 'initial_data', {}) + kwargs['initial']['resubmission'] = self.request.GET.get('resubmission') return kwargs @transaction.atomic @@ -78,13 +129,13 @@ class RequestSubmissionView(LoginRequiredMixin, PermissionRequiredMixin, CreateV """Redirect and send out mails if all data is valid.""" submission = form.save() submission.add_general_event('The manuscript has been submitted to %s.' - % submission.get_submitted_to_journal_display()) + % str(submission.submitted_to)) text = ('<h3>Thank you for your Submission to SciPost</h3>' 'Your Submission will soon be handled by an Editor.') messages.success(self.request, text) - if form.submission_is_resubmission(): + if form.is_resubmission(): # Send emails SubmissionUtils.load({'submission': submission}, self.request) SubmissionUtils.send_authors_resubmission_ack_email() @@ -106,35 +157,41 @@ class RequestSubmissionUsingArXivView(RequestSubmissionView): """Formview to submit a new Submission using arXiv.""" def get(self, request): - """Redirect to the arXiv prefill form if arXiv ID is not known.""" + """ + Redirect to the arXiv prefill form if arXiv ID is not known. + """ form = SubmissionIdentifierForm(request.GET or None, requested_by=self.request.user) if form.is_valid(): # Gather data from ArXiv API if prefill form is valid - self.initial_data = form.request_arxiv_preprint_form_prefill_data() + self.initial_data = form.get_initial_submission_data() return super().get(request) else: + for code, err in form.errors.items(): + messages.warning(request, err[0]) return redirect('submissions:prefill_using_identifier') def get_form_kwargs(self): """Form requires extra kwargs.""" kwargs = super().get_form_kwargs() - kwargs['use_arxiv_preprint'] = True + kwargs['preprint_server'] = 'arxiv' return kwargs class RequestSubmissionUsingSciPostView(RequestSubmissionView): """Formview to submit a new Submission using SciPost's preprint server.""" - def dispatch(self, request, *args, **kwargs): - """TEMPORARY: Not accessible unless in test group.""" - # if not is_test_user(request.user): - # raise Http404 - return super().dispatch(request, *args, **kwargs) + def get(self, request): + """Check for possible Resubmissions before dispatching.""" + if Submission.objects.candidate_for_resubmission(request.user).exists(): + if not request.GET.get('resubmission'): + return redirect('submissions:resubmit_manuscript') + return super().get(request) def get_form_kwargs(self): """Form requires extra kwargs.""" kwargs = super().get_form_kwargs() - kwargs['use_arxiv_preprint'] = False + # kwargs['use_arxiv_preprint'] = False + kwargs['preprint_server'] = 'scipost' return kwargs @@ -144,13 +201,10 @@ def prefill_using_arxiv_identifier(request): """Form view asking for the arXiv ID related to the new Submission to submit.""" query_form = SubmissionIdentifierForm(request.POST or None, initial=request.GET or None, requested_by=request.user) - if query_form.is_valid(): - prefill_data = query_form.request_arxiv_preprint_form_prefill_data() - form = RequestSubmissionForm( - initial=prefill_data, requested_by=request.user, use_arxiv_preprint=True) + if query_form.is_valid(): # Submit message to user - if query_form.submission_is_resubmission(): + if query_form.service.is_resubmission(): resubmessage = ('There already exists a preprint with this arXiv identifier ' 'but a different version number. \nYour Submission will be ' 'handled as a resubmission.') @@ -158,14 +212,10 @@ def prefill_using_arxiv_identifier(request): else: messages.success(request, strings.acknowledge_arxiv_query, fail_silently=True) - context = { - 'form': form, - } response = redirect('submissions:submit_manuscript_arxiv') response['location'] += '?identifier_w_vn_nr={}'.format( query_form.cleaned_data['identifier_w_vn_nr']) - # return render(request, 'submissions/submission_form.html', context) - return reponse + return response context = { 'form': query_form, @@ -188,7 +238,7 @@ class SubmissionListView(PaginationMixin, ListView): if 'to_journal' in self.request.GET: queryset = queryset.filter( latest_activity__gte=timezone.now() + datetime.timedelta(days=-60), - submitted_to_journal=self.request.GET['to_journal'] + submitted_to__doi_label=self.request.GET['to_journal'] ) elif 'discipline' in self.kwargs and 'nrweeksback' in self.kwargs: discipline = self.kwargs['discipline'] @@ -237,7 +287,8 @@ def submission_detail(request, identifier_w_vn_nr): """Public detail page of Submission.""" submission = get_object_or_404(Submission, preprint__identifier_w_vn_nr=identifier_w_vn_nr) context = { - 'can_read_editorial_information': False + 'can_read_editorial_information': False, + 'select_topic_form': SelectTopicForm(), } # Check if Contributor is author of the Submission @@ -255,7 +306,7 @@ def submission_detail(request, identifier_w_vn_nr): if is_author: context['proofs_decision_form'] = ProofsDecisionForm() - if submission.open_for_commenting and request.user.has_perms('scipost.can_submit_comments'): + if submission.open_for_commenting and request.user.has_perm('scipost.can_submit_comments'): context['comment_form'] = CommentForm() invited_reports = submission.reports.accepted().invited() @@ -267,7 +318,8 @@ def submission_detail(request, identifier_w_vn_nr): if request.user.is_authenticated: context['unfinished_report_for_user'] = submission.reports.in_draft().filter( author__user=request.user).first() - context['invitations'] = submission.referee_invitations.filter(referee__user=request.user) + context['invitations'] = submission.referee_invitations.non_cancelled().filter( + referee__user=request.user) if is_author or is_author_unchecked: # Authors are not allowed to read all editorial info! Whatever @@ -424,7 +476,8 @@ def editorial_workflow(request): @login_required @fellowship_or_admin_required() def pool(request, identifier_w_vn_nr=None): - """List page of Submissions in refereeing. + """ + List page of Submissions in refereeing. The Submissions pool contains all submissions which are undergoing the editorial process, from submission @@ -436,9 +489,12 @@ def pool(request, identifier_w_vn_nr=None): if search_form.is_valid(): submissions = search_form.search(Submission.objects.all(), request.user) else: - # Mainly as fallback for the old-pool while in test phase. submissions = Submission.objects.pool(request.user) + # Query optimization + submissions = submissions.select_related( + 'preprint', 'publication').prefetch_related('eicrecommendations') + recs_to_vote_on = EICRecommendation.objects.user_must_vote_on(request.user).filter( submission__in=submissions) recs_current_voted = EICRecommendation.objects.user_current_voted(request.user).filter( @@ -514,6 +570,50 @@ def add_remark(request, identifier_w_vn_nr): return redirect(reverse('submissions:pool', args=(identifier_w_vn_nr,))) +@permission_required('scipost.can_manage_ontology', raise_exception=True) +def submission_add_topic(request, identifier_w_vn_nr): + """ + Add a predefined Topic to an existing Submission object. + This also adds the Topic to all Submissions predating this one, + and to any existing associated Publication object. + """ + submission = get_object_or_404(Submission, + preprint__identifier_w_vn_nr=identifier_w_vn_nr) + select_topic_form = SelectTopicForm(request.POST or None) + if select_topic_form.is_valid(): + submission.topics.add(select_topic_form.cleaned_data['topic']) + for sub in submission.get_other_versions(): + sub.topics.add(select_topic_form.cleaned_data['topic']) + try: + if submission.publication: + submission.publication.topics.add(select_topic_form.cleaned_data['topic']) + except: + pass + messages.success(request, 'Successfully linked Topic to this Submission') + return redirect(submission.get_absolute_url()) + + +@permission_required('scipost.can_manage_ontology', raise_exception=True) +def submission_remove_topic(request, identifier_w_vn_nr, slug): + """ + Remove the Topic from the Submission, from all associated Submissions + and from any existing associated Publication object. + """ + submission = get_object_or_404(Submission, + preprint__identifier_w_vn_nr=identifier_w_vn_nr) + topic = get_object_or_404(Topic, slug=slug) + submission.topics.remove(topic) + for sub in submission.get_other_versions(): + sub.topics.remove(topic) + try: + if submission.publication: + submission.publication.topics.remove(topic) + except: + pass + messages.success(request, 'Successfully removed Topic') + return redirect(submission.get_absolute_url()) + + @login_required @permission_required('scipost.can_assign_submissions', raise_exception=True) def assign_submission(request, identifier_w_vn_nr): @@ -591,6 +691,7 @@ def editorial_assignment(request, identifier_w_vn_nr, assignment_id=None): SubmissionUtils.send_author_prescreening_passed_email() submission.add_general_event('The Editor-in-charge has been assigned.') + notify_editor_assigned(request.user, assignment, False) msg = 'Thank you for becoming Editor-in-charge of this submission.' url = reverse( 'submissions:editorial_page', args=(submission.preprint.identifier_w_vn_nr,)) @@ -654,23 +755,20 @@ def volunteer_as_EIC(request, identifier_w_vn_nr): messages.warning(request, errormessage) return redirect(reverse('submissions:pool')) - contributor = request.user.contributor # The Contributor may already have an EditorialAssignment due to an earlier invitation. assignment, __ = EditorialAssignment.objects.get_or_create( - submission=submission, to=contributor) + submission=submission, to=request.user.contributor) # Explicitly update afterwards, since update_or_create does not properly do the job. EditorialAssignment.objects.filter(id=assignment.id).update( status=STATUS_ACCEPTED, date_answered=timezone.now()) # Set deadlines - deadline = timezone.now() + datetime.timedelta(days=28) # for papers - if submission.submitted_to_journal == 'SciPostPhysLectNotes': - deadline += datetime.timedelta(days=28) + deadline = timezone.now() + submission.submitted_to.refereeing_period # Update Submission data Submission.objects.filter(id=submission.id).update( status=STATUS_EIC_ASSIGNED, - editor_in_charge=contributor, + editor_in_charge=request.user.contributor, open_for_reporting=True, reporting_deadline=deadline, open_for_commenting=True, @@ -688,6 +786,7 @@ def volunteer_as_EIC(request, identifier_w_vn_nr): # Add SubmissionEvents submission.add_general_event('The Editor-in-charge has been assigned.') + notify_editor_assigned(request.user, assignment, False) messages.success(request, 'Thank you for becoming Editor-in-charge of this submission.') return redirect(reverse('submissions:editorial_page', @@ -794,8 +893,7 @@ def cycle_form_submit(request, identifier_w_vn_nr): form = SubmissionCycleChoiceForm(request.POST or None, instance=submission) if form.is_valid(): submission = form.save() - submission.cycle.update_status() - submission.cycle.update_deadline() + submission.cycle.reset_refereeing_round() submission.cycle.reinvite_referees(form.cleaned_data['referees_reinvite'], request) messages.success(request, ('<h3>Your choice has been confirmed</h3>' 'The new cycle will be <em>%s</em>' @@ -811,24 +909,19 @@ def cycle_form_submit(request, identifier_w_vn_nr): @login_required @fellowship_or_admin_required() def select_referee(request, identifier_w_vn_nr): - """Invite scientist to referee a Submission. - - Accessible for: Editor-in-charge and Editorial Administration. - It'll list possible already registered Contributors that match the search. If the scientist - is not yet registered, he will be invited using a RegistrationInvitation as well. In - addition the page will show possible conflicts of interests, with that information - coming from the ArXiv API. + """ + Search for a referee in the set of Profiles, and if none is found, + create a new Profile and return to this page for further processing. """ submission = get_object_or_404(Submission.objects.filter_for_eic(request.user), preprint__identifier_w_vn_nr=identifier_w_vn_nr) context = {} queryresults = '' - ref_search_form = RefereeSelectForm(request.POST or None) - if ref_search_form.is_valid(): - contributors_found = Contributor.objects.filter( - user__last_name__icontains=ref_search_form.cleaned_data['last_name']) - context['contributors_found'] = contributors_found - + referee_search_form = RefereeSearchForm(request.GET or None) + if referee_search_form.is_valid(): + profiles_found = Profile.objects.filter( + last_name__icontains=referee_search_form.cleaned_data['last_name']) + context['profiles_found'] = profiles_found # Check for recent co-authorship (thus referee disqualification) try: sub_auth_boolean_str = '((' + (submission @@ -837,7 +930,7 @@ def select_referee(request, identifier_w_vn_nr): for author in submission.metadata['entries'][0]['authors'][1:]: sub_auth_boolean_str += '+OR+' + author['name'].split()[-1] sub_auth_boolean_str += ')+AND+' - search_str = sub_auth_boolean_str + ref_search_form.cleaned_data['last_name'] + ')' + search_str = sub_auth_boolean_str + referee_search_form.cleaned_data['last_name'] + ')' queryurl = ('https://export.arxiv.org/api/query?search_query=au:%s' % search_str + '&sortBy=submittedDate&sortOrder=descending' '&max_results=5') @@ -845,116 +938,106 @@ def select_referee(request, identifier_w_vn_nr): queryresults = arxivquery except KeyError: pass - context['ref_recruit_form'] = RefereeRecruitmentForm() - + context['profile_form'] = SimpleProfileForm() context.update({ 'submission': submission, - 'ref_search_form': ref_search_form, - 'queryresults': queryresults + 'workdays_left_to_report': workdays_between(timezone.now(), submission.reporting_deadline), + 'referee_search_form': referee_search_form, + 'queryresults': queryresults, + 'profile_email_form': ProfileEmailForm(initial={'primary': True}), }) - return render(request, 'submissions/referee_form.html', context) + return render(request, 'submissions/select_referee.html', context) @login_required @fellowship_or_admin_required() @transaction.atomic -def recruit_referee(request, identifier_w_vn_nr): - """Invite a non-registered scientist to register and referee a Submission. - - Accessible for: Editor-in-charge and Editorial Administration - If the Editor-in-charge does not find the desired referee among Contributors - (otherwise, the method send_refereeing_invitation is used), he/she can invite somebody - by providing name + contact details. This function emails a registration invitation to this - person. The pending refereeing invitation is then recognized upon registration, using the - invitation token. - """ +def add_referee_profile(request, identifier_w_vn_nr): submission = get_object_or_404(Submission.objects.filter_for_eic(request.user), preprint__identifier_w_vn_nr=identifier_w_vn_nr) - - if request.method == 'GET': - # This leads to unexpected 500 errors - return redirect(reverse('submissions:select_referee', args=(identifier_w_vn_nr,))) - - ref_recruit_form = RefereeRecruitmentForm( - request.POST or None, request=request, submission=submission) - if ref_recruit_form.is_valid(): - referee_invitation, registration_invitation = ref_recruit_form.save(commit=False) - mail_request = MailEditingSubView(request, - mail_code='referees/invite_unregistered_to_referee', - instance=referee_invitation) - mail_request.add_form(ref_recruit_form) - if mail_request.is_valid(): - referee_invitation.save() - registration_invitation.save() - - messages.success(request, 'Referee {} invited'.format( - registration_invitation.last_name)) - submission.add_event_for_author('A referee has been invited.') - submission.add_event_for_eic('{} has been recruited and invited as a referee.'.format( - referee_invitation.last_name)) - - mail_request.send() - return redirect(reverse('submissions:editorial_page', - kwargs={'identifier_w_vn_nr': identifier_w_vn_nr})) - else: - return mail_request.return_render() - - ref_search_form = RefereeSelectForm(request.POST or None) - contributors_found = Contributor.objects.filter( - user__email=ref_recruit_form.cleaned_data['email_address']) - context = { - 'ref_recruit_form': ref_recruit_form, - 'ref_search_form': ref_search_form, - 'submission': submission, - 'queryresults': [], - 'contributors_found': contributors_found, - } - return render(request, 'submissions/referee_form.html', context) + profile_form = SimpleProfileForm(request.POST or None) + if profile_form.is_valid(): + profile_form.save() + messages.success(request, + 'Profile added, you can now invite this referee using the links above') + else: + messages.error(request, 'Could not create this Profile') + for error_messages in profile_form.errors.values(): + messages.warning(request, *error_messages) + return redirect(reverse( + 'submissions:select_referee', + kwargs={'identifier_w_vn_nr': submission.preprint.identifier_w_vn_nr}) + + ('?last_name=%s' % profile_form.cleaned_data['last_name'])) @login_required @fellowship_or_admin_required() @transaction.atomic -def send_refereeing_invitation(request, identifier_w_vn_nr, contributor_id, - auto_reminders_allowed): - """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 - is called instead. - - Accessible for: Editor-in-charge and Editorial Administration +def invite_referee(request, identifier_w_vn_nr, profile_id, auto_reminders_allowed): + """ + Invite a referee linked to a Profile. + If the Profile has a Contributor object, a simple invitation is sent. + If there is no associated Contributor, a registration invitation is included. """ submission = get_object_or_404(Submission.objects.filter_for_eic(request.user), preprint__identifier_w_vn_nr=identifier_w_vn_nr) - contributor = get_object_or_404(Contributor, pk=contributor_id) - profile = Profile.objects.get_unique_from_email_or_None(contributor.user.email) + profile = get_object_or_404(Profile, pk=profile_id) - if not contributor.is_currently_available: - errormessage = ('This Contributor is marked as currently unavailable. ' - 'Please go back and select another referee.') - return render(request, 'scipost/error.html', {'errormessage': errormessage}) + contributor = None + if hasattr(profile, 'contributor') and profile.contributor: + contributor = profile.contributor - invitation = RefereeInvitation( + referee_invitation, created = RefereeInvitation.objects.get_or_create( profile=profile, - submission=submission, referee=contributor, - title=contributor.title, - first_name=contributor.user.first_name, - last_name=contributor.user.last_name, - email_address=contributor.user.email, + submission=submission, + title=profile.title, + first_name=profile.first_name, + last_name=profile.last_name, + email_address=profile.email, auto_reminders_allowed=auto_reminders_allowed, - # the key is only used for inviting unregistered users - date_invited=timezone.now(), invited_by=request.user.contributor) - mail_request = MailEditingSubView(request, mail_code='referees/invite_contributor_to_referee', - invitation=invitation) + key = '' + if created: + key = get_new_secrets_key() + referee_invitation.invitation_key = key + referee_invitation.save() + + registration_invitation = None + if contributor: + if not profile.contributor.is_currently_available: + errormessage = ('This Contributor is marked as currently unavailable. ' + 'Please go back and select another referee.') + return render(request, 'scipost/error.html', {'errormessage': errormessage}) + + mail_request = MailEditingSubView(request, + mail_code='referees/invite_contributor_to_referee', + invitation=referee_invitation) + else: # no Contributor, so registration invitation + registration_invitation, reginv_created = RegistrationInvitation.objects.get_or_create( + profile=profile, + title=profile.title, + first_name=profile.first_name, + last_name=profile.last_name, + email=profile.email, + invitation_type=INVITATION_REFEREEING, + created_by=request.user, + invited_by=request.user, + invitation_key=referee_invitation.invitation_key) + mail_request = MailEditingSubView(request, + mail_code='referees/invite_unregistered_to_referee', + invitation=referee_invitation) + if mail_request.is_valid(): - invitation.save() + referee_invitation.date_invited = timezone.now() + referee_invitation.save() + if registration_invitation: + registration_invitation.status = STATUS_SENT + registration_invitation.key_expires = timezone.now() + datetime.timedelta(days=365) + registration_invitation.save() submission.add_event_for_author('A referee has been invited.') - submission.add_event_for_eic('Referee %s has been invited.' % contributor.user.last_name) + submission.add_event_for_eic('Referee %s has been invited.' % profile.last_name) messages.success(request, 'Invitation sent') mail_request.send() return redirect(reverse('submissions:editorial_page', @@ -1059,6 +1142,7 @@ def accept_or_decline_ref_invitations(request, invitation_id=None): invitation.submission.add_event_for_eic('Referee %s has %s the refereeing invitation.' % (invitation.referee.user.last_name, decision_string)) + notify_eic_invitation_reponse(request.user, invitation, False) if request.user.contributor.referee_invitations.awaiting_response().exists(): return redirect('submissions:accept_or_decline_ref_invitations') @@ -1126,7 +1210,9 @@ def cancel_ref_invitation(request, identifier_w_vn_nr, invitation_id): invitation.submission.add_event_for_author('A referee invitation has been cancelled.') invitation.submission.add_event_for_eic('Referee invitation for %s has been cancelled.' % invitation.last_name) + notify_invitation_cancelled(request.user, invitation, False) + messages.success(request, 'Invitation cancelled') return redirect(reverse('submissions:editorial_page', kwargs={'identifier_w_vn_nr': identifier_w_vn_nr})) @@ -1141,12 +1227,14 @@ def extend_refereeing_deadline(request, identifier_w_vn_nr, days): submission = get_object_or_404(Submission.objects.filter_for_eic(request.user), preprint__identifier_w_vn_nr=identifier_w_vn_nr) - submission.reporting_deadline += datetime.timedelta(days=int(days)) - submission.open_for_reporting = True - submission.open_for_commenting = True - submission.status = STATUS_EIC_ASSIGNED - submission.latest_activity = timezone.now() - submission.save() + Submission.objects.filter(pk=submission.id).update( + reporting_deadline=submission.reporting_deadline + datetime.timedelta(days=int(days)), + open_for_reporting=True, + open_for_commenting=True, + latest_activity=timezone.now()) + + messages.success(request, 'Refereeing deadline set to {0}.'.format( + submission.reporting_deadline.strftime('%Y-%m-%d'))) submission.add_general_event('A new refereeing deadline is set.') return redirect(reverse('submissions:editorial_page', @@ -1162,15 +1250,18 @@ def set_refereeing_deadline(request, identifier_w_vn_nr): submission = get_object_or_404(Submission.objects.filter_for_eic(request.user), preprint__identifier_w_vn_nr=identifier_w_vn_nr) + if not submission.can_reset_reporting_deadline: + # Protect eg. published submissions. + messages.warning(request, 'Reporting deadline can not be reset.') + return redirect(reverse( + 'submissions:editorial_page', kwargs={'identifier_w_vn_nr': identifier_w_vn_nr})) + form = SetRefereeingDeadlineForm(request.POST or None) if form.is_valid(): - submission.reporting_deadline = form.cleaned_data['deadline'] - if form.cleaned_data['deadline'] > timezone.now().date(): - submission.open_for_reporting = True - submission.open_for_commenting = True - submission.latest_activity = timezone.now() - # submission.status = STATUS_EIC_ASSIGNED # This is dangerous as shit. - submission.save() + Submission.objects.filter(pk=submission.id).update( + reporting_deadline=form.cleaned_data['deadline'], + open_for_reporting=(form.cleaned_data['deadline'] >= timezone.now().date()), + latest_activity = timezone.now()) submission.add_general_event('A new refereeing deadline is set.') messages.success(request, 'New reporting deadline set.') else: @@ -1250,10 +1341,12 @@ def communication(request, identifier_w_vn_nr, comtype, referee_id=None): # Get the showpiece itself or return 404 submission = get_object_or_404(submissions_qs, preprint__identifier_w_vn_nr=identifier_w_vn_nr) - if referee_id and not referee: + if referee_id and comtype in ['EtoA', 'EtoR', 'EtoS']: # Get the Contributor to communicate with if not already defined (`Eto?` communication) # To Fix: Assuming the Editorial Administrator won't make any `referee_id` mistakes - referee = get_object_or_404(Contributor, pk=referee_id) + referee = get_object_or_404(Contributor.objects.active(), pk=referee_id) + elif comtype == 'EtoA': + referee = submission.submitted_by form = EditorialCommunicationForm(request.POST or None) if form.is_valid(): @@ -1266,11 +1359,12 @@ def communication(request, identifier_w_vn_nr, comtype, referee_id=None): SubmissionUtils.load({'communication': communication}) SubmissionUtils.send_communication_email() + messages.success(request, 'Communication submitted') if comtype in ['EtoA', 'EtoR', 'EtoS']: return redirect(reverse('submissions:editorial_page', kwargs={'identifier_w_vn_nr': identifier_w_vn_nr})) elif comtype == 'AtoE': - return redirect(reverse('scipost:personal_page')) + return redirect(submission.get_absolute_url()) elif comtype == 'StoE': return redirect(reverse('submissions:pool')) return redirect(submission.get_absolute_url()) @@ -1343,7 +1437,7 @@ def reformulate_eic_recommendation(request, identifier_w_vn_nr): """ submission = get_object_or_404(Submission.objects.filter_for_eic(request.user), preprint__identifier_w_vn_nr=identifier_w_vn_nr) - recommendation = submission.eicrecommendations.first() + recommendation = submission.eicrecommendations.last() if not recommendation: raise Http404('No EICRecommendation formulated yet.') @@ -1449,6 +1543,7 @@ def submit_report(request, identifier_w_vn_nr): % request.user.last_name) messages.success(request, 'Thank you for your Report') + notify_eic_new_report(request.user, newreport, False) return redirect(submission.get_absolute_url()) elif request.POST: messages.error(request, 'Report not submitted, please read the errors below.') @@ -1480,7 +1575,7 @@ def vet_submitted_report(request, report_id): After vetting an email is sent to the report author, bcc EIC. If report has not been refused, the submission author is also mailed. """ - if request.user.has_perms('scipost.can_vet_submitted_reports'): + if request.user.has_perm('scipost.can_vet_submitted_reports'): # Vetting Editors may vote on everything. report = get_object_or_404(Report.objects.awaiting_vetting(), id=report_id) else: @@ -1488,7 +1583,7 @@ def vet_submitted_report(request, report_id): 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}) + form = VetReportForm(request.POST or None, report=report) if form.is_valid(): report = form.process_vetting(request.user.contributor) @@ -1496,6 +1591,7 @@ def vet_submitted_report(request, report_id): SubmissionUtils.load({'report': report, 'email_response': form.cleaned_data['email_response_field']}) SubmissionUtils.acknowledge_report_email() # email report author, bcc EIC + notify_report_vetted(request.user, report, False) # Add SubmissionEvent for the EIC report.submission.add_event_for_eic('The Report by %s is vetted.' @@ -1506,6 +1602,7 @@ 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.') + notify_submission_author_new_report(request.user, report, False) message = 'Submitted Report vetted for <a href="{url}">{arxiv}</a>.'.format( url=report.submission.get_absolute_url(), @@ -1517,6 +1614,7 @@ def vet_submitted_report(request, report_id): return redirect(reverse('submissions:editorial_page', args=(report.submission.preprint.identifier_w_vn_nr,))) return redirect(reverse('submissions:vet_submitted_reports_list')) + context = {'report_to_vet': report, 'form': form} return render(request, 'submissions/vet_submitted_report.html', context) @@ -1543,12 +1641,17 @@ def prepare_for_voting(request, rec_id): return redirect(reverse('submissions:editorial_page', args=[recommendation.submission.preprint.identifier_w_vn_nr])) else: + secondary_areas = recommendation.submission.secondary_areas + if not secondary_areas: + secondary_areas = [] + fellows_with_expertise = recommendation.submission.fellows.filter( Q(contributor=recommendation.submission.editor_in_charge) | Q(contributor__expertises__contains=[recommendation.submission.subject_area]) | - Q(contributor__expertises__contains=recommendation.submission.secondary_areas)).order_by( + Q(contributor__expertises__contains=secondary_areas)).order_by( 'contributor__user__last_name') - coauthorships = recommendation.submission.flag_coauthorships_arxiv(fellows_with_expertise) + #coauthorships = recommendation.submission.flag_coauthorships_arxiv(fellows_with_expertise) + coauthorships = None context = { 'recommendation': recommendation, diff --git a/templates/email/contributors/inform_contributor_duplicate_accounts_merged.html b/templates/email/contributors/inform_contributor_duplicate_accounts_merged.html new file mode 100644 index 0000000000000000000000000000000000000000..13ddb815260a104d4936b83a1172b93bba1759f7 --- /dev/null +++ b/templates/email/contributors/inform_contributor_duplicate_accounts_merged.html @@ -0,0 +1,18 @@ +<p> + Dear {{ contrib_from.duplicate_of.get_title_display }} {{ contrib_from.duplicate_of.user.last_name }}, +</p> +<p> + We noticed that you had two separate registrations at SciPost, and have consolidated your two accounts into a single active one, namely your account with username <strong><em style="color: green;">{{ contrib_from.duplicate_of.user.username }}</em></strong>. +</p> +<p> + Your alternate account with username <strong><em style="color: red;">{{ contrib_from.user.username }}</em></strong> has been deactivated, but all the data associated to it has been transferred to your active account. +</p> +<p> + Please get in touch with us at <a href="mailto:techsupport@scipost.org">SciPost techsupport</a> (or by simply replying to this email) if you have any questions. +</p> +<p> + Many thanks, + <br><br> + The SciPost Team +</p> +{% include 'email/_footer.html' %} diff --git a/templates/email/contributors/inform_contributor_duplicate_accounts_merged.json b/templates/email/contributors/inform_contributor_duplicate_accounts_merged.json new file mode 100644 index 0000000000000000000000000000000000000000..f7987957d8141c238f624e2b2de5029267866efe --- /dev/null +++ b/templates/email/contributors/inform_contributor_duplicate_accounts_merged.json @@ -0,0 +1,8 @@ +{ + "subject": "SciPost: duplicate accounts merged", + "to_address": "user.email", + "bcc_to": "admin@scipost.org", + "from_address_name": "SciPost Admin", + "from_address": "admin@scipost.org", + "context_object": "contrib_from" +} diff --git a/templates/email/partners_followup_mail.html b/templates/email/partners_followup_mail.html index cea1dce97c43ceafa3ea6039a435a207799f9277..71a0920ecb6b68fe351b148e166be8099f62c2a2 100644 --- a/templates/email/partners_followup_mail.html +++ b/templates/email/partners_followup_mail.html @@ -3,15 +3,15 @@ </p> <p> - We recently contacted you concerning SciPost, and to probe your interest in joining its Supporting Partners Board. With this follow-up email, I would simply like to check whether you got the original message. + We recently contacted you concerning SciPost, and to probe your interest in joining its Sponsors Board. With this follow-up email, I would simply like to check whether you got the original message. </p> <p> - <a href="https://scipost.org">SciPost</a> is a next-generation publication portal aiming to transform the business of scientific publishing. You can find a one-page summary in <a href="https://scipost.org/static/scipost/SPB/SciPost_Supporting_Partners_Board_Prospectus.pdf">our online prospectus</a> outlining the reasons why joining would be beneficial for your institution. + <a href="https://scipost.org">SciPost</a> is a next-generation publication portal aiming to transform the business of scientific publishing. You can find a one-page summary in <a href="https://scipost.org/static/sponsors/SciPost_Sponsors_Board_Prospectus.pdf">our online prospectus</a> outlining the reasons why joining would be beneficial for your institution. </p> <p> - I will be happy to provide any required further details. If you are interested, you can simply get in touch via this address (partners@scipost.org). I sincerely hope that SciPost will be able to count on your support. + I will be happy to provide any required further details. If you are interested, you can simply get in touch via this address (sponsors@scipost.org). I sincerely hope that SciPost will be able to count on your support. </p> <p> diff --git a/templates/email/partners_followup_mail.json b/templates/email/partners_followup_mail.json index e2a4c15705f549d843406715290456ef0ab61545..4de328173c68023d94babddefe0b0fd292103c9d 100644 --- a/templates/email/partners_followup_mail.json +++ b/templates/email/partners_followup_mail.json @@ -1,8 +1,8 @@ { - "subject": "SciPost: Supporting Partners Board", + "subject": "SciPost: Sponsors Board", "to_address": "email", - "bcc_to": "partners@scipost.org", - "from_address_name": "SciPost Supporting Partners", - "from_address": "partners@scipost.org", + "bcc_to": "sponsors@scipost.org", + "from_address_name": "SciPost Sponsors", + "from_address": "sponsors@scipost.org", "context_object": "contact" } diff --git a/templates/email/partners_initial_mail.html b/templates/email/partners_initial_mail.html index 97683fed31ac84a67f8afd649b53dc54966103ba..131c40fa31245a6189abbced14db85e53f72de36 100644 --- a/templates/email/partners_initial_mail.html +++ b/templates/email/partners_initial_mail.html @@ -12,11 +12,11 @@ </p> <p> - Crucially, as explained on our <a href="https://scipost.org/partners">Partners page</a>, SciPost follows a completely different funding model than traditional publishers, and provides a cost-slashing alternative to existing platforms. SciPost charges neither subscription fees, nor article processing charges; its activities are instead to be collectively financed through a Supporting Partners Board, formed by a worldwide consortium of institutions and organizations which directly or indirectly benefit from SciPost’s activities. + Crucially, as explained on our <a href="https://scipost.org/sponsors">Sponsors page</a>, SciPost follows a completely different funding model than traditional publishers, and provides a cost-slashing alternative to existing platforms. SciPost charges neither subscription fees, nor article processing charges; its activities are instead to be collectively financed through a Sponsors Board, formed by a worldwide consortium of institutions and organizations which directly or indirectly benefit from SciPost’s activities. </p> <p> - A short summary of important aspects of SciPost and reasons for you to join its Supporting Partners Board are given in <a href="https://scipost.org/static/scipost/SPB/SciPost_Supporting_Partners_Board_Prospectus.pdf">our one-page prospectus</a>. + A short summary of important aspects of SciPost and reasons for you to join its Sponsors Board are given in <a href="https://scipost.org/static/sponsors/SciPost_Sponsors_Board_Prospectus.pdf">our one-page prospectus</a>. </p> <p> @@ -24,15 +24,15 @@ </p> <p> - In <a href="https://scipost.org/static/scipost/SPB/SciPost_Supporting_Partner_Agreement.pdf">the agreement template</a>, you will find many more specific details about our operations, requirements and funding strategy. I would greatly appreciate if you took a few minutes to read through this document. + In <a href="https://scipost.org/static/sponsors/SciPost_Sponsorship_Agreement.pdf">the agreement template</a>, you will find many more specific details about our operations, requirements and funding strategy. I would greatly appreciate if you took a few minutes to read through this document. </p> <p> - It would be a privilege to welcome you as members of our Supporting Partners Board. I am hereby contacting you to enquire whether your institution would consider joining. Your support at this time is crucially required to make our initiative sustainable, and to help make it possible for the community to reap all the benefits deriving form its viable implementation. + It would be a privilege to welcome you as members of our Sponsors Board. I am hereby contacting you to enquire whether your institution would consider joining. Your support at this time is crucially required to make our initiative sustainable, and to help make it possible for the community to reap all the benefits deriving form its viable implementation. </p> <p> - I will be happy to provide any required further details. If you are interested, you can simply get in touch via this address (partners@scipost.org). I sincerely hope that SciPost will be able to count on your support. + I will be happy to provide any required further details. If you are interested, you can simply get in touch via this address (sponsors@scipost.org). I sincerely hope that SciPost will be able to count on your support. </p> <p> diff --git a/templates/email/partners_initial_mail.json b/templates/email/partners_initial_mail.json index e2a4c15705f549d843406715290456ef0ab61545..4de328173c68023d94babddefe0b0fd292103c9d 100644 --- a/templates/email/partners_initial_mail.json +++ b/templates/email/partners_initial_mail.json @@ -1,8 +1,8 @@ { - "subject": "SciPost: Supporting Partners Board", + "subject": "SciPost: Sponsors Board", "to_address": "email", - "bcc_to": "partners@scipost.org", - "from_address_name": "SciPost Supporting Partners", - "from_address": "partners@scipost.org", + "bcc_to": "sponsors@scipost.org", + "from_address_name": "SciPost Sponsors", + "from_address": "sponsors@scipost.org", "context_object": "contact" } diff --git a/templates/email/potentialfellowships/invite_potential_fellow_initial.html b/templates/email/potentialfellowships/invite_potential_fellow_initial.html index 71fae8d06fa3d724a0931b18989c4c29b4f08367..8c54bc307825d84575af609fe48ab257692f04d1 100644 --- a/templates/email/potentialfellowships/invite_potential_fellow_initial.html +++ b/templates/email/potentialfellowships/invite_potential_fellow_initial.html @@ -1,23 +1,19 @@ <p>Dear {{ potfel.profile.get_title_display }} {{ potfel.profile.last_name }},</p> -<p>Hopefully you've already come across <a href="https://scipost.org{% url 'scipost:index' %}">SciPost</a> and are aware of our mission to establish a healthier infrastructure for scientific publishing.</p> +<p>Hopefully you've already come across <a href="https://scipost.org{% url 'scipost:index' %}">SciPost</a> and are aware of our mission to establish a community-based infrastructure for scientific publishing.</p> -<p>On behalf of the SciPost Foundation and in view of your professional expertise and reputation, I hereby would like to invite you to join our Editorial College and become one of our Editorial Fellows. We are currently making a big push for expansion of our activities, see <a href="https://scipost.org{% url 'scipost:ExpSustDrive2018' %}">this page</a> for details.</p> +<p>On behalf of the SciPost Foundation and in view of your professional expertise and reputation, I hereby would like to invite you to join our Editorial College by becoming one of our Editorial Fellows.</p> <p> - Please note that only well-known and respected senior academics are being contacted for this purpose. - Academic reputation and involvement in the community are the most important criteria guiding our considerations of who should belong to the Editorial College. - The current list of Fellows can be found at <a href="https://scipost.org{% url 'scipost:about' %}">scipost.org/about</a>; on this page, you will also find basic information on SciPost and its guiding principles (you can also take a look at our <a href="https://scipost.org{% url 'scipost:FAQ' %}">frequently asked questions page</a>). + Academic reputation is the most important criterion guiding our considerations of who should belong to the Editorial College. The current list of Fellows can be found at <a href="https://scipost.org{% url 'scipost:about' %}">scipost.org/about</a>; on this page, you will also find basic information on SciPost and its guiding principles. </p> <p> - We do not pose any conditions on your involvement, and you would always remain in complete control of your level of commitment (devoting even a couple of hours per month would be enough to help out significantly). - Functioning of the College proceeds according to the by-laws set out at <a href="https://scipost.org{% url 'scipost:EdCol_by-laws' %}">scipost.org/EdCol_by-laws</a>, - and a short summary of the editorial workflow can be found at <a href="https://scipost.org{% url 'submissions:editorial_workflow' %}">this page</a>. + We do not pose any conditions on your involvement, and you will always remain in complete control of your level of commitment (a couple of hours per month would already be significant). Functioning of the College proceeds according to the by-laws set out at <a href="https://scipost.org{% url 'scipost:EdCol_by-laws' %}">scipost.org/EdCol_by-laws</a>, and a short summary of the editorial workflow can be found at <a href="https://scipost.org{% url 'submissions:editorial_workflow' %}">this page</a>. </p> <p> - I would be very happy to provide you with more information should you require it. Could I beg you to give us a response (by replying to this email) within the next couple of weeks? + I would be very happy to provide you with more information should you require it. Could I beg you to give us a response (by replying to this email) within the next couple of weeks? </p> <p> diff --git a/templates/email/referees/invite_contributor_to_referee_reminder1.html b/templates/email/referees/invite_contributor_to_referee_reminder1.html index bc2d7809fa92ab650a597487bd7aea329ddea92b..3eda73474bd795b2de4f5b57862c359a82f12ddc 100644 --- a/templates/email/referees/invite_contributor_to_referee_reminder1.html +++ b/templates/email/referees/invite_contributor_to_referee_reminder1.html @@ -3,7 +3,7 @@ Dear {{invitation.referee.get_title_display}} {{invitation.referee.user.last_name}}, </p> <p> - Recently, on behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.get_title_display }} {{ invitation.submission.editor_in_charge.user.last_name }}, we invited you to consider refereeing a Submission to {{ invitation.submission.get_submitted_to_journal_display }}, namely<br><br> + Recently, on behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.get_title_display }} {{ invitation.submission.editor_in_charge.user.last_name }}, we invited you to consider refereeing a Submission to {{ invitation.submission.submitted_to }}, namely<br><br> {{ invitation.submission.title }}<br> by {{ invitation.submission.author_list }}<br> (see https://scipost.org{{ invitation.submission.get_absolute_url }} - first submitted {{ invitation.submission.original_submission_date|date:"d M Y" }}). diff --git a/templates/email/referees/invite_contributor_to_referee_reminder2.html b/templates/email/referees/invite_contributor_to_referee_reminder2.html index 6623646302f15ec1dd71e6f8eaec63d29c4e84df..3f26c5490069de7fc6a4d6c986d296a70a4dd908 100644 --- a/templates/email/referees/invite_contributor_to_referee_reminder2.html +++ b/templates/email/referees/invite_contributor_to_referee_reminder2.html @@ -3,7 +3,7 @@ Dear {{invitation.referee.get_title_display}} {{invitation.referee.user.last_name}}, </p> <p> - Recently, on behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.get_title_display }} {{ invitation.submission.editor_in_charge.user.last_name }}, we invited you to consider refereeing a Submission to {{ invitation.submission.get_submitted_to_journal_display }}, namely<br><br> + Recently, on behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.get_title_display }} {{ invitation.submission.editor_in_charge.user.last_name }}, we invited you to consider refereeing a Submission to {{ invitation.submission.submitted_to }}, namely<br><br> {{ invitation.submission.title }}<br> by {{ invitation.submission.author_list }}<br> (see https://scipost.org{{ invitation.submission.get_absolute_url }} - first submitted {{ invitation.submission.original_submission_date|date:"d M Y" }}). diff --git a/templates/email/referees/invite_unregistered_to_referee.html b/templates/email/referees/invite_unregistered_to_referee.html index cad3479466910922ee71e586130cd468cdd2f2fe..0080a835aa39cc95d790a036b30d9af8e7b12580 100644 --- a/templates/email/referees/invite_unregistered_to_referee.html +++ b/templates/email/referees/invite_unregistered_to_referee.html @@ -2,7 +2,7 @@ Dear {{ invitation.get_title_display }} {{ invitation.last_name }}, </p> <p> - On behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.get_title_display }} {{ invitation.submission.editor_in_charge.user.last_name }}, we would like to invite you to referee a Submission to {{ invitation.submission.get_submitted_to_journal_display }}, namely<br><br> + On behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.get_title_display }} {{ invitation.submission.editor_in_charge.user.last_name }}, we would like to invite you to referee a Submission to {{ invitation.submission.submitted_to }}, namely<br><br> {{ invitation.submission.title }}<br> by {{ invitation.submission.author_list }}<br> (see https://scipost.org{{ invitation.submission.get_absolute_url }} - first submitted {{ invitation.submission.original_submission_date|date:"d M Y" }}). diff --git a/templates/email/referees/invite_unregistered_to_referee.json b/templates/email/referees/invite_unregistered_to_referee.json index 13b5f05ee65ba24bf27dd527f63381f8774128ad..d603bf8e2193fe984a74247759e9dc7cea763d4f 100644 --- a/templates/email/referees/invite_unregistered_to_referee.json +++ b/templates/email/referees/invite_unregistered_to_referee.json @@ -1,7 +1,7 @@ { "subject": "SciPost: refereeing and registration invitation", "to_address": "email_address", - "bcc_to": "invited_by.email,edadmin@scipost.org", + "bcc_to": "submission.editor_in_charge.user.email,edadmin@scipost.org", "from_address_name": "SciPost Refereeing", "from_address": "refereeing@scipost.org", "context_object": "invitation" diff --git a/templates/email/referees/invite_unregistered_to_referee_reminder1.html b/templates/email/referees/invite_unregistered_to_referee_reminder1.html index 0825b9042b7f7174824c0e91430f1d35037a2069..aa37c5ac735071bbcdd4bf9b7109b7c32ba27c5a 100644 --- a/templates/email/referees/invite_unregistered_to_referee_reminder1.html +++ b/templates/email/referees/invite_unregistered_to_referee_reminder1.html @@ -3,7 +3,7 @@ Dear {{ invitation.get_title_display }} {{ invitation.last_name }}, </p> <p> - Recently, on behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.get_title_display }} {{ invitation.submission.editor_in_charge.user.last_name }}, we invited you to consider refereeing a Submission to {{ invitation.submission.get_submitted_to_journal_display }}, namely<br><br> + Recently, on behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.get_title_display }} {{ invitation.submission.editor_in_charge.user.last_name }}, we invited you to consider refereeing a Submission to {{ invitation.submission.submitted_to }}, namely<br><br> {{ invitation.submission.title }}<br> by {{ invitation.submission.author_list }}<br> (see https://scipost.org{{ invitation.submission.get_absolute_url }} - first submitted {{ invitation.submission.original_submission_date|date:"d M Y" }}). diff --git a/templates/email/referees/invite_unregistered_to_referee_reminder2.html b/templates/email/referees/invite_unregistered_to_referee_reminder2.html index f2dd29280d189c442c1c9717892d79040ac441f3..6fa3f0048eaf30c47b7c1ed638ec033cecb23f26 100644 --- a/templates/email/referees/invite_unregistered_to_referee_reminder2.html +++ b/templates/email/referees/invite_unregistered_to_referee_reminder2.html @@ -3,7 +3,7 @@ Dear {{ invitation.get_title_display }} {{ invitation.last_name }}, </p> <p> - Recently, on behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.get_title_display }} {{ invitation.submission.editor_in_charge.user.last_name }}, we invited you to consider refereeing a Submission to {{ invitation.submission.get_submitted_to_journal_display }}, namely<br><br> + Recently, on behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.get_title_display }} {{ invitation.submission.editor_in_charge.user.last_name }}, we invited you to consider refereeing a Submission to {{ invitation.submission.submitted_to }}, namely<br><br> {{ invitation.submission.title }}<br> by {{ invitation.submission.author_list }}<br> (see https://scipost.org{{ invitation.submission.get_absolute_url }} - first submitted {{ invitation.submission.original_submission_date|date:"d M Y" }}). diff --git a/templates/email/referees/reinvite_contributor_to_referee.html b/templates/email/referees/reinvite_contributor_to_referee.html new file mode 100644 index 0000000000000000000000000000000000000000..35471c59be3d5b9746f5a89cfac54074526c2692 --- /dev/null +++ b/templates/email/referees/reinvite_contributor_to_referee.html @@ -0,0 +1,29 @@ +<p>Dear {{ invitation.get_title_display }} {{ invitation.last_name }},</p> + +<p> + The authors of submission + + <br><br> + {{invitation.submission.title}} + <br> + by {{invitation.submission.author_list}} + <br> + (<a href="https://scipost.org{{ invitation.submission.get_absolute_url }}">see on SciPost.org</a>) +</p> +<p> + have resubmitted their manuscript to SciPost. On behalf of the Editor-in-charge {{invitation.submission.editor_in_charge.get_title_display}} {{invitation.submission.editor_in_charge.user.last_name}}, we would like to invite you to quickly review this new version. + Please accept or decline the invitation (login required) as soon as possible (ideally within the next 2 days). +</p> +<p> + If you accept, your report can be submitted by simply clicking on the "Contribute a Report" link on the Submission's Page before the reporting deadline (currently set at {{invitation.submission.reporting_deadline|date:'N j, Y'}}; your report will be automatically recognized as an invited report). +</p> +<p> + You might want to make sure you are familiar with our refereeing code of conduct and with the refereeing procedure. +</p> +<p> + We would be extremely grateful for your contribution, and thank you in advance for your consideration. + <br><br> + The SciPost Team. +</p> + +{% include 'email/_footer.html' %} diff --git a/templates/email/referees/reinvite_contributor_to_referee.json b/templates/email/referees/reinvite_contributor_to_referee.json new file mode 100644 index 0000000000000000000000000000000000000000..da780978277ba271b772343c20a6a04d38b1c500 --- /dev/null +++ b/templates/email/referees/reinvite_contributor_to_referee.json @@ -0,0 +1,8 @@ +{ + "subject": "SciPost: Invitation on resubmission", + "to_address": "email_address", + "bcc_to": "submission.editor_in_charge.user.email,edadmin@scipost.org", + "from_address_name": "SciPost Refereeing", + "from_address": "refereeing@scipost.org", + "context_object": "invitation" +} diff --git a/templates/search/search.html b/templates/search/search.html index c1e7f5e48df2a79a9e808d787a9430ff5a6af153..15ba545b1a3a2ad6f626fda6a43db02f2cb7ccff 100644 --- a/templates/search/search.html +++ b/templates/search/search.html @@ -3,7 +3,20 @@ {% load bootstrap %} {% load extra_form_widgets %} -{% block pagetitle %}: list{% endblock pagetitle %} +{% block pagetitle %} search{% if form.q.value %}: {{ form.q.value }}{% endif %}{% endblock pagetitle %} + + +{% block breadcrumb %} + <nav class="breadcrumb-container"> + <div class="container"> + <ol class="breadcrumb"> + <li class="breadcrumb-item"><a href="{% url 'scipost:index' %}" >Home</a></li> + <li class="breadcrumb-item active" aria-current="page">Search</span> + </ol> + </div> + </nav> +{% endblock %} + {% block content %} diff --git a/theses/templates/theses/thesislink_list.html b/theses/templates/theses/thesislink_list.html index 468529133cb9c6cc35c79a1fa7f6e121a19cfc8f..a0ad30e5f77b2ffdb16a6f3ce7d6a5f6596a839c 100644 --- a/theses/templates/theses/thesislink_list.html +++ b/theses/templates/theses/thesislink_list.html @@ -13,32 +13,26 @@ <div class="row"> <div class="col-md-4"> - <div class="card card-grey"> - <div class="card-body min-height-190"> - <h1 class="card-title">SciPost Theses</h1> - <h3><a href="{% url 'theses:request_thesislink' %}">Request a new Thesis Link</a></h3> - </div> + <div class="p-3 mb-3 bg-light scipost-bar border min-height-190"> + <h1 class="mb-3">SciPost Theses</h1> + <h3><a href="{% url 'theses:request_thesislink' %}">Request a new Thesis Link</a></h3> </div> </div> <div class="col-md-4"> - <div class="card card-grey"> - <div class="card-body min-height-190"> - <h2 class="card-title">Search SciPost Theses:</h2> - <form class="small" action="{% url 'theses:theses' %}" method="get"> - {{ form|bootstrap:'4,8,sm' }} - <input class="btn btn-outline-secondary" type="submit" value="Search" /> - </form> - </div> + <div class="p-3 mb-3 bg-light scipost-bar border min-height-190"> + <h2>Search SciPost Theses:</h2> + <form class="small" action="{% url 'theses:theses' %}" method="get"> + {{ form|bootstrap:'4,8,sm' }} + <input class="btn btn-outline-secondary" type="submit" value="Search" /> + </form> </div> </div> <div class="col-md-4"> - <div class="card card-grey"> - <div class="card-body min-height-190"> - <h2 class="card-title">View SciPost Theses</h2> - <ul> - <li>Physics: last <a href="{% url 'theses:browse' discipline='physics' nrweeksback=1 %}">week</a>, <a href="{% url 'theses:browse' discipline='physics' nrweeksback=4 %}">month</a> or <a href="{% url 'theses:browse' discipline='physics' nrweeksback=52 %}">year</a> </li> - </ul> - </div> + <div class="p-3 mb-3 bg-light scipost-bar border min-height-190"> + <h2>View SciPost Theses</h2> + <ul> + <li>Physics: last <a href="{% url 'theses:browse' discipline='physics' nrweeksback=1 %}">week</a>, <a href="{% url 'theses:browse' discipline='physics' nrweeksback=4 %}">month</a> or <a href="{% url 'theses:browse' discipline='physics' nrweeksback=52 %}">year</a> </li> + </ul> </div> </div> </div> diff --git a/virtualmeetings/templates/virtualmeetings/VGM_detail.html b/virtualmeetings/templates/virtualmeetings/VGM_detail.html index c3e7b279978cf42805616d53fb7d193f2bba3079..79aeb72bea91c99b67f8d9ed5fd56004ed148033 100644 --- a/virtualmeetings/templates/virtualmeetings/VGM_detail.html +++ b/virtualmeetings/templates/virtualmeetings/VGM_detail.html @@ -50,7 +50,7 @@ <div class="row" id="Feedback"> <div class="col-12"> <div id="FeedbackBox"> - <div class="card card-grey my-2"> + <div class="card card-gray my-2"> <div class="card-header"> <h2>Feedback on SciPost</h2> <a href="javascript:;" class="btn btn-outline-secondary" data-toggle="toggle" data-target="#submitFeedback">Provide feedback</a> @@ -100,7 +100,7 @@ <div class="row" id="Nominations"> <div class="col-12"> <div id="NominationBox"> - <div class="card card-grey my-2"> + <div class="card card-gray my-2"> <div class="card-header"> <h2>Nominations to the Editorial College</h2> <a href="javascript:;" class="btn btn-outline-secondary" data-toggle="toggle" data-target="#submitNominationForm">Nominate an Editorial Fellow candidate</a> @@ -238,7 +238,7 @@ <!-- Motions --> <div class="row" id="Motions"> <div class="col-12"> - <div class="card card-grey my-2"> + <div class="card card-gray my-2"> <div class="card-header"> <h2>Submit a new Motion</h2> <a href="javascript:;" class="btn btn-outline-secondary" data-toggle="toggle" data-target="#submitMotionForm">Put a new Motion forward</a>