diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py index 8fd730d747a36290c629701311008135a53c02a9..b2d50e8e7c3aceae9f9eb0667aab921949511065 100644 --- a/SciPost_v1/settings/base.py +++ b/SciPost_v1/settings/base.py @@ -98,6 +98,7 @@ INSTALLED_APPS = ( 'production', 'partners', 'funders', + 'stats', 'webpack_loader', ) @@ -113,7 +114,7 @@ HAYSTACK_CONNECTIONS = { # Brute force automatically re-index Haystack using post_save signals on all models. # When write-traffic increases, a custom processor is preferred which only connects # signals to eg. `vet-accepted` signals possibly using cron jobs instead of realtime updates. -HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' +HAYSTACK_SIGNAL_PROCESSOR = 'SciPost_v1.signalprocessors.AutoSearchIndexingProcessor' SPHINXDOC_BASE_TEMPLATE = 'scipost/base.html' diff --git a/SciPost_v1/signalprocessors.py b/SciPost_v1/signalprocessors.py new file mode 100644 index 0000000000000000000000000000000000000000..8d74cb8efb8bd2d42b6803518eddf3944846c7de --- /dev/null +++ b/SciPost_v1/signalprocessors.py @@ -0,0 +1,34 @@ +from haystack import signals +from haystack.exceptions import NotHandled + +from submissions.models import Submission + + +class AutoSearchIndexingProcessor(signals.RealtimeSignalProcessor): + def prepare_submission_indexing(self, sender, submissions): + """ + Given an individual model instance, determine which backends the + update should be sent to & update the object on those backends. + """ + try: + using_backends = self.connection_router.for_write(instance=submissions[0]) + except IndexError: + # No submissions given, stop processing here + return None + + for instance in submissions: + for using in using_backends: + try: + index = self.connections[using].get_unified_index().get_index(sender) + index.remove_object(instance, using=using) + except NotHandled: + # TODO: Maybe log it or let the exception bubble? + pass + + def handle_save(self, sender, instance, **kwargs): + if isinstance(instance, Submission): + # Submission have complex status handling, so a status change should lead to + # more drastic reindexing. + self.prepare_submission_indexing(sender, [instance]) + self.prepare_submission_indexing(sender, instance.other_versions) + super().handle_save(sender, instance, **kwargs) diff --git a/SciPost_v1/urls.py b/SciPost_v1/urls.py index ba767eb8166b0f60df1e78d25b141bf652b53732..aabec88b1b1947e7d1087ee37e9d4ac3734646c2 100644 --- a/SciPost_v1/urls.py +++ b/SciPost_v1/urls.py @@ -44,7 +44,7 @@ urlpatterns = [ url(r'^notifications/', include('notifications.urls', namespace="notifications")), url(r'^production/', include('production.urls', namespace="production")), url(r'^partners/', include('partners.urls', namespace="partners")), - + url(r'^stats/', include('stats.urls', namespace="stats")), # Keep temporarily for historical reasons url(r'^supporting_partners/', include('partners.urls', namespace="_partners")), ] diff --git a/comments/migrations/0020_comment_doideposit_needs_updating.py b/comments/migrations/0020_comment_doideposit_needs_updating.py new file mode 100644 index 0000000000000000000000000000000000000000..1adcc31e75acd94e91bf853495e6df830f122894 --- /dev/null +++ b/comments/migrations/0020_comment_doideposit_needs_updating.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-09-10 12:17 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comments', '0019_auto_20170909_1649'), + ] + + operations = [ + migrations.AddField( + model_name='comment', + name='doideposit_needs_updating', + field=models.BooleanField(default=False), + ), + ] diff --git a/comments/models.py b/comments/models.py index 06225901ff8030dadb2293710432eae288b6d146..5410c48ffe73fc3ae9b174b69216fc497bb964e3 100644 --- a/comments/models.py +++ b/comments/models.py @@ -38,8 +38,6 @@ class Comment(TimeStampedModel): content_object = GenericForeignKey() nested_comments = GenericRelation('comments.Comment', related_query_name='comments') - genericdoideposit = GenericRelation('journals.GenericDOIDeposit', - related_query_name='genericdoideposit') # -- U/S # These fields will be removed in the future. @@ -90,6 +88,9 @@ class Comment(TimeStampedModel): blank=True) needs_doi = models.NullBooleanField(default=None) + doideposit_needs_updating = models.BooleanField(default=False) + genericdoideposit = GenericRelation('journals.GenericDOIDeposit', + related_query_name='genericdoideposit') doi_label = models.CharField(max_length=200, blank=True) objects = CommentQuerySet.as_manager() diff --git a/comments/search_indexes.py b/comments/search_indexes.py index 3ccccda8f8bf33b3eb77de14c759c08ef86b33ef..23575385c3b38dd73218f0abdd2015508eb1b03c 100644 --- a/comments/search_indexes.py +++ b/comments/search_indexes.py @@ -7,6 +7,7 @@ from .models import Comment class CommentIndex(indexes.SearchIndex, indexes.Indexable): text = indexes.CharField(document=True, model_attr='comment_text', use_template=True) + authors = indexes.CharField(model_attr='author') date = indexes.DateTimeField(model_attr='date_submitted') def get_model(self): diff --git a/journals/migrations/0044_publication_doideposit_needs_updating.py b/journals/migrations/0044_publication_doideposit_needs_updating.py new file mode 100644 index 0000000000000000000000000000000000000000..e602a8820bb05d3eec81c5264b1b3af105d7269e --- /dev/null +++ b/journals/migrations/0044_publication_doideposit_needs_updating.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-09-10 12:54 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0043_auto_20170909_1649'), + ] + + operations = [ + migrations.AddField( + model_name='publication', + name='doideposit_needs_updating', + field=models.BooleanField(default=False), + ), + ] diff --git a/journals/models.py b/journals/models.py index 0adcaf9eb6fa054bde303b5f11348c022551f8bf..4dc87d39843fc4957040606eb819aaf8259f3712 100644 --- a/journals/models.py +++ b/journals/models.py @@ -155,6 +155,7 @@ class Publication(models.Model): BiBTeX_entry = models.TextField(blank=True, null=True) doi_label = models.CharField(max_length=200, unique=True, db_index=True, validators=[doi_publication_validator]) + doideposit_needs_updating = models.BooleanField(default=False) submission_date = models.DateField(verbose_name='submission date') acceptance_date = models.DateField(verbose_name='acceptance date') publication_date = models.DateField(verbose_name='publication date') diff --git a/journals/templates/journals/manage_comment_metadata.html b/journals/templates/journals/manage_comment_metadata.html index aebd9cbcd8a5f82a50d751837c9ed48511775c57..efd231d30dac48b5caf5cf6addf0c04f7f0663c8 100644 --- a/journals/templates/journals/manage_comment_metadata.html +++ b/journals/templates/journals/manage_comment_metadata.html @@ -28,6 +28,7 @@ event: "focusin" <th>Comment</th> <th>Needs doi</th> <th>Latest successful Crossref deposit</th> + <th>Deposit needs updating?</th> </tr> </thead> @@ -37,6 +38,7 @@ event: "focusin" <td>{{ comment }}</td> <td>{{ comment.needs_doi }}</td> <td>{{ comment|latest_successful_crossref_deposit_comment }}</td> + <td>{{ comment.doideposit_needs_updating }}</td> </tr> <tr id="collapse{{ comment.id }}" class="collapse" role="tabpanel" aria-labelledby="heading{{ comment.id }}" style="background-color: #fff;"> <td colspan="5"> diff --git a/journals/templates/journals/manage_metadata.html b/journals/templates/journals/manage_metadata.html index 7025abe2757158aeed4e78e5dce2144ecc013401..56f960111f3f71caae0111c8e6ca43e3a8150e75 100644 --- a/journals/templates/journals/manage_metadata.html +++ b/journals/templates/journals/manage_metadata.html @@ -46,6 +46,7 @@ event: "focusin" <th>Publication date</th> <th>Latest metadata update</th> <th>Latest successful Crossref deposit</th> + <th>Deposit needs updating?</th> <th>DOAJ</th> </tr> </thead> @@ -63,10 +64,11 @@ event: "focusin" <td>No info available</td> {% endif %} <td>{{ publication|latest_successful_crossref_deposit }}</td> + <td>{{ publication.doideposit_needs_updating }}</td> <td>{{ publication|latest_successful_DOAJ_deposit }}</td> </tr> <tr id="collapse{{ publication.id }}" class="collapse" role="tabpanel" aria-labelledby="heading{{ publication.id }}" style="background-color: #fff;"> - <td colspan="5"> + <td colspan="6"> <h2 class="ml-3">Actions</h2> <div class="row"> @@ -174,7 +176,7 @@ event: "focusin" </tr> {% empty %} <tr> - <td colspan="5">No Deposits found for this publication</td> + <td colspan="6">No Deposits found for this publication</td> </tr> {% endfor %} </tbody> @@ -203,7 +205,7 @@ event: "focusin" </tr> {% empty %} <tr> - <td colspan="4">No Deposits found for this publication</td> + <td colspan="6">No Deposits found for this publication</td> </tr> {% endfor %} </tbody> @@ -213,7 +215,7 @@ event: "focusin" </tr> {% empty %} <tr> - <td colspan="4">No publications found.</td> + <td colspan="6">No publications found.</td> </tr> {% endfor %} </tbody> diff --git a/journals/templates/journals/manage_report_metadata.html b/journals/templates/journals/manage_report_metadata.html index 16fc0d726a9ca9e3a15bb4b682140828225ae015..cd2dce24d5ebcee24ff2c2c2860dec19c8319f3e 100644 --- a/journals/templates/journals/manage_report_metadata.html +++ b/journals/templates/journals/manage_report_metadata.html @@ -29,6 +29,7 @@ event: "focusin" <th>Report nr</th> <th>Needs doi</th> <th>Latest successful Crossref deposit</th> + <th>Deposit needs updating?</th> </tr> </thead> @@ -39,6 +40,7 @@ event: "focusin" <td>{{ report.report_nr }}</td> <td>{{ report.needs_doi }}</td> <td>{{ report|latest_successful_crossref_deposit_report }}</td> + <td>{{ report.doideposit_needs_updating }}</td> </tr> <tr id="collapse{{ report.id }}" class="collapse" role="tabpanel" aria-labelledby="heading{{ report.id }}" style="background-color: #fff;"> <td colspan="5"> diff --git a/journals/templates/journals/sign_existing_report.html b/journals/templates/journals/sign_existing_report.html new file mode 100644 index 0000000000000000000000000000000000000000..99b8db35f47f12abc642bc14ddad4f0b96eb5a78 --- /dev/null +++ b/journals/templates/journals/sign_existing_report.html @@ -0,0 +1,33 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: sign existing Report{% endblock pagetitle %} + +{% block content %} + +<div class="row"> + <div class="col-12"> + <hr class="hr12"> + <div class="row"> + <div class="col-6"> + <h2>Confirmation page: do you wish to sign this Report?</h2> + <h3>(your Report is reproduced below for your convenience)</h3> + </div> + <div class="col-6"> + <form action="{% url 'journals:sign_existing_report' report_id=report.id %}" method="post"> + {% csrf_token %} + {{ form|bootstrap }} + <input class="btn btn-secondary" type="submit" value="Submit" /> + </form> + </div> + </div> + + <h3>Report on Submission <a href="{{report.submission.get_absolute_url}}">{{report.submission.title}}</a></h3> + + {% include 'submissions/_single_public_report_without_comments.html' with report=report user=request.user perms=perms %} + + </div> +</div> + +{% endblock %} diff --git a/journals/urls/general.py b/journals/urls/general.py index 163b3735fef156fa6a8c08288c0441d43fc2c81b..5570b662718dd4458c5d43ddd33f02efb8df178d 100644 --- a/journals/urls/general.py +++ b/journals/urls/general.py @@ -86,6 +86,9 @@ urlpatterns = [ url(r'^harvest_citedby_links/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})$', journals_views.harvest_citedby_links, name='harvest_citedby_links'), + url(r'^sign_existing_report/(?P<report_id>[0-9]+)$', + journals_views.sign_existing_report, + name='sign_existing_report'), url(r'^manage_report_metadata/$', journals_views.manage_report_metadata, name='manage_report_metadata'), diff --git a/journals/views.py b/journals/views.py index 323702d0592277e74ecd59fc40f8c704d30612df..fb5245c315d70fd7a832e0720739ad1e19e835fc 100644 --- a/journals/views.py +++ b/journals/views.py @@ -6,6 +6,7 @@ import requests import string import xml.etree.ElementTree as ET +from django.contrib.auth.decorators import login_required from django.contrib.contenttypes.models import ContentType from django.core.urlresolvers import reverse from django.core.files.base import ContentFile @@ -31,6 +32,7 @@ from submissions.models import Submission, Report from scipost.models import Contributor from funders.forms import FunderSelectForm, GrantSelectForm +from scipost.forms import ConfirmationForm from guardian.decorators import permission_required @@ -937,6 +939,30 @@ def harvest_citedby_links(request, doi_label): return render(request, 'journals/harvest_citedby_links.html', context) +@login_required +def sign_existing_report(request, report_id): + """ + Allows the author of a Report, originally submitted anonymously, + to sign the Report. + """ + report = get_object_or_404(Report, pk=report_id) + if report.author != request.user.contributor: + errormessage = 'Only the author of this Report can change its anonymity status' + return render(request, 'scipost/error.html', context={'errormessage': errormessage}) + form = ConfirmationForm(request.POST or None) + if form.is_valid(): + if form.cleaned_data['confirm'] == 'True': + report.anonymous = False + report.doideposit_needs_updating = True + report.save() + messages.success(request, 'Your Report is now publicly signed.') + else: + messages.error(request, 'Report signing operation cancelled.') + return redirect(reverse('scipost:personal_page')) + context = {'report': report, 'form': form} + return render(request, 'journals/sign_existing_report.html', context) + + @permission_required('scipost.can_publish_accepted_submission', return_403=True) def manage_report_metadata(request): """ @@ -1073,6 +1099,8 @@ def mark_generic_deposit_success(request, deposit_id, success): deposit = get_object_or_404(GenericDOIDeposit, pk=deposit_id) if success == '1': deposit.deposit_successful = True + deposit.content_object.doideposit_needs_updating = False + deposit.content_object.save() elif success == '0': deposit.deposit_successful = False deposit.save() diff --git a/news/migrations/0004_auto_20170913_1339.py b/news/migrations/0004_auto_20170913_1339.py new file mode 100644 index 0000000000000000000000000000000000000000..05a43bd4f778bf77b0ab13ba517eb2ec1a7adc56 --- /dev/null +++ b/news/migrations/0004_auto_20170913_1339.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-09-13 11:39 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0003_auto_20170812_0043'), + ] + + operations = [ + migrations.AlterField( + model_name='newsitem', + name='followup_link', + field=models.URLField(blank=True, default=''), + preserve_default=False, + ), + migrations.AlterField( + model_name='newsitem', + name='followup_link_text', + field=models.CharField(blank=True, default='', max_length=300), + preserve_default=False, + ), + ] diff --git a/news/models.py b/news/models.py index 77637dad968fcaa031addf2307f5fb9efadab477..0ead952d1cbeccba08572b13757b529a68c43e73 100644 --- a/news/models.py +++ b/news/models.py @@ -1,3 +1,4 @@ +from django.core.urlresolvers import reverse from django.db import models from .managers import NewsManager @@ -7,8 +8,8 @@ class NewsItem(models.Model): date = models.DateField() headline = models.CharField(max_length=300) blurb = models.TextField() - followup_link = models.URLField(blank=True, null=True) - followup_link_text = models.CharField(max_length=300, blank=True, null=True) + followup_link = models.URLField(blank=True) + followup_link_text = models.CharField(max_length=300, blank=True) on_homepage = models.BooleanField(default=True) objects = NewsManager() @@ -19,3 +20,6 @@ class NewsItem(models.Model): def __str__(self): return self.date.strftime('%Y-%m-%d') + ', ' + self.headline + + def get_absolute_url(self): + return reverse('news:news') + '#news_' + str(self.id) diff --git a/scipost/forms.py b/scipost/forms.py index 4c6ce047b4f09de5e982a8ec2c4560dfc9528be7..50e6a5557616ce4364db520a96e53de2719a7679 100644 --- a/scipost/forms.py +++ b/scipost/forms.py @@ -25,7 +25,10 @@ from .models import Contributor, DraftInvitation, RegistrationInvitation,\ from common.forms import MonthYearWidget from partners.decorators import has_contact + +from comments.models import Comment from journals.models import Publication +from submissions.models import Report REGISTRATION_REFUSAL_CHOICES = ( @@ -287,6 +290,26 @@ class UpdatePersonalDataForm(forms.ModelForm): # for _list in original_lists: # _list.update_membership([contributor], status='unsubscribed') + def propagate_orcid(self): + """ + This method is called if a Contributor updates his/her personal data, + and changes the orcid_id. It marks all Publications, Reports and Comments + authors by this Contributor with a deposit_requires_update == True. + """ + publications = Publication.objects.filter(authors=self.instance) + for publication in publications: + publication.doideposit_needs_updating = True + publication.save() + reports = Report.objects.filter(author=self.instance, anonymous=False) + for report in reports: + report.doideposit_needs_updating = True + report.save() + comments = Comment.objects.filter(author=self.instance, anonymous=False) + for comment in comments: + comment.doideposit_needs_updating = True + comment.save() + return + class VetRegistrationForm(forms.Form): decision = forms.ChoiceField(widget=forms.RadioSelect, @@ -500,3 +523,8 @@ class SendPrecookedEmailForm(forms.Form): required=False, initial=False, label='Include SciPost summary at end of message') from_address = forms.ChoiceField(choices=SCIPOST_FROM_ADDRESSES) + + +class ConfirmationForm(forms.Form): + confirm = forms.ChoiceField(widget=forms.RadioSelect, + choices=((True, 'Confirm'), (False, 'Abort'))) diff --git a/scipost/migrations/0065_authorshipclaim_publication.py b/scipost/migrations/0065_authorshipclaim_publication.py new file mode 100644 index 0000000000000000000000000000000000000000..086e6116addd08a9916f7e3dda269cd5866808c0 --- /dev/null +++ b/scipost/migrations/0065_authorshipclaim_publication.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-09-10 13:46 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0044_publication_doideposit_needs_updating'), + ('scipost', '0064_auto_20170909_1649'), + ] + + operations = [ + migrations.AddField( + model_name='authorshipclaim', + name='publication', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='journals.Publication'), + ), + ] diff --git a/scipost/models.py b/scipost/models.py index 31ad4a40f60bb649e49f8819bd5fd17f9a286558..fe7afc37b63adc4dae58d7aeeb6d57e9d42bc4dc 100644 --- a/scipost/models.py +++ b/scipost/models.py @@ -244,9 +244,12 @@ class CitationNotification(models.Model): class AuthorshipClaim(models.Model): - claimant = models.ForeignKey(Contributor, + claimant = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE, related_name='claimant') + publication = models.ForeignKey('journals.Publication', + on_delete=models.CASCADE, + blank=True, null=True) submission = models.ForeignKey('submissions.Submission', on_delete=models.CASCADE, blank=True, null=True) @@ -256,7 +259,7 @@ class AuthorshipClaim(models.Model): thesislink = models.ForeignKey('theses.ThesisLink', on_delete=models.CASCADE, blank=True, null=True) - vetted_by = models.ForeignKey(Contributor, + vetted_by = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE, blank=True, null=True) status = models.SmallIntegerField(choices=AUTHORSHIP_CLAIM_STATUS, diff --git a/scipost/templates/scipost/claim_authorships.html b/scipost/templates/scipost/claim_authorships.html index 2630c6844582f8b7e476ad2063d3884044977a44..8a774d4378e36742c6786cc783f4678f543d93c6 100644 --- a/scipost/templates/scipost/claim_authorships.html +++ b/scipost/templates/scipost/claim_authorships.html @@ -1,4 +1,9 @@ -{% extends 'scipost/base.html' %} +{% extends 'scipost/_personal_page_base.html' %} + +{% block breadcrumb_items %} + {{block.super}} + <span class="breadcrumb-item">Authorship Claims</span> +{% endblock %} {% block pagetitle %}: authorship claims{% endblock pagetitle %} @@ -14,6 +19,34 @@ </div> </div> +{% if publication_authorships_to_claim %} + <div class="row"> + <div class="col-12"> + <h2 class="highlight">SciPost Publications</h2> + <h3>Potential authorships to claim (auto-detected)</h3> + </div> + </div> + <div class="row"> + <div class="col-12"> + {% for pub in publication_authorships_to_claim %} + <div class="card card-grey card-publication" id="{{pub.doi_label}}"> + {% include 'journals/_publication_card_content.html' with publication=pub %} + <div class="card-footer"> + <form class="d-inline-block" action="{% url 'scipost:claim_pub_authorship' publication_id=pub.id claim=1 %}" method="post"> + {% csrf_token %} + <input class="btn btn-secondary" type="submit" value="I am an author" /> + </form> + <form class="d-inline-block ml-1" action="{% url 'scipost:claim_pub_authorship' publication_id=pub.id claim=0 %}" method="post"> + {% csrf_token %} + <input class="btn btn-danger" type="submit" value="I am not an author" /> + </form> + </div> + </div> + {% endfor %} + </div> + </div> +{% endif %} + {% if submission_authorships_to_claim %} <div class="row"> <div class="col-12"> @@ -42,6 +75,7 @@ </div> {% endif %} + {% if commentary_authorships_to_claim %} <div class="row"> <div class="col-12"> @@ -98,7 +132,7 @@ </div> {% endif %} -{% if not submission_authorships_to_claim and not commentary_authorships_to_claim and not thesis_authorships_to_claim %} +{% if not submission_authorships_to_claim and not commentary_authorships_to_claim and not thesis_authorships_to_claim and not publication_authorships_to_claim %} <div class="row"> <div class="col-12"> <h2>You have no authorships to claim</h2> diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html index d8d69f94ae67efdf2fa37ea723c017914d997132..0ff78b8eeff9914108b8d946cbc9e338cc1e7c15 100644 --- a/scipost/templates/scipost/personal_page.html +++ b/scipost/templates/scipost/personal_page.html @@ -48,6 +48,9 @@ <a class="nav-link" data-toggle="tab" href="#refereeing">Refereeing {% if refereeing_tab_total_count %}({{refereeing_tab_total_count}}){% endif %}</a> </li> {% endif %} + <li class="nav-item btn btn-secondary"> + <a class="nav-link" data-toggle="tab" href="#publications">Publications</a> + </li> <li class="nav-item btn btn-secondary"> <a class="nav-link" data-toggle="tab" href="#submissions">Submissions</a> </li> @@ -319,6 +322,7 @@ <h3>Refereeing overview</h3> <ul> <li>View (and act on) outstanding refereeing invitations in the <a href="{% url 'submissions:refereeing_overview' %}">refereeing overview</a></li> + <li><a href="{% url 'stats:statistics' %}">View statistics</a> for submissions, refereeing, publishing</li> </ul> <h3>Voting</h3> <ul> @@ -372,7 +376,49 @@ </div> {% endif %} - {% if perms.scipost.can_referee %} + <!-- Tab: Publications --> + <div class="tab-pane" id="publications" role="tabpanel"> + <div class="row"> + <div class="col-12"> + <div class="card card-grey"> + <div class="card-body"> + <h2 class="card-title">Publications</h2> + <ul class="mb-0"> + {% if nr_publication_authorships_to_claim > 0 %} + <li><a href="{% url 'scipost:claim_authorships' %}">Potential authorships to claim (auto-detected: {{ nr_publication_authorships_to_claim}})</a></li> + {% endif %} + </ul> + </div> + </div> + </div> + </div> + + {# {% if own_publications %}#} + <div class="row" id="mypublicationslist"> + <div class="col-12"> + <h3 class="mb-3">Publications for which you are identified as an author:</h3> + </div> + <div class="col-12"> + <ul class="list-unstyled"> + {% for pub in own_publications %} + <li> + <div class="card card-grey card-publication" id="{{pub.doi_label}}"> + {% include 'journals/_publication_card_content.html' with publication=pub current_user=request.user %} + </div> + </li> + {% empty %} + <li> + <em>No Publications found</em> + </li> + {% endfor %} + </ul> + </div> + </div> + {# {% endif %}#} + </div><!-- End tab --> + + + {% if perms.scipost.can_referee %} <!-- Tab: Refereeing --> <div class="tab-pane" id="refereeing" role="tabpanel"> <div class="row"> @@ -444,8 +490,12 @@ <tr> <th>Status:</th><td {% if report.status == 'vetted' %}class="text-success"{% elif report.status == 'unvetted' %}class="text-danger"{% endif %}>{{report.get_status_display}}</td> </tr> + {% if report.doi_label %} + <tr> + <th>DOI:</th><td>{{ report.doi_string }}</td></th> +{% endif %} <tr> - <th>Anonymous:</th><td>{{report.anonymous|yesno:'Yes,No'}}</td> + <th>Anonymous:</th><td>{{report.anonymous|yesno:'Yes,No'}}</td>{% if report.anonymous %}<td>You can <a href="{% url 'journals:sign_existing_report' report_id=report.id %}">click here to sign this Report</a> (leads to confirmation page){% endif %}</td> </tr> </table> </div> diff --git a/scipost/templates/scipost/vet_authorship_claims.html b/scipost/templates/scipost/vet_authorship_claims.html index b43041b92c80bee7be604c8ae23bed3128da8205..54711cdaa79a973b44afc7c8b825d5830167b1e0 100644 --- a/scipost/templates/scipost/vet_authorship_claims.html +++ b/scipost/templates/scipost/vet_authorship_claims.html @@ -1,5 +1,7 @@ {% extends 'scipost/_personal_page_base.html' %} +{% load bootstrap %} + {% block breadcrumb_items %} {{block.super}} <span class="breadcrumb-item">Vet authorship claims</span> @@ -32,9 +34,14 @@ <ul class="list-group list-group-flush"> {% for claim in claims_to_vet %} - <li class="list-group-item"> + <li class="list-unstyled"> <div class="card w-100"> - {% if claim.submission %} + {% if claim.publication %} + <div class="card-header"> + <h4>Contributor {{ claim.claimant.user.first_name }} {{ claim.claimant.user.last_name }} claims to be an author of Publication:</h4> + </div> + {% include 'journals/_publication_card_content.html' with publication=claim.publication %} + {% elif claim.submission %} <div class="card-header"> <h4>Contributor {{ claim.claimant.user.first_name }} {{ claim.claimant.user.last_name }} claims to be an author of Submission:</h4> </div> diff --git a/scipost/urls.py b/scipost/urls.py index e67452102c333fe77542e5a64e4fc24cf28e22dd..f3af0c4a67bc30f8a0c836d7f4692467359db08a 100644 --- a/scipost/urls.py +++ b/scipost/urls.py @@ -145,6 +145,8 @@ urlpatterns = [ # Authorship claims url(r'^claim_authorships$', views.claim_authorships, name="claim_authorships"), + url(r'^claim_pub_authorship/(?P<publication_id>[0-9]+)/(?P<claim>[0-1])$', + views.claim_pub_authorship, name='claim_pub_authorship'), url(r'^claim_sub_authorship/(?P<submission_id>[0-9]+)/(?P<claim>[0-1])$', views.claim_sub_authorship, name='claim_sub_authorship'), url(r'^claim_com_authorship/(?P<commentary_id>[0-9]+)/(?P<claim>[0-1])$', diff --git a/scipost/views.py b/scipost/views.py index b2a04826966cf778595b70b0fb5fed260f9ead1b..5c09fe3e60b3b05544df1ba5244b9039ef2286a0 100644 --- a/scipost/views.py +++ b/scipost/views.py @@ -752,11 +752,20 @@ def personal_page(request): # Verify if there exist objects authored by this contributor, # whose authorship hasn't been claimed yet + own_publications = (Publication.objects + .filter(authors__in=[contributor]) + .order_by('-publication_date')) own_submissions = (Submission.objects .filter(authors__in=[contributor], is_current=True) .order_by('-submission_date')) own_commentaries = Commentary.objects.filter(authors=contributor).order_by('-latest_activity') own_thesislinks = ThesisLink.objects.filter(author_as_cont__in=[contributor]) + nr_publication_authorships_to_claim = (Publication.objects.filter( + author_list__contains=contributor.user.last_name) + .exclude(authors__in=[contributor]) + .exclude(authors_claims__in=[contributor]) + .exclude(authors_false_claims__in=[contributor]) + .count()) nr_submission_authorships_to_claim = (Submission.objects.filter( author_list__contains=contributor.user.last_name) .exclude(authors__in=[contributor]) @@ -799,12 +808,14 @@ def personal_page(request): 'nr_recommendations_to_prepare_for_voting': nr_recommendations_to_prepare_for_voting, 'nr_assignments_to_consider': nr_assignments_to_consider, 'active_assignments': active_assignments, + 'nr_publication_authorships_to_claim': nr_publication_authorships_to_claim, 'nr_submission_authorships_to_claim': nr_submission_authorships_to_claim, 'nr_commentary_authorships_to_claim': nr_commentary_authorships_to_claim, 'nr_thesis_authorships_to_claim': nr_thesis_authorships_to_claim, 'nr_ref_inv_to_consider': nr_ref_inv_to_consider, 'pending_ref_tasks': pending_ref_tasks, 'refereeing_tab_total_count': refereeing_tab_total_count, + 'own_publications': own_publications, 'own_submissions': own_submissions, 'own_commentaries': own_commentaries, 'own_thesislinks': own_thesislinks, @@ -871,6 +882,8 @@ def _update_personal_data_contributor(request): user_form.save() cont_form.save() cont_form.sync_lists() + if 'orcid_id' in cont_form.changed_data: + cont_form.propagate_orcid() messages.success(request, 'Your personal data has been updated.') return redirect(reverse('scipost:personal_page')) else: @@ -898,6 +911,12 @@ def claim_authorships(request): """ contributor = Contributor.objects.get(user=request.user) + publication_authorships_to_claim = (Publication.objects + .filter(author_list__contains=contributor.user.last_name) + .exclude(authors__in=[contributor]) + .exclude(authors_claims__in=[contributor]) + .exclude(authors_false_claims__in=[contributor])) + pub_auth_claim_form = AuthorshipClaimForm() submission_authorships_to_claim = (Submission.objects .filter(author_list__contains=contributor.user.last_name) .exclude(authors__in=[contributor]) @@ -917,16 +936,35 @@ def claim_authorships(request): .exclude(author_false_claims__in=[contributor])) thesis_auth_claim_form = AuthorshipClaimForm() - context = {'submission_authorships_to_claim': submission_authorships_to_claim, - 'sub_auth_claim_form': sub_auth_claim_form, - 'commentary_authorships_to_claim': commentary_authorships_to_claim, - 'com_auth_claim_form': com_auth_claim_form, - 'thesis_authorships_to_claim': thesis_authorships_to_claim, - 'thesis_auth_claim_form': thesis_auth_claim_form, - } + context = { + 'publication_authorships_to_claim': publication_authorships_to_claim, + 'pub_auth_claim_form': pub_auth_claim_form, + 'submission_authorships_to_claim': submission_authorships_to_claim, + 'sub_auth_claim_form': sub_auth_claim_form, + 'commentary_authorships_to_claim': commentary_authorships_to_claim, + 'com_auth_claim_form': com_auth_claim_form, + 'thesis_authorships_to_claim': thesis_authorships_to_claim, + 'thesis_auth_claim_form': thesis_auth_claim_form, + } return render(request, 'scipost/claim_authorships.html', context) +@login_required +@user_passes_test(has_contributor) +def claim_pub_authorship(request, publication_id, claim): + if request.method == 'POST': + contributor = Contributor.objects.get(user=request.user) + publication = get_object_or_404(Publication, pk=publication_id) + if claim == '1': + publication.authors_claims.add(contributor) + newclaim = AuthorshipClaim(claimant=contributor, publication=publication) + newclaim.save() + elif claim == '0': + publication.authors_false_claims.add(contributor) + publication.save() + return redirect('scipost:claim_authorships') + + @login_required @user_passes_test(has_contributor) def claim_sub_authorship(request, submission_id, claim): @@ -988,6 +1026,15 @@ def vet_authorship_claim(request, claim_id, claim): vetting_contributor = Contributor.objects.get(user=request.user) claim_to_vet = AuthorshipClaim.objects.get(pk=claim_id) + if claim_to_vet.publication is not None: + claim_to_vet.publication.authors_claims.remove(claim_to_vet.claimant) + if claim == '1': + claim_to_vet.publication.authors.add(claim_to_vet.claimant) + claim_to_vet.status = '1' + elif claim == '0': + claim_to_vet.publication.authors_false_claims.add(claim_to_vet.claimant) + claim_to_vet.status = '-1' + claim_to_vet.publication.save() if claim_to_vet.submission is not None: claim_to_vet.submission.authors_claims.remove(claim_to_vet.claimant) if claim == '1': @@ -996,7 +1043,7 @@ def vet_authorship_claim(request, claim_id, claim): elif claim == '0': claim_to_vet.submission.authors_false_claims.add(claim_to_vet.claimant) claim_to_vet.status = '-1' - claim_to_vet.submission.save() + claim_to_vet.submission.save() if claim_to_vet.commentary is not None: claim_to_vet.commentary.authors_claims.remove(claim_to_vet.claimant) if claim == '1': @@ -1005,7 +1052,7 @@ def vet_authorship_claim(request, claim_id, claim): elif claim == '0': claim_to_vet.commentary.authors_false_claims.add(claim_to_vet.claimant) claim_to_vet.status = '-1' - claim_to_vet.commentary.save() + claim_to_vet.commentary.save() if claim_to_vet.thesislink is not None: claim_to_vet.thesislink.author_claims.remove(claim_to_vet.claimant) if claim == '1': @@ -1014,7 +1061,7 @@ def vet_authorship_claim(request, claim_id, claim): elif claim == '0': claim_to_vet.thesislink.author_false_claims.add(claim_to_vet.claimant) claim_to_vet.status = '-1' - claim_to_vet.thesislink.save() + claim_to_vet.thesislink.save() claim_to_vet.vetted_by = vetting_contributor claim_to_vet.save() diff --git a/stats/__init__.py b/stats/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/stats/admin.py b/stats/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e --- /dev/null +++ b/stats/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/stats/apps.py b/stats/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..2d09b92ca1ba59e93693ada2c0c0dd7340a9df15 --- /dev/null +++ b/stats/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class StatsConfig(AppConfig): + name = 'stats' diff --git a/stats/migrations/__init__.py b/stats/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/stats/models.py b/stats/models.py new file mode 100644 index 0000000000000000000000000000000000000000..71a836239075aa6e6e4ecb700e9c42c95c022d91 --- /dev/null +++ b/stats/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/stats/templates/stats/statistics.html b/stats/templates/stats/statistics.html new file mode 100644 index 0000000000000000000000000000000000000000..afe2e43169338f2304e354f5c728868913baa7e2 --- /dev/null +++ b/stats/templates/stats/statistics.html @@ -0,0 +1,114 @@ +{% extends 'submissions/_pool_base.html' %} + +{% block pagetitle %}: statistics for submisisons{% endblock pagetitle %} + +{% load scipost_extras %} +{% load stats_extras %} + +{% load bootstrap %} + +{% block breadcrumb_items %} + {{block.super}} + <span class="breadcrumb-item">Statistics</span> +{% endblock %} + +{% block content %} + +<h1 class="highlight">Statistics</h1> + +<h2>Aggregate statistics</h2> +<p>For each Journal, clicking on a year gives aggregate statistics for Submissions (so including rejections and withdrawals) which were first submitted in that year.</p> +<p>Clicking on a Journal/Volume/Issue gives aggregates for all publications in that object.</p> +<ul> + {% for journal in journals|dictsort:"doi_label" %} + <li><a href="{% url 'stats:statistics' journal_doi_label=journal.doi_label %}">{{ journal }}</a></li> + <ul> + {% for year in journal|journal_publication_years %} + <li><a href="{% url 'stats:statistics' journal_doi_label=journal.doi_label year=year %}">{{ year }}</a></li> + {% endfor %} + </ul> + <ul> +{% for volume in journal.volume_set.all|dictsort:"number" %} +<li><a href="{% url 'stats:statistics' journal_doi_label=journal.doi_label volume_nr=volume.number %}">{{ volume }}</a></li> +<ul> + {% for issue in volume.issue_set.all|dictsort:"number" %} + <li><a href="{% url 'stats:statistics' journal_doi_label=journal.doi_label volume_nr=volume.number issue_nr=issue.number %}">{{ issue }}</a></li> + {% endfor %} +</ul> +{% endfor %} + </ul> + {% endfor %} +</ul> + +{% if journal %} + <h2>Results:</h2> + <table class="table"> + <tr> + <th>DOI label</th> + {% if year %} + <th>Year</th> + <th>Nr submissions<br/>(distinct)</th> + <th>Nr submissions<br/>(including resubmissions)</th> + <th>Nr assignment failed</th> + <th>Nr accepted/<br/>published</th> + <th>Nr rejected</th> + <th>Nr withdrawn</th> + {% else %} + <th>Nr publications</th> + <th>Duration average</th> + {% endif %} + </tr> + <tr> + {% if issue %} + <td>{{ issue.doi_label }}</td> + <td>{{ issue|issue_nr_publications }}</td> + <td>{{ issue|issue_avg_processing_duration|floatformat:2 }} days</td> + {% elif volume %} + <td>{{ volume.doi_label }}</td> + <td>{{ volume|volume_nr_publications }}</td> + <td>{{ volume|volume_avg_processing_duration|floatformat:2 }} days</td> + {% else %} + <td>{{ journal.doi_label }}</td> + {% if year %} + <td>{{ year }}</td> + <td>{{ submissions|submissions_count_distinct }}</td> + <td>{{ submissions|length }}</td> + <td>{{ submissions.assignment_failed.count }}</td> + <td>{{ submissions.accepted.count|add:submissions.published.count }}</td> + <td>{{ submissions.rejected.count }}</td> + <td>{{ submissions.withdrawn.count }}</td> + {% else %} + <td>{{ journal|journal_nr_publications }}</td> + <td>{{ journal|journal_avg_processing_duration|floatformat:2 }} days</td> + {% endif %} + {% endif %} + </tr> + </table> + {% endif %} + + {% if year %} + <h2>Refereeing stats for {{ year }}</h2> + <table class="table"> + <tr> + <th>Nr refereeing<br/>invitations</th> + <th>Nr accepted</th> + <th>Nr declined</th> + <th>Nr pending</th> + <th>Nr reports<br/>obtained</th> + <th>Nr obtained<br/>(invited)</th> + <th>Nr obtained<br/>(contributed)</th> + </tr> + <tr> + <td>{{ nr_ref_inv }}</td> + <td>{{ nr_acc }} ({% widthratio nr_acc nr_ref_inv 100 %}%)</td> + <td>{{ nr_dec }} ({% widthratio nr_dec nr_ref_inv 100 %}%)</td> + <td>{{ nr_pen }} ({% widthratio nr_pen nr_ref_inv 100 %}%)</td> + <td>{{ nr_rep_obt }}</td> + <td>{{ nr_rep_obt_inv }} ({% widthratio nr_rep_obt_inv nr_rep_obt 100 %}%)</td> + <td>{{ nr_rep_obt_con }} ({% widthratio nr_rep_obt_con nr_rep_obt 100 %}%)</td> + </tr> + </table> + {% endif %} + + +{% endblock content %} diff --git a/stats/templatetags/stats_extras.py b/stats/templatetags/stats_extras.py new file mode 100644 index 0000000000000000000000000000000000000000..9c5c6e636f86716021b322f4c66afe2090b2a294 --- /dev/null +++ b/stats/templatetags/stats_extras.py @@ -0,0 +1,69 @@ +from django import template +from django.db.models import Avg, F + +from journals.models import Publication +from submissions.constants import SUBMISSION_STATUS_OUT_OF_POOL +from submissions.models import Submission + +register = template.Library() + + + +@register.filter(name='submissions_count_distinct') +def submissions_count_distinct(submissions): + identifiers_wo_vn_nr = [] + for submission in submissions: + if submission.arxiv_identifier_wo_vn_nr not in identifiers_wo_vn_nr: + identifiers_wo_vn_nr.append(submission.arxiv_identifier_wo_vn_nr) + return len(identifiers_wo_vn_nr) + + +@register.filter(name='journal_publication_years') +def journal_publication_years(journal): + years = [] + for volume in journal.volume_set.all(): + if volume.until_date.year not in years: + years.append(volume.until_date.year) + return sorted(years) + + +@register.filter(name='journal_nr_publications') +def journal_nr_publications(journal): + return Publication.objects.filter(in_issue__in_volume__in_journal=journal).count() + + +@register.filter(name='journal_avg_processing_duration') +def journal_avg_processing_duration(journal): + duration = Publication.objects.filter( + in_issue__in_volume__in_journal=journal).aggregate( + avg=Avg(F('publication_date') - F('submission_date')))['avg'] + if not duration: return 0 + return duration.days + duration.seconds/86400 + + +@register.filter(name='volume_nr_publications') +def volume_nr_publications(volume): + return Publication.objects.filter(in_issue__in_volume=volume).count() + + +@register.filter(name='volume_avg_processing_duration') +def volume_avg_processing_duration(volume): + duration = Publication.objects.filter( + in_issue__in_volume=volume).aggregate( + avg=Avg(F('publication_date') - F('submission_date')))['avg'] + if not duration: return 0 + return duration.days + duration.seconds/86400 + + +@register.filter(name='issue_nr_publications') +def issue_nr_publications(issue): + return Publication.objects.filter(in_issue=issue).count() + + +@register.filter(name='issue_avg_processing_duration') +def issue_avg_processing_duration(issue): + duration = Publication.objects.filter( + in_issue=issue).aggregate( + avg=Avg(F('publication_date') - F('submission_date')))['avg'] + if not duration: return 0 + return duration.days + duration.seconds/86400 diff --git a/stats/tests.py b/stats/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6 --- /dev/null +++ b/stats/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/stats/urls.py b/stats/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..cddb6d0f7376ab3143acf99f2937b93c09dd8282 --- /dev/null +++ b/stats/urls.py @@ -0,0 +1,14 @@ +from django.conf.urls import url + +from . import views + +urlpatterns = [ + url(r'^statistics/(?P<journal_doi_label>[a-zA-Z]+)/(?P<volume_nr>[0-9]+)/(?P<issue_nr>[0-9]+)$', + views.statistics, name='statistics'), + url(r'^statistics/(?P<journal_doi_label>[a-zA-Z]+)/(?P<volume_nr>[0-9]+)$', + views.statistics, name='statistics'), + url(r'^statistics/(?P<journal_doi_label>[a-zA-Z]+)$', views.statistics, name='statistics'), + url(r'^statistics/(?P<journal_doi_label>[a-zA-Z]+)/year/(?P<year>[0-9]{4,})$', + views.statistics, name='statistics'), + url(r'^statistics$', views.statistics, name='statistics'), +] diff --git a/stats/views.py b/stats/views.py new file mode 100644 index 0000000000000000000000000000000000000000..3b3ac22ac7d4c29fcb6271d184c53ddc905e1210 --- /dev/null +++ b/stats/views.py @@ -0,0 +1,57 @@ +import datetime + +from django.contrib.auth.decorators import login_required, permission_required +from django.shortcuts import get_object_or_404, render + +from journals.constants import SCIPOST_JOURNALS_SUBMIT +from journals.models import Journal, Volume, Issue, Publication +from submissions.models import Submission + + +@permission_required('scipost.can_oversee_refereeing', raise_exception=True) +def statistics(request, journal_doi_label=None, volume_nr=None, issue_nr=None, year=None): + journals = Journal.objects.all() + context = { + 'journals': journals, + } + if journal_doi_label: + journal = get_object_or_404(Journal, doi_label=journal_doi_label) + context['journal'] = journal + if year: + context['year'] = year + submissions = Submission.objects.filter(submitted_to_journal=journal_doi_label + ).originally_submitted(datetime.date(int(year), 1, 1), datetime.date(int(year), 12, 31)) + context['submissions'] = submissions + nr_ref_inv = 0 + nr_acc = 0 + nr_dec = 0 + nr_pen = 0 + nr_rep_obt = 0 + nr_rep_obt_inv = 0 + nr_rep_obt_con = 0 + nr_rep_ref = 0 + nr_aw_vet = 0 + for submission in submissions: + nr_ref_inv += submission.referee_invitations.count() + nr_acc += submission.count_accepted_invitations() + nr_dec += submission.count_declined_invitations() + nr_pen += submission.count_pending_invitations() + nr_rep_obt += submission.count_obtained_reports() + nr_rep_obt_inv += submission.count_invited_reports() + nr_rep_obt_con += submission.count_contrib_reports() + context['nr_ref_inv'] = nr_ref_inv + context['nr_acc'] = nr_acc + context['nr_dec'] = nr_dec + context['nr_pen'] = nr_pen + context['nr_rep_obt'] = nr_rep_obt + context['nr_rep_obt_inv'] = nr_rep_obt_inv + context['nr_rep_obt_con'] = nr_rep_obt_con + if volume_nr: + volume = get_object_or_404(Volume, in_journal=journal, + number=volume_nr) + context['volume'] = volume + if issue_nr: + issue = get_object_or_404(Issue, in_volume=volume, + number=issue_nr) + context['issue'] = issue + return render(request, 'stats/statistics.html', context) diff --git a/submissions/constants.py b/submissions/constants.py index 943de541f9e517ddce9b8ceb90bbaebda95e9cf5..5252e752e27ddad562a456d07f9893ef681cef8f 100644 --- a/submissions/constants.py +++ b/submissions/constants.py @@ -2,6 +2,7 @@ from journals.constants import SCIPOST_JOURNAL_PHYSICS STATUS_UNASSIGNED = 'unassigned' +STATUS_ASSIGNMENT_FAILED = 'assignment_failed' STATUS_RESUBMISSION_INCOMING = 'resubmitted_incoming' STATUS_REVISION_REQUESTED = 'revision_requested' STATUS_EIC_ASSIGNED = 'EICassigned' @@ -14,10 +15,15 @@ STATUS_REJECTED_VISIBLE = 'rejected_visible' STATUS_RESUBMITTED = 'resubmitted' STATUS_RESUBMITTED_REJECTED = 'resubmitted_and_rejected' STATUS_RESUBMITTED_REJECTED_VISIBLE = 'resubmitted_and_rejected_visible' +STATUS_VOTING_IN_PREPARATION = 'voting_in_preparation' +STATUS_PUT_TO_EC_VOTING = 'put_to_EC_voting' +STATUS_EC_VOTE_COMPLETED = 'EC_vote_completed' +STATUS_WITHDRAWN = 'withdrawn' + SUBMISSION_STATUS = ( (STATUS_UNASSIGNED, 'Unassigned, undergoing pre-screening'), (STATUS_RESUBMISSION_INCOMING, 'Resubmission incoming'), - ('assignment_failed', 'Failed to assign Editor-in-charge; manuscript rejected'), + (STATUS_ASSIGNMENT_FAILED, 'Failed to assign Editor-in-charge; manuscript rejected'), (STATUS_EIC_ASSIGNED, 'Editor-in-charge assigned, manuscript under review'), (STATUS_REVIEW_CLOSED, 'Review period closed, editorial recommendation pending'), # If revisions required: resubmission creates a new Submission object @@ -27,22 +33,22 @@ SUBMISSION_STATUS = ( (STATUS_RESUBMITTED_REJECTED_VISIBLE, 'Has been resubmitted and subsequently rejected (still publicly visible)'), # If acceptance/rejection: - ('voting_in_preparation', 'Voting in preparation (eligible Fellows being selected)'), - ('put_to_EC_voting', 'Undergoing voting at the Editorial College'), + (STATUS_VOTING_IN_PREPARATION, 'Voting in preparation (eligible Fellows being selected)'), + (STATUS_PUT_TO_EC_VOTING, 'Undergoing voting at the Editorial College'), (STATUS_AWAITING_ED_REC, 'Awaiting Editorial Recommendation'), - ('EC_vote_completed', 'Editorial College voting rounded up'), + (STATUS_EC_VOTE_COMPLETED, 'Editorial College voting rounded up'), (STATUS_ACCEPTED, 'Publication decision taken: accept'), (STATUS_REJECTED, 'Publication decision taken: reject'), (STATUS_REJECTED_VISIBLE, 'Publication decision taken: reject (still publicly visible)'), (STATUS_PUBLISHED, 'Published'), # If withdrawn: - ('withdrawn', 'Withdrawn by the Authors'), + (STATUS_WITHDRAWN, 'Withdrawn by the Authors'), ) SUBMISSION_HTTP404_ON_EDITORIAL_PAGE = [ - 'assignment_failed', + STATUS_ASSIGNMENT_FAILED, STATUS_PUBLISHED, - 'withdrawn', + STATUS_WITHDRAWN, STATUS_REJECTED, STATUS_REJECTED_VISIBLE, ] @@ -55,9 +61,9 @@ SUBMISSION_EXCLUDE_FROM_REPORTING = SUBMISSION_HTTP404_ON_EDITORIAL_PAGE + [ STATUS_AWAITING_ED_REC, STATUS_REVIEW_CLOSED, STATUS_ACCEPTED, - 'voting_in_preparation', - 'put_to_EC_voting', - 'withdrawn', + STATUS_VOTING_IN_PREPARATION, + STATUS_PUT_TO_EC_VOTING, + STATUS_WITHDRAWN, ] # Submissions which are allowed/required to submit a EIC Recommendation @@ -71,10 +77,10 @@ SUBMISSION_EIC_RECOMMENDATION_REQUIRED = [ SUBMISSION_STATUS_PUBLICLY_INVISIBLE = [ STATUS_UNASSIGNED, STATUS_RESUBMISSION_INCOMING, - 'assignment_failed', + STATUS_ASSIGNMENT_FAILED, STATUS_RESUBMITTED_REJECTED, STATUS_REJECTED, - 'withdrawn', + STATUS_WITHDRAWN, ] # Submissions which should not appear in search lists @@ -88,7 +94,7 @@ SUBMISSION_STATUS_PUBLICLY_UNLISTED = SUBMISSION_STATUS_PUBLICLY_INVISIBLE + [ SUBMISSION_STATUS_VOTING_DEPRECATED = [ STATUS_REJECTED, STATUS_PUBLISHED, - 'withdrawn', + STATUS_WITHDRAWN, ] SUBMISSION_TYPE = ( diff --git a/submissions/managers.py b/submissions/managers.py index e4b5b5f2a6ee91e784ba9b0d144543c2fe02f373..ca6bf98c7215811cffda6278abe18d7aa54fb85b 100644 --- a/submissions/managers.py +++ b/submissions/managers.py @@ -8,9 +8,11 @@ from .constants import SUBMISSION_STATUS_OUT_OF_POOL, SUBMISSION_STATUS_PUBLICLY 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, STATUS_PUBLISHED,\ - SUBMISSION_EXCLUDE_FROM_REPORTING, STATUS_REJECTED_VISIBLE,\ + SUBMISSION_EXCLUDE_FROM_REPORTING,\ + STATUS_REJECTED, STATUS_REJECTED_VISIBLE,\ STATUS_ACCEPTED, STATUS_RESUBMITTED, STATUS_RESUBMITTED_REJECTED_VISIBLE,\ - EVENT_FOR_EIC, EVENT_GENERAL, EVENT_FOR_AUTHOR, STATUS_UNASSIGNED + EVENT_FOR_EIC, EVENT_GENERAL, EVENT_FOR_AUTHOR,\ + STATUS_UNASSIGNED, STATUS_ASSIGNMENT_FAILED, STATUS_WITHDRAWN class SubmissionQuerySet(models.QuerySet): @@ -108,9 +110,38 @@ class SubmissionQuerySet(models.QuerySet): return self.filter(status__in=[STATUS_ACCEPTED, STATUS_REJECTED_VISIBLE, STATUS_PUBLISHED, STATUS_RESUBMITTED, STATUS_RESUBMITTED_REJECTED_VISIBLE]) + def originally_submitted(self, from_date, until_date): + """ + Returns the submissions originally received between from_date and until_date + (including subsequent resubmissions, even if those came in later). + """ + identifiers = [] + for sub in self.filter(is_resubmission=False, + submission_date__range=(from_date, until_date)): + identifiers.append(sub.arxiv_identifier_wo_vn_nr) + return self.filter(arxiv_identifier_wo_vn_nr__in=identifiers) + + def accepted(self): return self.filter(status=STATUS_ACCEPTED) + + def published(self): + return self.filter(status=STATUS_PUBLISHED) + + + def assignment_failed(self): + return self.filter(status=STATUS_ASSIGNMENT_FAILED) + + + def rejected(self): + return self._newest_version_only(self.filter(status__in=[STATUS_REJECTED, + STATUS_REJECTED_VISIBLE])) + + def withdrawn(self): + return self._newest_version_only(self.filter(status=STATUS_WITHDRAWN)) + + def open_for_reporting(self): """ Return Submissions that have appriopriate status for reporting. diff --git a/submissions/migrations/0069_report_doideposit_needs_updating.py b/submissions/migrations/0069_report_doideposit_needs_updating.py new file mode 100644 index 0000000000000000000000000000000000000000..9d6b987794cae2f9ab974268211092cbefe438b8 --- /dev/null +++ b/submissions/migrations/0069_report_doideposit_needs_updating.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-09-10 12:15 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('submissions', '0068_auto_20170909_1649'), + ] + + operations = [ + migrations.AddField( + model_name='report', + name='doideposit_needs_updating', + field=models.BooleanField(default=False), + ), + ] diff --git a/submissions/models.py b/submissions/models.py index d5660b121db8bc165fb883d2ead4f648dee62649..65896271ea5b02c93bb43f65b0508f129c934139 100644 --- a/submissions/models.py +++ b/submissions/models.py @@ -385,6 +385,7 @@ class Report(SubmissionRelatedObjectMixin, models.Model): remarks_for_editors = models.TextField(blank=True, verbose_name='optional remarks for the Editors only') needs_doi = models.NullBooleanField(default=None) + doideposit_needs_updating = models.BooleanField(default=False) genericdoideposit = GenericRelation('journals.GenericDOIDeposit', related_query_name='genericdoideposit') doi_label = models.CharField(max_length=200, blank=True) diff --git a/submissions/templates/submissions/_form_submission_cycle_choice.html b/submissions/templates/submissions/_form_submission_cycle_choice.html index 9159c6abe2f20a8a7de161f592163792fc3def02..a03cc07fd93f5e2cfe3b184a31bebc9633158b5a 100644 --- a/submissions/templates/submissions/_form_submission_cycle_choice.html +++ b/submissions/templates/submissions/_form_submission_cycle_choice.html @@ -12,27 +12,28 @@ <div class="form-group row"> <label class="col-form-label col-md-2 ">Refereeing cycle</label> <div class="col-md-10"> - {% for field in form.refereeing_cycle %} - <div class="radio" data-reinvite="{% if field.choice_value == 'direct_rec' %}0{% else %}1{% endif %}"> - <label for="{{field.id_for_label}}" class="mb-0"> - {{field.tag}} - {{field.choice_label}} + {% for widget in form.refereeing_cycle.subwidgets %} + <div class="radio" data-reinvite="{% if widget.data.value == 'direct_rec' %}0{% else %}1{% endif %}"> + <label for="{{widget.id_for_label}}" class="mb-0"> + {{widget.tag}} + {{widget.choice_label}} </label> </div> <p class="help-block text-muted"> - {{ field.help_text|safe }} - {% if field.choice_value == 'short' %} + {{ widget.help_text|safe }} + {% if widget.data.value == 'short' %} Run a speedy refereeing round: two weeks, with option of reinviting previous referees - {% elif field.choice_value == 'direct_rec' %} + {% elif widget.data.value == 'direct_rec' %} Immediately write an editorial recommendation. {% else %} Run a new full refereeing round: four weeks as usual, can invite previous referees and/or new ones. {% endif %} </p> - {% for error in field.errors %} - <span class="help-block {{ form.error_css_class }}">{{ error }}</span> - {% endfor %} + + {% endfor %} + {% for error in form.refereeing_cycle.errors %} + <span class="help-block {{ form.error_css_class }}">{{ error }}</span> {% endfor %} </div> </div><!-- end refereeing cycle --> diff --git a/submissions/templatetags/submissions_extras.py b/submissions/templatetags/submissions_extras.py index 83a792a917a31309c6bb14fe9976a15be9e4337b..de71caafbddb5aae5dc1edbe542a717123796841 100644 --- a/submissions/templatetags/submissions_extras.py +++ b/submissions/templatetags/submissions_extras.py @@ -1,7 +1,6 @@ import datetime from django import template -from django.utils import timezone from submissions.constants import SUBMISSION_STATUS_OUT_OF_POOL from submissions.models import Submission diff --git a/templates/debug.html b/templates/debug.html new file mode 100644 index 0000000000000000000000000000000000000000..c84f4a46e1823345ec706052253b1791331100c6 --- /dev/null +++ b/templates/debug.html @@ -0,0 +1 @@ +{{var}} diff --git a/templates/search/indexes/comments/comment_text.txt b/templates/search/indexes/comments/comment_text.txt index ab1ca5595e0f2abc4146aad5a299b966b8e12665..e187fa7fe26f1fc628c3190ea008c6eeec87dc31 100644 --- a/templates/search/indexes/comments/comment_text.txt +++ b/templates/search/indexes/comments/comment_text.txt @@ -1,2 +1,3 @@ {{object.comment_text}} {{object.date_submitted}} +{{object.author}}