diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py index 861544adb28ad4486484f1974dffafa2cb563a9a..28b54d4d47b36226691056e38982ab5f4569efe6 100644 --- a/SciPost_v1/settings/base.py +++ b/SciPost_v1/settings/base.py @@ -95,6 +95,7 @@ INSTALLED_APPS = ( 'virtualmeetings', 'production', 'partners', + 'funders', 'webpack_loader', ) diff --git a/SciPost_v1/urls.py b/SciPost_v1/urls.py index adf26e3aa41c52e07475da80c70f1e99700c13c7..cb5b11d4bc6ea39ec73f242818ed3c5c5ebd5435 100644 --- a/SciPost_v1/urls.py +++ b/SciPost_v1/urls.py @@ -20,6 +20,7 @@ urlpatterns = [ url(r'^commentaries/', include('commentaries.urls', namespace="commentaries")), url(r'^commentary/', include('commentaries.urls', namespace="commentaries")), url(r'^comments/', include('comments.urls', namespace="comments")), + url(r'^funders/', include('funders.urls', namespace="funders")), url(r'^journals/', include('journals.urls.general', namespace="journals")), url(r'^mailing_list/', include('mailing_lists.urls', namespace="mailing_lists")), url(r'^submissions/', include('submissions.urls', namespace="submissions")), diff --git a/commentaries/models.py b/commentaries/models.py index da8321b411630469c64122437bb5de53f452a870..27db6d732b4144dfb15a8ca6c86997a6ac04b7b1 100644 --- a/commentaries/models.py +++ b/commentaries/models.py @@ -4,7 +4,7 @@ from django.core.urlresolvers import reverse from django.template import Template, Context from journals.constants import SCIPOST_JOURNALS_DOMAINS -from scipost.behaviors import ArxivCallable, TimeStampedModel +from scipost.behaviors import TimeStampedModel from scipost.models import Contributor from scipost.constants import SCIPOST_DISCIPLINES, DISCIPLINE_PHYSICS, SCIPOST_SUBJECT_AREAS @@ -12,7 +12,7 @@ from .constants import COMMENTARY_TYPES from .managers import CommentaryManager -class Commentary(ArxivCallable, TimeStampedModel): +class Commentary(TimeStampedModel): """ A Commentary contains all the contents of a SciPost Commentary page for a given publication. """ @@ -72,10 +72,6 @@ class Commentary(ArxivCallable, TimeStampedModel): def __str__(self): return self.pub_title - @classmethod - def same_version_exists(self, identifier): - return self.objects.filter(arxiv_identifier=identifier).exists() - def title_label(self): context = Context({ 'scipost_url': reverse('commentaries:commentary', args=(self.arxiv_or_DOI_string,)), diff --git a/comments/templates/comments/_comment_identifier.html b/comments/templates/comments/_comment_identifier.html index f30c460f3925344561cf1272f2c3c705523c92c9..3247be4b6f071feae71a2f1279ded6e0973f9f74 100644 --- a/comments/templates/comments/_comment_identifier.html +++ b/comments/templates/comments/_comment_identifier.html @@ -16,7 +16,7 @@ {% if comment.in_reply_to_comment %} (in reply to <a href="#comment_id{{comment.in_reply_to_comment.id}}">{{comment.in_reply_to_comment.comment.get_author_str}}</a> on {{comment.in_reply_to_comment.date_submitted|date:'Y-m-d'}}) {% elif comment.in_reply_to_report %} - (in reply to <a href="#report_id{{comment.in_reply_to_report.id}}"> + (in reply to <a href="#report_{{comment.in_reply_to_report.report_nr}}"> {% if not comment.in_reply_to_report.anonymous %} {{comment.in_reply_to_report.get_author_str}} diff --git a/comments/templates/comments/_comment_tex_template.html b/comments/templates/comments/_comment_tex_template.html new file mode 100644 index 0000000000000000000000000000000000000000..b6540e82c6d45520961cb04feacc921b65b4e5d0 --- /dev/null +++ b/comments/templates/comments/_comment_tex_template.html @@ -0,0 +1,11 @@ +{% spaceless %} +Received {{comment.date_submitted|date:'d-m-Y'}}\ \\ +{% if comment.file_attachment %}- \textit{This {% if comment.is_author_reply %}Author Reply{% else %}Comment{% endif %} has an Attachment, please download it online.}\ \\{% endif %}\ \\{{comment.comment_text}} +{% endspaceless %} + +{% for subcomment in comment.nested_comments.vetted %} + \addcontentsline{toc}{subsection}{\protect\numberline{}{% if subcomment.is_author_reply %}Author Reply{% else %}Comment{% endif %} {{forloop.counter}} by {% if subcomment.anonymous %}anonymous{% else %}{{subcomment.author.user.first_name}} {{subcomment.author.user.last_name}}{% endif %} } + + \subsection*{ {% if subcomment.is_author_reply %}Author Reply{% else %}Comment{% endif %} {{forloop.parentloop.counter}}.{{forloop.counter}} by {% if subcomment.anonymous %}anonymous{% else %}{{subcomment.author.user.first_name}} {{subcomment.author.user.last_name}}{% endif %} } + {% include 'comments/_comment_tex_template.html' with comment=subcomment %} +{% endfor %} diff --git a/comments/templates/comments/reply_to_report.html b/comments/templates/comments/reply_to_report.html index 348d82701826f73a80777e21357ef24fbc042db9..d796bb1210d20cd90057e987d2eb59a5c9755c63 100644 --- a/comments/templates/comments/reply_to_report.html +++ b/comments/templates/comments/reply_to_report.html @@ -14,7 +14,7 @@ <div class="row"> <div class="col-12"> {% if not is_author %} - <h2>(you are not identified as an author of this Submission; if you are, you can claim authorship on your Personal Page)</h2> + <h2>You are not identified as an author of this Submission; if you are, you can claim authorship on your Personal Page.</h2> {% else %} <h2>The Submission concerned:</h2> diff --git a/funders/__init__.py b/funders/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/funders/admin.py b/funders/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..d049c6c0f9c56085ce4857538938ef681b322d2a --- /dev/null +++ b/funders/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin + +from .models import Funder, Grant + + +admin.site.register(Funder) + + +admin.site.register(Grant) diff --git a/funders/apps.py b/funders/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..413a2e31ce19ca0141ad1185bba7b99059676c38 --- /dev/null +++ b/funders/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class FundersConfig(AppConfig): + name = 'funders' diff --git a/funders/forms.py b/funders/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..64676af4446f442411f856981a0393bf8a512945 --- /dev/null +++ b/funders/forms.py @@ -0,0 +1,22 @@ +from django import forms + +from .models import Funder, Grant + +class FunderRegistrySearchForm(forms.Form): + name = forms.CharField(max_length=128) + + +class FunderForm(forms.ModelForm): + class Meta: + model = Funder + fields = ['name', 'identifier',] + + +class GrantForm(forms.ModelForm): + class Meta: + model = Grant + fields = ['funder', 'number', 'recipient_name', 'recipient',] + + +class GrantSelectForm(forms.Form): + grant = forms.ModelChoiceField(queryset=Grant.objects.all()) diff --git a/funders/migrations/0001_initial.py b/funders/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..bf7e7e4fbf8e63399cdbd03efaeaad0edc080a0d --- /dev/null +++ b/funders/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-25 15:40 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('scipost', '0059_auto_20170701_1356'), + ] + + operations = [ + migrations.CreateModel( + name='Funder', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=256)), + ('identifier', models.CharField(max_length=200, unique=True)), + ], + ), + migrations.CreateModel( + name='Grant', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('number', models.CharField(max_length=64)), + ('recipient_name', models.CharField(blank=True, max_length=64, null=True)), + ('funder', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='funders.Funder')), + ('recipient', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='scipost.Contributor')), + ], + ), + ] diff --git a/funders/migrations/0002_auto_20170725_2011.py b/funders/migrations/0002_auto_20170725_2011.py new file mode 100644 index 0000000000000000000000000000000000000000..c689cf120f8d2cb66918c65a7d4726e3250c0ac9 --- /dev/null +++ b/funders/migrations/0002_auto_20170725_2011.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-25 18:11 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('funders', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='funder', + options={'ordering': ['name']}, + ), + migrations.AlterModelOptions( + name='grant', + options={'ordering': ['funder', 'recipient', 'recipient_name', 'number']}, + ), + ] diff --git a/funders/migrations/0003_auto_20170726_0606.py b/funders/migrations/0003_auto_20170726_0606.py new file mode 100644 index 0000000000000000000000000000000000000000..d7c8df068e205aade1009695525a669cf16c6100 --- /dev/null +++ b/funders/migrations/0003_auto_20170726_0606.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-26 04:06 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('funders', '0002_auto_20170725_2011'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='grant', + unique_together=set([('funder', 'number')]), + ), + ] diff --git a/funders/migrations/__init__.py b/funders/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/funders/models.py b/funders/models.py new file mode 100644 index 0000000000000000000000000000000000000000..3957bf92f13ae9a3825a06aa780e68c6063e6b73 --- /dev/null +++ b/funders/models.py @@ -0,0 +1,41 @@ +from django.db import models + + +class Funder(models.Model): + """ + Funding info metadata is linked to funders from Crossref's + Fundref registry. + """ + name = models.CharField(max_length=256) + identifier = models.CharField(max_length=200, unique=True) + + class Meta: + ordering = ['name'] + + def __str__(self): + return self.name + + +class Grant(models.Model): + """ + An instance of a grant, award or other funding. + In a Publication's metadata, all grants are listed + in the Crossmark part of the metadata. + """ + funder = models.ForeignKey(Funder, on_delete=models.CASCADE) + number = models.CharField(max_length=64) + recipient_name = models.CharField(max_length=64, blank=True, null=True) + recipient = models.ForeignKey('scipost.Contributor', blank=True, null=True, + on_delete=models.CASCADE) + + class Meta: + ordering = ['funder', 'recipient', 'recipient_name', 'number'] + unique_together = ('funder', 'number') + + def __str__(self): + grantstring = '%s, grant number %s' % (str(self.funder), self.number) + if self.recipient: + grantstring += ' (%s)' % str(self.recipient) + elif self.recipient_name: + grantstring += ' (%s)' % self.recipient_name + return grantstring diff --git a/funders/templates/funders/funders.html b/funders/templates/funders/funders.html new file mode 100644 index 0000000000000000000000000000000000000000..7ae67d06b8fa03d6fb504cd5475f30f1c22120cb --- /dev/null +++ b/funders/templates/funders/funders.html @@ -0,0 +1,133 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: Funders{% endblock pagetitle %} + +{% load bootstrap %} + +{% block content %} + +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Funders (and associated grants)</h1> + </div> +</div> + + +<div class="row"> + <div class="col-12"> + <div class="tab-nav-container"> + <div class="tab-nav-inner"> + <!-- Nav tabs --> + <ul class="nav btn-group personal-page-nav" role="tablist"> + <li class="nav-item btn btn-secondary"> + <a href="#funders" class="nav-link active" data-toggle="tab">Funders</a> + </li> + <li class="nav-item btn btn-secondary"> + <a href="#grants" class="nav-link" data-toggle="tab">Grants</a> + </li> + </ul> + </div> + </div> + </div> +</div> + + + +<div class="tab-content"> + + <!-- Tab: Funders --> + <div class="tab-pane active" id="funders" role="tabpanel"> + <div class="row"> + <div class="col-12"> + <h2 class="highlight">Funders</h2> + </div> + </div> + + <div class="row"> + <div class="col-12"> + <h2>Find a new funder in the Fundref registry</h2> + <form action="{% url 'funders:query_crossref_for_funder' %}" method="post"> + {% csrf_token %} + {{form|bootstrap}} + <input class="btn btn-secondary" type="submit" value="Search"> + </form> + <br/> + <h2>Funders in the SciPost database</h2> + <table class="table table-hover mb-5"> + <thead class="thead-default"> + <tr> + <th>Name</th> + <th>Identifier</th> + </tr> + </thead> + <tbody id="accordion" role="tablist" aria-multiselectable="true"> + {% for funder in funders %} + <tr data-toggle="collapse" data-parent="#accordion" href="#collapse{{ funder.id }}" aria-expanded="true" aria-controls="collapse{{ funder.id }}" style="cursor: pointer;"> + <td>{{ funder.name }}</td> + <td>{{ funder.identifier }}</td> + </tr> + {% empty %} + <tr> + <td colspan="3">No funders found</td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + </div> + </div> + + + <!-- Tab: Grants --> + <div class="tab-pane" id="grants" role="tabpanel"> + <div class="row"> + <div class="col-12"> + <h2 class="highlight">Grants</h2> + </div> + </div> + + <div class="row"> + <div class="col-12"> + <h2>Add a grant</h2> + <form action="{% url 'funders:add_grant' %}" method="post"> + {% csrf_token %} + {{grant_form|bootstrap}} + <input class="btn btn-secondary" type="submit" value="Add"> + </form> + <br/> + <h2>Grants in the SciPost database</h2> + <table class="table table-hover mb-5"> + <thead class="thead-default"> + <tr> + <th>Funder Name</th> + <th>Recipient</th> + <th>Number</th> + </tr> + </thead> + <tbody id="accordion" role="tablist" aria-multiselectable="true"> + {% for grant in grants %} + <tr data-toggle="collapse" data-parent="#accordion" href="#collapse{{ grant.id }}" aria-expanded="true" aria-controls="collapse{{ grant.id }}" style="cursor: pointer;"> + <td>{{ grant.funder.name }}</td> + {% if grant.recipient %} + <td>{{ grant.recipient }}</td> + {% elif grant.recipient_name %} + <td>{{ grant.recipient_name }}</td> + {% else %} + <td></td> + {% endif %} + <td>{{ grant.number }}</td> + </tr> + {% empty %} + <tr> + <td colspan="3">No grants found</td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + </div> + </div> +</div> + + +{% endblock content %} diff --git a/funders/templates/funders/query_crossref_for_funder.html b/funders/templates/funders/query_crossref_for_funder.html new file mode 100644 index 0000000000000000000000000000000000000000..b8459c8a21ff63b830f9c8919fdefb2fe28f9174 --- /dev/null +++ b/funders/templates/funders/query_crossref_for_funder.html @@ -0,0 +1,48 @@ +{% extends 'scipost/_personal_page_base.html' %} + +{% block pagetitle %}: Query Crossref for funder{% endblock pagetitle %} + +{% load bootstrap %} + +{% block content %} + +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Query Crossref Fundref Registry for Funders</h1> + <form action="{% url 'funders:query_crossref_for_funder' %}" method="post"> + {% csrf_token %} + {{form|bootstrap}} + <input class="btn btn-secondary" type="submit" value="Search"> + </form> + {% if response_headers %} + <p>{{ response_headers }}</p> + {% endif %} + {% if response_text %} + <p>{{ response_text }}</p> + {% endif %} + {% if response %} + <p>{{ response }}</p> + <ul> + {% for item in response.message.items %} + <li> + {{ item.name }}, {{ item.id }}, {{ item.uri }} + <form action="{% url 'funders:add_funder' %}" method="post"> + {% csrf_token %} + <input name='name' style="width: 64%" value='{{ item.name }}'> + <input name='identifier' style="width: 64%" value='{{ item.uri }}'> + <input class="btn btn-secondary" type="submit" value="Add this funder"> + </form> + </li> + {% endfor %} + </ul> + <form action="{% url 'funders:add_funder' %}" method="post"> + {% csrf_token %} + {{funder_form|bootstrap}} + <input class="btn btn-secondary" type="submit" value="Submit"> + </form> + {% endif %} + </div> +</div> + + +{% endblock content %} diff --git a/funders/tests.py b/funders/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6 --- /dev/null +++ b/funders/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/funders/urls.py b/funders/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..ec3521ede5af0392724a07aca2ab22f95403c0c7 --- /dev/null +++ b/funders/urls.py @@ -0,0 +1,18 @@ +from django.conf.urls import url +from django.views.generic import TemplateView + +from . import views + +urlpatterns = [ + url(r'^$', views.funders, + name='funders'), + url(r'^query_crossref_for_funder$', + views.query_crossref_for_funder, + name='query_crossref_for_funder'), + url(r'^add_funder$', + views.add_funder, + name='add_funder'), + url(r'^add_grant$', + views.add_grant, + name='add_grant'), +] diff --git a/funders/views.py b/funders/views.py new file mode 100644 index 0000000000000000000000000000000000000000..50f46f967e463036e4f4d78e4a3a641643ed182c --- /dev/null +++ b/funders/views.py @@ -0,0 +1,65 @@ +import requests +import json + +from django.contrib import messages +from django.contrib.auth.decorators import permission_required +from django.core.urlresolvers import reverse +from django.shortcuts import render, redirect + +from .models import Funder, Grant +from .forms import FunderRegistrySearchForm, FunderForm, GrantForm + + +@permission_required('scipost.can_publish_accepted_submission', raise_exception=True) +def funders(request): + funders = Funder.objects.all() + form = FunderRegistrySearchForm() + grants = Grant.objects.all() + grant_form = GrantForm() + context = {'form': form, 'funders': funders, + 'grants': grants, 'grant_form': grant_form} + return render(request, 'funders/funders.html', context) + + +@permission_required('scipost.can_publish_accepted_submission', raise_exception=True) +def query_crossref_for_funder(request): + """ + Checks Crossref's Fundref Registry for an entry + corresponding to the funder name being looked for. + If found, creates a funders.Funder instance. + """ + form = FunderRegistrySearchForm(request.POST or None) + context = {'form': form} + if form.is_valid(): + queryurl = 'http://api.crossref.org/funders?query=%s' % form.cleaned_data['name'] + query = requests.get(queryurl) + response = json.loads(query.text) + context['response_headers'] = query.headers + context['response_text'] = query.text + context['response'] = response + context['funder_form'] = FunderForm() + return render(request, 'funders/query_crossref_for_funder.html', context) + + +@permission_required('scipost.can_publish_accepted_submission', raise_exception=True) +def add_funder(request): + form = FunderForm(request.POST or None) + if form.is_valid(): + funder = form.save() + messages.success(request, ('<h3>Funder %s successfully created</h3>') % + str(funder)) + elif form.has_changed(): + messages.warning(request, 'The form was invalidly filled.') + return redirect(reverse('funders:funders')) + + +@permission_required('scipost.can_publish_accepted_submission', raise_exception=True) +def add_grant(request): + grant_form = GrantForm(request.POST or None) + if grant_form.is_valid(): + grant = grant_form.save() + messages.success(request, ('<h3>Grant %s successfully added</h3>') % + str(grant)) + elif grant_form.has_changed(): + messages.warning(request, 'The form was invalidly filled (grant already exists?).') + return redirect(reverse('funders:funders')) diff --git a/journals/admin.py b/journals/admin.py index 6b5f4cecdf14c7289cd748ca007979ae20f083c9..0ed07f17e294622f403b9d3823ca201d673bb53e 100644 --- a/journals/admin.py +++ b/journals/admin.py @@ -64,6 +64,7 @@ class PublicationAdmin(admin.ModelAdmin): admin.site.register(Publication, PublicationAdmin) + class DepositAdmin(admin.ModelAdmin): list_display = ('publication', 'timestamp', 'doi_batch_id', 'deposition_date',) readonly_fields = ('publication', 'doi_batch_id', 'metadata_xml', 'deposition_date',) diff --git a/journals/forms.py b/journals/forms.py index eb54e4deb587df7ccbc6bbb533d2eb6450988883..688ade7e6c2f450da0473aeadc48cabb756c1dff 100644 --- a/journals/forms.py +++ b/journals/forms.py @@ -8,21 +8,13 @@ from .models import UnregisteredAuthor, Issue, Publication from submissions.models import Submission - class InitiatePublicationForm(forms.Form): - accepted_submission = forms.ModelChoiceField( - queryset=Submission.objects.filter(status='accepted')) - original_submission_date = forms.DateField() - acceptance_date = forms.DateField() + accepted_submission = forms.ModelChoiceField(queryset=Submission.objects.accepted()) to_be_issued_in = forms.ModelChoiceField( queryset=Issue.objects.filter(until_date__gt=timezone.now())) def __init__(self, *args, **kwargs): super(InitiatePublicationForm, self).__init__(*args, **kwargs) - self.fields['original_submission_date'].widget.attrs.update( - {'placeholder': 'YYYY-MM-DD'}) - self.fields['acceptance_date'].widget.attrs.update( - {'placeholder': 'YYYY-MM-DD'}) class ValidatePublicationForm(forms.ModelForm): @@ -30,7 +22,7 @@ class ValidatePublicationForm(forms.ModelForm): model = Publication exclude = ['authors', 'authors_claims', 'authors_false_claims', 'metadata', 'metadata_xml', - 'latest_activity', ] + 'latest_activity'] class UnregisteredAuthorForm(forms.ModelForm): @@ -66,16 +58,22 @@ class FundingInfoForm(forms.Form): funding_statement = forms.CharField(widget=forms.Textarea()) def __init__(self, *args, **kwargs): - super(FundingInfoForm, self).__init__(*args, **kwargs) - self.fields['funding_statement'].widget.attrs.update( - {'rows': 10, 'cols': 50, - 'placeholder': 'Paste the funding info statement here'}) + super().__init__(*args, **kwargs) + self.fields['funding_statement'].widget.attrs.update({ + 'rows': 10, + 'cols': 50, + 'placeholder': 'Paste the funding info statement here' + }) -class CreateMetadataXMLForm(forms.Form): - metadata_xml = forms.CharField(widget=forms.Textarea()) +class CreateMetadataXMLForm(forms.ModelForm): + class Meta: + model = Publication + fields = ['metadata_xml'] def __init__(self, *args, **kwargs): - super(CreateMetadataXMLForm, self).__init__(*args, **kwargs) - self.fields['metadata_xml'].widget.attrs.update( - {'rows': 50, 'cols': 50, }) + super().__init__(*args, **kwargs) + self.fields['metadata_xml'].widget.attrs.update({ + 'rows': 50, + 'cols': 50 + }) diff --git a/journals/migrations/0036_auto_20170725_1729.py b/journals/migrations/0036_auto_20170725_1729.py new file mode 100644 index 0000000000000000000000000000000000000000..65ec9846bc191d8d6962a9c67e79a59a8af30302 --- /dev/null +++ b/journals/migrations/0036_auto_20170725_1729.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-25 15:29 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('scipost', '0059_auto_20170701_1356'), + ('journals', '0035_auto_20170714_0609'), + ] + + operations = [ + migrations.CreateModel( + name='Funder', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=256)), + ('identifier', models.CharField(max_length=200, unique=True)), + ], + ), + migrations.CreateModel( + name='Grant', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('number', models.CharField(max_length=64)), + ('recipient_name', models.CharField(blank=True, max_length=64, null=True)), + ('funder', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='journals.Funder')), + ('recipient', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='scipost.Contributor')), + ], + ), + migrations.AddField( + model_name='publication', + name='grants', + field=models.ManyToManyField(blank=True, null=True, to='journals.Grant'), + ), + ] diff --git a/journals/migrations/0036_auto_20170725_2048.py b/journals/migrations/0036_auto_20170725_2048.py new file mode 100644 index 0000000000000000000000000000000000000000..0f740ec130985368925a88ae469fc465d5b4d1a9 --- /dev/null +++ b/journals/migrations/0036_auto_20170725_2048.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-25 18:48 +from __future__ import unicode_literals + +import django.contrib.postgres.fields.jsonb +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0035_auto_20170714_0609'), + ] + + operations = [ + migrations.AlterField( + model_name='publication', + name='metadata_DOAJ', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default={}, null=True), + ), + ] diff --git a/journals/migrations/0037_auto_20170725_1730.py b/journals/migrations/0037_auto_20170725_1730.py new file mode 100644 index 0000000000000000000000000000000000000000..db456745ef9aa668ae34fc88ee197461a03ec30f --- /dev/null +++ b/journals/migrations/0037_auto_20170725_1730.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-25 15:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0036_auto_20170725_1729'), + ] + + operations = [ + migrations.AlterField( + model_name='publication', + name='grants', + field=models.ManyToManyField(blank=True, to='journals.Grant'), + ), + ] diff --git a/journals/migrations/0038_auto_20170725_1738.py b/journals/migrations/0038_auto_20170725_1738.py new file mode 100644 index 0000000000000000000000000000000000000000..0d32403d115b27f88615e7f48c243a70f6c71d9b --- /dev/null +++ b/journals/migrations/0038_auto_20170725_1738.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-25 15:38 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0037_auto_20170725_1730'), + ] + + operations = [ + migrations.RemoveField( + model_name='grant', + name='funder', + ), + migrations.RemoveField( + model_name='grant', + name='recipient', + ), + migrations.RemoveField( + model_name='publication', + name='grants', + ), + migrations.DeleteModel( + name='Funder', + ), + migrations.DeleteModel( + name='Grant', + ), + ] diff --git a/journals/migrations/0039_publication_grants.py b/journals/migrations/0039_publication_grants.py new file mode 100644 index 0000000000000000000000000000000000000000..8b2f9b1c3e2c66f38a68cc2d6290d5e4c2da2614 --- /dev/null +++ b/journals/migrations/0039_publication_grants.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-25 15:40 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('funders', '0001_initial'), + ('journals', '0038_auto_20170725_1738'), + ] + + operations = [ + migrations.AddField( + model_name='publication', + name='grants', + field=models.ManyToManyField(blank=True, to='funders.Grant'), + ), + ] diff --git a/journals/migrations/0040_merge_20170726_0945.py b/journals/migrations/0040_merge_20170726_0945.py new file mode 100644 index 0000000000000000000000000000000000000000..d2c7a67aca6e2f637275e1ea48b8fc99b88b63eb --- /dev/null +++ b/journals/migrations/0040_merge_20170726_0945.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-26 07:45 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0036_auto_20170725_2048'), + ('journals', '0039_publication_grants'), + ] + + operations = [ + ] diff --git a/journals/models.py b/journals/models.py index 722fdccdf2790733789b411c760645a8a462d4e4..0953c805a28042c65e1c62ea131ea8772344c48f 100644 --- a/journals/models.py +++ b/journals/models.py @@ -142,10 +142,11 @@ class Publication(models.Model): abstract = models.TextField() pdf_file = models.FileField(upload_to='UPLOADS/PUBLICATIONS/%Y/%m/', max_length=200) cc_license = models.CharField(max_length=32, choices=CC_LICENSES, default=CCBY4) + grants = models.ManyToManyField('funders.Grant', blank=True) metadata = JSONField(default={}, blank=True, null=True) metadata_xml = models.TextField(blank=True, null=True) # for Crossref deposit latest_metadata_update = models.DateTimeField(blank=True, null=True) - metadata_DOAJ = JSONField(blank=True, null=True) + metadata_DOAJ = JSONField(default={}, blank=True, null=True) BiBTeX_entry = models.TextField(blank=True, null=True) doi_label = models.CharField(max_length=200, unique=True, db_index=True, validators=[doi_publication_validator]) @@ -216,7 +217,6 @@ class Publication(models.Model): return template.render(context) - class Deposit(models.Model): """ Each time a Crossref deposit is made for a Publication, diff --git a/journals/templates/journals/manage_metadata.html b/journals/templates/journals/manage_metadata.html index b9ca627d8401da8c0d6225fff22d306c32f39045..89f1796afce4c103f9dba4c3e3b991e10555f8b6 100644 --- a/journals/templates/journals/manage_metadata.html +++ b/journals/templates/journals/manage_metadata.html @@ -56,11 +56,12 @@ event: "focusin" </tr> <tr id="collapse{{ publication.id }}" class="collapse" role="tabpanel" aria-labelledby="heading{{ publication.id }}" style="background-color: #fff;"> <td colspan="5"> - <h3 class="ml-3">Actions</h3> - <ul> - <li>Mark the first author (currently: {% if publication.first_author %}{{ publication.first_author }} {% elif publication.first_author_unregistered %}{{ publication.first_author_unregistered }} (unregistered){% endif %}) - <div class="row"> - <div class="col-md-5"> + + <h2 class="ml-3">Actions</h2> + <div class="row"> + <div class="col-md-5"> + <ul> + <li>Mark the first author (currently: {% if publication.first_author %}{{ publication.first_author }} {% elif publication.first_author_unregistered %}{{ publication.first_author_unregistered }} (unregistered){% endif %}) <p>registered authors:</p> <ul> {% for author in publication.authors.all %} @@ -69,8 +70,6 @@ event: "focusin" </li> {% endfor %} </ul> - </div> - <div class="col-md-5"> <p>unregistered authors:</p> <ul> {% for author_unreg in publication.authors_unregistered.all %} @@ -79,20 +78,50 @@ event: "focusin" </li> {% endfor %} </ul> - </div> - </div> - </li> - <li><a href="{% url 'journals:add_author' publication.id %}">Add a missing author</a></li> - <li><a href="{% url 'journals:create_citation_list_metadata' publication.doi_label %}">Create/update citation list metadata</a></li> - <li><a href="{% url 'journals:create_funding_info_metadata' publication.doi_label %}">Create/update funding info metadata</a></li> - - <li><a href="{% url 'journals:create_metadata_xml' publication.doi_label %}">(re)create metadata</a></li> - <li><a href="{% url 'journals:metadata_xml_deposit' publication.doi_label 'test' %}">Test metadata deposit (via Crossref test server)</a></li> - <li><a href="{% url 'journals:metadata_xml_deposit' publication.doi_label 'deposit' %}">Deposit the metadata to Crossref</a></li> - <li><a href="{% url 'journals:produce_metadata_DOAJ' doi_label=publication.doi_label %}">Produce DOAJ metadata</a></li> - <li><a href="{% url 'journals:metadata_DOAJ_deposit' doi_label=publication.doi_label %}">Deposit the metadata to DOAJ</a></li> - </ul> - <h3 class="ml-3">Crossref Deposits</h3> + </li> + <li><a href="{% url 'journals:add_author' publication.id %}">Add a missing author</a></li> + <li><a href="{% url 'journals:create_citation_list_metadata' publication.doi_label %}">Create/update citation list metadata</a></li> + <li><a href="{% url 'journals:create_funding_info_metadata' publication.doi_label %}">Create/update funding info metadata</a></li> + + <li><a href="{% url 'journals:create_metadata_xml' publication.doi_label %}">(re)create metadata</a></li> + <li><a href="{% url 'journals:metadata_xml_deposit' publication.doi_label 'test' %}">Test metadata deposit (via Crossref test server)</a></li> + <li><a href="{% url 'journals:metadata_xml_deposit' publication.doi_label 'deposit' %}">Deposit the metadata to Crossref</a></li> + <li><a href="{% url 'journals:produce_metadata_DOAJ' doi_label=publication.doi_label %}">Produce DOAJ metadata</a></li> + <li><a href="{% url 'journals:metadata_DOAJ_deposit' doi_label=publication.doi_label %}">Deposit the metadata to DOAJ</a></li> + </ul> + </div> + + <div class="col-md-5"> + <h2>Funding info for this publication:</h2> + {% if publication.funding_info %} + <p>{{ publication.funding_info }}</p> + {% else %} + <p>No funding info was found</p> + {% endif %} + <h2>Grants associated to this publication:</h2> + <ul> + {% for grant in publication.grants.all %} + <li> {{ grant }}</li> + {% empty %} + <li>no associated grants found</li> + {% endfor %} + </ul> + <br/> + <h3>Associate a grant to this publication:</h3> + <form action="{% url 'journals:add_associated_grant' publication.doi_label %}" method="post"> + {% csrf_token %} + {{associate_grant_form|bootstrap}} + <input class="btn btn-secondary" type="submit" value="Add"> + </form> + <h3>Other grant-related actions:</h3> + <ul> + <li><a href="{% url 'funders:funders' %}" target="_blank">go to the Funders page to add a Funder and/or Grant instance</a></li> + </ul> + </div> + </div> + + + <h2 class="ml-3">Crossref Deposits</h2> <table class="ml-5"> <thead class="thead-default"> <th>Timestamp</th> @@ -123,7 +152,7 @@ event: "focusin" </tbody> </table> - <h3 class="ml-3">DOAJ Deposits</h3> + <h2 class="ml-3">DOAJ Deposits</h2> <table class="ml-5"> <thead class="thead-default"> <th>Timestamp</th> diff --git a/journals/urls/general.py b/journals/urls/general.py index c785ce47b247e68ad07e3e2fa2422214f2ae15b7..b1ba8d539c6094835068874e38bb11008623b14e 100644 --- a/journals/urls/general.py +++ b/journals/urls/general.py @@ -50,6 +50,9 @@ urlpatterns = [ url(r'^create_funding_info_metadata/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})$', journals_views.create_funding_info_metadata, name='create_funding_info_metadata'), + url(r'^add_associated_grant/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})$', + journals_views.add_associated_grant, + name='add_associated_grant'), url(r'^create_metadata_xml/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})$', journals_views.create_metadata_xml, name='create_metadata_xml'), diff --git a/journals/views.py b/journals/views.py index bcfa7155dfcab9fd7881050ee0fca52a880dbc9d..4f073a1a6d6f7550840ed21fe9d4014398ca920b 100644 --- a/journals/views.py +++ b/journals/views.py @@ -9,10 +9,9 @@ from django.core.urlresolvers import reverse from django.core.files.base import ContentFile from django.conf import settings from django.contrib import messages +from django.http import Http404 from django.utils import timezone from django.shortcuts import get_object_or_404, render, redirect -from django.template import Context -from django.template.loader import get_template from django.db import transaction from django.http import HttpResponse @@ -23,9 +22,12 @@ from .forms import FundingInfoForm, InitiatePublicationForm, ValidatePublication UnregisteredAuthorForm, CreateMetadataXMLForm, CitationListBibitemsForm from .utils import JournalUtils +from funders.models import Funder from submissions.models import Submission from scipost.models import Contributor +from funders.forms import GrantSelectForm + from guardian.decorators import permission_required @@ -91,9 +93,9 @@ def accepted(request, doi_label): have been accepted but are not yet published. """ journal = get_object_or_404(Journal, doi_label=doi_label) - accepted_SP_submissions = Submission.objects.filter( - submitted_to_journal=journal.name, status='accepted' - ).order_by('-latest_activity') + accepted_SP_submissions = (Submission.objects.accepted() + .filter(submitted_to_journal=journal.name) + .order_by('-latest_activity')) context = { 'accepted_SP_submissions': accepted_SP_submissions, 'journal': journal @@ -151,72 +153,61 @@ def initiate_publication(request): This method prefills a ValidatePublicationForm for further processing (verification in validate_publication method). """ - if request.method == 'POST': - initiate_publication_form = InitiatePublicationForm(request.POST) - if initiate_publication_form.is_valid(): - submission = get_object_or_404(Submission, pk=initiate_publication_form.cleaned_data[ - 'accepted_submission'].id) - current_issue = get_object_or_404(Issue, pk=initiate_publication_form.cleaned_data[ - 'to_be_issued_in'].id) - - # Determine next available paper number: - papers_in_current_volume = Publication.objects.filter( - in_issue__in_volume=current_issue.in_volume) - paper_nr = 1 - while papers_in_current_volume.filter(paper_nr=paper_nr).exists(): - paper_nr += 1 - if paper_nr > 999: - raise PaperNumberingError(paper_nr) - doi_label = ( - current_issue.in_volume.in_journal.name - + '.' + str(current_issue.in_volume.number) - + '.' + str(current_issue.number) + '.' + paper_nr_string(paper_nr) - ) - doi_string = '10.21468/' + doi_label - BiBTeX_entry = ( - '@Article{' + doi_label + ',\n' - '\ttitle={{' + submission.title + '}},\n' - '\tauthor={' + submission.author_list.replace(',', ' and') + '},\n' - '\tjournal={' - + current_issue.in_volume.in_journal.get_abbreviation_citation() - + '},\n' - '\tvolume={' + str(current_issue.in_volume.number) + '},\n' - '\tissue={' + str(current_issue.number) + '},\n' - '\tpages={' + paper_nr_string(paper_nr) + '},\n' - '\tyear={' + current_issue.until_date.strftime('%Y') + '},\n' - '\tpublisher={SciPost},\n' - '\tdoi={' + doi_string + '},\n' - '\turl={https://scipost.org/' + doi_string + '},\n' - '}\n' - ) - initial = { - 'accepted_submission': submission, - 'in_issue': current_issue, - 'paper_nr': paper_nr, - 'discipline': submission.discipline, - 'domain': submission.domain, - 'subject_area': submission.subject_area, - 'secondary_areas': submission.secondary_areas, - 'title': submission.title, - 'author_list': submission.author_list, - 'abstract': submission.abstract, - 'BiBTeX_entry': BiBTeX_entry, - 'doi_label': doi_label, - 'submission_date': initiate_publication_form.cleaned_data['original_submission_date'], - 'acceptance_date': initiate_publication_form.cleaned_data['acceptance_date'], - 'publication_date': timezone.now(), - 'latest_activity': timezone.now(), - } - validate_publication_form = ValidatePublicationForm(initial=initial) - context = {'validate_publication_form': validate_publication_form, } - return render(request, 'journals/validate_publication.html', context) - else: - errormessage = 'The form was not filled validly.' - context = {'initiate_publication_form': initiate_publication_form, - 'errormessage': errormessage} - return render(request, 'journals/initiate_publication.html', context) - else: - initiate_publication_form = InitiatePublicationForm() + initiate_publication_form = InitiatePublicationForm(request.POST or None) + if initiate_publication_form.is_valid(): + submission = initiate_publication_form.cleaned_data['accepted_submission'] + current_issue = initiate_publication_form.cleaned_data['to_be_issued_in'] + + # Determine next available paper number: + paper_nr = Publication.objects.filter(in_issue__in_volume=current_issue.in_volume).count() + paper_nr += 1 + if paper_nr > 999: + raise PaperNumberingError(paper_nr) + + # Build form data + doi_label = ( + current_issue.in_volume.in_journal.name + + '.' + str(current_issue.in_volume.number) + + '.' + str(current_issue.number) + '.' + paper_nr_string(paper_nr) + ) + doi_string = '10.21468/' + doi_label + BiBTeX_entry = ( + '@Article{' + doi_label + ',\n' + '\ttitle={{' + submission.title + '}},\n' + '\tauthor={' + submission.author_list.replace(',', ' and') + '},\n' + '\tjournal={' + + current_issue.in_volume.in_journal.get_abbreviation_citation() + + '},\n' + '\tvolume={' + str(current_issue.in_volume.number) + '},\n' + '\tissue={' + str(current_issue.number) + '},\n' + '\tpages={' + paper_nr_string(paper_nr) + '},\n' + '\tyear={' + current_issue.until_date.strftime('%Y') + '},\n' + '\tpublisher={SciPost},\n' + '\tdoi={' + doi_string + '},\n' + '\turl={https://scipost.org/' + doi_string + '},\n' + '}\n' + ) + initial = { + 'accepted_submission': submission, + 'in_issue': current_issue, + 'paper_nr': paper_nr, + 'discipline': submission.discipline, + 'domain': submission.domain, + 'subject_area': submission.subject_area, + 'secondary_areas': submission.secondary_areas, + 'title': submission.title, + 'author_list': submission.author_list, + 'abstract': submission.abstract, + 'BiBTeX_entry': BiBTeX_entry, + 'doi_label': doi_label, + 'acceptance_date': submission.acceptance_date, + 'submission_date': submission.submission_date, + 'publication_date': timezone.now(), + } + validate_publication_form = ValidatePublicationForm(initial=initial) + context = {'validate_publication_form': validate_publication_form} + return render(request, 'journals/validate_publication.html', context) + context = {'initiate_publication_form': initiate_publication_form} return render(request, 'journals/initiate_publication.html', context) @@ -237,6 +228,7 @@ def validate_publication(request): request.FILES or None) if validate_publication_form.is_valid(): publication = validate_publication_form.save() + # Fill in remaining data publication.pdf_file = request.FILES['pdf_file'] submission = publication.accepted_submission @@ -244,6 +236,7 @@ def validate_publication(request): publication.authors_claims.add(*submission.authors_claims.all()) publication.authors_false_claims.add(*submission.authors_false_claims.all()) publication.save() + # Move file to final location initial_path = publication.pdf_file.path new_dir = (settings.MEDIA_ROOT + publication.in_issue.path + '/' @@ -253,17 +246,23 @@ def validate_publication(request): os.rename(initial_path, new_path) publication.pdf_file.name = new_path publication.save() + # Mark the submission as having been published: - publication.accepted_submission.published_as = publication - publication.accepted_submission.status = 'published' - publication.accepted_submission.save() + submission.published_as = publication + submission.status = 'published' + submission.save() + # TODO: Create a Commentary Page # Email authors JournalUtils.load({'publication': publication}) JournalUtils.send_authors_paper_published_email() - ack_header = 'The publication has been validated.' - context['ack_header'] = ack_header - return render(request, 'scipost/acknowledgement.html', context) + + # Add SubmissionEvents + submission.add_general_event('The Submission has been published as %s.' + % publication.doi_label) + + messages.success(request, 'The publication has been validated.') + return redirect(publication.get_absolute_url()) else: context['errormessage'] = 'The form was invalid.' @@ -274,8 +273,10 @@ def validate_publication(request): @permission_required('scipost.can_publish_accepted_submission', return_403=True) def manage_metadata(request): publications = Publication.objects.order_by('-publication_date', '-paper_nr') + associate_grant_form = GrantSelectForm() context = { - 'publications': publications + 'publications': publications, + 'associate_grant_form': associate_grant_form, } return render(request, 'journals/manage_metadata.html', context) @@ -361,14 +362,10 @@ def add_new_unreg_author(request, publication_id): if request.method == 'POST': new_unreg_author_form = UnregisteredAuthorForm(request.POST) if new_unreg_author_form.is_valid(): - new_unreg_author = UnregisteredAuthor( - first_name=new_unreg_author_form.cleaned_data['first_name'], - last_name=new_unreg_author_form.cleaned_data['last_name'],) - new_unreg_author.save() + new_unreg_author = new_unreg_author_form.save() publication.authors_unregistered.add(new_unreg_author) return redirect(publication.get_absolute_url()) - errormessage = 'Method add_new_unreg_author can only be called with POST.' - return render(request, 'scipost/error.html', context={'errormessage': errormessage}) + raise Http404 @permission_required('scipost.can_publish_accepted_submission', return_403=True) @@ -404,27 +401,43 @@ def create_funding_info_metadata(request, doi_label): in the metadata field in a Publication instance. """ publication = get_object_or_404(Publication, doi_label=doi_label) - if request.method == 'POST': - funding_info_form = FundingInfoForm(request.POST) - if funding_info_form.is_valid(): - publication.metadata['funding_statement'] = funding_info_form.cleaned_data[ - 'funding_statement'] - publication.save() - initial = {'funding_statement': '', } - funding_statement = '' + funding_info_form = FundingInfoForm(request.POST or None) + if funding_info_form.is_valid(): + publication.metadata['funding_statement'] = funding_info_form.cleaned_data[ + 'funding_statement'] + publication.save() + try: - initial['funding_statement'] = publication.metadata['funding_statement'] + initial = {'funding_statement': publication.metadata['funding_statement']} funding_statement = initial['funding_statement'] except KeyError: - pass + initial = {'funding_statement': ''} + funding_statement = '' + context = {'publication': publication, 'funding_info_form': FundingInfoForm(initial=initial), - 'funding_statement': funding_statement, } + 'funding_statement': funding_statement} return render(request, 'journals/create_funding_info_metadata.html', context) +@permission_required('scipost.can_publish_accepted_submission', return_403=True) +@transaction.atomic +def add_associated_grant(request, doi_label): + """ + Called by an Editorial Administrator. + This associates a grant from the database to this publication. + """ + publication = get_object_or_404(Publication, doi_label=doi_label) + grant_select_form = GrantSelectForm(request.POST or None) + if grant_select_form.is_valid(): + publication.grants.add(grant_select_form.cleaned_data['grant']) + publication.save() + messages.success(request, 'Grant added to publication %s' % str(publication)) + return redirect(reverse('journals:manage_metadata')) + + @permission_required('scipost.can_publish_accepted_submission', return_403=True) @transaction.atomic def create_metadata_xml(request, doi_label): @@ -436,12 +449,11 @@ def create_metadata_xml(request, doi_label): """ publication = get_object_or_404(Publication, doi_label=doi_label) - if request.method == 'POST': - create_metadata_xml_form = CreateMetadataXMLForm(request.POST) - if create_metadata_xml_form.is_valid(): - publication.metadata_xml = create_metadata_xml_form.cleaned_data['metadata_xml'] - publication.save() - return redirect(reverse('journals:manage_metadata')) + create_metadata_xml_form = CreateMetadataXMLForm(request.POST or None, instance=publication) + if create_metadata_xml_form.is_valid(): + create_metadata_xml_form.save() + messages.success(request, 'Metadata XML saved') + return redirect(reverse('journals:manage_metadata')) # create a doi_batch_id salt = "" @@ -457,6 +469,7 @@ def create_metadata_xml(request, doi_label): '<?xml version="1.0" encoding="UTF-8"?>\n' '<doi_batch version="4.4.0" xmlns="http://www.crossref.org/schema/4.4.0" ' 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' + 'xmlns:fr="http://www.crossref.org/fundref.xsd" ' 'xsi:schemaLocation="http://www.crossref.org/schema/4.4.0 ' 'http://www.crossref.org/shema/deposit/crossref4.4.0.xsd">\n' '<head>\n' @@ -497,6 +510,7 @@ def create_metadata_xml(request, doi_label): '<titles><title>' + publication.title + '</title></titles>\n' '<contributors>\n' ) + # Precondition: all authors MUST be listed in authors field of publication instance, # this to be checked by EdAdmin before publishing. for author in publication.authors.all(): @@ -547,6 +561,31 @@ def create_metadata_xml(request, doi_label): '<crossmark_domain><domain>scipost.org</domain></crossmark_domain>\n' '</crossmark_domains>\n' '<crossmark_domain_exclusive>false</crossmark_domain_exclusive>\n' + '<custom_metadata>\n' + ) + funders = Funder.objects.filter(grant__in=publication.grants.all()).distinct() + nr_funders = funders.count() + if nr_funders > 0: + initial['metadata_xml'] += '<fr:program name="fundref">\n' + for funder in funders: + if nr_funders > 1: + initial['metadata_xml'] += '<fr:assertion name="fundgroup">\n' + initial['metadata_xml'] += ( + '<fr:assertion name="funder_name">' + funder.name + '\n' + '<fr:assertion name="funder_identifier">' + + funder.identifier + '</fr:assertion>\n' + '</fr:assertion>\n') + for grant in publication.grants.all(): + if grant.funder == funder: + initial['metadata_xml'] += ( + '<fr:assertion name="award_number">' + + grant.number + '</fr:assertion>\n') + if nr_funders > 1: + initial['metadata_xml'] += '</fr:assertion>\n' + initial['metadata_xml'] += '</fr:program>\n' + + initial['metadata_xml'] += ( + '</custom_metadata>\n' '</crossmark>\n' '<archive_locations><archive name="CLOCKSS"></archive></archive_locations>\n' '<doi_data>\n' @@ -576,12 +615,13 @@ def create_metadata_xml(request, doi_label): '</journal>\n' ) initial['metadata_xml'] += '</body>\n</doi_batch>' + publication.latest_metadata_update = timezone.now() publication.save() - - context = {'publication': publication, - 'create_metadata_xml_form': CreateMetadataXMLForm(initial=initial), - } + context = { + 'publication': publication, + 'create_metadata_xml_form': CreateMetadataXMLForm(initial=initial, instance=publication), + } return render(request, 'journals/create_metadata_xml.html', context) @@ -621,10 +661,7 @@ def metadata_xml_deposit(request, doi_label, option='test'): 'login_passwd': settings.CROSSREF_LOGIN_PASSWORD, } files = {'fname': ('metadata.xml', publication.metadata_xml, 'multipart/form-data')} - r = requests.post(url, - params=params, - files=files, - ) + r = requests.post(url, params=params, files=files) response_headers = r.headers response_text = r.text @@ -731,13 +768,6 @@ def metadata_DOAJ_deposit(request, doi_label): f.write(publication.metadata_DOAJ) f.close() - # response_headers = r.headers - # response_text = r.text - # context = { - # 'publication': publication, - # 'response_headers': response_headers, - # 'response_text': response_text, - # } messages.success(request, '<h3>%s</h3>Successfull deposit of metadata DOAJ.' % publication.doi_label) return redirect(reverse('journals:manage_metadata')) @@ -795,7 +825,7 @@ def harvest_citedby_links(request, doi_label): 'pwd': settings.CROSSREF_LOGIN_PASSWORD, 'qdata': query_xml, 'doi': publication.doi_string, } - r = requests.post(url, params=params,) + r = requests.post(url, params=params) if r.status_code == 401: messages.warning(request, ('<h3>Crossref credentials are invalid.</h3>' 'Please contact the SciPost Admin.')) diff --git a/partners/managers.py b/partners/managers.py index 2b3e4b390a9ce49512f1a0dc01783ea7c582a968..d542b138578ce59188e4a7e20b462d54d0dec067 100644 --- a/partners/managers.py +++ b/partners/managers.py @@ -1,5 +1,4 @@ from django.db import models -from django.db.models import F from django.utils import timezone from .constants import MEMBERSHIP_SUBMITTED, PROSPECTIVE_PARTNER_PROCESSED, REQUEST_INITIATED diff --git a/partners/migrations/0012_auto_20170625_1253.py b/partners/migrations/0012_auto_20170625_1253.py new file mode 100644 index 0000000000000000000000000000000000000000..30925ad9583c28dc83c6796e46480923b23d73f2 --- /dev/null +++ b/partners/migrations/0012_auto_20170625_1253.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-06-25 10:53 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('partners', '0011_auto_20170609_2234'), + ] + + operations = [ + migrations.AlterField( + model_name='prospectivepartnerevent', + name='event', + field=models.CharField(choices=[('requested', 'Requested (from online form)'), ('comment', 'Comment added'), ('email_sent', 'Email sent'), ('negotiating', 'Initiated negotiation'), ('marked_as_uninterested', 'Marked as uninterested'), ('promoted', 'Promoted to Partner')], max_length=64), + ), + ] diff --git a/partners/migrations/0027_merge_20170707_1857.py b/partners/migrations/0027_merge_20170707_1857.py new file mode 100644 index 0000000000000000000000000000000000000000..f7cc870609266767c610c3011cac24be5ecca2c0 --- /dev/null +++ b/partners/migrations/0027_merge_20170707_1857.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-07 16:57 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('partners', '0012_auto_20170625_1253'), + ('partners', '0026_auto_20170627_1809'), + ] + + operations = [ + ] diff --git a/partners/migrations/0028_merge_20170724_1840.py b/partners/migrations/0028_merge_20170724_1840.py new file mode 100644 index 0000000000000000000000000000000000000000..9f4c6d0143be160c234a0e5284516bc1145d53a3 --- /dev/null +++ b/partners/migrations/0028_merge_20170724_1840.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-24 16:40 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('partners', '0027_merge_20170707_1857'), + ('partners', '0027_membershipagreement_end_date'), + ] + + operations = [ + ] diff --git a/partners/migrations/0028_merge_20170724_1958.py b/partners/migrations/0028_merge_20170724_1958.py new file mode 100644 index 0000000000000000000000000000000000000000..5f39aef937f2a6b8532c416da7a1d1504a6cabc6 --- /dev/null +++ b/partners/migrations/0028_merge_20170724_1958.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-24 17:58 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('partners', '0027_membershipagreement_end_date'), + ('partners', '0027_merge_20170707_1857'), + ] + + operations = [ + ] diff --git a/partners/migrations/0029_merge_20170726_0945.py b/partners/migrations/0029_merge_20170726_0945.py new file mode 100644 index 0000000000000000000000000000000000000000..04f23ee6b5245d487ce7c1e100e37fdec75ef684 --- /dev/null +++ b/partners/migrations/0029_merge_20170726_0945.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-26 07:45 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('partners', '0028_merge_20170724_1958'), + ('partners', '0028_merge_20170724_1840'), + ] + + operations = [ + ] diff --git a/scipost/behaviors.py b/scipost/behaviors.py index 1140c889dd21a75820f85e10182286069b4a13f3..7904262e934152b73b11ce7374c979ee55b02c1f 100644 --- a/scipost/behaviors.py +++ b/scipost/behaviors.py @@ -4,16 +4,6 @@ from django.utils import timezone from .db.fields import AutoDateTimeField -class ArxivCallable(object): - '''Models that contain a Arxiv identification should contain these - methods to be compatible with the ArxivCaller(). - ''' - @classmethod - def same_version_exists(self, identifier): - '''Check if the given identifier already is present in the database.''' - raise NotImplementedError - - class TimeStampedModel(models.Model): """ All objects should inherit from this abstract model. diff --git a/scipost/constants.py b/scipost/constants.py index 7a37244c270c6ec72a85cc3312c5695332e9cc8f..30565b7314c13104136891ffca8981cfd41d6be7 100644 --- a/scipost/constants.py +++ b/scipost/constants.py @@ -1,5 +1,3 @@ - - DISCIPLINE_PHYSICS = 'physics' DISCIPLINE_ASTROPHYSICS = 'astrophysics' DISCIPLINE_MATH = 'mathematics' diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py index d3d038c9c4facfd50eb84c6d950e4be179c5613a..f0c48b82fcd072ae442c9d478b61f52408025418 100644 --- a/scipost/management/commands/add_groups_and_permissions.py +++ b/scipost/management/commands/add_groups_and_permissions.py @@ -174,6 +174,12 @@ class Command(BaseCommand): name='Can oversee refereeing', content_type=content_type) + # Reports + can_manage_reports, created = Permission.objects.get_or_create( + codename='can_manage_reports', + name='Can manage Reports', + content_type=content_type) + # Voting can_prepare_recommendations_for_voting, created = Permission.objects.get_or_create( codename='can_prepare_recommendations_for_voting', @@ -252,6 +258,7 @@ class Command(BaseCommand): can_view_timesheets, can_publish_accepted_submission, can_attend_VGMs, + can_manage_reports, ]) EditorialCollege.permissions.set([ diff --git a/scipost/services.py b/scipost/services.py index 7a6707e8f7b84fab44f80726a0046724ed38bd23..9e61ccd731882f140a3095e7d0b7dfb786250892 100644 --- a/scipost/services.py +++ b/scipost/services.py @@ -1,15 +1,9 @@ # Module for making external api calls as needed in the submissions cycle import feedparser import requests -import re import datetime import dateutil.parser -from django.template import Template, Context -from .behaviors import ArxivCallable - -from strings import arxiv_caller_errormessages - class DOICaller: def __init__(self, doi_string): diff --git a/scipost/static/scipost/assets/css/_code.scss b/scipost/static/scipost/assets/css/_code.scss index 6ef7af126c765ddc43e46b916902bc3ad54eea65..4a8d281e75da807866bdf5bc6a927612fb854f70 100644 --- a/scipost/static/scipost/assets/css/_code.scss +++ b/scipost/static/scipost/assets/css/_code.scss @@ -9,3 +9,7 @@ pre { margin: 0; } } + +.clickfocus { + cursor: pointer; +} diff --git a/scipost/static/scipost/assets/css/_list_group.scss b/scipost/static/scipost/assets/css/_list_group.scss index 773da713e8db185741b7b2c6288588e6f557b5a8..53b101c3fca8f20a15148c8065f733cd8ea12217 100644 --- a/scipost/static/scipost/assets/css/_list_group.scss +++ b/scipost/static/scipost/assets/css/_list_group.scss @@ -2,3 +2,47 @@ .list-group-noborder .list-group-item { border: 0; } + +ul.events-list { + padding-left: 30px; + + &:before { + content: ''; + width: 3px; + height: calc(100% - 16px); + position: absolute; + left: 20px; + background: $scipost-darkblue; + top: 16px; + z-index: 97; + } + + li { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + + &:before { + content: ''; + width: 13px; + height: 13px; + left: -25px; + border-radius: 99px; + position: absolute; + top: 11px; + background: #fff; + border: 3px solid $scipost-darkblue; + z-index: 99; + } + + &:last-child:after { + content: ''; + width: 3px; + height: calc(100% - 16px); + position: absolute; + left: -20px; + background: #fff; + bottom: 0; + z-index: 98; + } + } +} diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html index 0e9f37b82cf2420c147d8329978e11a0877838e9..66207b001f604fe14d31e5234325c6fcb33daaf6 100644 --- a/scipost/templates/scipost/personal_page.html +++ b/scipost/templates/scipost/personal_page.html @@ -267,8 +267,12 @@ <h3>Editorial Admin actions</h3> <ul> {% if perms.scipost.can_publish_accepted_submission %} - <li><a href="{% url 'journals:manage_metadata' %}">Manage metadata</a></li> - <li><a href="{% url 'journals:harvest_citedby_list' %}">Harvest citedby data</a></li> + <li><a href="{% url 'journals:manage_metadata' %}">Manage metadata</a></li> + <li><a href="{% url 'journals:harvest_citedby_list' %}">Harvest citedby data</a></li> + {% endif %} + {% if perms.scipost.can_manage_reports %} + <li><a href="{% url 'submissions:reports_accepted_list' %}">Accepted Reports</a>{% if nr_reports_without_pdf %} ({{nr_reports_without_pdf}} unfinished){% endif %}</li> + <li><a href="{% url 'submissions:treated_submissions_list' %}">Fully treated Submissions</a>{% if nr_treated_submissions_without_pdf %} ({{nr_treated_submissions_without_pdf}} unfinished){% endif %}</li> {% endif %} </ul> {% endif %} @@ -392,17 +396,50 @@ </div> </div> - {% if unfinished_reports %} + {% if contributor.reports.in_draft.exists %} <div class="row"> <div class="col-12"> <h3>Unfinished reports:</h3> </div> <div class="col-12"> <ul class="list-group list-group-flush"> - {% for report in unfinished_reports %} + {% for report in contributor.reports.in_draft.all %} <li class="list-group-item"> <div class="w-100">{% include 'submissions/_submission_card_content.html' with submission=report.submission %}</div> - <div class="px-2"><a class="px-1" href="{% url 'submissions:submit_report' report.submission.arxiv_identifier_w_vn_nr %}">Finish report</a></div> + <div class="px-2 mb-2"><a class="px-1" href="{% url 'submissions:submit_report' report.submission.arxiv_identifier_w_vn_nr %}">Finish report</a></div> + </li> + {% endfor %} + </ul> + </div> + </div> + {% endif %} + + {% if contributor.reports.non_draft.exists %} + <div class="row"> + <div class="col-12"> + <h3>Finished reports:</h3> + </div> + <div class="col-12"> + <ul class="list-group list-group-flush"> + {% for report in contributor.reports.non_draft.all %} + <li class="list-group-item"> + {% comment %} + Temporary: There is already a template for a "Report summary" in a parallel (unmerged) branch. Awaiting merge to use that template. + {% endcomment %} + <div class="card-block {% block cardblock_class_block %}{% endblock %}"> + <h3>Report on Submission <a href="{{report.submission.get_absolute_url}}">{{report.submission.title}}</a></h3> + <table> + <tr> + <th style='min-width: 100px;'>Received:</th><td>{{ report.date_submitted|date:'Y-n-j' }}<td> + </tr> + <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> + <tr> + <th>Anonymous:</th><td>{{report.anonymous|yesno:'Yes,No'}}</td> + </tr> + </table> + </div> </li> {% endfor %} </ul> diff --git a/scipost/templatetags/texfilters.py b/scipost/templatetags/texfilters.py new file mode 100644 index 0000000000000000000000000000000000000000..f8e3d05e6f587e54ef526741ccd29c4e0f7ebce7 --- /dev/null +++ b/scipost/templatetags/texfilters.py @@ -0,0 +1,26 @@ +from django import template +from django.utils.html import escape +from django.utils.text import normalize_newlines +from django.utils.safestring import SafeData, mark_safe + +register = template.Library() + + +@register.filter(is_safe=False, needs_autoescape=True) +def linebreaktex(value, autoescape=True): + """ + Convert all newlines in a piece of plain text to HTML line breaks + """ + autoescape = autoescape and not isinstance(value, SafeData) + value = normalize_newlines(value) + if autoescape: + value = escape(value) + return mark_safe(value.replace('\n', '\\ \n')) + + +@register.filter(is_safe=False, needs_autoescape=True) +def safe_tex_url(value, autoescape=True): + """ + Convert all newlines in a piece of plain text to HTML line breaks + """ + return mark_safe(value.replace('#', '\#')) diff --git a/scipost/views.py b/scipost/views.py index 146b08f53e11cdf3b330c898eb4a6c7ed62a2094..cbaf80452b461c83839133e8c7707353f93fb18f 100644 --- a/scipost/views.py +++ b/scipost/views.py @@ -98,7 +98,7 @@ def documentsSearchResults(query): .filter(publication_query).order_by('-publication_date')) commentary_search_queryset = (Commentary.objects.vetted() .filter(commentary_query).order_by('-pub_date')) - submission_search_queryset = (Submission.objects.public() + submission_search_queryset = (Submission.objects.public_unlisted() .filter(submission_query).order_by('-submission_date')) thesislink_search_list = (ThesisLink.objects.vetted() .filter(thesislink_query).order_by('-defense_date')) @@ -175,9 +175,8 @@ def index(request): '''Main page.''' context = { 'latest_newsitems': NewsItem.objects.all().order_by('-date')[:1], - 'submissions': Submission.objects.public().order_by('-submission_date')[:3], - 'publications': Publication.objects.published().order_by('-publication_date', - '-paper_nr')[:3] + 'submissions': Submission.objects.public_unlisted().order_by('-submission_date')[:3], + 'publications': Publication.objects.published().order_by('-publication_date')[:3] } return render(request, 'scipost/index.html', context) @@ -796,8 +795,6 @@ def personal_page(request): nr_submissions_to_assign = 0 nr_recommendations_to_prepare_for_voting = 0 if contributor.is_SP_Admin(): - intwodays = now + timezone.timedelta(days=2) - # count the number of pending registration requests nr_reg_to_vet = Contributor.objects.filter(user__is_active=True, status=0).count() nr_reg_awaiting_validation = (Contributor.objects.awaiting_validation() @@ -806,6 +803,7 @@ def personal_page(request): nr_submissions_to_assign = Submission.objects.filter(status__in=['unassigned']).count() nr_recommendations_to_prepare_for_voting = EICRecommendation.objects.filter( submission__status__in=['voting_in_preparation']).count() + nr_assignments_to_consider = 0 active_assignments = None nr_reports_to_vet = 0 @@ -833,9 +831,8 @@ def personal_page(request): referee=contributor, accepted=None, cancelled=False).count() pending_ref_tasks = RefereeInvitation.objects.filter( referee=contributor, accepted=True, fulfilled=False) - unfinished_reports = Report.objects.in_draft().filter(author=contributor) refereeing_tab_total_count = nr_ref_inv_to_consider + len(pending_ref_tasks) - refereeing_tab_total_count += len(unfinished_reports) + refereeing_tab_total_count += Report.objects.in_draft().filter(author=contributor).count() # Verify if there exist objects authored by this contributor, # whose authorship hasn't been claimed yet @@ -895,13 +892,20 @@ def personal_page(request): 'nr_ref_inv_to_consider': nr_ref_inv_to_consider, 'pending_ref_tasks': pending_ref_tasks, 'refereeing_tab_total_count': refereeing_tab_total_count, - 'unfinished_reports': unfinished_reports, 'own_submissions': own_submissions, 'own_commentaries': own_commentaries, 'own_thesislinks': own_thesislinks, - 'own_comments': own_comments, 'own_authorreplies': own_authorreplies, + 'own_comments': own_comments, + 'own_authorreplies': own_authorreplies, } + # Only add variables if user has right permission + if request.user.has_perm('scipost.can_manage_reports'): + context['nr_reports_without_pdf'] = (Report.objects.accepted() + .filter(pdf_report='').count()) + context['nr_treated_submissions_without_pdf'] = (Submission.objects.treated() + .filter(pdf_refereeing_pack='').count()) + return render(request, 'scipost/personal_page.html', context) @@ -1112,7 +1116,7 @@ def contributor_info(request, contributor_id): """ contributor = get_object_or_404(Contributor, pk=contributor_id) contributor_publications = Publication.objects.published().filter(authors=contributor) - contributor_submissions = Submission.objects.public().filter(authors=contributor) + contributor_submissions = Submission.objects.public_unlisted().filter(authors=contributor) contributor_commentaries = Commentary.objects.filter(authors=contributor) contributor_theses = ThesisLink.objects.vetted().filter(author_as_cont=contributor) contributor_comments = (Comment.objects.vetted() diff --git a/submissions/admin.py b/submissions/admin.py index 0e394cedfb8da410dfbcda6c3c72047380a6b39e..1095426dbbb30269de89a0db9bb3979aea60546a 100644 --- a/submissions/admin.py +++ b/submissions/admin.py @@ -4,7 +4,7 @@ from django import forms from guardian.admin import GuardedModelAdmin from submissions.models import Submission, EditorialAssignment, RefereeInvitation, Report,\ - EditorialCommunication, EICRecommendation + EditorialCommunication, EICRecommendation, SubmissionEvent from scipost.models import Contributor @@ -100,6 +100,7 @@ class ReportAdmin(admin.ModelAdmin): list_display_links = ('author',) date_hierarchy = 'date_submitted' list_filter = ('status',) + readonly_fields = ('report_nr',) form = ReportAdminForm @@ -148,3 +149,5 @@ class EICRecommendationAdmin(admin.ModelAdmin): admin.site.register(EICRecommendation, EICRecommendationAdmin) + +admin.site.register(SubmissionEvent) diff --git a/submissions/constants.py b/submissions/constants.py index 2b247ffa6a78a9c83e72934b3871e42c21ec3435..aac77500f373188103b1435b6b7d50599df0bc72 100644 --- a/submissions/constants.py +++ b/submissions/constants.py @@ -45,7 +45,7 @@ SUBMISSION_HTTP404_ON_EDITORIAL_PAGE = [ ] SUBMISSION_STATUS_OUT_OF_POOL = SUBMISSION_HTTP404_ON_EDITORIAL_PAGE + [ - 'resubmitted' + STATUS_RESUBMITTED ] SUBMISSION_EXCLUDE_FROM_REPORTING = SUBMISSION_HTTP404_ON_EDITORIAL_PAGE + [ @@ -69,24 +69,22 @@ SUBMISSION_STATUS_PUBLICLY_INVISIBLE = [ STATUS_UNASSIGNED, STATUS_RESUBMISSION_INCOMING, 'assignment_failed', - 'resubmitted_rejected', STATUS_RESUBMITTED_REJECTED, - 'rejected', + STATUS_REJECTED, 'withdrawn', ] # Submissions which should not appear in search lists SUBMISSION_STATUS_PUBLICLY_UNLISTED = SUBMISSION_STATUS_PUBLICLY_INVISIBLE + [ - 'resubmitted', - 'resubmitted_rejected_visible', + STATUS_RESUBMITTED, STATUS_RESUBMITTED_REJECTED_VISIBLE, - 'published' + STATUS_PUBLISHED ] # Submissions for which voting on a related recommendation is deprecated: SUBMISSION_STATUS_VOTING_DEPRECATED = [ - 'rejected', - 'published', + STATUS_REJECTED, + STATUS_PUBLISHED, 'withdrawn', ] @@ -124,6 +122,7 @@ ASSIGNMENT_REFUSAL_REASONS = ( ) REFEREE_QUALIFICATION = ( + (None, '-'), (4, 'expert in this subject'), (3, 'very knowledgeable in this subject'), (2, 'knowledgeable in this subject'), @@ -132,6 +131,7 @@ REFEREE_QUALIFICATION = ( ) QUALITY_SPEC = ( + (None, '-'), (6, 'perfect'), (5, 'excellent'), (4, 'good'), @@ -143,7 +143,7 @@ QUALITY_SPEC = ( # Only values between 0 and 100 are kept, anything outside those limits is discarded. RANKING_CHOICES = ( - (101, '-'), + (None, '-'), (100, 'top'), (80, 'high'), (60, 'good'), @@ -153,6 +153,7 @@ RANKING_CHOICES = ( ) REPORT_REC = ( + (None, '-'), (1, 'Publish as Tier I (top 10% of papers in this journal, qualifies as Select) NOTE: SELECT NOT YET OPEN, STARTS EARLY 2017'), (2, 'Publish as Tier II (top 50% of papers in this journal)'), (3, 'Publish as Tier III (meets the criteria of this journal)'), @@ -205,3 +206,12 @@ SUBMISSION_CYCLES = ( (CYCLE_SHORT, 'Short cycle'), (CYCLE_DIRECT_REC, 'Direct editorial recommendation'), ) + +EVENT_GENERAL = 'gen' +EVENT_FOR_EIC = 'eic' +EVENT_FOR_AUTHOR = 'auth' +EVENT_TYPES = ( + (EVENT_GENERAL, 'General comment'), + (EVENT_FOR_EIC, 'Comment for Editor-in-charge'), + (EVENT_FOR_AUTHOR, 'Comment for author'), +) diff --git a/submissions/exceptions.py b/submissions/exceptions.py index 0e8794a8cc841af0df2896138ce2ec0209ee0c7e..a6e26c2d6c946fec39d3b402415bd0110e63378a 100644 --- a/submissions/exceptions.py +++ b/submissions/exceptions.py @@ -1,4 +1,4 @@ -class CycleUpdateDeadlineError(Exception): +class BaseCustomException(Exception): def __init__(self, name): self.name = name @@ -6,9 +6,9 @@ class CycleUpdateDeadlineError(Exception): return self.name -class InvalidReportVettingValue(Exception): - def __init__(self, name): - self.name = name +class CycleUpdateDeadlineError(BaseCustomException): + pass - def __str__(self): - return self.name + +class InvalidReportVettingValue(BaseCustomException): + pass diff --git a/submissions/forms.py b/submissions/forms.py index 7f2d39772f1086f15e09c39ef36facb7bda4cb61..e6b3cd433e0f6adc4f31f247a07fd4751ce104fa 100644 --- a/submissions/forms.py +++ b/submissions/forms.py @@ -32,7 +32,7 @@ class SubmissionSearchForm(forms.Form): def search_results(self): """Return all Submission objects according to search""" - return Submission.objects.public_overcomplete().filter( + return Submission.objects.public_newest().filter( title__icontains=self.cleaned_data.get('title', ''), author_list__icontains=self.cleaned_data.get('author', ''), abstract__icontains=self.cleaned_data.get('abstract', ''), @@ -332,6 +332,12 @@ class RequestSubmissionForm(SubmissionChecks, forms.ModelForm): return submission +class SubmissionReportsForm(forms.ModelForm): + class Meta: + model = Submission + fields = ['pdf_refereeing_pack'] + + ###################### # Editorial workflow # ###################### @@ -412,6 +418,12 @@ class VotingEligibilityForm(forms.Form): # Reports: ############ +class ReportPDFForm(forms.ModelForm): + class Meta: + model = Report + fields = ['pdf_report'] + + class ReportForm(forms.ModelForm): class Meta: model = Report @@ -420,6 +432,17 @@ class ReportForm(forms.ModelForm): 'recommendation', 'remarks_for_editors', 'anonymous'] def __init__(self, *args, **kwargs): + if kwargs.get('instance'): + if kwargs['instance'].is_followup_report: + # Prefill data from latest report in the series + latest_report = kwargs['instance'].latest_report_from_series() + kwargs.update({ + 'initial': { + 'qualification': latest_report.qualification, + 'anonymous': latest_report.anonymous + } + }) + super(ReportForm, self).__init__(*args, **kwargs) self.fields['strengths'].widget.attrs.update({ 'placeholder': ('Give a point-by-point ' @@ -440,7 +463,28 @@ class ReportForm(forms.ModelForm): 'cols': 100 }) - def save(self, submission, current_contributor): + # If the Report is not a followup: Explicitly assign more fields as being required! + if not self.instance.is_followup_report: + required_fields = [ + 'strengths', + 'weaknesses', + 'requested_changes', + 'validity', + 'significance', + 'originality', + 'clarity', + 'formatting', + 'grammar' + ] + for field in required_fields: + self.fields[field].required = True + + # Let user know the field is required! + for field in self.fields: + if self.fields[field].required: + self.fields[field].label += ' *' + + def save(self, submission): """ Update meta data if ModelForm is submitted (non-draft). Possibly overwrite the default status if user asks for saving as draft. @@ -448,7 +492,6 @@ class ReportForm(forms.ModelForm): report = super().save(commit=False) report.submission = submission - report.author = current_contributor report.date_submitted = timezone.now() # Save with right status asked by user @@ -458,7 +501,7 @@ class ReportForm(forms.ModelForm): report.status = STATUS_UNVETTED # Update invitation and report meta data if exist - invitation = submission.referee_invitations.filter(referee=current_contributor).first() + invitation = submission.referee_invitations.filter(referee=report.author).first() if invitation: invitation.fulfilled = True invitation.save() @@ -466,7 +509,7 @@ class ReportForm(forms.ModelForm): # Check if report author if the report is being flagged on the submission if submission.referees_flagged: - if current_contributor.user.last_name in submission.referees_flagged: + if report.author.user.last_name in submission.referees_flagged: report.flagged = True report.save() return report @@ -597,6 +640,6 @@ class SubmissionCycleChoiceForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['refereeing_cycle'].default = None - other_submission = self.instance.other_versions().first() + other_submission = self.instance.other_versions.first() if other_submission: self.fields['referees_reinvite'].queryset = other_submission.referee_invitations.all() diff --git a/submissions/managers.py b/submissions/managers.py index 7e8a148b350b085842210c73263aba07e4e7832c..cba54e4f69b167a34a4a550b2b3a34339991fc49 100644 --- a/submissions/managers.py +++ b/submissions/managers.py @@ -4,8 +4,10 @@ from django.db.models import Q from .constants import SUBMISSION_STATUS_OUT_OF_POOL, SUBMISSION_STATUS_PUBLICLY_UNLISTED,\ SUBMISSION_STATUS_PUBLICLY_INVISIBLE, STATUS_UNVETTED, STATUS_VETTED,\ STATUS_UNCLEAR, STATUS_INCORRECT, STATUS_NOT_USEFUL, STATUS_NOT_ACADEMIC,\ - SUBMISSION_HTTP404_ON_EDITORIAL_PAGE, STATUS_DRAFT,\ - SUBMISSION_EXCLUDE_FROM_REPORTING + SUBMISSION_HTTP404_ON_EDITORIAL_PAGE, STATUS_DRAFT, STATUS_PUBLISHED,\ + SUBMISSION_EXCLUDE_FROM_REPORTING, STATUS_REJECTED_VISIBLE,\ + STATUS_ACCEPTED, STATUS_RESUBMITTED, STATUS_RESUBMITTED_REJECTED_VISIBLE,\ + EVENT_FOR_EIC, EVENT_GENERAL, EVENT_FOR_AUTHOR class SubmissionManager(models.Manager): @@ -57,6 +59,13 @@ class SubmissionManager(models.Manager): .order_by('-submission_date')) def public(self): + """ + This query contains set of public submissions, i.e. also containing + submissions with status "published" or "resubmitted". + """ + return self.exclude(status__in=SUBMISSION_STATUS_PUBLICLY_INVISIBLE) + + def public_unlisted(self): """ List only all public submissions. Should be used as a default filter! @@ -66,13 +75,12 @@ class SubmissionManager(models.Manager): """ return self.exclude(status__in=SUBMISSION_STATUS_PUBLICLY_UNLISTED) - def public_overcomplete(self): + def public_newest(self): """ - This query contains an overcomplete set of public submissions, i.e. also containing - submissions with status "published" or "resubmitted". + This query contains set of public() submissions, filtered to only the newest available + version. """ - queryset = self.exclude(status__in=SUBMISSION_STATUS_PUBLICLY_INVISIBLE) - return self._newest_version_only(queryset) + return self._newest_version_only(self.public()) def open_for_reporting(self): """ @@ -81,6 +89,30 @@ class SubmissionManager(models.Manager): """ return self.exclude(status__in=SUBMISSION_EXCLUDE_FROM_REPORTING) + def treated(self): + """ + This query returns all Submissions that are expected to be 'done'. + """ + return self.filter(status__in=[STATUS_ACCEPTED, STATUS_REJECTED_VISIBLE, STATUS_PUBLISHED, + STATUS_RESUBMITTED, STATUS_RESUBMITTED_REJECTED_VISIBLE]) + + def accepted(self): + return self.filter(status=STATUS_ACCEPTED) + + +class SubmissionEventQuerySet(models.QuerySet): + def for_author(self): + """ + Return all events that are meant to be for the author(s) of a submission. + """ + return self.filter(event__in=[EVENT_FOR_AUTHOR, EVENT_GENERAL]) + + def for_eic(self): + """ + Return all events that are meant to be for the Editor-in-charge of a submission. + """ + return self.filter(event__in=[EVENT_FOR_EIC, EVENT_GENERAL]) + class EditorialAssignmentManager(models.Manager): def get_for_user_in_pool(self, user): @@ -129,3 +161,6 @@ class ReportManager(models.Manager): def in_draft(self): return self.filter(status=STATUS_DRAFT) + + def non_draft(self): + return self.exclude(status=STATUS_DRAFT) diff --git a/submissions/migrations/0046_auto_20170625_1311.py b/submissions/migrations/0046_auto_20170625_1311.py new file mode 100644 index 0000000000000000000000000000000000000000..d33f029e92a097989c4398e3319df4cb57609b32 --- /dev/null +++ b/submissions/migrations/0046_auto_20170625_1311.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-06-25 11:11 +from __future__ import unicode_literals + +from django.db import migrations, models + + +def set_report_counters(apps, schema_editor): + Report = apps.get_model('submissions', 'Report') + for report in Report.objects.order_by('date_submitted'): + if not report.report_nr: + report.report_nr = report.submission.reports.filter(report_nr__gte=1).count() + 1 + report.save() + print('Updated all Report counters.') + + +def do_nothing(*args): + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0045_auto_20170608_1710'), + ] + + operations = [ + migrations.AddField( + model_name='report', + name='report_nr', + field=models.PositiveSmallIntegerField(default=0), + ), + migrations.RunPython(set_report_counters, do_nothing) + ] diff --git a/submissions/migrations/0047_auto_20170625_1331.py b/submissions/migrations/0047_auto_20170625_1331.py new file mode 100644 index 0000000000000000000000000000000000000000..151166d05dd98ce29eef6b9b44b137516fe1b4bb --- /dev/null +++ b/submissions/migrations/0047_auto_20170625_1331.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-06-25 11:31 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0046_auto_20170625_1311'), + ] + + operations = [ + migrations.AddField( + model_name='report', + name='pdf_report', + field=models.FileField(blank=True, max_length=200, upload_to='UPLOADS/REPORTS/%Y/%m/'), + ), + migrations.AlterField( + model_name='report', + name='report_nr', + field=models.PositiveSmallIntegerField(default=0, help_text='This number is a unique number refeering to the Report nr. of the Submission'), + ), + migrations.AlterUniqueTogether( + name='report', + unique_together=set([('submission', 'report_nr')]), + ), + ] diff --git a/submissions/migrations/0048_auto_20170721_0931.py b/submissions/migrations/0048_auto_20170721_0931.py new file mode 100644 index 0000000000000000000000000000000000000000..3f0a084e264d15c611c97fe05ec58596f3769f18 --- /dev/null +++ b/submissions/migrations/0048_auto_20170721_0931.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-21 07:31 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0047_submission_acceptance_date'), + ] + + operations = [ + migrations.AlterField( + model_name='report', + name='requested_changes', + field=models.TextField(blank=True, verbose_name='requested changes'), + ), + migrations.AlterField( + model_name='report', + name='strengths', + field=models.TextField(blank=True), + ), + migrations.AlterField( + model_name='report', + name='weaknesses', + field=models.TextField(blank=True), + ), + ] diff --git a/submissions/migrations/0048_auto_20170721_0936.py b/submissions/migrations/0048_auto_20170721_0936.py new file mode 100644 index 0000000000000000000000000000000000000000..657a049399d2aadca650ebbc94e78fbd0b70712d --- /dev/null +++ b/submissions/migrations/0048_auto_20170721_0936.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-21 07:36 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0047_submission_acceptance_date'), + ] + + operations = [ + migrations.AlterField( + model_name='report', + name='requested_changes', + field=models.TextField(blank=True, verbose_name='requested changes'), + ), + migrations.AlterField( + model_name='report', + name='strengths', + field=models.TextField(blank=True), + ), + migrations.AlterField( + model_name='report', + name='weaknesses', + field=models.TextField(blank=True), + ), + ] diff --git a/submissions/migrations/0048_merge_20170707_1857.py b/submissions/migrations/0048_merge_20170707_1857.py new file mode 100644 index 0000000000000000000000000000000000000000..e0b38b3d8fe4c76efb193145855351d562885c9b --- /dev/null +++ b/submissions/migrations/0048_merge_20170707_1857.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-07 16:57 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0047_auto_20170625_1331'), + ('submissions', '0047_submission_acceptance_date'), + ] + + operations = [ + ] diff --git a/submissions/migrations/0049_auto_20170721_1010.py b/submissions/migrations/0049_auto_20170721_1010.py new file mode 100644 index 0000000000000000000000000000000000000000..4627f2592d5f4a8d77755911038deb9dc7fe64a1 --- /dev/null +++ b/submissions/migrations/0049_auto_20170721_1010.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-21 08:10 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0048_auto_20170721_0936'), + ] + + operations = [ + migrations.AlterField( + model_name='report', + name='formatting', + field=models.SmallIntegerField(blank=True, choices=[(6, 'perfect'), (5, 'excellent'), (4, 'good'), (3, 'reasonable'), (2, 'acceptable'), (1, 'below threshold'), (0, 'mediocre')], null=True, verbose_name='Quality of paper formatting'), + ), + migrations.AlterField( + model_name='report', + name='grammar', + field=models.SmallIntegerField(blank=True, choices=[(6, 'perfect'), (5, 'excellent'), (4, 'good'), (3, 'reasonable'), (2, 'acceptable'), (1, 'below threshold'), (0, 'mediocre')], null=True, verbose_name='Quality of English grammar'), + ), + migrations.AlterField( + model_name='report', + name='qualification', + field=models.PositiveSmallIntegerField(choices=[(4, 'expert in this subject'), (3, 'very knowledgeable in this subject'), (2, 'knowledgeable in this subject'), (1, 'generally qualified'), (0, 'not qualified')], verbose_name='Qualification to referee this: I am'), + ), + migrations.AlterField( + model_name='report', + name='remarks_for_editors', + field=models.TextField(blank=True, verbose_name='optional remarks for the Editors only'), + ), + ] diff --git a/submissions/migrations/0049_submission_pdf_refereeing_pack.py b/submissions/migrations/0049_submission_pdf_refereeing_pack.py new file mode 100644 index 0000000000000000000000000000000000000000..32166d197857ea1466eae54f2008776c5d3499f3 --- /dev/null +++ b/submissions/migrations/0049_submission_pdf_refereeing_pack.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-07 16:59 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0048_merge_20170707_1857'), + ] + + operations = [ + migrations.AddField( + model_name='submission', + name='pdf_refereeing_pack', + field=models.FileField(blank=True, max_length=200, upload_to='UPLOADS/REFEREE/%Y/%m/'), + ), + ] diff --git a/submissions/migrations/0050_auto_20170707_2126.py b/submissions/migrations/0050_auto_20170707_2126.py new file mode 100644 index 0000000000000000000000000000000000000000..6a454b3e19e435c2e0ac9c57536c3faccc598db8 --- /dev/null +++ b/submissions/migrations/0050_auto_20170707_2126.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-07 19:26 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0049_submission_pdf_refereeing_pack'), + ] + + operations = [ + migrations.AlterModelOptions( + name='report', + options={'ordering': ['report_nr']}, + ), + migrations.AddField( + model_name='report', + name='doi_label', + field=models.CharField(blank=True, max_length=200), + ), + ] diff --git a/submissions/migrations/0050_auto_20170721_1042.py b/submissions/migrations/0050_auto_20170721_1042.py new file mode 100644 index 0000000000000000000000000000000000000000..f81add96676950e1daed51e7c308e02dc48f82d2 --- /dev/null +++ b/submissions/migrations/0050_auto_20170721_1042.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-21 08:42 +from __future__ import unicode_literals + +from django.db import migrations, models + + +def report_101_to_none(apps, schema_editor): + Report = apps.get_model('submissions', 'Report') + for rep in Report.objects.all(): + if rep.clarity == 101: + rep.clarity = None + if rep.originality == 101: + rep.originality = None + if rep.significance == 101: + rep.significance = None + if rep.validity == 101: + rep.validity = None + rep.save() + print('\nChanged all Report fields: {clarites,originality,significance,validity} with value' + ' `101` to `None`.') + + +def report_none_to_101(apps, schema_editor): + Report = apps.get_model('submissions', 'Report') + for rep in Report.objects.all(): + if not rep.clarity: + rep.clarity = 101 + if not rep.originality: + rep.originality = 101 + if not rep.significance: + rep.significance = 101 + if not rep.validity: + rep.validity = 101 + rep.save() + print('\nChanged all Report fields: {clarites,originality,significance,validity} with value' + ' `None` to `101`.') + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0049_auto_20170721_1010'), + ] + + operations = [ + migrations.AlterField( + model_name='report', + name='clarity', + field=models.PositiveSmallIntegerField(blank=True, choices=[(None, '-'), (100, 'top'), (80, 'high'), (60, 'good'), (40, 'ok'), (20, 'low'), (0, 'poor')], null=True), + ), + migrations.AlterField( + model_name='report', + name='originality', + field=models.PositiveSmallIntegerField(blank=True, choices=[(None, '-'), (100, 'top'), (80, 'high'), (60, 'good'), (40, 'ok'), (20, 'low'), (0, 'poor')], null=True), + ), + migrations.AlterField( + model_name='report', + name='significance', + field=models.PositiveSmallIntegerField(blank=True, choices=[(None, '-'), (100, 'top'), (80, 'high'), (60, 'good'), (40, 'ok'), (20, 'low'), (0, 'poor')], null=True), + ), + migrations.AlterField( + model_name='report', + name='validity', + field=models.PositiveSmallIntegerField(blank=True, choices=[(None, '-'), (100, 'top'), (80, 'high'), (60, 'good'), (40, 'ok'), (20, 'low'), (0, 'poor')], null=True), + ), + migrations.RunPython(report_101_to_none, report_none_to_101), + ] diff --git a/submissions/migrations/0051_auto_20170721_1049.py b/submissions/migrations/0051_auto_20170721_1049.py new file mode 100644 index 0000000000000000000000000000000000000000..5ccf06a410f6f585b3f2ce23faecf963d0a88af6 --- /dev/null +++ b/submissions/migrations/0051_auto_20170721_1049.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-21 08:49 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0050_auto_20170721_1042'), + ] + + operations = [ + migrations.AlterField( + model_name='report', + name='formatting', + field=models.SmallIntegerField(blank=True, choices=[(None, '-'), (6, 'perfect'), (5, 'excellent'), (4, 'good'), (3, 'reasonable'), (2, 'acceptable'), (1, 'below threshold'), (0, 'mediocre')], null=True, verbose_name='Quality of paper formatting'), + ), + migrations.AlterField( + model_name='report', + name='grammar', + field=models.SmallIntegerField(blank=True, choices=[(None, '-'), (6, 'perfect'), (5, 'excellent'), (4, 'good'), (3, 'reasonable'), (2, 'acceptable'), (1, 'below threshold'), (0, 'mediocre')], null=True, verbose_name='Quality of English grammar'), + ), + ] diff --git a/submissions/migrations/0051_merge_20170721_0934.py b/submissions/migrations/0051_merge_20170721_0934.py new file mode 100644 index 0000000000000000000000000000000000000000..2f8075edd4eaf34bd361875a9bf701fd761d7b83 --- /dev/null +++ b/submissions/migrations/0051_merge_20170721_0934.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-21 07:34 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0050_auto_20170707_2126'), + ('submissions', '0048_auto_20170721_0931'), + ] + + operations = [ + ] diff --git a/submissions/migrations/0052_auto_20170721_1057.py b/submissions/migrations/0052_auto_20170721_1057.py new file mode 100644 index 0000000000000000000000000000000000000000..c89df6fc0f4aba11de3118a756d79c46e47f29d5 --- /dev/null +++ b/submissions/migrations/0052_auto_20170721_1057.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-21 08:57 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0051_auto_20170721_1049'), + ] + + operations = [ + migrations.AlterField( + model_name='eicrecommendation', + name='recommendation', + field=models.SmallIntegerField(choices=[(None, '-'), (1, 'Publish as Tier I (top 10% of papers in this journal, qualifies as Select) NOTE: SELECT NOT YET OPEN, STARTS EARLY 2017'), (2, 'Publish as Tier II (top 50% of papers in this journal)'), (3, 'Publish as Tier III (meets the criteria of this journal)'), (-1, 'Ask for minor revision'), (-2, 'Ask for major revision'), (-3, 'Reject')]), + ), + migrations.AlterField( + model_name='report', + name='qualification', + field=models.PositiveSmallIntegerField(choices=[(None, '-'), (4, 'expert in this subject'), (3, 'very knowledgeable in this subject'), (2, 'knowledgeable in this subject'), (1, 'generally qualified'), (0, 'not qualified')], verbose_name='Qualification to referee this: I am'), + ), + migrations.AlterField( + model_name='report', + name='recommendation', + field=models.SmallIntegerField(choices=[(None, '-'), (1, 'Publish as Tier I (top 10% of papers in this journal, qualifies as Select) NOTE: SELECT NOT YET OPEN, STARTS EARLY 2017'), (2, 'Publish as Tier II (top 50% of papers in this journal)'), (3, 'Publish as Tier III (meets the criteria of this journal)'), (-1, 'Ask for minor revision'), (-2, 'Ask for major revision'), (-3, 'Reject')]), + ), + ] diff --git a/submissions/migrations/0053_auto_20170721_1100.py b/submissions/migrations/0053_auto_20170721_1100.py new file mode 100644 index 0000000000000000000000000000000000000000..ebb3fc0bfb4327f4c5a8ed239a130468efd4eae1 --- /dev/null +++ b/submissions/migrations/0053_auto_20170721_1100.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-21 09:00 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0052_auto_20170721_1057'), + ] + + operations = [ + migrations.AlterField( + model_name='report', + name='author', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reports', to='scipost.Contributor'), + ), + ] diff --git a/submissions/migrations/0054_auto_20170721_1148.py b/submissions/migrations/0054_auto_20170721_1148.py new file mode 100644 index 0000000000000000000000000000000000000000..983b6b8409fd3f8249dac03b25e01f8eef31e516 --- /dev/null +++ b/submissions/migrations/0054_auto_20170721_1148.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-21 09:48 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0053_auto_20170721_1100'), + ] + + operations = [ + migrations.AlterModelOptions( + name='report', + options={'ordering': ['-date_submitted']}, + ), + ] diff --git a/submissions/migrations/0055_auto_20170724_1734.py b/submissions/migrations/0055_auto_20170724_1734.py new file mode 100644 index 0000000000000000000000000000000000000000..aeab30e64171c0ae2c5b0c3247c932b218882b80 --- /dev/null +++ b/submissions/migrations/0055_auto_20170724_1734.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-24 15:34 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import scipost.db.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0054_auto_20170721_1148'), + ] + + operations = [ + migrations.CreateModel( + name='SubmissionEvent', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(default=django.utils.timezone.now)), + ('latest_activity', scipost.db.fields.AutoDateTimeField(blank=True, default=django.utils.timezone.now, editable=False)), + ('event', models.CharField(choices=[('gen', 'General comment'), ('eic', 'Comment for Editor-in-charge'), ('auth', 'Comment for author')], default='gen', max_length=4)), + ('blub', models.TextField()), + ('submission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='events', to='submissions.Submission')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AlterModelOptions( + name='editorialcommunication', + options={'ordering': ['timestamp']}, + ), + migrations.AlterField( + model_name='editorialcommunication', + name='submission', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='editorial_communications', to='submissions.Submission'), + ), + ] diff --git a/submissions/migrations/0055_merge_20170724_1958.py b/submissions/migrations/0055_merge_20170724_1958.py new file mode 100644 index 0000000000000000000000000000000000000000..54ca985482cecb5aa225e844789ea2733674e3fc --- /dev/null +++ b/submissions/migrations/0055_merge_20170724_1958.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-24 17:58 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0051_merge_20170721_0934'), + ('submissions', '0054_auto_20170721_1148'), + ] + + operations = [ + ] diff --git a/submissions/migrations/0056_auto_20170724_1818.py b/submissions/migrations/0056_auto_20170724_1818.py new file mode 100644 index 0000000000000000000000000000000000000000..aa28c521016056f66eca92029c5996d37d01eb35 --- /dev/null +++ b/submissions/migrations/0056_auto_20170724_1818.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-24 16:18 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0055_auto_20170724_1734'), + ] + + operations = [ + migrations.RenameField( + model_name='submissionevent', + old_name='blub', + new_name='text', + ), + ] diff --git a/submissions/migrations/0057_merge_20170724_1840.py b/submissions/migrations/0057_merge_20170724_1840.py new file mode 100644 index 0000000000000000000000000000000000000000..4aadf6b7eb41e6a1e83106facef409d5b80a739e --- /dev/null +++ b/submissions/migrations/0057_merge_20170724_1840.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-24 16:40 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0056_auto_20170724_1818'), + ('submissions', '0051_merge_20170721_0934'), + ] + + operations = [ + ] diff --git a/submissions/migrations/0058_auto_20170724_1857.py b/submissions/migrations/0058_auto_20170724_1857.py new file mode 100644 index 0000000000000000000000000000000000000000..788449d8efc8b6e3819f5f81fd765c5041f56791 --- /dev/null +++ b/submissions/migrations/0058_auto_20170724_1857.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-24 16:57 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0057_merge_20170724_1840'), + ] + + operations = [ + migrations.AlterModelOptions( + name='report', + options={'ordering': ['-date_submitted']}, + ), + ] diff --git a/submissions/migrations/0059_auto_20170725_2048.py b/submissions/migrations/0059_auto_20170725_2048.py new file mode 100644 index 0000000000000000000000000000000000000000..292342c7362a11dccd2e418283798f970769fca8 --- /dev/null +++ b/submissions/migrations/0059_auto_20170725_2048.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-25 18:48 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0058_auto_20170724_1857'), + ] + + operations = [ + migrations.AlterModelOptions( + name='submissionevent', + options={'ordering': ['-created']}, + ), + migrations.AlterField( + model_name='eicrecommendation', + name='remarks_for_editorial_college', + field=models.TextField(blank=True, default='', verbose_name='optional remarks for the Editorial College'), + preserve_default=False, + ), + ] diff --git a/submissions/migrations/0060_merge_20170726_0945.py b/submissions/migrations/0060_merge_20170726_0945.py new file mode 100644 index 0000000000000000000000000000000000000000..c48723096d587337ff501c4aef5f3885769a7edd --- /dev/null +++ b/submissions/migrations/0060_merge_20170726_0945.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2017-07-26 07:45 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0055_merge_20170724_1958'), + ('submissions', '0059_auto_20170725_2048'), + ] + + operations = [ + ] diff --git a/submissions/models.py b/submissions/models.py index 6aa62b53228b673f215516a6866c70a54952983f..790619f7d3891d2ee7a4f93118c8f52bd5a30c2b 100644 --- a/submissions/models.py +++ b/submissions/models.py @@ -4,18 +4,20 @@ from django.utils import timezone from django.db import models from django.contrib.postgres.fields import JSONField from django.urls import reverse +from django.utils.functional import cached_property from .constants import ASSIGNMENT_REFUSAL_REASONS, ASSIGNMENT_NULLBOOL,\ SUBMISSION_TYPE, ED_COMM_CHOICES, REFEREE_QUALIFICATION, QUALITY_SPEC,\ RANKING_CHOICES, REPORT_REC, SUBMISSION_STATUS, STATUS_UNASSIGNED,\ REPORT_STATUSES, STATUS_UNVETTED, SUBMISSION_EIC_RECOMMENDATION_REQUIRED,\ - SUBMISSION_CYCLES, CYCLE_DEFAULT, CYCLE_SHORT, CYCLE_DIRECT_REC + SUBMISSION_CYCLES, CYCLE_DEFAULT, CYCLE_SHORT, CYCLE_DIRECT_REC,\ + EVENT_GENERAL, EVENT_TYPES, EVENT_FOR_AUTHOR, EVENT_FOR_EIC from .managers import SubmissionManager, EditorialAssignmentManager, EICRecommendationManager,\ - ReportManager + ReportManager, SubmissionEventQuerySet from .utils import ShortSubmissionCycle, DirectRecommendationSubmissionCycle,\ GeneralSubmissionCycle -from scipost.behaviors import ArxivCallable +from scipost.behaviors import TimeStampedModel from scipost.constants import TITLE_CHOICES from scipost.fields import ChoiceArrayField from scipost.models import Contributor @@ -27,7 +29,7 @@ from journals.models import Publication ############### # Submissions: ############### -class Submission(ArxivCallable, models.Model): +class Submission(models.Model): # Main submission fields author_comments = models.TextField(blank=True, null=True) author_list = models.CharField(max_length=1000, verbose_name="author list") @@ -75,6 +77,9 @@ class Submission(ArxivCallable, models.Model): arxiv_vn_nr = models.PositiveSmallIntegerField(default=1) arxiv_link = models.URLField(verbose_name='arXiv link (including version nr)') + pdf_refereeing_pack = models.FileField(upload_to='UPLOADS/REFEREE/%Y/%m/', + max_length=200, blank=True) + # Metadata metadata = JSONField(default={}, blank=True, null=True) submission_date = models.DateField(verbose_name='submission date', default=datetime.date.today) @@ -132,6 +137,7 @@ class Submission(ArxivCallable, models.Model): def reporting_deadline_has_passed(self): return timezone.now() > self.reporting_deadline + @cached_property def other_versions(self): return Submission.objects.filter( arxiv_identifier_wo_vn_nr=self.arxiv_identifier_wo_vn_nr @@ -156,11 +162,54 @@ class Submission(ArxivCallable, models.Model): def count_obtained_reports(self): return self.reports.accepted().filter(invited__isnull=False).count() - def count_refused_reports(self): - return self.reports.rejected().count() + def add_general_event(self, message): + event = SubmissionEvent( + submission=self, + event=EVENT_GENERAL, + text=message, + ) + event.save() + + def add_event_for_author(self, message): + event = SubmissionEvent( + submission=self, + event=EVENT_FOR_AUTHOR, + text=message, + ) + event.save() + + def add_event_for_eic(self, message): + event = SubmissionEvent( + submission=self, + event=EVENT_FOR_EIC, + text=message, + ) + event.save() + + +class SubmissionEvent(TimeStampedModel): + """ + The SubmissionEvent's goal is to act as a messaging/logging model + for the Submission cycle. Its main audience will be the author(s) and + the Editor-in-charge of a Submission. + + Be aware! + Both the author and editor-in-charge will read the submission event. + Make sure the right text is given to the right event-type, to protect + the fellow's identity. + """ + submission = models.ForeignKey('submissions.Submission', on_delete=models.CASCADE, + related_name='events') + event = models.CharField(max_length=4, choices=EVENT_TYPES, default=EVENT_GENERAL) + text = models.TextField() - def count_awaiting_vetting(self): - return self.reports.awaiting_vetting().count() + objects = SubmissionEventQuerySet.as_manager() + + class Meta: + ordering = ['-created'] + + def __str__(self): + return '%s: %s' % (str(self.submission), self.get_event_display()) ###################### @@ -235,47 +284,108 @@ class RefereeInvitation(models.Model): ########### class Report(models.Model): - """ Both types of reports, invited or contributed. """ + """ + Both types of reports, invited or contributed. + + This Report model acts as both a regular `Report` and a `FollowupReport`; A normal Report + should have all fields required, whereas a FollowupReport only has the `report` field as + a required field. + + Important note! + Due to the construction of the two different types within a single model, it is important + to explicitly implement the perticular differences in for example the form used. + """ status = models.CharField(max_length=16, choices=REPORT_STATUSES, default=STATUS_UNVETTED) submission = models.ForeignKey('submissions.Submission', related_name='reports', on_delete=models.CASCADE) + report_nr = models.PositiveSmallIntegerField(default=0, + help_text='This number is a unique number ' + 'refeering to the Report nr. of ' + 'the Submission') vetted_by = models.ForeignKey('scipost.Contributor', related_name="report_vetted_by", blank=True, null=True, on_delete=models.CASCADE) + # `invited' filled from RefereeInvitation objects at moment of report submission invited = models.BooleanField(default=False) + # `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) qualification = models.PositiveSmallIntegerField( choices=REFEREE_QUALIFICATION, - verbose_name="Qualification to referee this: I am ") + verbose_name="Qualification to referee this: I am") + # Text-based reporting - strengths = models.TextField() - weaknesses = models.TextField() + strengths = models.TextField(blank=True) + weaknesses = models.TextField(blank=True) report = models.TextField() - requested_changes = models.TextField(verbose_name="requested changes") + requested_changes = models.TextField(verbose_name="requested changes", blank=True) + # Qualities: - validity = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, default=101) - significance = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, default=101) - originality = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, default=101) - clarity = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, default=101) - formatting = models.SmallIntegerField(choices=QUALITY_SPEC, + validity = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, + null=True, blank=True) + significance = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, + null=True, blank=True) + originality = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, + null=True, blank=True) + clarity = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, + null=True, blank=True) + formatting = models.SmallIntegerField(choices=QUALITY_SPEC, null=True, blank=True, verbose_name="Quality of paper formatting") - grammar = models.SmallIntegerField(choices=QUALITY_SPEC, + grammar = models.SmallIntegerField(choices=QUALITY_SPEC, null=True, blank=True, verbose_name="Quality of English grammar") recommendation = models.SmallIntegerField(choices=REPORT_REC) - remarks_for_editors = models.TextField(default='', blank=True, + remarks_for_editors = models.TextField(blank=True, verbose_name='optional remarks for the Editors only') + doi_label = models.CharField(max_length=200, blank=True) anonymous = models.BooleanField(default=True, verbose_name='Publish anonymously') + pdf_report = models.FileField(upload_to='UPLOADS/REPORTS/%Y/%m/', max_length=200, blank=True) objects = ReportManager() + class Meta: + unique_together = ('submission', 'report_nr') + default_related_name = 'reports' + ordering = ['-date_submitted'] + def __str__(self): return (self.author.user.first_name + ' ' + self.author.user.last_name + ' on ' + self.submission.title[:50] + ' by ' + self.submission.author_list[:50]) + def get_absolute_url(self): + return self.submission.get_absolute_url() + '#report_' + str(self.report_nr) + + @property + def doi_string(self): + if self.doi_label: + return '10.21468/' + self.doi_label + + def save(self, *args, **kwargs): + # Control Report count per Submission. + if not self.report_nr: + self.report_nr = self.submission.reports.count() + 1 + return super().save(*args, **kwargs) + + @cached_property + def is_followup_report(self): + """ + Check if current Report is a `FollowupReport`. A Report is a `FollowupReport` if the + author of the report already has a vetted report in the series of the specific Submission. + """ + return (self.author.reports.accepted() + .filter(submission__arxiv_identifier_wo_vn_nr=self.submission.arxiv_identifier_wo_vn_nr) + .exists()) + + def latest_report_from_series(self): + """ + Get latest Report from the same author for the Submission series. + """ + return (self.author.reports.accepted() + .filter(submission__arxiv_identifier_wo_vn_nr=self.submission.arxiv_identifier_wo_vn_nr) + .order_by('submission__arxiv_identifier_wo_vn_nr').last()) + ########################## # EditorialCommunication # @@ -286,13 +396,17 @@ class EditorialCommunication(models.Model): Each individual communication between Editor-in-charge to and from Referees and Authors becomes an instance of this class. """ - submission = models.ForeignKey('submissions.Submission', on_delete=models.CASCADE) + submission = models.ForeignKey('submissions.Submission', on_delete=models.CASCADE, + related_name='editorial_communications') referee = models.ForeignKey('scipost.Contributor', related_name='referee_in_correspondence', blank=True, null=True, on_delete=models.CASCADE) comtype = models.CharField(max_length=4, choices=ED_COMM_CHOICES) timestamp = models.DateTimeField(default=timezone.now) text = models.TextField() + class Meta: + ordering = ['timestamp'] + def __str__(self): output = self.comtype if self.referee is not None: @@ -313,10 +427,11 @@ class EICRecommendation(models.Model): date_submitted = models.DateTimeField('date submitted', default=timezone.now) remarks_for_authors = models.TextField(blank=True, null=True) requested_changes = models.TextField(verbose_name="requested changes", blank=True, null=True) - remarks_for_editorial_college = models.TextField( - default='', blank=True, null=True, - verbose_name='optional remarks for the Editorial College') + remarks_for_editorial_college = models.TextField(blank=True, + verbose_name='optional remarks for the' + ' Editorial College') recommendation = models.SmallIntegerField(choices=REPORT_REC) + # Editorial Fellows who have assessed this recommendation: eligible_to_vote = models.ManyToManyField(Contributor, blank=True, related_name='eligible_to_vote') diff --git a/submissions/templates/submissions/_refereeing_pack_tex_template.html b/submissions/templates/submissions/_refereeing_pack_tex_template.html new file mode 100644 index 0000000000000000000000000000000000000000..536202975620088d90e191870ed6d99f45f55df6 --- /dev/null +++ b/submissions/templates/submissions/_refereeing_pack_tex_template.html @@ -0,0 +1,200 @@ +{% load texfilters %}\documentclass{SciPost} + +% Prevent all line breaks in inline equations. +\binoppenalty=10000 +\relpenalty=10000 + +\hypersetup{ + colorlinks, + linkcolor={red!50!black}, + citecolor={blue!50!black}, + urlcolor={blue!80!black} +} + +\usepackage[bitstream-charter]{mathdesign} +\urlstyle{sf} + +\fancypagestyle{SPstyle}{ +\fancyhf{} +\lhead{\raisebox{-1.5mm}[0pt][0pt]{\href{https://scipost.org}{\includegraphics[width=20mm]{logo_scipost_with_bgd.pdf}}}} + +{% if report.doi_string %} + \rhead{\small \href{https://scipost.org{{report.doi_string|safe_tex_url}} }{ {{report.doi_string|safe_tex_url}} ({{report.date_submitted|date:'Y'}})}} +{% endif %} + +\renewcommand{\headrulewidth}{1pt} +\fancyfoot[C]{\textbf{\thepage}} +} + +\begin{document} + +\pagestyle{SPstyle} + +\begin{center} +\Large\color{scipostdeepblue}{\textbf{ +%%%%%%%%%% TITLE + AUTHORS +Refereeing Package of\href{https://scipost.org{{submission.get_absolute_url|safe_tex_url}} }{\color{scipostdeepblue}{ {{submission.title}} }}by {{submission.author_list}} +}} +\end{center} + +\begin{center} +\Large\color{scipostdeepblue}{\textbf{ +%%%%%%%%%% ARXIV CODE +\href{https://scipost.org{{submission.get_absolute_url|safe_tex_url}} }{\color{scipostdeepblue}{ {{submission.arxiv_identifier_w_vn_nr}} }} +}} +\end{center} + +\vspace{10pt} + +%%%%%%%%%% 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}} +} + + +%%%%% TABLE OF CONTENT +\vspace{10pt} +\noindent\rule{\textwidth}{1pt} +\tableofcontents +\noindent\rule{\textwidth}{1pt} +\vspace{10pt} + + +%%%%%%%%%% CONTENT + +{% for report in submission.reports.accepted %} + \newpage + \setcounter{section}{0} + + \addcontentsline{toc}{section}{\protect\numberline{}Report {{report.report_nr}}{% if report.doi_string %} $\cdot$ doi: {{report.doi_string|safe_tex_url}}{% endif %} } + + \fancypagestyle{SPstylereport{{report.id}} }{ + \fancyhf{} + \lhead{\raisebox{-1.5mm}[0pt][0pt]{\href{https://scipost.org}{\includegraphics[width=20mm]{logo_scipost_with_bgd.pdf}}}} + + {% if report.doi_string %} + \rhead{\small \href{https://scipost.org{{report.doi_string|safe_tex_url}} }{ {{report.doi_string|safe_tex_url}} ({{report.date_submitted|date:'Y'}})}} + {% endif %} + + \renewcommand{\headrulewidth}{1pt} + \fancyfoot[C]{\textbf{\thepage}} + } + \pagestyle{SPstylereport{{report.id}} } + + \begin{center} + \Large\color{scipostdeepblue}{\textbf{ + %%%%%%%%%% TITLE + Report {{report.report_nr}} on\href{https://scipost.org{{report.get_absolute_url|safe_tex_url}} }{\color{scipostdeepblue}{ {{report.submission.title}} }}by {{report.submission.author_list}} + }} + \end{center} + + {% if report.doi_string %} + \begin{center} + \Large\color{scipostdeepblue}{\textbf{ + %%%%%%%%%% TITLE + doi:\href{https://scipost.org{{report.get_absolute_url|safe_tex_url}} }{\color{scipostdeepblue}{ {{report.doi_string|safe_tex_url}} }} + }} + \end{center} + {% endif %} + + \begin{center} + \large\textbf{ + %%%%%%%%%% AUTHORS + Report by {% if report.anonymous %}anonymous{% else %}{{report.author.user.first_name}} {{report.author.user.last_name}}\textsuperscript{1}{% endif %} + } + \end{center} + + {% if not report.anonymous %} + \begin{center} + %%%%%%%%%% AFFILIATIONS + {\bf 1} {{report.author.affiliation}}\\ + \end{center} + {% endif %} + + + \vspace{10pt} + + + \begin{center} + \begin{tabular}{lr} + \begin{minipage}{0.5\textwidth} + \raisebox{-1mm}[0pt][0pt]{\includegraphics[width=12mm]{by.eps}} + + %%%%%%%%%% COPYRIGHT + + {\small Copyright {% if report.anonymous %}anonymous{% else %}{{report.author.user.first_name}} {{report.author.user.last_name}}{% endif %}. \newline + This work is licensed under the Creative Commons \newline + \href{http://creativecommons.org/licenses/by/4.0/}{Attribution 4.0 International License}. \newline + Published by the SciPost Foundation. + } + \end{minipage} + & + \begin{minipage}{0.5\textwidth} + %%%%%%%%%% DATES + {\small Received {{report.date_submitted|date:'d-m-Y'}} + {% if report.doi_string %} + doi:\href{//dx.doi.org{{report.doi_string|safe_tex_url}} }{ {{report.doi_string|safe_tex_url}} } + {% endif %} + } + \end{minipage} + \end{tabular} + \end{center} + + \vspace{10pt} + \noindent\rule{\textwidth}{1pt} + + %%%%%%%% CONTENTS + + \section*{Ratings} + \begin{center} + \begin{tabular}{r p{0.15\columnwidth} r l} + {\bf Validity} & {{report.get_validity_display}} & {\bf Clarity} & {{report.get_clarity_display}} \\ + {\bf Significance} & {{report.get_significance_display}} & {\bf Formatting} & {{report.get_formatting_display}} \\ + {\bf Originality} & {{report.get_originality_display}} & {\bf Grammar} & {{report.get_grammar_display}} + \end{tabular} + \end{center} + + \section*{Strengths} + {{report.strengths|linebreaktex}} + + \section*{Weaknesses} + {{report.weaknesses|linebreaktex}} + + \section*{Report} + {{report.report|linebreaktex}} + + + \section*{Requested changes} + {{report.requested_changes|linebreaktex}} + + + {% if report.comment_set.vetted %} + \newpage + \pagestyle{SPstyle} + \section*{Comments and Author Replies to this Report} + + {% for comment in report.comment_set.vetted %} + \addcontentsline{toc}{subsection}{\protect\numberline{}{% if comment.is_author_reply %}Author Reply{% else %}Comment{% endif %} {{forloop.counter}} to Report by {% if comment.anonymous %}anonymous{% else %}{{comment.author.user.first_name}} {{comment.author.user.last_name}}{% endif %} } + + \subsection*{ {% if comment.is_author_reply %}Author Reply{% else %}Comment{% endif %} {{forloop.counter}} to Report by {% if comment.anonymous %}anonymous{% else %}{{comment.author.user.first_name}} {{comment.author.user.last_name}}{% endif %} } + {% include 'comments/_comment_tex_template.html' with comment=comment %} + {% endfor %} + {% endif %} + +{% endfor %} + +{% if submission.comments.vetted %} + \newpage + \setcounter{section}{0} + \pagestyle{SPstyle} + + {% for comment in submission.comments.vetted %} + \addcontentsline{toc}{section}{\protect\numberline{}{% if comment.is_author_reply %}Author Reply{% else %}Comment{% endif %} {{forloop.counter}} by {% if comment.anonymous %}anonymous{% else %}{{comment.author.user.first_name}} {{comment.author.user.last_name}}{% endif %} } + + \section*{ {% if comment.is_author_reply %}Author Reply{% else %}Comment{% endif %} {{forloop.counter}} by {% if comment.anonymous %}anonymous{% else %}{{comment.author.user.first_name}} {{comment.author.user.last_name}}{% endif %} } + {% include 'comments/_comment_tex_template.html' with comment=comment %} + {% endfor %} +{% endif %} + +\end{document} diff --git a/submissions/templates/submissions/_report_tex_template.html b/submissions/templates/submissions/_report_tex_template.html new file mode 100644 index 0000000000000000000000000000000000000000..edb59afc172ed68722591107bcd2b97c5a737a01 --- /dev/null +++ b/submissions/templates/submissions/_report_tex_template.html @@ -0,0 +1,112 @@ +{% load texfilters %}\documentclass{SciPost} + +% Prevent all line breaks in inline equations. +\binoppenalty=10000 +\relpenalty=10000 + +\hypersetup{ + colorlinks, + linkcolor={red!50!black}, + citecolor={blue!50!black}, + urlcolor={blue!80!black} +} + +\usepackage[bitstream-charter]{mathdesign} +\urlstyle{sf} + +\fancypagestyle{SPstyle}{ +\fancyhf{} +\lhead{\raisebox{-1.5mm}[0pt][0pt]{\href{https://scipost.org}{\includegraphics[width=20mm]{logo_scipost_with_bgd.pdf}}}} + +{% if report.doi_string %} + \rhead{\small \href{https://scipost.org{{report.doi_string|safe_tex_url}} }{ {{report.doi_string|safe_tex_url}} ({{report.date_submitted|date:'Y'}})}} +{% endif %} + +\renewcommand{\headrulewidth}{1pt} +\fancyfoot[C]{\textbf{\thepage}} +} + +\begin{document} + +\pagestyle{SPstyle} + +\begin{center} +\Large\color{scipostdeepblue}{\textbf{ +%%%%%%%%%% TITLE +Report {{report.report_nr}} on\href{https://scipost.org{{report.get_absolute_url|safe_tex_url}} }{\color{scipostdeepblue}{ {{report.submission.title}} }}by {{report.submission.author_list}} +}} +\end{center} + +\begin{center} +\large\textbf{ +%%%%%%%%%% AUTHORS +Report by {% if report.anonymous %}anonymous{% else %}{{report.author.user.first_name}} {{report.author.user.last_name}}\textsuperscript{1}{% endif %} +} +\end{center} + +{% if not report.anonymous %} + \begin{center} + %%%%%%%%%% AFFILIATIONS + {\bf 1} {{report.author.affiliation}}\\ + \end{center} +{% endif %} + + +\vspace{10pt} + + +\begin{center} +\begin{tabular}{lr} +\begin{minipage}{0.5\textwidth} +\raisebox{-1mm}[0pt][0pt]{\includegraphics[width=12mm]{by.eps}} + +%%%%%%%%%% COPYRIGHT + +{\small Copyright {% if report.anonymous %}anonymous{% else %}{{report.author.user.first_name}} {{report.author.user.last_name}}{% endif %}. \newline + +This work is licensed under the Creative Commons \newline +\href{http://creativecommons.org/licenses/by/4.0/}{Attribution 4.0 International License}. \newline +Published by the SciPost Foundation. +} +\end{minipage} +& +\begin{minipage}{0.5\textwidth} +%%%%%%%%%% DATES +{\small Received {{report.date_submitted|date:'d-m-Y'}} +{% if report.doi_string %} + doi:\href{//dx.doi.org{{report.doi_string|safe_tex_url}} }{ {{report.doi_string|safe_tex_url}} } +{% endif %} +} +\end{minipage} +\end{tabular} +\end{center} + +\vspace{10pt} +\noindent\rule{\textwidth}{1pt} + +%%%%%%%% CONTENTS + +\section*{Ratings} +\begin{center} +\begin{tabular}{r p{0.15\columnwidth} r l} + {\bf Validity} & {{report.get_validity_display}} & {\bf Clarity} & {{report.get_clarity_display}} \\ + {\bf Significance} & {{report.get_significance_display}} & {\bf Formatting} & {{report.get_formatting_display}} \\ + {\bf Originality} & {{report.get_originality_display}} & {\bf Grammar} & {{report.get_grammar_display}} +\end{tabular} +\end{center} + +\section{Strengths} +{{report.strengths|linebreaktex}} + +\section{Weaknesses} +{{report.weaknesses|linebreaktex}} + +\section{Report} +{{report.report|linebreaktex}} + + +\section{Requested changes} +{{report.requested_changes|linebreaktex}} + + +\end{document} diff --git a/submissions/templates/submissions/_single_public_report.html b/submissions/templates/submissions/_single_public_report.html index a733936561c287c2c8b5ccf6f579efe52b20fd56..cc4b233b4540fd5b969181a41377ac181d727a70 100644 --- a/submissions/templates/submissions/_single_public_report.html +++ b/submissions/templates/submissions/_single_public_report.html @@ -1,8 +1,10 @@ {% extends 'submissions/_single_public_report_without_comments.html' %} {% block single_report_footer %} - <hr class="small"> - <h3><a href="{% url 'comments:reply_to_report' report_id=report.id %}">Reply to this Report</a> (authors only)</h3> + {% if user.is_authenticated and perms.scipost.can_submit_comments %} + <hr class="small"> + <h3><a href="{% url 'comments:reply_to_report' report_id=report.id %}">Reply to this Report</a> (authors only)</h3> + {% endif %} {% for reply in report.comment_set.vetted %} {% include 'comments/_single_comment_with_link.html' with comment=reply perms=perms user=user %} diff --git a/submissions/templates/submissions/_single_public_report_without_comments.html b/submissions/templates/submissions/_single_public_report_without_comments.html index bad492424de08ddb7c528cca2a7d70e0eba1e438..2c5c69adc82528e3cc09108b9c08eadff738020b 100644 --- a/submissions/templates/submissions/_single_public_report_without_comments.html +++ b/submissions/templates/submissions/_single_public_report_without_comments.html @@ -3,13 +3,19 @@ <div class="row"> <div class="col-12"> - <div class="report"> + <div class="report" id="report_{{report.report_nr}}"> {% if user.contributor == submission.editor_in_charge or user|is_in_group:'Editorial Administrators' and user|is_not_author_of_submission:submission.arxiv_identifier_w_vn_nr %} <div class="reportid"> <h3>{% if report.anonymous %}(chose public anonymity) {% endif %}<a href="{% url 'scipost:contributor_info' report.author.id %}">{{ report.author.user.first_name }} {{ report.author.user.last_name }}</a> on {{ report.date_submitted|date:'Y-n-j' }}</h3> </h3> + {% if report.pdf_report %} + <a href="{% url 'submissions:report_detail_pdf' report.submission.arxiv_identifier_w_vn_nr report.report_nr %}" target="_blank">Download as PDF</a> + {% endif %} + {% if perms.scipost.can_manage_reports %} + {% if report.pdf_report %}· {% endif %}<a href="{% url 'submissions:report_pdf_compile' report.id %}"{% if not report.pdf_report %}class="btn btn-warning btn-sm"{% endif %}>Update/Compile the Report pdf</a> + {% endif %} </div> {% if report.flagged %} @@ -28,9 +34,10 @@ <div class="row"> <div class="col-12"> <h3>Remarks for editors</h3> - <div class="pl-md-4">{{ report.remarks_for_editors }}</div> + <div class="pl-md-4">{{ report.remarks_for_editors|default:'-' }}</div> </div> </div> + <div class="row"> <div class="col-12"> <h3>Recommendation</h3> @@ -39,9 +46,15 @@ </div> {% else %} <div class="reportid"> - <h3 id="report_id{{report.id}}">{% if report.anonymous %}Anonymous Report {{report.id}}{% else %}<a href="{% url 'scipost:contributor_info' report.author.id %}">{{ report.author.user.first_name }} {{ report.author.user.last_name }}</a>{% endif %} + <h3>{% if report.anonymous %}Anonymous Report {{report.report_nr}}{% else %}<a href="{% url 'scipost:contributor_info' report.author.id %}">{{ report.author.user.first_name }} {{ report.author.user.last_name }}</a>{% endif %} on {{ report.date_submitted|date:'Y-n-j' }}</h3> </h3> + {% if report.pdf_report %} + <a href="{% url 'submissions:report_detail_pdf' report.submission.arxiv_identifier_w_vn_nr report.report_nr %}" target="_blank">Download as PDF</a> + {% endif %} + {% if perms.scipost.can_manage_reports %} + {% if report.pdf_report %}· {% endif %}<a href="{% url 'submissions:report_pdf_compile' report.id %}"{% if not report.pdf_report %}class="btn btn-warning btn-sm"{% endif %}>Update/Compile the Report pdf</a> + {% endif %} </div> {% include 'submissions/_single_report_content.html' with report=report %} diff --git a/submissions/templates/submissions/_single_report_card_summary.html b/submissions/templates/submissions/_single_report_card_summary.html new file mode 100644 index 0000000000000000000000000000000000000000..e0f9fb2fe14cf4714659a4fde5045bc15771d5eb --- /dev/null +++ b/submissions/templates/submissions/_single_report_card_summary.html @@ -0,0 +1,5 @@ +<div class="card-block {% block cardblock_class_block %}{% endblock %}"> + <h3>{{report.get_status_display}} Report {{report.report_nr}} by {% if report.anonymous %}<em>anonymous</em>{% else %}<a href="{{report.author.get_absolute_url}}">{{ report.author.user.first_name }} {{ report.author.user.last_name }}</a>{% endif %}</h3> + <h4>Received: {{ report.date_submitted|date:'Y-n-j' }}</h4> + On Submission: <a href="{{report.submission.get_absolute_url}}">{{report.submission}}</a> +</div> diff --git a/submissions/templates/submissions/_single_report_content.html b/submissions/templates/submissions/_single_report_content.html index 82e06f943f39743d148e56b154382759fe1b2fa8..3e1874d8a8f4d81b1b1550dfb0bca28d0b0ce4be 100644 --- a/submissions/templates/submissions/_single_report_content.html +++ b/submissions/templates/submissions/_single_report_content.html @@ -1,27 +1,36 @@ -<div class="row"> - <div class="col-12"> - <h3 class="highlight tight">Strengths</h3> - <div class="pl-md-4">{{ report.strengths|linebreaks }}</div> +{% if report.strengths %} + <div class="row"> + <div class="col-12"> + <h3 class="highlight tight">Strengths</h3> + <div class="pl-md-4">{{ report.strengths|linebreaks }}</div> + </div> </div> -</div> -<div class="row"> - <div class="col-12"> - <h3 class="highlight tight">Weaknesses</h3> - <div class="pl-md-4">{{ report.weaknesses|linebreaks }}</div> +{% endif %} + +{% if report.weaknesses %} + <div class="row"> + <div class="col-12"> + <h3 class="highlight tight">Weaknesses</h3> + <div class="pl-md-4">{{ report.weaknesses|linebreaks }}</div> + </div> </div> -</div> +{% endif %} + <div class="row"> <div class="col-12"> <h3 class="highlight tight">Report</h3> <div class="pl-md-4">{{ report.report|linebreaks }}</div> </div> </div> -<div class="row"> - <div class="col-12"> - <h3 class="highlight tight">Requested changes</h3> - <div class="pl-md-4"> - <p>{{ report.requested_changes|linebreaks }}</p> - {% include 'submissions/_single_report_ratings.html' with report=report %} - </div> - </div> -</div> + +{% if report.requested_changes %} + <div class="row"> + <div class="col-12"> + <h3 class="highlight tight">Requested changes</h3> + <div class="pl-md-4"> + <p>{{ report.requested_changes|linebreaksbr }}</p> + {% include 'submissions/_single_report_ratings.html' with report=report %} + </div> + </div> + </div> +{% endif %} diff --git a/submissions/templates/submissions/_submission_card_author_content.html b/submissions/templates/submissions/_submission_card_author_content.html index 89a1ae41aa7b9982b41dcb8bf59095a0ee47da88..1dabd69564b13275a5cc7742cbf8f826e3baeb2f 100644 --- a/submissions/templates/submissions/_submission_card_author_content.html +++ b/submissions/templates/submissions/_submission_card_author_content.html @@ -8,8 +8,10 @@ <p class="card-text">Status: {{submission.get_status_display}}</p> {% if current_user and current_user.contributor == submission.submitted_by %} - <p> - <a href="{% url 'submissions:communication' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr comtype='AtoE' %}">Write to the Editor-in-charge</a> + <p class="card-text"> + {% if submission.editor_in_charge %} + <a href="{% url 'submissions:communication' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr comtype='AtoE' %}">Write to the Editor-in-charge</a> + {% endif %} {% if submission.status == 'revision_requested' %} · <a href="{% url 'submissions:prefill_using_identifier' %}?identifier={{submission.arxiv_identifier_wo_vn_nr}}">Resubmit this manuscript</a> {% endif %} diff --git a/submissions/templates/submissions/_submission_refereeing_status.html b/submissions/templates/submissions/_submission_refereeing_status.html index 50066b96f9b564b3e9d18b1dc24d191c56e922f6..5d1ea7b1d2c3baea8d9b6efab91ba7bc895382c8 100644 --- a/submissions/templates/submissions/_submission_refereeing_status.html +++ b/submissions/templates/submissions/_submission_refereeing_status.html @@ -1,6 +1,6 @@ {% if submission.refereeing_cycle != 'direct_rec' %} <div class="card-block"> <p class="card-text">Nr referees invited: {{submission.referee_invitations.count}} <span>[{{submission.count_accepted_invitations}} acccepted / {{submission.count_declined_invitations}} declined / {{submission.count_pending_invitations}} response pending]</span></p> - <p class="card-text">Nr reports obtained: {{submission.count_obtained_reports}} [{{submission.count_invited_reports}} invited / {{submission.count_contrib_reports}} contributed], nr refused: {{submission.count_refused_reports}}, nr awaiting vetting: {{submission.count_awaiting_vetting}}</p> + <p class="card-text">Nr reports obtained: {{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}}</p> </div> {% endif %} diff --git a/submissions/templates/submissions/_submission_summary.html b/submissions/templates/submissions/_submission_summary.html index 334f2d67912757293c26183e4412ee0e8f6d1601..613cc023ff67d310dc160c307b72ce562be694b6 100644 --- a/submissions/templates/submissions/_submission_summary.html +++ b/submissions/templates/submissions/_submission_summary.html @@ -1,6 +1,11 @@ {% extends 'submissions/_submission_summary_short.html' %} {% block submission_summary_footer %} + {% if submission.pdf_refereeing_pack %} + <p class="mt-3"> + <a href="{% url 'submissions:refereeing_package_pdf' submission.arxiv_identifier_w_vn_nr %}" target="_blank" class="btn btn-outline-primary">Download Refereeing Package</a> + </p> + {% endif %} <h3 class="mt-3">Abstract</h3> <p>{{submission.abstract}}</p> {% endblock %} diff --git a/submissions/templates/submissions/_submission_summary_short.html b/submissions/templates/submissions/_submission_summary_short.html index bf6915e89f54fe758948d39f7ae3d7c438025498..a5e999c09085d7ff121f00ad501ae30edc04af47 100644 --- a/submissions/templates/submissions/_submission_summary_short.html +++ b/submissions/templates/submissions/_submission_summary_short.html @@ -19,7 +19,7 @@ </tr> <tr> - <td>arxiv Link:</td> + <td>Arxiv Link:</td> <td> <a href="{{submission.arxiv_link}}" target="_blank">{{submission.arxiv_link}}</a> </td> diff --git a/submissions/templates/submissions/editorial_page.html b/submissions/templates/submissions/editorial_page.html index c2a20c722a204e2b42e55e0f762038b6af899185..069d183b679e7f09def5b4bb8cf87e95253f314b 100644 --- a/submissions/templates/submissions/editorial_page.html +++ b/submissions/templates/submissions/editorial_page.html @@ -26,10 +26,10 @@ <p class="card-text">(go to the <a href="{% url 'submissions:submission' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr %}">Submissions Page</a> to view Reports and Comments)</h3> </div> </div> - {% if other_versions %} + {% if submission.other_versions %} <h3>Other versions of this Submission exist:</h3> <div class="pl-4"> - {% for vn in other_versions %} + {% for vn in submission.other_versions %} {% include 'submissions/_submission_version.html' with submission=vn %} {% endfor %} </div> @@ -71,24 +71,22 @@ </div> </div> -{% if recommendation %} - <div class="row"> - <div class="col-12"> - <div class="card card-outline-secondary"> - {% include 'submissions/_recommendation_author_content.html' with recommendation=recommendation %} +{% with recommendation as submission.eicrecommendations.first %} + {% if recommendation %} + <div class="row"> + <div class="col-12"> + <div class="card card-outline-secondary"> + {% include 'submissions/_recommendation_author_content.html' with recommendation=recommendation %} + </div> </div> </div> - </div> -{% endif %} + {% endif %} +{% endwith %} -<div class="row"> - <div class="col-12"> - <div class="card card-grey"> - <div class="card-block"> - <h1 class="card-title">Editorial Workflow</h1> - <a href="{% url 'submissions:editorial_workflow' %}">How-to guide: summary of the editorial workflow</a> - </div> - </div> +<div class="card card-grey my-4"> + <div class="card-block"> + <h2 class="card-title">Editorial Workflow</h2> + <a href="{% url 'submissions:editorial_workflow' %}">How-to guide: summary of the editorial workflow</a> </div> </div> @@ -111,7 +109,6 @@ </div> </div> {% else %} - {% if submission.refereeing_cycle != 'direct_rec' %} <div class="row"> <div class="col-12"> @@ -123,7 +120,7 @@ <div class="row"> <div class="col-12"> <h3 class="mb-2">Detail of refereeing invitations:</h3> - {% include 'submissions/_submission_refereeing_invitations.html' with submission=submission invitations=ref_invitations %} + {% include 'submissions/_submission_refereeing_invitations.html' with submission=submission invitations=submission.referee_invitations.all %} </div> </div> {% endif %} @@ -148,7 +145,7 @@ <div class="row"> <div class="col-12"> - <h3>Actions:</h3> + <h2 class="mt-3">Actions</h2> <ul> {% if submission.refereeing_cycle != 'direct_rec' %} <li> @@ -173,7 +170,7 @@ </div> </form> </li> - <li><a href="{% url 'submissions:vet_submitted_reports' %}">Vet submitted Reports</a> ({{ nr_reports_to_vet }})</li> + <li><a href="{% url 'submissions:vet_submitted_reports' %}">Vet submitted Reports</a> ({{ submission.reports.awaiting_vetting.count }})</li> {% if not submission.reporting_deadline_has_passed %} <li><a href="{% url 'submissions:close_refereeing_round' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr %}">Close the refereeing round</a> (deactivates submission of new Reports and Comments)</li> {% endif %} @@ -188,28 +185,29 @@ </div> {% endif %} -<div class="row"> - <div class="col-12"> - <h1 class="highlight">Communications</h1> - <ul> - <li><a href="{% url 'submissions:communication' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr comtype='EtoA' %}">Draft and send a communication with the submitting Author</a></li> - <li><a href="{% url 'submissions:communication' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr comtype='EtoS' %}">Draft and send a communication with SciPost Editorial Administration</a></li> - </ul> - </div> -</div> +<h2 class="mt-3">Communications</h2> +<ul> + <li><a href="{% url 'submissions:communication' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr comtype='EtoA' %}">Draft and send a communication with the submitting Author</a></li> + <li><a href="{% url 'submissions:communication' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr comtype='EtoS' %}">Draft and send a communication with SciPost Editorial Administration</a></li> +</ul> <div class="row"> <div class="col-12"> - <ul class="list-group list-group-flush"> - {% for comm in communications %} - <li class="list-group-item"> - {% include 'submissions/_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> + <ul class="list-group list-group-flush"> + {% for comm in submission.editorial_communications.all %} + <li class="list-group-item"> + {% include 'submissions/_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> +<h2 class="mt-3">Events</h2> +{% include 'submissions/submission_event_list.html' with events=submission.events.for_eic %} + + +<div class="mb-5"></div> {% endblock content %} diff --git a/submissions/templates/submissions/reports_accepted_list.html b/submissions/templates/submissions/reports_accepted_list.html new file mode 100644 index 0000000000000000000000000000000000000000..b4a2ff77d264bdbcf7531ec7d39e47e39e15573b --- /dev/null +++ b/submissions/templates/submissions/reports_accepted_list.html @@ -0,0 +1,54 @@ +{% extends 'scipost/_personal_page_base.html' %} + +{% block breadcrumb_items %} + {{block.super}} + <span class="breadcrumb-item">Accepted Reports</span> +{% endblock %} + +{% load bootstrap %} + +{% block pagetitle %}: Accepted Reports{% endblock pagetitle %} + +{% block content %} + +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Accepted Reports</h1> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <table class="table"> + <thead> + <tr> + <th>Report nr. of Submission</th> + <th>Submission</th> + <th>Report author</th> + <th>Has PDF</th> + <th>Action</th> + </tr> + </thead> + <tbody> + {% for report in reports %} + <tr{% if not report.pdf_report %} class="table-danger"{% endif %}> + <td>{{report.report_nr}}</td> + <td><a href="{{report.get_absolute_url}}">{{report.submission.arxiv_identifier_w_vn_nr}}</a></td> + <td>{% if report.anonymous %}<em>Anonymous</em>{% else %}{{report.author}}{% endif %}</td> + <td> + {{report.pdf_report|yesno:"Yes,No"}} + {% if report.pdf_report %} + · <a href="{% url 'submissions:report_detail_pdf' report.submission.arxiv_identifier_w_vn_nr report.report_nr %}" target="_blank">Download</a> + {% endif %} + </td> + <td> + <a href="{% url 'submissions:report_pdf_compile' report.id %}">Compile/upload report</a> + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> +</div> + +{% endblock %} diff --git a/submissions/templates/submissions/reports_pdf_compile.html b/submissions/templates/submissions/reports_pdf_compile.html new file mode 100644 index 0000000000000000000000000000000000000000..bf46e0b6860c836b3d5a3c7d843508ea94253412 --- /dev/null +++ b/submissions/templates/submissions/reports_pdf_compile.html @@ -0,0 +1,74 @@ +{% extends 'scipost/_personal_page_base.html' %} + +{% block pagetitle %}: Upload Report PDF{% endblock pagetitle %} + +{% load bootstrap %} + +{% block breadcrumb_items %} + {{block.super}} + <a href="{% url 'submissions:reports_accepted_list' %}" class="breadcrumb-item">Accepted Reports</a> + <span class="breadcrumb-item">Upload Report PDF</span> +{% endblock %} + +{% block content %} + + <div class="row"> + <div class="col-12"> + <h1 class="highlight">Upload Report PDF</h1> + <div class="card"> + {% include 'submissions/_single_report_card_summary.html' with submission=report %} + </div> + </div> + </div> + + <div class="row"> + <div class="col-12"> + <h3>Please process this code in your Tex Compiler:</h3> + <p>To compile, one needs the SciPost Latex Package. Please <a href="mailto: info@scipost.org">contact SciPost</a> if you did not receive it.</p> + <pre class="clickfocus" style="max-height: 200px;"><code>{% include 'submissions/_report_tex_template.html' with report=report %}</code></pre> + </div> + </div> + + + <div class="row"> + <div class="col-12"> + <form method="post" enctype="multipart/form-data"> + {% csrf_token %} + {{ form|bootstrap }} + <input class="btn btn-secondary" type="submit" value="Upload"/> + </form> + </div> + </div> + +<script> + jQuery.fn.selectText = function(){ + this.find('input').each(function() { + if($(this).prev().length == 0 || !$(this).prev().hasClass('p_copy')) { + $('<p class="p_copy" style="position: absolute; z-index: -1;"></p>').insertBefore($(this)); + } + $(this).prev().html($(this).val()); + }); + var doc = document; + var element = this[0]; + + if (doc.body.createTextRange) { + var range = document.body.createTextRange(); + range.moveToElementText(element); + range.select(); + } else if (window.getSelection) { + var selection = window.getSelection(); + var range = document.createRange(); + range.selectNodeContents(element); + selection.removeAllRanges(); + selection.addRange(range); + } + }; + + $(function() { + $('.clickfocus').on('click', function() { + $(this).find('code').selectText(); + }); + }); +</script> + +{% endblock %} diff --git a/submissions/templates/submissions/submission_detail.html b/submissions/templates/submissions/submission_detail.html index f255cf191d11632a6776dda1ba0baf10e76e4ffd..4e6188df9338e987679e3648cd003496bd609181 100644 --- a/submissions/templates/submissions/submission_detail.html +++ b/submissions/templates/submissions/submission_detail.html @@ -50,10 +50,10 @@ </div> </div> - {% if other_versions %} + {% if submission.other_versions %} <h3>Other versions of this Submission (with Reports) exist:</h3> <div class="pl-4"> - {% for vn in other_versions %} + {% for vn in submission.other_versions %} {% include 'submissions/_submission_version.html' with submission=vn %} {% endfor %} </div> @@ -99,6 +99,14 @@ </div> {% endif %} {% endif %} + + <div class="mb-4"> + <h2>Events</h2> + <a href="javascript:;" data-toggle="toggle" data-target="#eventslist">Show/hide events</a> + <div id="eventslist"> + {% include 'submissions/submission_event_list.html' with events=submission.events.for_author %} + </div> + </div> {% endif %} @@ -139,6 +147,14 @@ <li>Commenting on this Submission is closed.</li> {% endif %} </ul> + {% if perms.scipost.can_manage_reports %} + <h3>Admin Actions</h3> + <ul> + <li> + <a href="{% url 'submissions:treated_submission_pdf_compile' submission.arxiv_identifier_w_vn_nr %}">Update the Refereeing Package pdf</a> + </li> + <ul> + {% endif %} </div> </div> {% endif %} diff --git a/submissions/templates/submissions/submission_event_list.html b/submissions/templates/submissions/submission_event_list.html new file mode 100644 index 0000000000000000000000000000000000000000..01e3d1b2dad54dc98a151d7ca30b49035114e786 --- /dev/null +++ b/submissions/templates/submissions/submission_event_list.html @@ -0,0 +1,16 @@ +<div class="row"> + <div class="col-12"> + <ul class="list-group list-group-flush events-list"> + {% for event in events %} + <li class="list-group-item"> + <div> + {{event.text}}<br> + <span class="text-muted">{{event.created}}</span> + </div> + </li> + {% empty %} + <li class="list-group-item">There have been no events for this Submission.</li> + {% endfor %} + </ul> + </div> +</div> diff --git a/submissions/templates/submissions/submit_report.html b/submissions/templates/submissions/submit_report.html index 63f80e447b69a04de026d1a51db389b58df83e29..fb4029c449a0df7ba6ebd109247bdb01de5e6255 100644 --- a/submissions/templates/submissions/submit_report.html +++ b/submissions/templates/submissions/submit_report.html @@ -81,13 +81,20 @@ <div class="col-12"> <div class="card card-grey"> <div class="card-block"> - <h1>Your report:</h1> - <p class="mb-0">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> + <h1>Your {% if form.instance.is_followup_report %}followup {% endif %}report:</h1> + <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> + <p class="mb-0">Any fields with an asterisk (*) are required.</p> + {% if form.instance.is_followup_report %} + <p class="mb-0"> + Because you have already submitted a Report for this Submission series, not all fields are required. + </p> + {% endif %} </div> </div> <form action="{% url 'submissions:submit_report' arxiv_identifier_w_vn_nr=submission.arxiv_identifier_w_vn_nr %}" method="post"> {% csrf_token %} {{ form|bootstrap:'3,9' }} + <p>Any fields with an asterisk (*) are required.</p> <input class="btn btn-primary" type="submit" name="save_submit" value="Submit your report"/> <input class="btn btn-secondary ml-2" type="submit" name="save_draft" value="Save your report as draft"/> <div class="my-4"> diff --git a/submissions/templates/submissions/treated_submission_pdf_compile.html b/submissions/templates/submissions/treated_submission_pdf_compile.html new file mode 100644 index 0000000000000000000000000000000000000000..19e456fefbb06e35c872c25f1cc8c53e0020ea62 --- /dev/null +++ b/submissions/templates/submissions/treated_submission_pdf_compile.html @@ -0,0 +1,82 @@ +{% extends 'scipost/_personal_page_base.html' %} + +{% block pagetitle %}: Upload Submission Refereeing PDF{% endblock pagetitle %} + +{% load bootstrap %} + +{% block breadcrumb_items %} + {{block.super}} + <a href="{% url 'submissions:treated_submissions_list' %}" class="breadcrumb-item">Treated Submissions</a> + <span class="breadcrumb-item">Upload Submission Refereeing PDF</span> +{% endblock %} + +{% block content %} + + <div class="row"> + <div class="col-12"> + <h1 class="highlight">Upload Submission Refereeing PDF</h1> + {% include 'submissions/_submission_summary_short.html' with submission=submission %} + <p class="my-2"><a href="{{submission.get_absolute_url}}">Go to Submission page</a></p> + </div> + </div> + <hr> + + <div class="row"> + <div class="col-12"> + <h3>Please process this code in your Tex Compiler</h3> + <p> + You may need to compile twice because of the Table of Content.<br> + To compile, one needs the SciPost Latex Package. Please <a href="mailto: info@scipost.org">contact SciPost</a> if you did not receive it. + </p> + <h3>Content of the Refereeing Package</h3> + <p> + 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 'submissions/_refereeing_pack_tex_template.html' with submission=submission %}</code></pre> + </div> + </div> + + + <div class="row"> + <div class="col-12"> + <form method="post" enctype="multipart/form-data"> + {% csrf_token %} + {{ form|bootstrap }} + <input class="btn btn-secondary" type="submit" value="Upload"/> + </form> + </div> + </div> + +<script> + jQuery.fn.selectText = function(){ + this.find('input').each(function() { + if($(this).prev().length == 0 || !$(this).prev().hasClass('p_copy')) { + $('<p class="p_copy" style="position: absolute; z-index: -1;"></p>').insertBefore($(this)); + } + $(this).prev().html($(this).val()); + }); + var doc = document; + var element = this[0]; + + if (doc.body.createTextRange) { + var range = document.body.createTextRange(); + range.moveToElementText(element); + range.select(); + } else if (window.getSelection) { + var selection = window.getSelection(); + var range = document.createRange(); + range.selectNodeContents(element); + selection.removeAllRanges(); + selection.addRange(range); + } + }; + + $(function() { + $('.clickfocus').on('click', function() { + $(this).find('code').selectText(); + }); + }); +</script> + +{% endblock %} diff --git a/submissions/templates/submissions/treated_submissions_list.html b/submissions/templates/submissions/treated_submissions_list.html new file mode 100644 index 0000000000000000000000000000000000000000..c9b167bd9832f074302c794376cfd497b0413f2c --- /dev/null +++ b/submissions/templates/submissions/treated_submissions_list.html @@ -0,0 +1,56 @@ +{% extends 'scipost/_personal_page_base.html' %} + +{% block breadcrumb_items %} + {{block.super}} + <span class="breadcrumb-item">Treated Submissions</span> +{% endblock %} + +{% load bootstrap %} + +{% block pagetitle %}: Treated Submissions{% endblock pagetitle %} + +{% block content %} + +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Treated Submissions</h1> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <table class="table"> + <thead> + <tr> + <th>Submission</th> + <th>Status</th> + <th>Accepted</th> + <th>Number of Reports</th> + <th>Has PDF</th> + <th>Action</th> + </tr> + </thead> + <tbody> + {% for submission in submissions %} + <tr{% if not submission.pdf_refereeing_pack %} class="table-danger"{% endif %}> + <td><a href="{{submission.get_absolute_url}}">{{submission.arxiv_identifier_w_vn_nr}}</a></td> + <td>{{submission.get_status_display}}</td> + <td>{{submission.acceptance_date|default_if_none:'Date unknown'}}</td> + <td>{{submission.reports.accepted.count}}</td> + <td> + {{submission.pdf_refereeing_pack|yesno:"Yes,No"}} + {% if submission.pdf_refereeing_pack %} + · <a href="{% url 'submissions:refereeing_package_pdf' submission.arxiv_identifier_w_vn_nr %}" target="_blank">Download</a> + {% endif %} + </td> + <td> + <a href="{% url 'submissions:treated_submission_pdf_compile' submission.arxiv_identifier_w_vn_nr %}">Compile/upload Refereeing Package</a> + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> +</div> + +{% endblock %} diff --git a/submissions/templatetags/lookup.py b/submissions/templatetags/lookup.py index 803edd8acbd338d2202c2c1666955ac082787673..c2f7e549124208067d554ffd7ce304d5f8758ae2 100644 --- a/submissions/templatetags/lookup.py +++ b/submissions/templatetags/lookup.py @@ -8,7 +8,7 @@ class SubmissionLookup(LookupChannel): def get_query(self, q, request): return (self.model.objects - .public() + .public_unlisted() .order_by('-submission_date') .filter(title__icontains=q) .prefetch_related('publication')[:10]) diff --git a/submissions/urls.py b/submissions/urls.py index 6e776fcf71b60a8500c7f7fc8657ac7eb0f9e8e2..c4d8d4fc656accf12e14509b8e9386d804caa865 100644 --- a/submissions/urls.py +++ b/submissions/urls.py @@ -18,6 +18,13 @@ urlpatterns = [ name='submission_wo_vn_nr'), url(r'^(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/$', views.submission_detail, name='submission'), + url(r'^(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/reports/(?P<report_nr>[0-9]+)/pdf$', + views.report_detail_pdf, name='report_detail_pdf'), + url(r'^(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/reports/pdf$', + views.submission_refereeing_package_pdf, name='refereeing_package_pdf'), + url(r'^treated_submissions$', views.treated_submissions_list, name='treated_submissions_list'), + url(r'^(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/reports/compile$', + views.treated_submission_pdf_compile, name='treated_submission_pdf_compile'), url(r'^submit_manuscript$', views.RequestSubmission.as_view(), name='submit_manuscript'), url(r'^submit_manuscript/prefill$', views.prefill_using_arxiv_identifier, name='prefill_using_identifier'), @@ -26,6 +33,7 @@ urlpatterns = [ views.submissions_by_status, name='submissions_by_status'), url(r'^add_remark/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$', views.add_remark, name='add_remark'), + # Assignment of Editor-in-charge url(r'^assign_submission/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$', views.assign_submission, name='assign_submission'), @@ -73,9 +81,13 @@ urlpatterns = [ url(r'^cycle/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/submit$', views.cycle_form_submit, name='cycle_confirmation'), # Reports - url(r'^submit_report/(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})$', + url(r'^(?P<arxiv_identifier_w_vn_nr>[0-9]{4,}.[0-9]{5,}v[0-9]{1,2})/reports/submit$', views.submit_report, name='submit_report'), - url(r'^vet_submitted_reports$', views.vet_submitted_reports, name='vet_submitted_reports'), + url(r'^reports/vet_submitted$', views.vet_submitted_reports, name='vet_submitted_reports'), + url(r'^reports/list$', views.reports_accepted_list, name='reports_accepted_list'), + url(r'^reports/(?P<report_id>[0-9]+)/compile$', + views.report_pdf_compile, name='report_pdf_compile'), + # Voting url(r'^prepare_for_voting/(?P<rec_id>[0-9]+)$', views.prepare_for_voting, name='prepare_for_voting'), url(r'^vote_on_rec/(?P<rec_id>[0-9]+)$', views.vote_on_rec, name='vote_on_rec'), diff --git a/submissions/utils.py b/submissions/utils.py index 908f7f35c9a12bfd2a592b2a712b31952f468bce..385dbfc3402e25ff730686360b4f49f6561e8428 100644 --- a/submissions/utils.py +++ b/submissions/utils.py @@ -126,7 +126,6 @@ class BaseSubmissionCycle: self.submission.reporting_deadline = deadline self.submission.save() - def get_required_actions(self): '''Return list of the submission its required actions''' if not self.updated_action: diff --git a/submissions/views.py b/submissions/views.py index dbe926c6d43fff757dc1943d7400f2fe395de3c6..7d54e68dd9ecff81d84478413d99eda70cad92af 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -6,11 +6,13 @@ from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.models import Group from django.core.urlresolvers import reverse, reverse_lazy from django.db import transaction -from django.http import Http404, HttpResponseRedirect +from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render, redirect from django.template import Template, Context from django.utils import timezone from django.utils.decorators import method_decorator +from django.views.generic.edit import CreateView +from django.views.generic.list import ListView from guardian.decorators import permission_required_or_403 from guardian.shortcuts import assign_perm @@ -25,7 +27,7 @@ from .forms import SubmissionIdentifierForm, RequestSubmissionForm, SubmissionSe SetRefereeingDeadlineForm, RefereeSelectForm, RefereeRecruitmentForm,\ ConsiderRefereeInvitationForm, EditorialCommunicationForm,\ EICRecommendationForm, ReportForm, VetReportForm, VotingEligibilityForm,\ - SubmissionCycleChoiceForm + SubmissionCycleChoiceForm, ReportPDFForm, SubmissionReportsForm from .utils import SubmissionUtils from scipost.forms import ModifyPersonalMessageForm, RemarkForm @@ -35,9 +37,6 @@ from scipost.utils import Utils from comments.forms import CommentForm from production.models import ProductionStream -from django.views.generic.edit import CreateView -from django.views.generic.list import ListView - import strings @@ -63,6 +62,9 @@ class RequestSubmission(CreateView): @transaction.atomic def form_valid(self, form): submission = form.save() + submission.add_general_event('The manuscript has been submitted to %s.' + % submission.get_submitted_to_journal_display()) + text = ('<h3>Thank you for your Submission to SciPost</h3>' 'Your Submission will soon be handled by an Editor.') messages.success(self.request, text) @@ -119,7 +121,7 @@ class SubmissionListView(ListView): paginate_by = 10 def get_queryset(self): - queryset = Submission.objects.public_overcomplete() + queryset = Submission.objects.public_newest() self.form = self.form(self.request.GET) if 'to_journal' in self.kwargs: queryset = queryset.filter( @@ -186,9 +188,6 @@ def submission_detail(request, arxiv_identifier_w_vn_nr): 'Editorial College']).exists() and not is_author): raise Http404 - other_versions = Submission.objects.filter( - arxiv_identifier_wo_vn_nr=submission.arxiv_identifier_wo_vn_nr - ).exclude(pk=submission.id) form = CommentForm() @@ -206,7 +205,6 @@ def submission_detail(request, arxiv_identifier_w_vn_nr): recommendation = None context = {'submission': submission, - 'other_versions': other_versions, 'recommendation': recommendation, 'comments': comments, 'invited_reports': invited_reports, @@ -219,6 +217,91 @@ def submission_detail(request, arxiv_identifier_w_vn_nr): return render(request, 'submissions/submission_detail.html', context) +def report_detail_pdf(request, arxiv_identifier_w_vn_nr, report_nr): + """ + Download the PDF of a Report if available. + """ + report = get_object_or_404(Report.objects.accepted(), + submission__arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr, + pdf_report__isnull=False, report_nr=report_nr) + response = HttpResponse(report.pdf_report.read(), content_type='application/pdf') + filename = '%s_report-%i.pdf' % (report.submission.arxiv_identifier_w_vn_nr, report.report_nr) + response['Content-Disposition'] = ('filename=' + filename) + return response + + +def submission_refereeing_package_pdf(request, arxiv_identifier_w_vn_nr): + """ + This view let's the user download all Report PDF's in a single merged PDF. + The merging takes places every time its downloaded to make sure all available report PDF's + are included and the EdColAdmin doesn't have to compile the package every time again. + """ + submission = get_object_or_404(Submission.objects.public().exclude(pdf_refereeing_pack=''), + arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) + response = HttpResponse(submission.pdf_refereeing_pack.read(), content_type='application/pdf') + filename = '%s-refereeing-package.pdf' % submission.arxiv_identifier_w_vn_nr + response['Content-Disposition'] = ('filename=' + filename) + return response + + +@permission_required('scipost.can_manage_reports', raise_exception=True) +def reports_accepted_list(request): + """ + This view lists all accepted Reports. This shows if Report needs a PDF update/compile + in a convenient way. + """ + reports = (Report.objects.accepted() + .order_by('pdf_report', 'submission').prefetch_related('submission')) + context = { + 'reports': reports + } + return render(request, 'submissions/reports_accepted_list.html', context) + + +@permission_required('scipost.can_manage_reports', raise_exception=True) +def report_pdf_compile(request, report_id): + report = get_object_or_404(Report.objects.accepted(), id=report_id) + form = ReportPDFForm(request.POST or None, request.FILES or None, instance=report) + if form.is_valid(): + report = form.save() + messages.success(request, 'Upload complete.') + return redirect(reverse('submissions:reports_accepted_list')) + context = { + 'report': report, + 'form': form + } + return render(request, 'submissions/reports_pdf_compile.html', context) + + +@permission_required('scipost.can_manage_reports', raise_exception=True) +def treated_submissions_list(request): + """ + This view lists all accepted Reports. This shows if Report needs a PDF update/compile + in a convenient way. + """ + submissions = Submission.objects.treated().order_by('pdf_refereeing_pack', '-acceptance_date') + context = { + 'submissions': submissions + } + return render(request, 'submissions/treated_submissions_list.html', context) + + +@permission_required('scipost.can_manage_reports', raise_exception=True) +def treated_submission_pdf_compile(request, arxiv_identifier_w_vn_nr): + submission = get_object_or_404(Submission.objects.treated(), + arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) + form = SubmissionReportsForm(request.POST or None, request.FILES or None, instance=submission) + if form.is_valid(): + form.save() + messages.success(request, 'Upload complete.') + return redirect(reverse('submissions:treated_submissions_list')) + context = { + 'submission': submission, + 'form': form + } + return render(request, 'submissions/treated_submission_pdf_compile.html', context) + + ###################### # Editorial workflow # ###################### @@ -397,6 +480,9 @@ def accept_or_decline_assignment_ack(request, assignment_id): assign_perm('can_take_editorial_actions', ed_admins, assignment.submission) SubmissionUtils.send_EIC_appointment_email() SubmissionUtils.send_author_prescreening_passed_email() + + # Add SubmissionEvents + assignment.submission.add_general_event('The Editor-in-charge has been assigned.') else: assignment.accepted = False assignment.refusal_reason = form.cleaned_data['refusal_reason'] @@ -457,6 +543,9 @@ def volunteer_as_EIC(request, arxiv_identifier_w_vn_nr): SubmissionUtils.send_EIC_appointment_email() SubmissionUtils.send_author_prescreening_passed_email() + # Add SubmissionEvents + submission.add_general_event('The Editor-in-charge has been assigned.') + messages.success(request, 'Thank you for becoming Editor-in-charge of this submission.') return redirect(reverse('submissions:editorial_page', args=[submission.arxiv_identifier_w_vn_nr])) @@ -526,30 +615,11 @@ def assignments(request): def editorial_page(request, arxiv_identifier_w_vn_nr): submission = get_object_or_404(Submission.objects.filter_editorial_page(request.user), arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) - other_versions = (Submission.objects - .filter(arxiv_identifier_wo_vn_nr=submission.arxiv_identifier_wo_vn_nr) - .exclude(pk=submission.id)) - ref_invitations = RefereeInvitation.objects.filter(submission=submission) - nr_reports_to_vet = (Report.objects.awaiting_vetting() - .filter(submission=submission, - submission__editor_in_charge=request.user.contributor) - .count()) - communications = (EditorialCommunication.objects - .filter(submission=submission).order_by('timestamp')) - try: - recommendation = (EICRecommendation.objects.get_for_user_in_pool(request.user) - .get(submission=submission)) - except EICRecommendation.DoesNotExist: - recommendation = None + context = { 'submission': submission, - 'other_versions': other_versions, - 'recommendation': recommendation, 'set_deadline_form': SetRefereeingDeadlineForm(), 'cycle_choice_form': SubmissionCycleChoiceForm(instance=submission), - 'ref_invitations': ref_invitations, - 'nr_reports_to_vet': nr_reports_to_vet, - 'communications': communications } return render(request, 'submissions/editorial_page.html', context) @@ -661,6 +731,9 @@ def recruit_referee(request, arxiv_identifier_w_vn_nr): reg_invitation.save() Utils.load({'invitation': reg_invitation}) Utils.send_registration_invitation_email() + submission.add_event_for_author('A referee has been invited.') + submission.add_event_for_eic('%s has been recruited and invited as a referee.' + % ref_recruit_form.cleaned_data['last_name']) # Copy the key to the refereeing invitation: ref_invitation.invitation_key = reg_invitation.invitation_key ref_invitation.save() @@ -700,6 +773,9 @@ def send_refereeing_invitation(request, arxiv_identifier_w_vn_nr, contributor_id invitation.save() SubmissionUtils.load({'invitation': invitation}) SubmissionUtils.send_refereeing_invitation_email() + submission.add_event_for_author('A referee has been invited.') + submission.add_event_for_eic('Referee %s has been invited.' % contributor.user.last_name) + messages.success(request, 'Invitation sent') return redirect(reverse('submissions:editorial_page', kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr})) @@ -753,14 +829,23 @@ def accept_or_decline_ref_invitation_ack(request, invitation_id): invitation.date_responded = timezone.now() if form.cleaned_data['accept'] == 'True': invitation.accepted = True + decision_string = 'accepted' else: invitation.accepted = False + decision_string = 'declined' invitation.refusal_reason = form.cleaned_data['refusal_reason'] invitation.save() SubmissionUtils.load({'invitation': invitation}, request) SubmissionUtils.email_referee_response_to_EIC() SubmissionUtils.email_referee_in_response_to_decision() + # Add SubmissionEvents + invitation.submission.add_event_for_author('A referee has %s the refereeing invitation.' + % decision_string) + invitation.submission.add_event_for_eic('Referee %s has %s the refereeing invitation.' + % (invitation.referee.user.last_name, + decision_string)) + context = {'invitation': invitation} return render(request, 'submissions/accept_or_decline_ref_invitation_ack.html', context) @@ -776,6 +861,13 @@ def decline_ref_invitation(request, invitation_key): invitation.save() SubmissionUtils.load({'invitation': invitation}, request) SubmissionUtils.email_referee_response_to_EIC() + + # Add SubmissionEvents + invitation.submission.add_event_for_author('A referee has declined the' + ' refereeing invitation.') + invitation.submission.add_event_for_eic('Referee %s has declined the refereeing ' + 'invitation.' % invitation.referee.user.last_name) + messages.success(request, 'Thank you for informing us that you will not provide a Report.') return redirect(reverse('scipost:index')) context = {'invitation': invitation, 'form': form} @@ -796,6 +888,12 @@ def cancel_ref_invitation(request, arxiv_identifier_w_vn_nr, invitation_id): invitation.save() SubmissionUtils.load({'invitation': invitation}) SubmissionUtils.send_ref_cancellation_email() + + # Add SubmissionEvents + 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) + return redirect(reverse('submissions:editorial_page', kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr})) @@ -812,6 +910,8 @@ def extend_refereeing_deadline(request, arxiv_identifier_w_vn_nr, days): submission.status = 'EICassigned' submission.latest_activity = timezone.now() submission.save() + + submission.add_general_event('A new refereeing deadline is set.') return redirect(reverse('submissions:editorial_page', kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr})) @@ -832,6 +932,7 @@ def set_refereeing_deadline(request, arxiv_identifier_w_vn_nr): submission.status = 'EICassigned' submission.latest_activity = timezone.now() submission.save() + submission.add_general_event('A new refereeing deadline is set.') context = {'ack_header': 'New reporting deadline set.', 'followup_message': 'Return to the ', 'followup_link': reverse('submissions:editorial_page', @@ -866,6 +967,8 @@ def close_refereeing_round(request, arxiv_identifier_w_vn_nr): submission.reporting_deadline = timezone.now() submission.latest_activity = timezone.now() submission.save() + submission.add_general_event('Refereeing round has been closed.') + return redirect(reverse('submissions:editorial_page', kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr})) @@ -945,16 +1048,12 @@ def eic_recommendation(request, arxiv_identifier_w_vn_nr): form = EICRecommendationForm(request.POST or None) if form.is_valid(): - recommendation = EICRecommendation( - submission=submission, - date_submitted=timezone.now(), - remarks_for_authors=form.cleaned_data['remarks_for_authors'], - requested_changes=form.cleaned_data['requested_changes'], - remarks_for_editorial_college=form.cleaned_data['remarks_for_editorial_college'], - recommendation=form.cleaned_data['recommendation'], - voting_deadline=timezone.now() + datetime.timedelta(days=7), - ) + # Create new EICRecommendation + recommendation = form.save(commit=False) + recommendation.submission = submission + recommendation.voting_deadline = timezone.now() + datetime.timedelta(days=7) recommendation.save() + # If recommendation is to accept or reject, # it is forwarded to the Editorial College for voting # If it is to carry out minor or major revisions, @@ -964,12 +1063,21 @@ def eic_recommendation(request, arxiv_identifier_w_vn_nr): recommendation.recommendation == 3 or recommendation.recommendation == -3): submission.status = 'voting_in_preparation' + + # Add SubmissionEvent for EIC + submission.add_event_for_eic('An Editorial Recommendation has been formulated: %s.' + % recommendation.get_recommendation_display()) + elif (recommendation.recommendation == -1 or recommendation.recommendation == -2): submission.status = 'revision_requested' SubmissionUtils.load({'submission': submission, 'recommendation': recommendation}) SubmissionUtils.send_author_revision_requested_email() + + # Add SubmissionEvents + submission.add_general_event('An Editorial Recommendation has been formulated: %s.' + % recommendation.get_recommendation_display()) submission.open_for_reporting = False submission.save() @@ -1032,6 +1140,7 @@ def submit_report(request, arxiv_identifier_w_vn_nr): errormessage = ('The system flagged you as a potential author of this Submission. ' 'Please go to your personal page under the Submissions tab' ' to clarify this.') + if errormessage: messages.warning(request, errormessage) return redirect(reverse('scipost:personal_page')) @@ -1040,12 +1149,12 @@ def submit_report(request, arxiv_identifier_w_vn_nr): try: report_in_draft = submission.reports.in_draft().get(author=current_contributor) except Report.DoesNotExist: - report_in_draft = None + report_in_draft = Report(author=current_contributor, submission=submission) form = ReportForm(request.POST or None, instance=report_in_draft) # Check if data sent is valid if form.is_valid(): - newreport = form.save(submission, current_contributor) + newreport = form.save(submission) if newreport.status == STATUS_DRAFT: messages.success(request, ('Your Report has been saved. ' 'You may carry on working on it,' @@ -1058,6 +1167,10 @@ def submit_report(request, arxiv_identifier_w_vn_nr): SubmissionUtils.email_EIC_report_delivered() SubmissionUtils.email_referee_report_delivered() + # Add SubmissionEvents for the EIC only, as it can also be rejected still + submission.add_event_for_eic('%s has submitted a new Report.' + % request.user.last_name) + messages.success(request, 'Thank you for your Report') return redirect(reverse('scipost:personal_page')) @@ -1069,16 +1182,17 @@ def submit_report(request, arxiv_identifier_w_vn_nr): @permission_required('scipost.can_take_charge_of_submissions', raise_exception=True) def vet_submitted_reports(request): """ - Reports with status `unvetted` will be shown one-by-one. A user may only + Reports with status `unvetted` will be shown one-by-one (oldest first). A user may only vet reports of submissions he/she is EIC of. After vetting an email is sent to the report author, bcc EIC. If report has not been refused, the submission author is also mailed. """ - contributor = Contributor.objects.get(user=request.user) + contributor = request.user.contributor report_to_vet = (Report.objects.awaiting_vetting() .select_related('submission') - .filter(submission__editor_in_charge=contributor).first()) + .filter(submission__editor_in_charge=contributor) + .order_by('date_submitted').first()) form = VetReportForm(request.POST or None, initial={'report': report_to_vet}) if form.is_valid(): @@ -1088,9 +1202,17 @@ def vet_submitted_reports(request): SubmissionUtils.load({'report': report, 'email_response': form.cleaned_data['email_response_field']}) SubmissionUtils.acknowledge_report_email() # email report author, bcc EIC + + # Add SubmissionEvent for the EIC + report.submission.add_event_for_eic('The Report by %s is vetted.' + % report.author.user.last_name) + if report.status == STATUS_VETTED: SubmissionUtils.send_author_report_received_email() + # Add SubmissionEvent to tell the author about the new report + report.submission.add_event_for_author('A new Report has been submitted.') + message = 'Submitted Report vetted for <a href="%s">%s</a>.' % ( reverse('submissions:editorial_page', args=(report.submission.arxiv_identifier_w_vn_nr,)), @@ -1125,6 +1247,11 @@ def prepare_for_voting(request, rec_id): recommendation.submission.status = 'put_to_EC_voting' recommendation.submission.save() messages.success(request, 'We have registered your selection.') + + # Add SubmissionEvents + recommendation.submission.add_event_for_eic('The Editorial Recommendation has been ' + 'put forward to the College for voting.') + return redirect(reverse('submissions:editorial_page', args=[recommendation.submission.arxiv_identifier_w_vn_nr])) else: @@ -1231,27 +1358,40 @@ def fix_College_decision(request, rec_id): """ recommendation = get_object_or_404((EICRecommendation.objects .get_for_user_in_pool(request.user)), pk=rec_id) + submission = recommendation.submission if recommendation.recommendation in [1, 2, 3]: # Publish as Tier I, II or III - recommendation.submission.status = 'accepted' - recommendation.submission.acceptance_date = datetime.date.today() + submission.status = 'accepted' + submission.acceptance_date = datetime.date.today() + # Create a ProductionStream object - prodstream = ProductionStream(submission=recommendation.submission) + prodstream = ProductionStream(submission=submission) prodstream.save() + + # Add SubmissionEvent for authors + # Do not write a new event for minor/major modification: already done at moment of + # creation. + submission.add_event_for_author('An Editorial Recommendation has been formulated: %s.' + % recommendation.get_recommendation_display()) elif recommendation.recommendation == -3: - # Reject - recommendation.submission.status = 'rejected' - previous_submissions = Submission.objects.filter( - arxiv_identifier_wo_vn_nr=recommendation.submission.arxiv_identifier_wo_vn_nr - ).exclude(pk=recommendation.submission.id) - for sub in previous_submissions: + # Reject + update-reject other versions of submission + submission.status = 'rejected' + for sub in submission.other_versions: sub.status = 'resubmitted_rejected' sub.save() - recommendation.submission.save() - SubmissionUtils.load({'submission': recommendation.submission, - 'recommendation': recommendation}) + # Add SubmissionEvent for authors + # Do not write a new event for minor/major modification: already done at moment of + # creation. + submission.add_event_for_author('An Editorial Recommendation has been formulated: %s.' + % recommendation.get_recommendation_display()) + + # Add SubmissionEvent for EIC + submission.add_event_for_eic('The Editorial College\'s decision has been fixed: %s.' + % recommendation.get_recommendation_display()) + + submission.save() + SubmissionUtils.load({'submission': submission, 'recommendation': recommendation}) SubmissionUtils.send_author_College_decision_email() - ack_message = 'The Editorial College\'s decision has been fixed.' - return render(request, 'scipost/acknowledgement.html', - context={'ack_message': ack_message}) + messages.success(request, 'The Editorial College\'s decision has been fixed.') + return redirect(reverse('submissions:pool'))