diff --git a/README.md b/README.md index 68684ffab21961cff447deb30eb129a90eb61f5b..2f3301f5bcea84d447400ccf2c6dd1c94598d037 100644 --- a/README.md +++ b/README.md @@ -215,7 +215,7 @@ Any regular method or class based view may be used together with the builtin wys ```python from django.views.generic.edit import UpdateView -from mails.mixins import MailEditorMixin +from mails.views import MailEditorMixin class AnyUpdateView(MailEditorMixin, UpdateView): mail_code = '<any_valid_mail_code>' diff --git a/funders/forms.py b/funders/forms.py index b7db43fcb0ecb60b2ed0e9e8ebb0e3afacc2b6fb..a5c7d8d29a2f363cf7f8adb125e5b9eff9ad782f 100644 --- a/funders/forms.py +++ b/funders/forms.py @@ -2,6 +2,7 @@ from django import forms from .models import Funder, Grant +from scipost.forms import HttpRefererFormMixin from scipost.models import Contributor @@ -12,20 +13,20 @@ class FunderRegistrySearchForm(forms.Form): class FunderForm(forms.ModelForm): class Meta: model = Funder - fields = ['name', 'acronym', 'identifier',] + fields = ['name', 'acronym', 'identifier'] class FunderSelectForm(forms.Form): funder = forms.ModelChoiceField(queryset=Funder.objects.all()) -class GrantForm(forms.ModelForm): +class GrantForm(HttpRefererFormMixin, forms.ModelForm): class Meta: model = Grant fields = ['funder', 'number', 'recipient_name', 'recipient', 'further_details'] def __init__(self, *args, **kwargs): - super(GrantForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['recipient'] = forms.ModelChoiceField( queryset=Contributor.objects.all().order_by('user__last_name'), required=False) diff --git a/funders/templates/funders/grant_form.html b/funders/templates/funders/grant_form.html new file mode 100644 index 0000000000000000000000000000000000000000..65f59883b8a8e2577796626395becace74bba7b2 --- /dev/null +++ b/funders/templates/funders/grant_form.html @@ -0,0 +1,25 @@ +{% extends 'scipost/base.html' %} + +{% block pagetitle %}: Create new Grant{% endblock pagetitle %} + +{% load bootstrap %} + +{% block content %} + +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Create new Grant</h1> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <form method="post"> + {% csrf_token %} + {{ form|bootstrap }} + <input class="btn btn-primary" type="submit" value="Submit"> + </form> + </div> +</div> + +{% endblock content %} diff --git a/funders/urls.py b/funders/urls.py index 6766a095b6aead907943bc6f501317e76d1ce5ff..c885eeeecfdb062270f1273d507e0607b24d000e 100644 --- a/funders/urls.py +++ b/funders/urls.py @@ -9,5 +9,5 @@ urlpatterns = [ url(r'^add$', views.add_funder, name='add_funder'), url(r'^(?P<funder_id>[0-9]+)/$', views.funder_publications, name='funder_publications'), - url(r'^grants/add$', views.add_grant, name='add_grant'), + url(r'^grants/add$', views.CreateGrantView.as_view(), name='add_grant'), ] diff --git a/funders/views.py b/funders/views.py index b11b7a15baf0991b61bb864575f8d9e4a34a510f..c909254843a9b6fc1b5a06ee8d0874b73c6b1015 100644 --- a/funders/views.py +++ b/funders/views.py @@ -3,19 +3,24 @@ import json from django.contrib import messages from django.contrib.auth.decorators import permission_required -from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse, reverse_lazy +from django.db import transaction +from django.utils.decorators import method_decorator +from django.views.generic.edit import CreateView from django.shortcuts import get_object_or_404, render, redirect from .models import Funder, Grant from .forms import FunderRegistrySearchForm, FunderForm, GrantForm +from scipost.mixins import PermissionsMixin + @permission_required('scipost.can_view_all_funding_info', raise_exception=True) def funders(request): funders = Funder.objects.all() form = FunderRegistrySearchForm() grants = Grant.objects.all() - grant_form = GrantForm() + grant_form = GrantForm(request=request) context = {'form': form, 'funders': funders, 'grants': grants, 'grant_form': grant_form} return render(request, 'funders/funders.html', context) @@ -53,7 +58,6 @@ def add_funder(request): return redirect(reverse('funders:funders')) -# @permission_required('scipost.can_view_all_funding_info', raise_exception=True) def funder_publications(request, funder_id): """ See details of specific Funder (publicly accessible). @@ -63,13 +67,24 @@ def funder_publications(request, funder_id): return render(request, 'funders/funder_details.html', context) -@permission_required('scipost.can_view_all_funding_info', 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')) +class HttpRefererMixin: + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs['request'] = self.request + return kwargs + + def form_valid(self, form): + if form.cleaned_data.get('http_referer'): + self.success_url = form.cleaned_data['http_referer'] + return super().form_valid(form) + + +@method_decorator(transaction.atomic, name='dispatch') +class CreateGrantView(PermissionsMixin, HttpRefererMixin, CreateView): + """ + Create a Grant in a separate window which may also be used by Production Supervisors. + """ + permission_required = 'scipost.can_create_grants' + model = Grant + form_class = GrantForm + success_url = reverse_lazy('funders:funders') diff --git a/invitations/mixins.py b/invitations/mixins.py index bf6629acd43fdf91ca9bb2d7d7e1fb4f6d59e9e4..fbb722b95b33dde713ecd2c21197b3ee5d095e54 100644 --- a/invitations/mixins.py +++ b/invitations/mixins.py @@ -1,6 +1,5 @@ from django.db import transaction from django.contrib import messages -from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin from .constants import INVITATION_EDITORIAL_FELLOW from .models import RegistrationInvitation @@ -16,10 +15,6 @@ class RequestArgumentMixin: return kwargs -class PermissionsMixin(LoginRequiredMixin, PermissionRequiredMixin): - pass - - class BaseFormViewMixin: send_mail = None diff --git a/invitations/views.py b/invitations/views.py index 6967619d1a03a73963b67a2916df0dbffb0bf685..fdad1c7896970f468b2236f0e3eddc0ef941cb44 100644 --- a/invitations/views.py +++ b/invitations/views.py @@ -10,12 +10,12 @@ from .forms import RegistrationInvitationForm, RegistrationInvitationReminderFor RegistrationInvitationMarkForm, RegistrationInvitationMapToContributorForm,\ CitationNotificationForm, SuggestionSearchForm, RegistrationInvitationFilterForm,\ CitationNotificationProcessForm, RegistrationInvitationAddCitationForm -from .mixins import RequestArgumentMixin, PermissionsMixin, SaveAndSendFormMixin, SendMailFormMixin +from .mixins import RequestArgumentMixin, SaveAndSendFormMixin, SendMailFormMixin from .models import RegistrationInvitation, CitationNotification from scipost.models import Contributor -from scipost.mixins import PaginationMixin -from mails.mixins import MailEditorMixin +from scipost.mixins import PaginationMixin, PermissionsMixin +from mails.views import MailEditorMixin class RegistrationInvitationsView(PaginationMixin, PermissionsMixin, ListView): diff --git a/journals/admin.py b/journals/admin.py index 8fccd05cca1475fe757dfacc81748b78ab7fa9f2..955fef5eddbb325a49a02e51f3796c06b4c4d0f8 100644 --- a/journals/admin.py +++ b/journals/admin.py @@ -64,7 +64,7 @@ class AuthorsInline(admin.TabularInline): class PublicationAdmin(admin.ModelAdmin): search_fields = ['title', 'author_list'] - list_display = ['title', 'author_list', 'in_issue', 'doi_string', 'publication_date'] + list_display = ['title', 'author_list', 'in_issue', 'doi_string', 'publication_date', 'status'] date_hierarchy = 'publication_date' list_filter = ['in_issue'] inlines = [AuthorsInline, ReferenceInline] diff --git a/journals/constants.py b/journals/constants.py index da5c678eae99d18964113ca71b204208c397769e..8bbd0497025de767f66a792dcc78ddcf0baeea26 100644 --- a/journals/constants.py +++ b/journals/constants.py @@ -57,6 +57,13 @@ ISSUE_STATUSES = ( (STATUS_PUBLISHED, 'Published'), ) +PUBLICATION_PREPUBLISHED, PUBLICATION_PUBLISHED = ('prepub', 'pub') +PUBLICATION_STATUSES = ( + (STATUS_DRAFT, 'Draft'), + (PUBLICATION_PREPUBLISHED, 'Pre-published'), + (PUBLICATION_PUBLISHED, 'Published'), +) + CCBY4 = 'CC BY 4.0' CCBYSA4 = 'CC BY-SA 4.0' CCBYNC4 = 'CC BY-NC 4.0' diff --git a/journals/factories.py b/journals/factories.py index 802dc70c73bd9ebd6ebe630e85adfb757de6a093..e6b9ef92328909e0a8826e93e4cd34113c8a0989 100644 --- a/journals/factories.py +++ b/journals/factories.py @@ -80,7 +80,7 @@ class PublicationFactory(factory.django.DjangoModelFactory): @factory.post_generation def doi(self, create, extracted, **kwargs): - paper_nr = self.in_issue.publication_set.count() + paper_nr = self.in_issue.publications.count() self.paper_nr = paper_nr self.doi_label = self.in_issue.doi_label + '.' + str(paper_nr).rjust(3, '0') diff --git a/journals/forms.py b/journals/forms.py index 55d0cb2bcdf4c8435ba6be7605d2a634442845ce..5bbddf4ab3502e1fb62c95322d7b5d3027a4476e 100644 --- a/journals/forms.py +++ b/journals/forms.py @@ -1,73 +1,82 @@ +import hashlib +import os +import random import re +import string from datetime import datetime from django import forms +from django.conf import settings from django.forms import BaseModelFormSet, modelformset_factory +from django.template import loader from django.utils import timezone -from .models import Issue, Publication, Reference, UnregisteredAuthor +from .constants import STATUS_DRAFT, PUBLICATION_PREPUBLISHED, PUBLICATION_PUBLISHED +from .exceptions import PaperNumberingError +from .models import Issue, Publication, Reference, UnregisteredAuthor, PublicationAuthorsTable +from .utils import JournalUtils + +from funders.models import Grant, Funder +from mails.utils import DirectMailUtil +from production.constants import PROOFS_PUBLISHED +from production.models import ProductionEvent +from production.signals import notify_stream_status_change +from scipost.forms import RequestFormMixin from scipost.services import DOICaller from submissions.models import Submission -class InitiatePublicationForm(forms.Form): - accepted_submission = forms.ModelChoiceField(queryset=Submission.objects.accepted()) - to_be_issued_in = forms.ModelChoiceField( - queryset=Issue.objects.filter(until_date__gte=timezone.now())) - - def __init__(self, *args, **kwargs): - super(InitiatePublicationForm, self).__init__(*args, **kwargs) - - -class ValidatePublicationForm(forms.ModelForm): - class Meta: - model = Publication - exclude = ['authors_claims', 'authors_false_claims', - 'metadata', 'metadata_xml', 'authors_registered', - 'authors_unregistered', 'latest_activity'] - - class UnregisteredAuthorForm(forms.ModelForm): class Meta: model = UnregisteredAuthor fields = ('first_name', 'last_name') -class CitationListBibitemsForm(forms.Form): +class CitationListBibitemsForm(forms.ModelForm): latex_bibitems = forms.CharField(widget=forms.Textarea()) + class Meta: + model = Publication + fields = () + def __init__(self, *args, **kwargs): - super(CitationListBibitemsForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['latex_bibitems'].widget.attrs.update( - {'rows': 30, 'cols': 50, 'placeholder': 'Paste the .tex bibitems here'}) + {'placeholder': 'Paste the .tex bibitems here'}) def extract_dois(self): entries_list = self.cleaned_data['latex_bibitems'] entries_list = re.sub(r'(?m)^\%.*\n?', '', entries_list) entries_list = entries_list.split('\doi{') dois = [] - nentries = 1 + n_entry = 1 for entry in entries_list[1:]: # drop first bit before first \doi{ dois.append( - {'key': 'ref' + str(nentries), + {'key': 'ref' + str(n_entry), 'doi': entry.partition('}')[0], } ) - nentries += 1 + n_entry += 1 return dois + def save(self, *args, **kwargs): + self.instance.metadata['citation_list'] = self.extract_dois() + return super().save(*args, **kwargs) + class FundingInfoForm(forms.ModelForm): funding_statement = forms.CharField(widget=forms.Textarea({ - 'rows': 10, - 'placeholder': 'Paste the funding info statement here' - })) + 'placeholder': 'Paste the funding info statement here'})) class Meta: model = Publication fields = () + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['funding_statement'].initial = self.instance.metadata.get('funding_statement') + def save(self, *args, **kwargs): self.instance.metadata['funding_statement'] = self.cleaned_data['funding_statement'] return super().save(*args, **kwargs) @@ -79,8 +88,40 @@ class CreateMetadataXMLForm(forms.ModelForm): fields = ['metadata_xml'] def __init__(self, *args, **kwargs): + kwargs['initial'] = { + 'metadata_xml': self.new_xml(kwargs.get('instance')) + } super().__init__(*args, **kwargs) - self.fields['metadata_xml'].widget.attrs.update({'rows': 50}) + + def save(self, *args, **kwargs): + self.instance.latest_metadata_update = timezone.now() + return super().save(*args, **kwargs) + + def new_xml(self, publication): + """ + Create new XML structure, return as a string + """ + # Create a doi_batch_id + salt = "" + for i in range(5): + salt = salt + random.choice(string.ascii_letters) + salt = salt.encode('utf8') + idsalt = publication.title[:10] + idsalt = idsalt.encode('utf8') + doi_batch_id = hashlib.sha1(salt+idsalt).hexdigest() + + funders = (Funder.objects.filter(grant__in=publication.grants.all()) + | publication.funders_generic.all()).distinct() + + # Render from template + template = loader.get_template('xml/publication_crossref.html') + context = { + 'publication': publication, + 'doi_batch_id': doi_batch_id, + 'deposit_email': settings.CROSSREF_DEPOSIT_EMAIL, + 'funders': funders, + } + return template.render(context) class CreateMetadataDOAJForm(forms.ModelForm): @@ -234,3 +275,260 @@ class ReferenceForm(forms.ModelForm): ReferenceFormSet = modelformset_factory(Reference, formset=BaseReferenceFormSet, form=ReferenceForm, can_delete=True) + + +class DraftPublicationForm(forms.ModelForm): + """ + This Form is used by the Production Supervisors to create a new Publication object + and prefill all data. It is only able to create a `draft` version of a Publication object. + """ + class Meta: + model = Publication + fields = [ + 'doi_label', + 'pdf_file', + 'in_issue', + 'paper_nr', + 'title', + 'author_list', + 'abstract', + 'discipline', + 'domain', + 'subject_area', + 'secondary_areas', + 'cc_license', + 'BiBTeX_entry', + 'submission_date', + 'acceptance_date', + 'publication_date'] + + def __init__(self, data=None, arxiv_identifier_w_vn_nr=None, issue_id=None, *args, **kwargs): + # Use separate instance to be able to prefill the form without any existing Publication + self.submission = None + self.issue = None + if arxiv_identifier_w_vn_nr: + try: + self.submission = Submission.objects.accepted().get( + arxiv_identifier_w_vn_nr=arxiv_identifier_w_vn_nr) + except Submission.DoesNotExist: + self.submission = None + if issue_id: + try: + self.issue = self.get_possible_issues().get(id=issue_id) + except Issue.DoesNotExist: + self.issue = None + + super().__init__(data, *args, **kwargs) + if kwargs.get('instance') or self.issue: + # When updating: fix in_issue, because many fields are directly related to the issue. + del self.fields['in_issue'] + self.prefill_fields() + else: + self.fields['in_issue'].queryset = self.get_possible_issues() + self.delete_secondary_fields() + + def get_possible_issues(self): + return Issue.objects.filter(until_date__gte=timezone.now()) + + def delete_secondary_fields(self): + """ + Delete fields from the self.fields dictionary. Later on, this submitted sparse form can + be used to prefill these secondary fields. + """ + del self.fields['doi_label'] + del self.fields['pdf_file'] + del self.fields['paper_nr'] + del self.fields['title'] + del self.fields['author_list'] + del self.fields['abstract'] + del self.fields['discipline'] + del self.fields['domain'] + del self.fields['subject_area'] + del self.fields['secondary_areas'] + del self.fields['cc_license'] + del self.fields['BiBTeX_entry'] + del self.fields['submission_date'] + del self.fields['acceptance_date'] + del self.fields['publication_date'] + + def save(self, *args, **kwargs): + """ + Save the Publication object always as a draft and prefill the Publication with + related Submission data only when appending the Publication. + """ + do_prefill = False + if not self.instance.id: + do_prefill = True + if self.submission: + self.instance.accepted_submission = self.submission + self.instance.in_issue = self.issue + self.instance = super().save(*args, **kwargs) + if do_prefill: + self.first_time_fill() + return self.instance + + def first_time_fill(self): + """ + Take over fields from related Submission object. This can only be done after + the Publication object has been added to the database due to m2m relations. + """ + self.instance.status = STATUS_DRAFT + + if self.submission: + # Copy all existing author and non-author relations to Publication + for submission_author in self.submission.authors.all(): + PublicationAuthorsTable.objects.create( + publication=self.instance, contributor=submission_author) + self.instance.authors_claims.add(*self.submission.authors_claims.all()) + self.instance.authors_false_claims.add(*self.submission.authors_false_claims.all()) + + # Add Institutions to the publication related to the current authors + for author in self.instance.authors_registered.all(): + for current_affiliation in author.affiliations.active(): + self.instance.institutions.add(current_affiliation.institution) + + def prefill_fields(self): + if self.submission: + self.fields['title'].initial = self.submission.title + self.fields['author_list'].initial = self.submission.author_list + self.fields['abstract'].initial = self.submission.abstract + self.fields['discipline'].initial = self.submission.discipline + self.fields['domain'].initial = self.submission.domain + self.fields['subject_area'].initial = self.submission.subject_area + self.fields['secondary_areas'].initial = self.submission.secondary_areas + self.fields['submission_date'].initial = self.submission.submission_date + self.fields['acceptance_date'].initial = self.submission.acceptance_date + + # Fill data that may be derived from the issue data + issue = self.instance.in_issue if hasattr(self.instance, 'in_issue') else self.issue + if issue: + # Determine next available paper number: + paper_nr = Publication.objects.filter(in_issue__in_volume=issue.in_volume).count() + paper_nr += paper_nr + if paper_nr > 999: + raise PaperNumberingError(paper_nr) + self.fields['paper_nr'].initial = str(paper_nr) + doi_label = '{journal}.{vol}.{issue}.{paper}'.format( + journal=issue.in_volume.in_journal.name, + vol=issue.in_volume.number, + issue=issue.number, + paper=str(paper_nr).rjust(3, '0')) + self.fields['doi_label'].initial = doi_label + + doi_string = '10.21468/{doi}'.format(doi=doi_label) + bibtex_entry = ( + '@Article{%s,\n' + '\ttitle={{%s},\n' + '\tauthor={%s},\n' + '\tjournal={%s},\n' + '\tvolume={%i},\n' + '\tissue={%i},\n' + '\tpages={%i},\n' + '\tyear={%s},\n' + '\tpublisher={SciPost},\n' + '\tdoi={%s},\n' + '\turl={https://scipost.org/%s},\n' + '}' + ) % ( + doi_string, + self.submission.title, + self.submission.author_list.replace(',', ' and'), + issue.in_volume.in_journal.get_abbreviation_citation(), + issue.in_volume.number, + issue.number, + paper_nr, + issue.until_date.strftime('%Y'), + doi_string, + doi_string) + self.fields['BiBTeX_entry'].initial = bibtex_entry + if not self.instance.BiBTeX_entry: + self.instance.BiBTeX_entry = bibtex_entry + + +class DraftPublicationApprovalForm(forms.ModelForm): + class Meta: + model = Publication + fields = () + + def save(self, commit=True): + self.instance.status = PUBLICATION_PREPUBLISHED + if commit: + self.instance.save() + mail_sender = DirectMailUtil(mail_code='publication_ready', instance=self.instance) + mail_sender.send() + return self.instance + + +class PublicationGrantsForm(forms.ModelForm): + grant = forms.ModelChoiceField(queryset=Grant.objects.none()) + + class Meta: + model = Publication + fields = [] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['grant'].queryset = Grant.objects.exclude( + id__in=self.instance.grants.values_list('id', flat=True)) + + def save(self, commit=True): + if commit: + self.instance.grants.add(self.cleaned_data['grant']) + return self.instance + + +class PublicationPublishForm(RequestFormMixin, forms.ModelForm): + class Meta: + model = Publication + fields = [] + + def move_pdf(self): + # Move file to final location + initial_path = self.instance.pdf_file.path + new_dir = (settings.MEDIA_ROOT + self.instance.in_issue.path + '/' + + self.instance.get_paper_nr()) + new_path = new_dir + '/' + self.instance.doi_label.replace('.', '_') + '.pdf' + os.makedirs(new_dir) + os.rename(initial_path, new_path) + self.instance.pdf_file.name = new_path + self.instance.status = PUBLICATION_PUBLISHED + self.instance.save() + + def update_submission(self): + # Mark the submission as having been published: + submission = self.instance.accepted_submission + submission.published_as = self.instance + submission.status = 'published' + submission.save() + + # Add SubmissionEvents + submission.add_general_event( + 'The Submission has been published as %s.' % self.instance.doi_label) + + def update_stream(self): + # Update ProductionStream + submission = self.instance.accepted_submission + if hasattr(submission, 'production_stream'): + stream = submission.production_stream + stream.status = PROOFS_PUBLISHED + stream.save() + if self.request.user.production_user: + prodevent = ProductionEvent( + stream=stream, + event='status', + comments=' published the manuscript.', + noted_by=self.request.user.production_user + ) + prodevent.save() + notify_stream_status_change(self.request.user, stream, False) + + def save(self, commit=True): + if commit: + self.move_pdf() + self.update_submission() + self.update_stream() + + # Email authors + JournalUtils.load({'publication': self.instance}) + JournalUtils.send_authors_paper_published_email() + return self.instance diff --git a/journals/managers.py b/journals/managers.py index 0b058c1b3a78d47baca5c366cf9d94c8a71e60a3..9d9bb31a1e25049162b5c0737079c214ec400723 100644 --- a/journals/managers.py +++ b/journals/managers.py @@ -2,7 +2,7 @@ from django.db import models from django.http import Http404 from django.utils import timezone -from .constants import STATUS_PUBLISHED, STATUS_DRAFT +from .constants import STATUS_PUBLISHED, STATUS_DRAFT, PUBLICATION_PUBLISHED class JournalManager(models.Manager): @@ -42,12 +42,18 @@ class IssueManager(models.Manager): class PublicationQuerySet(models.QuerySet): def get_published(self, *args, **kwargs): try: - return self.published(*args, **kwargs)[0] + return self.published().filter(*args, **kwargs)[0] except IndexError: raise Http404 def published(self, **kwargs): - return self.filter(in_issue__status=STATUS_PUBLISHED, **kwargs) + return self.filter(status=PUBLICATION_PUBLISHED, in_issue__status=STATUS_PUBLISHED) + + def unpublished(self): + return self.exclude(status=PUBLICATION_PUBLISHED) def in_draft(self, **kwargs): return self.filter(in_issue__status=STATUS_DRAFT, **kwargs) + + def drafts(self): + return self.filter(status=STATUS_DRAFT) diff --git a/journals/migrations/0014_publication_status.py b/journals/migrations/0014_publication_status.py new file mode 100644 index 0000000000000000000000000000000000000000..0a10cb32d3d71e98d10230537c9ec76a05e038a6 --- /dev/null +++ b/journals/migrations/0014_publication_status.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-03-02 13:03 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0013_auto_20180216_0850'), + ] + + operations = [ + migrations.AddField( + model_name='publication', + name='status', + field=models.CharField(choices=[('draft', 'Draft'), ('prepub', 'Pre-published'), ('pub', 'Published')], default='pub', max_length=8), + ), + ] diff --git a/journals/migrations/0015_auto_20180302_1404.py b/journals/migrations/0015_auto_20180302_1404.py new file mode 100644 index 0000000000000000000000000000000000000000..d1efbff642228e4db55409f7a6a2268b56672584 --- /dev/null +++ b/journals/migrations/0015_auto_20180302_1404.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-03-02 13:04 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0014_publication_status'), + ] + + operations = [ + migrations.AlterField( + model_name='publication', + name='status', + field=models.CharField(choices=[('draft', 'Draft'), ('prepub', 'Pre-published'), ('pub', 'Published')], default='draft', max_length=8), + ), + ] diff --git a/journals/migrations/0016_auto_20180303_0918.py b/journals/migrations/0016_auto_20180303_0918.py new file mode 100644 index 0000000000000000000000000000000000000000..f9448c4ab78a4ec88146dd6d6dd9d8a4cfd8fcb2 --- /dev/null +++ b/journals/migrations/0016_auto_20180303_0918.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-03-03 08:18 +from __future__ import unicode_literals + +from django.db import migrations, models + + +def null_to_blank(apps, schema_editor): + Publication = apps.get_model('journals', 'Publication') + for pub in Publication.objects.all(): + if pub.BiBTeX_entry is None: + pub.BiBTeX_entry = '' + if pub.metadata_xml is None: + pub.metadata_xml = '' + pub.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0015_auto_20180302_1404'), + ] + + operations = [ + migrations.RunPython(null_to_blank), + migrations.AlterField( + model_name='publication', + name='BiBTeX_entry', + field=models.TextField(blank=True, default=''), + preserve_default=False, + ), + migrations.AlterField( + model_name='publication', + name='metadata_xml', + field=models.TextField(blank=True, default=''), + preserve_default=False, + ), + ] diff --git a/journals/mixins.py b/journals/mixins.py new file mode 100644 index 0000000000000000000000000000000000000000..730d920a08ce0f4947af6da6376019dae9ec175e --- /dev/null +++ b/journals/mixins.py @@ -0,0 +1,22 @@ +from .models import Publication + +from scipost.mixins import PermissionsMixin + + +class PublicationMixin: + model = Publication + slug_field = slug_url_kwarg = 'doi_label' + + +class ProdSupervisorPublicationPermissionMixin(PermissionsMixin): + """ + This will give permission to Production Supervisors if Publication is in_draft. + If Publication is not in draft, it will only give permission to administrators. + """ + permission_required = 'scipost.can_draft_publication' + + def has_permission(self): + has_perm = super().has_permission() + if has_perm and self.get_object().is_draft: + return True + return self.request.user.has_perm('scipost.can_publish_accepted_submission') diff --git a/journals/models.py b/journals/models.py index 31c9cdc87eec453db0c84da6cf35349172b8c8ee..b0d64fbeca8a2bb2c703b5c9e669562e89505235 100644 --- a/journals/models.py +++ b/journals/models.py @@ -9,8 +9,8 @@ from django.urls import reverse from .behaviors import doi_journal_validator, doi_volume_validator,\ doi_issue_validator, doi_publication_validator from .constants import SCIPOST_JOURNALS, SCIPOST_JOURNALS_DOMAINS,\ - STATUS_DRAFT, STATUS_PUBLISHED, ISSUE_STATUSES,\ - CCBY4, CC_LICENSES, CC_LICENSES_URI + STATUS_DRAFT, STATUS_PUBLISHED, ISSUE_STATUSES, PUBLICATION_PUBLISHED,\ + CCBY4, CC_LICENSES, CC_LICENSES_URI, PUBLICATION_STATUSES from .helpers import paper_nr_string, journal_name_abbrev_citation from .managers import IssueManager, PublicationQuerySet, JournalManager @@ -264,8 +264,11 @@ class Publication(models.Model): # Publication data accepted_submission = models.OneToOneField('submissions.Submission', on_delete=models.CASCADE, related_name='publication') - in_issue = models.ForeignKey('journals.Issue', on_delete=models.CASCADE) + in_issue = models.ForeignKey('journals.Issue', on_delete=models.CASCADE, + related_name='publications') paper_nr = models.PositiveSmallIntegerField() + status = models.CharField(max_length=8, + choices=PUBLICATION_STATUSES, default=STATUS_DRAFT) # Core fields title = models.CharField(max_length=300) @@ -306,11 +309,11 @@ class Publication(models.Model): # Metadata metadata = JSONField(default={}, blank=True, null=True) - metadata_xml = models.TextField(blank=True, null=True) # for Crossref deposit + metadata_xml = models.TextField(blank=True) # for Crossref deposit metadata_DOAJ = JSONField(default={}, blank=True, null=True) doi_label = models.CharField(max_length=200, unique=True, db_index=True, validators=[doi_publication_validator]) - BiBTeX_entry = models.TextField(blank=True, null=True) + BiBTeX_entry = models.TextField(blank=True) doideposit_needs_updating = models.BooleanField(default=False) citedby = JSONField(default={}, blank=True, null=True) @@ -343,6 +346,30 @@ class Publication(models.Model): def doi_string(self): return '10.21468/' + self.doi_label + @property + def is_draft(self): + return self.status == STATUS_DRAFT + + @property + def is_published(self): + return self.status == PUBLICATION_PUBLISHED and self.in_issue.status == STATUS_PUBLISHED + + @property + def has_xml_metadata(self): + return self.metadata_xml != '' + + @property + def has_bibtex_entry(self): + return self.BiBTeX_entry != '' + + @property + def has_citation_list(self): + return 'citation_list' in self.metadata and len(self.metadata['citation_list']) > 0 + + @property + def has_funding_statement(self): + return 'funding_statement' in self.metadata and self.metadata['funding_statement'] + def get_paper_nr(self): return paper_nr_string(self.paper_nr) diff --git a/journals/search_indexes.py b/journals/search_indexes.py index 08c3b2898fcfc8613a8e4eb8d48a398ca914d87f..89035e4731cb1cd2644d7c25225f5ca29ec414d0 100644 --- a/journals/search_indexes.py +++ b/journals/search_indexes.py @@ -1,5 +1,3 @@ -# import datetime - from haystack import indexes from .models import Publication diff --git a/journals/templates/journals/create_citation_list_metadata.html b/journals/templates/journals/create_citation_list_metadata.html index c6f5f6d5f79e130b4ed33a83ce96a1371c370480..724637a48de011831f16a8f857cd0ee4fd414e28 100644 --- a/journals/templates/journals/create_citation_list_metadata.html +++ b/journals/templates/journals/create_citation_list_metadata.html @@ -22,15 +22,14 @@ <div class="row"> <div class="col-12"> <h1 class="highlight">Create citation list metadata page for <a href="{{ publication.get_absolute_url }}">{{ publication.doi_label }}</a></h1> - </div> -</div> - + <p> + The following field is prefilled with the current citation list of the Publication object. Once you submit, it will overwrite the current citation list, shown below. + </p> + <br> -<div class="row"> - <div class="col-12"> <form action="{% url 'journals:create_citation_list_metadata' publication.doi_label %}" method="post"> {% csrf_token %} - {{ bibitems_form|bootstrap }} + {{ form|bootstrap }} <input type="submit" class="btn btn-primary" value="Submit"> <a href="{% url 'journals:manage_metadata' doi_label=publication.doi_label %}" class="ml-3 btn btn-link">Back to Admin for {{ publication.doi_label }}</a> </form> @@ -38,17 +37,14 @@ <hr class="divider"> <h3>Current citation list metadata:</h3> + <br> <table class="table"> - {% for citation in citation_list %} + {% for citation in publication.metadata.citation_list %} <tr> <td>{{ citation.key }}</td><td>{{ citation.doi }}</td> </tr> {% endfor %} </table> - - <hr> - - <p>Once you're happy with this metadata, you can <a href="{{publication.get_absolute_url}}">return to the publication's page</a> or to the <a href="{% url 'journals:manage_metadata' %}">metadata management page</a> or to <a href="{% url 'journals:manage_metadata' doi_label=publication.doi_label %}">this publication's metadata management page</a></p> </div> </div> diff --git a/journals/templates/journals/create_funding_info_metadata.html b/journals/templates/journals/create_funding_info_metadata.html index 2373370f7b3d9ca053ec85ace517e5e520b3e775..43c046b30c36fe66ce7be51270f324f28d50bd1c 100644 --- a/journals/templates/journals/create_funding_info_metadata.html +++ b/journals/templates/journals/create_funding_info_metadata.html @@ -22,32 +22,21 @@ <div class="row"> <div class="col-12"> <h1 class="highlight">Create funding info metadata page for <a href="{{ publication.get_absolute_url }}">{{ publication.doi_label }}</a></h1> - </div> -</div> - - -<div class="row"> - <div class="col-12"> - - {% if errormessage %} - <h2 class="text-danger">{{ errormessage }}</h2> - {% endif %} + <p> + The following field is prefilled with the current funding info of the Publication object. Once you submit, it will overwrite the current funding info, shown below. + </p> + <br> <form method="post"> {% csrf_token %} - {{ funding_info_form|bootstrap }} + {{ form|bootstrap }} <input type="submit" class="btn btn-primary" value="Submit"> <a href="{% url 'journals:manage_metadata' doi_label=publication.doi_label %}" class="ml-3 btn btn-link">Back to Admin for {{ publication.doi_label }}</a> </form> <hr class="divider"> <h3>Current funding info metadata:</h3> - <p>{{ funding_statement|linebreaksbr }}</p> - - <hr> - - <p class="mb-0">Once you're happy with this metadata, you can <a href="{{publication.get_absolute_url}}">return to the publication's page</a> or to the <a href="{% url 'journals:manage_metadata' %}">metadata management page</a> or to <a href="{% url 'journals:manage_metadata' doi_label=publication.doi_label %}">this publication's metadata management page</a></p> - + <p class="mb-0">{{ publication.metadata.funding_statement|linebreaksbr }}</p> </div> </div> diff --git a/journals/templates/journals/create_metadata_xml.html b/journals/templates/journals/create_metadata_xml.html index a6627c2567926544804cc917b2105849d0ee91b6..dff180d06c8f5142f944b9bd3121165b7d9f74a7 100644 --- a/journals/templates/journals/create_metadata_xml.html +++ b/journals/templates/journals/create_metadata_xml.html @@ -22,18 +22,13 @@ <div class="row"> <div class="col-12"> <h1 class="highlight">Create metadata XML (for Crossref deposit)</h1> - </div> -</div> - -<div class="row"> - <div class="col-12"> - {% if errormessage %} - <h2 class="text-danger">{{ errormessage }}</h2> - {% endif %} - + <p> + The following field is prefilled with data from the Publication object. Once you accept them, they will overwrite the current metadata, shown below. + </p> + <br> <form action="{% url 'journals:create_metadata_xml' publication.doi_label %}" method="post"> {% csrf_token %} - {{ create_metadata_xml_form|bootstrap }} + {{ form|bootstrap }} <input type="submit" class="btn btn-primary" value="Accept the metadata"> <a href="{% url 'journals:manage_metadata' doi_label=publication.doi_label %}" class="ml-3 btn btn-link">Back to Admin for {{ publication.doi_label }}</a> </form> @@ -43,9 +38,6 @@ <h3>Current metadata xml</h3> <br> <pre><code>{{ publication.metadata_xml|linebreaksbr }}</code></pre> - <br> - <p class="mb-0">Once you're happy with this metadata, you can <a href="{{publication.get_absolute_url}}">return to the publication's page</a> or to the <a href="{% url 'journals:manage_metadata' %}">metadata management page</a></p> - </div> </div> diff --git a/journals/templates/journals/grants_form.html b/journals/templates/journals/grants_form.html new file mode 100644 index 0000000000000000000000000000000000000000..75c4ed4f2488b3a92f707bde1c93d2c21e2ee38c --- /dev/null +++ b/journals/templates/journals/grants_form.html @@ -0,0 +1,34 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: Publication related grant(s){% endblock pagetitle %} + +{% block content %} + + +<h1>Publication related grant(s)</h1> + + +<h3>Add existing grant to this Publication</h3> +<form method="post" enctype="multipart/form-data"> + {% csrf_token %} + {{ form|bootstrap }} + <input type="submit" class="btn btn-primary" value="Add"> + <a class="btn btn-link ml-2" href="{% url 'funders:add_grant' %}">Create new Grant</a> +</form> + +<hr class="divider"> + + +<h3>Current grant(s)</h3> +<ul> + {% for grant in publication.grants.all %} + <li>{{ grant }} - <a class="text-danger" href="{% url 'journals:remove_grant' form.instance.doi_label grant.id %}">Remove grant from Publication</a></li> + {% empty %} + <li><em>No grants added</em></li> + {% endfor %} +</ul> + + +{% endblock %} diff --git a/journals/templates/journals/initiate_publication.html b/journals/templates/journals/initiate_publication.html deleted file mode 100644 index c9fa7acc87fb2a42b4829a2ce0363c7bec8d1863..0000000000000000000000000000000000000000 --- a/journals/templates/journals/initiate_publication.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends 'scipost/base.html' %} - -{% load bootstrap %} - -{% block pagetitle %}: Initiate publication{% endblock pagetitle %} - -{% block content %} - - -<h1>Initiate publication page</h1> - -<form action="{% url 'journals:initiate_publication' %}" method="post" enctype="multipart/form-data"> - {% csrf_token %} - {{ initiate_publication_form|bootstrap }} - <input type="submit" class="btn btn-primary" value="Submit"> -</form> - - - - -{% endblock %} diff --git a/journals/templates/journals/journal_landing_page.html b/journals/templates/journals/journal_landing_page.html index 1cd761620af9cf7acc1489e50227f2a4c8443e1c..c7c7f3fa13c4ea8156d6f11d2d1ab29a2796884e 100644 --- a/journals/templates/journals/journal_landing_page.html +++ b/journals/templates/journals/journal_landing_page.html @@ -20,7 +20,7 @@ <div class="row"> <div class="col-12"> <ul class="list-unstyled"> - {% for paper in current_issue.publication_set.all|dictsort:"paper_nr" %} + {% for paper in current_issue.publications.published|dictsort:"paper_nr" %} <li> {% include 'partials/journals/publication_card.html' with publication=paper %} </li> @@ -43,7 +43,7 @@ <div class="row"> <div class="col-12"> <ul class="list-unstyled"> - {% for paper in latest_issue.publication_set.all|dictsort:"paper_nr" %} + {% for paper in latest_issue.publications.published|dictsort:"paper_nr" %} <li> {% include 'partials/journals/publication_card.html' with publication=paper %} </li> diff --git a/journals/templates/journals/manage_metadata.html b/journals/templates/journals/manage_metadata.html index fc3c61d69d847dd2b23a96a8daaf7722bc7514bb..d77c5d434fc2d2de50eccbccec42be9fe947d996 100644 --- a/journals/templates/journals/manage_metadata.html +++ b/journals/templates/journals/manage_metadata.html @@ -53,7 +53,7 @@ event: "focusin" <tbody id="accordion" role="tablist" aria-multiselectable="true"> {% for publication in publications %} - <tr data-toggle="collapse" data-parent="#accordion" href="#collapse{{ publication.id }}" aria-expanded="true" aria-controls="collapse{{ publication.id }}" style="cursor: pointer;"> + <tr data-toggle="collapse" data-parent="#accordion" href="#collapse{{ publication.id }}" aria-expanded="true" aria-controls="collapse{{ publication.id }}" style="cursor: pointer;"{% if not publication.is_published %} class="table-warning"{% endif %}> <td><a href="{{ publication.get_absolute_url }}">{{ publication.doi_label }}</a></td> <td>{{ publication.publication_date }}</td> {% if publication.latest_metadata_update %} @@ -68,11 +68,16 @@ event: "focusin" <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="6"> - - <h2 class="ml-3">Actions</h2> - <div class="row"> - <div class="col-md-5"> + <td colspan="6" class="py-3"> + <div class="row"> + {% if not publication.is_published %} + <div class="col-12"> + <h3 class="text-center bg-warning text-white mb-3">Current status: <em>{{ publication.get_status_display }}</em></h3> + </div> + {% endif %} + + <div class="col-md-6"> + <h2 class="ml-3">Actions</h2> <ul> <li>Mark the first author <ul class="list-unstyled pl-4"> @@ -93,10 +98,14 @@ event: "focusin" <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> <li><a href="{% url 'journals:harvest_citedby_links' publication.doi_label %}">Update Crossref cited-by links</a></li> + {% if not publication.is_published %} + <li><a href="{% url 'journals:update_publication' publication.accepted_submission.arxiv_identifier_w_vn_nr %}">Update Publication object</a></li> + <li><strong><a href="{% url 'journals:publish_publication' publication.doi_label %}">Publish this Publication</a></strong></li> + {% endif %} </ul> </div> - <div class="col-md-5"> + <div class="col-md-6"> <h2>Funding statement for this publication:</h2> {% if publication.metadata.funding_statement %} <p>{{ publication.metadata.funding_statement }}</p> diff --git a/journals/templates/journals/metadata_xml_deposit.html b/journals/templates/journals/metadata_xml_deposit.html index 960e3737b67c6a3036eb52c7d1d9b177b94967a2..a95c2c9d2c8979034fa4ec9cb586a44e30e52ef3 100644 --- a/journals/templates/journals/metadata_xml_deposit.html +++ b/journals/templates/journals/metadata_xml_deposit.html @@ -34,8 +34,10 @@ <h3 class="mt-3">Response text:</h3> <pre><code>{{ response_text|linebreaks }}</code></pre> - <br> - <p><a href="{{publication.get_absolute_url}}">return to the publication's page</a>, to the <a href="{% url 'journals:manage_metadata' %}">general metadata management page</a> or to <a href="{% url 'journals:manage_metadata' doi_label=publication.doi_label %}">this publication's metadata management page</a></p> + {% if perms.scipost.can_publish_accepted_submission %} + <br> + <p><a href="{{publication.get_absolute_url}}">return to the publication's page</a>, to the <a href="{% url 'journals:manage_metadata' %}">general metadata management page</a> or to <a href="{% url 'journals:manage_metadata' doi_label=publication.doi_label %}">this publication's metadata management page</a></p> + {% endif %} </div> @@ -48,8 +50,11 @@ You might want to <a href="{% url 'journals:create_metadata_xml' doi_label=publication.doi_label %}">produce new metadata</a> to do a new deposit instead. <p> - <br> - <a href="{% url 'journals:manage_metadata' doi_label=publication.doi_label %}">Back to Admin for {{ publication.doi_label }}</a> + {% if perms.scipost.can_publish_accepted_submission %} + <p> + <a href="{% url 'journals:manage_metadata' doi_label=publication.doi_label %}">Back to Admin for {{ publication.doi_label }}</a> + </p> + {% endif %} </div> </div> {% endif %} diff --git a/journals/templates/journals/publication_approval_form.html b/journals/templates/journals/publication_approval_form.html new file mode 100644 index 0000000000000000000000000000000000000000..4daa978337c5de968add6bda355b8327ee3d4499 --- /dev/null +++ b/journals/templates/journals/publication_approval_form.html @@ -0,0 +1,46 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: Send Publication for approval{% endblock pagetitle %} + +{% block content %} + + +<h1 class="highlight">Send Publication for approval</h1> +{% include 'partials/journals/publication_summary.html' with publication=form.instance %} + +<h3>Authors</h3> +<ul> + {% for author in form.instance.authors.all %} + <li>{{ author }}</li> + {% empty %} + <li>No authors assigned</li> + {% endfor %} +</ul> + +<h3>Funding statement</h3> +<p>{{ form.instance.metadata.funding_statement|default:'<em>No funding statement found.</em>' }}</p> + +<h3>Grants</h3> +<ul> + {% for grant in form.instance.grants.all %} + <li>{{ grant }}</li> + {% empty %} + <li>No grants assigned</li> + {% endfor %} +</ul> + +{% include 'partials/journals/references.html' with publication=form.instance %} + +<br> +<form method="post" enctype="multipart/form-data"> + {% csrf_token %} + {{ form|bootstrap }} + <input type="submit" class="btn btn-primary" value="Submit"> +</form> + + + + +{% endblock %} diff --git a/journals/templates/journals/publication_detail.html b/journals/templates/journals/publication_detail.html index 1522969e8df4a5fd4fd4940b92cd7d863b92509a..f8e1d9ecf2c8156e4fee4b90ee7e2b66f06f79e6 100644 --- a/journals/templates/journals/publication_detail.html +++ b/journals/templates/journals/publication_detail.html @@ -1,6 +1,7 @@ {% extends 'journals/_base.html' %} {% load journals_extras %} +{% load publication_administration %} {% load staticfiles %} {% load scipost_extras %} {% load user_groups %} @@ -34,7 +35,6 @@ <meta name="citation_issue" content="{{ publication.in_issue.number }}"/> <meta name="citation_firstpage" content="{{ publication.paper_nr|paper_nr_string_filter }}"/> <meta name="citation_pdf_url" content="https://scipost.org/{{ publication.doi_string }}/pdf"/> - <meta name="dc.identifier" content="doi:{{ publication.doi_string }}"/> <script> @@ -50,6 +50,17 @@ {% endblock headsup %} {% block content %} + {% if not publication.is_published %} + <div class="card bg-warning text-white"> + <div class="card-body"> + <p class="card-text text-center"> + This Publication is not published yet. + Current status: {{ publication.get_status_display }} + </p> + </div> + </div> + {% endif %} + {% is_edcol_admin request.user as is_edcol_admin %} {% include 'partials/journals/publication_summary.html' with publication=publication %} @@ -127,35 +138,63 @@ </ul> {% endif %} + {% if publication.status == 'draft' and perms.scipost.can_draft_publication %} + <hr class="divider"> + <div class="row"> + <div class="col-12"> + <h3>Publication preparation</h3> + <ul class="fa-ul"> + <li><i class="fa-li fa fa-check-square text-success"></i><a href="{% url 'journals:update_publication' publication.accepted_submission.arxiv_identifier_w_vn_nr %}">Create/update Publication object</a></li> + <li><i class="fa-li fa {% if publication|has_all_author_relations %}fa-check-square text-success{% else %}fa-square{% endif %}"></i><a href="{% url 'journals:add_author' publication.doi_label %}">Create all author relations</a></li> + <li><i class="fa-li fa {% if publication.has_citation_list %}fa-check-square text-success{% else %}fa-square{% endif %}"></i><a href="{% url 'journals:create_citation_list_metadata' publication.doi_label %}">Create/update citation metadata</a></li> + <li><i class="fa-li fa {% if publication.has_funding_statement %}fa-check-square text-success{% else %}fa-square{% endif %}"></i><a href="{% url 'journals:create_funding_info_metadata' publication.doi_label %}">Create/update funding info metadata</a></li> + <li><i class="fa-li fa {% if publication.grants.exists %}fa-check-square text-success{% else %}fa-square{% endif %}"></i><a href="{% url 'journals:update_grants' publication.doi_label %}">Create/update grants</a></li> + <li><i class="fa-li fa {% if publication.has_xml_metadata %}fa-check-square text-success{% else %}fa-square{% endif %}"></i><a href="{% url 'journals:create_metadata_xml' publication.doi_label %}">Create/update Crossref metadata</a> <em>(please do after citation and funding info are added)</em></li> + <li><i class="fa-li fa {% if publication.references.exists %}fa-check-square text-success{% else %}fa-square{% endif %}"></i><a href="{% url 'journals:update_references' doi_label=publication.doi_label %}">Create/update references</a></li> + </ul> + + <h3>Tools</h3> + <ul> + <li><a href="{% url 'journals:metadata_xml_deposit' publication.doi_label 'test' %}">Test Crossref deposit</a></li> + </ul> + Preparation completed? <a href="{% url 'journals:send_publication_for_approval' publication.doi_label %}">Send to administration for approval</a>. + </div> + </div> + {% endif %} + {% if is_edcol_admin %} - <hr> - <div class="row"> - <div class="col-12"> - <h3>Editorial Administration tools: </h3> - <ul> - <li> - Mark the first author - <ul class="list-unstyled pl-4"> - {% for author in publication.authors.all %} - <li> - {{ author.order }}. <a href="{% url 'journals:mark_first_author' doi_label=publication.doi_label author_object_id=author.id %}">{{ author }}</a> - </li> - {% endfor %} - </ul> - </li> - <li><a href="{% url 'journals:add_author' doi_label=publication.doi_label %}">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 %}">Create/update the XML 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:harvest_citedby_links' publication.doi_label %}">Update Crossref cited-by links</a></li> - <li><a href="{% url 'journals:manage_metadata' %}">Metadata management page</a></li> - <li><a href="{% url 'journals:manage_metadata' doi_label=publication.doi_label %}">This publication's metadata management page</a></li> - <li><a href="{% url 'journals:update_references' doi_label=publication.doi_label %}">Update references</a></li> - </ul> + <hr class="divider"> + <div class="row"> + <div class="col-12"> + <h3>Editorial Administration tools</h3> + <ul class="mb-0"> + <li> + Mark the first author + <ul class="list-unstyled pl-4"> + {% for author in publication.authors.all %} + <li> + {{ author.order }}. <a href="{% url 'journals:mark_first_author' doi_label=publication.doi_label author_object_id=author.id %}">{{ author }}</a> + </li> + {% endfor %} + </ul> + </li> + <li><a href="{% url 'journals:add_author' doi_label=publication.doi_label %}">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 %}">Create/update the XML 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:harvest_citedby_links' publication.doi_label %}">Update Crossref cited-by links</a></li> + <li><a href="{% url 'journals:manage_metadata' %}">Metadata management page</a></li> + <li><a href="{% url 'journals:manage_metadata' doi_label=publication.doi_label %}">This publication's metadata management page</a></li> + <li><a href="{% url 'journals:update_references' doi_label=publication.doi_label %}">Update references</a></li> + {% if not publication.is_published %} + <li><a href="{% url 'journals:update_publication' publication.accepted_submission.arxiv_identifier_w_vn_nr %}">Update Publication object</a></li> + <li><strong><a href="{% url 'journals:publish_publication' publication.doi_label %}">Publish this Publication</a></strong></li> + {% endif %} + </ul> + </div> </div> - </div> {% endif %} {% endblock content %} diff --git a/journals/templates/journals/publication_form.html b/journals/templates/journals/publication_form.html new file mode 100644 index 0000000000000000000000000000000000000000..6217eef9d55b80a892301f5dc2cc4f45310b2702 --- /dev/null +++ b/journals/templates/journals/publication_form.html @@ -0,0 +1,32 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: Draft publication{% endblock pagetitle %} + +{% block content %} + + +<h1>Draft publication</h1> + +{% if request.GET.issue or form.instance.id %} + <form method="post" enctype="multipart/form-data"> + {% csrf_token %} + {{ form|bootstrap }} + <input type="submit" class="btn btn-primary" value="Save"> + </form> +{% else %} + <h3>Pick the Issue to publish in</h3> + <ul> + {% for issue in form.get_possible_issues %} + <li><a href="?issue={{ issue.id }}">{{ issue }}</a></li> + {% empty %} + <li><em>No Issues found. Please contact administration</em></li> + {% endfor %} + </ul> +{% endif %} + + + + +{% endblock %} diff --git a/journals/templates/journals/publication_publish_form.html b/journals/templates/journals/publication_publish_form.html new file mode 100644 index 0000000000000000000000000000000000000000..b361b0c290a7bf02204221da22a49548b99e4d4d --- /dev/null +++ b/journals/templates/journals/publication_publish_form.html @@ -0,0 +1,57 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: Publish Publication{% endblock pagetitle %} + +{% block content %} + + +<h1 class="highlight">Publish Publication</h1> +{% include 'partials/journals/publication_summary.html' with publication=form.instance %} + +<h3>Authors</h3> +<ul> + {% for author in form.instance.authors.all %} + <li>{{ author }}</li> + {% empty %} + <li>No authors assigned</li> + {% endfor %} +</ul> + +<h3>Funding statement</h3> +<p>{{ form.instance.metadata.funding_statement|default:'<em>No funding statement found.</em>' }}</p> + +<h3>Grants</h3> +<ul> + {% for grant in form.instance.grants.all %} + <li>{{ grant }}</li> + {% empty %} + <li>No grants assigned</li> + {% endfor %} +</ul> + +{% include 'partials/journals/references.html' with publication=form.instance %} + +<hr class="divider"> +<h3>Publishing will do the following:</h3> +<div> + <ul> + <li>Move the pdf file to the appropriate folder</li> + <li>Update the Submission status</li> + <li>Update the Production Stream status</li> + <li>Send the authors a publication-email</li> + </ul> + <em>Reminder: Is the metadata already deposited at Crossref?</em> +</div> +<br> +<form method="post" enctype="multipart/form-data"> + {% csrf_token %} + {{ form|bootstrap }} + <input type="submit" class="btn btn-primary" value="Publish"> +</form> + + + + +{% endblock %} diff --git a/journals/templates/journals/validate_publication.html b/journals/templates/journals/validate_publication.html deleted file mode 100644 index db78a13e090435434f2826e8dee528feed773071..0000000000000000000000000000000000000000 --- a/journals/templates/journals/validate_publication.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends 'scipost/base.html' %} - -{% load bootstrap %} - -{% block pagetitle %}: Validate publication{% endblock pagetitle %} - -{% block content %} - -<div class="row"> - <div class="col-12"> - <h1 class="highlight">Validate publication page</h1> - </div> -</div> - -<div class="row justify-content-center"> - <div class="col-lg-10"> - {% if errormessage %} - <h2 style="color: red;">{{ errormessage }}</h2> - {% endif %} - - <form action="{% url 'journals:validate_publication' %}" method="post" enctype="multipart/form-data"> - {% csrf_token %} - {{ validate_publication_form|bootstrap }} - <input class="btn btn-secondary" type="submit" value="Submit"> - </form> - </div> -</div> - -{% endblock content %} diff --git a/journals/templates/partials/journals/publication_summary.html b/journals/templates/partials/journals/publication_summary.html index 014da1f23f015d0a82d9b59bf0be546a35470a09..2042920f8142d3eb59fab3a87a31db0142ad2289 100644 --- a/journals/templates/partials/journals/publication_summary.html +++ b/journals/templates/partials/journals/publication_summary.html @@ -1,7 +1,7 @@ <div class="row"> <div class="col-12"> - <h2 class="pb-1 text-blue">{{publication.title}}</h2> + <h2 class="pb-1 text-blue">{{publication.title}}{% if publication.status == 'draft' %} <label class="label label-warning label-sm">{{ publication.get_status_display }}</label>{% endif %}</h2> <p class="mb-1">{{ publication.author_list }}</p> <p class="text-muted mb-0"> diff --git a/journals/templates/xml/publication_crossref.html b/journals/templates/xml/publication_crossref.html new file mode 100644 index 0000000000000000000000000000000000000000..99d744783788e9cf739c5ee08bc568c14b6b54be --- /dev/null +++ b/journals/templates/xml/publication_crossref.html @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8"?> +<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" xmlns:ai="http://www.crossref.org/AccessIndicators.xsd"> + <head> + <doi_batch_id>{{ doi_batch_id }}</doi_batch_id> + <timestamp>{% now "YmdHis" %}</timestamp> + <depositor> + <depositor_name>scipost</depositor_name> + <email_address>{{ deposit_email }}</email_address> + </depositor> + <registrant>scipost</registrant> + </head> + <body> + <journal> + <journal_metadata> + <full_title>{{ publication.in_issue.in_volume.in_journal.get_name_display }}</full_title> + <abbrev_title>{{ publication.in_issue.in_volume.in_journal.get_abbreviation_citation }}</abbrev_title> + <issn media_type='electronic'>{{ publication.in_issue.in_volume.in_journal.issn }}</issn> + <doi_data> + <doi>{{ publication.in_issue.in_volume.in_journal.doi_string }}</doi> + <resource>https://scipost.org/{{ publication.in_issue.in_volume.in_journal.doi_string }}</resource> + </doi_data> + </journal_metadata> + <journal_issue> + <publication_date media_type='online'> + <year>{{ publication.publication_date|date:'Y' }}</year> + </publication_date> + <journal_volume> + <volume>{{ publication.in_issue.in_volume.number }}</volume> + </journal_volume> + <issue>{{ publication.in_issue.number }}</issue> + </journal_issue> + <journal_article publication_type='full_text'> + <titles> + <title>{{ publication.title }}</title> + </titles> + + <contributors> + {% for author_object in publication.authors.all %} + {% if author_object.order == 1 %} + <person_name sequence='first' contributor_role='author'> + {% else %} + <person_name sequence='additional' contributor_role='author'> + {% endif %} + <given_name>{{ author_object.first_name }}</given_name> + <surname>{{ author_object.last_name }}</surname> + {% if author_object.contributor and author_object.contributor.orcid_id %} + <ORCID>http://orcid.org/'{{ author_object.contributor.orcid_id }}</ORCID> + {% endif %} + </person_name> + {% endfor %} + </contributors> + + <publication_date media_type='online'> + <month>{{ publication.publication_date|date:'m' }}</month> + <day>{{ publication.publication_date|date:'d' }}</day> + <year>{{ publication.publication_date|date:'Y' }}</year> + </publication_date> + <publisher_item> + <item_number item_number_type='article_number'>{{ publication.paper_nr }}</item_number> + </publisher_item> + <crossmark> + <crossmark_policy>10.21468/SciPost.CrossmarkPolicy</crossmark_policy> + <crossmark_domains> + <crossmark_domain><domain>scipost.org</domain></crossmark_domain> + </crossmark_domains> + <crossmark_domain_exclusive>false</crossmark_domain_exclusive> + <custom_metadata> + {% if funders %} + <fr:program name='fundref'> + {% for funder in funders %} + {% if funders|length > 1 %} + <fr:assertion name='fundgroup'> + {% endif %} + + <fr:assertion name='funder_name'>{{ funder.name }} + <fr:assertion name='funder_identifier'>{{ funder.identifier }}</fr:assertion> + </fr:assertion> + + {% for grant in publication.grants.all %} + {% if grant.funder == funder %} + <fr:assertion name='award_number'>{{ grant.number }}</fr:assertion> + {% endif %} + {% endfor %} + + {% if funders|length > 1 %} + </fr:assertion> + {% endif %} + {% endfor %} + </fr:program> + {% endif %} + + <ai:program name="AccessIndicators"> + <ai:license_ref>{{ publication.get_cc_license_URI }}</ai:license_ref> + </ai:program> + </custom_metadata> + </crossmark> + <archive_locations> + <archive name="CLOCKSS"></archive> + </archive_locations> + <doi_data> + <doi>{{ publication.doi_string }}</doi> + <resource>https://scipost.org/{{ publication.doi_string }}</resource> + <collection property='crawler-based'> + <item crawler='iParadigms'> + <resource>https://scipost.org/{{ publication.doi_string }}/pdf</resource> + </item> + </collection> + <collection property='text-mining'> + <item> + <resource mime_type='application/pdf'>https://scipost.org/{{ publication.doi_string }}/pdf</resource> + </item> + </collection> + </doi_data> + {% if publication.metadata.citation_list %} + <citation_list> + {% for ref in publication.metadata.citation_list %} + <citation key='{{ ref.key }}'> + <doi>{{ ref.doi }}</doi> + </citation> + {% endfor %} + </citation_list> + {% endif %} + </journal_article> + </journal> + </body> +</doi_batch> diff --git a/journals/templatetags/journals_extras.py b/journals/templatetags/journals_extras.py index fa6370f73bf45bfcb683840a82f28d5767d9e93d..ee55822049fe25ea07c7e8408e4a1af39154bb08 100644 --- a/journals/templatetags/journals_extras.py +++ b/journals/templatetags/journals_extras.py @@ -9,6 +9,7 @@ register = template.Library() def paper_nr_string_filter(nr): return paper_nr_string(nr) + @register.filter(name='latest_successful_crossref_deposit') def latest_successful_crossref_deposit(publication): latest = publication.deposit_set.filter( @@ -18,6 +19,7 @@ def latest_successful_crossref_deposit(publication): else: return "No successful deposit found" + @register.filter(name='latest_successful_DOAJ_deposit') def latest_successful_DOAJ_deposit(publication): latest = publication.doajdeposit_set.filter( @@ -27,8 +29,9 @@ def latest_successful_DOAJ_deposit(publication): else: return "No successful deposit found" + @register.filter(name='latest_successful_crossref_deposit_report') -def latest_successful_crossref_deposit(report): +def latest_successful_crossref_deposit_report(report): latest = report.genericdoideposit.filter( deposit_successful=True).order_by('-deposition_date').first() if latest: @@ -36,8 +39,9 @@ def latest_successful_crossref_deposit(report): else: return "No successful deposit found" + @register.filter(name='latest_successful_crossref_deposit_comment') -def latest_successful_crossref_deposit(comment): +def latest_successful_crossref_deposit_comment(comment): latest = comment.genericdoideposit.filter( deposit_successful=True).order_by('-deposition_date').first() if latest: diff --git a/journals/templatetags/publication_administration.py b/journals/templatetags/publication_administration.py new file mode 100644 index 0000000000000000000000000000000000000000..96e108fbd1f9e9a68e0d0bc3557d1d340b776d20 --- /dev/null +++ b/journals/templatetags/publication_administration.py @@ -0,0 +1,11 @@ +from django import template + +register = template.Library() + + +@register.filter +def has_all_author_relations(publication): + """ + Check if all authors are added to the Publication object, just by counting. + """ + return len(publication.author_list.split(',')) == publication.authors.count() diff --git a/journals/urls/general.py b/journals/urls/general.py index ca60342a93f2fb64dbd834e5fbe14fb8a7f44b56..0293ca0430c050edf854f57ed0956195414ce73e 100644 --- a/journals/urls/general.py +++ b/journals/urls/general.py @@ -2,6 +2,8 @@ from django.conf.urls import url from django.urls import reverse_lazy from django.views.generic import TemplateView, RedirectView +from submissions.constants import SUBMISSIONS_COMPLETE_REGEX + from journals import views as journals_views urlpatterns = [ @@ -16,13 +18,24 @@ urlpatterns = [ TemplateView.as_view(template_name='journals/crossmark_policy.html'), name='crossmark_policy'), + # Publication creation + url(r'^admin/publications/{regex}/$'.format(regex=SUBMISSIONS_COMPLETE_REGEX), + journals_views.DraftPublicationUpdateView.as_view(), + name='update_publication'), + url(r'^admin/publications/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/publish$', + journals_views.PublicationPublishView.as_view(), + name='publish_publication'), + url(r'^admin/publications/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/approval$', + journals_views.DraftPublicationApprovalView.as_view(), + name='send_publication_for_approval'), + url(r'^admin/publications/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/grants$', + journals_views.PublicationGrantsView.as_view(), + name='update_grants'), + url(r'^admin/publications/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/grants/(?P<grant_id>[0-9]+)/remove$', + journals_views.PublicationGrantsRemovalView.as_view(), + name='remove_grant'), + # Editorial and Administrative Workflow - url(r'^admin/initiate_publication$', - journals_views.initiate_publication, - name='initiate_publication'), - url(r'^admin/validate_publication$', - journals_views.validate_publication, - name='validate_publication'), url(r'^admin/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/authors/add/(?P<contributor_id>[0-9]+)$', journals_views.add_author, name='add_author'), @@ -42,12 +55,12 @@ urlpatterns = [ journals_views.manage_metadata, name='manage_metadata'), url(r'^admin/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/citation_list_metadata$', - journals_views.create_citation_list_metadata, + journals_views.CitationUpdateView.as_view(), name='create_citation_list_metadata'), url(r'^admin/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/update_references$', journals_views.update_references, name='update_references'), url(r'^admin/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/funders/create_metadata$', - journals_views.create_funding_info_metadata, + journals_views.FundingInfoView.as_view(), name='create_funding_info_metadata'), url(r'^admin/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/funders/add_generic$', journals_views.add_generic_funder, @@ -58,7 +71,7 @@ urlpatterns = [ # Metadata handling url(r'^admin/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/metadata/crossref/create$', - journals_views.create_metadata_xml, + journals_views.CreateMetadataXMLView.as_view(), name='create_metadata_xml'), url(r'^admin/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/metadata/crossref/deposit/(?P<option>[a-z]+)$', journals_views.metadata_xml_deposit, diff --git a/journals/views.py b/journals/views.py index 432e6dc162641a77220292eb28ea14f78ed1d0cd..5b237c52282bca6040b796f4b9e77dbb098c3b58 100644 --- a/journals/views.py +++ b/journals/views.py @@ -10,6 +10,7 @@ import xml.etree.ElementTree as ET from django.contrib.auth.decorators import login_required from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import PermissionDenied from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.urlresolvers import reverse from django.conf import settings @@ -17,27 +18,29 @@ from django.contrib import messages from django.db import transaction from django.http import Http404, HttpResponse from django.utils import timezone +from django.utils.decorators import method_decorator +from django.views.generic.detail import DetailView +from django.views.generic.edit import UpdateView from django.shortcuts import get_object_or_404, render, redirect -from .exceptions import PaperNumberingError +from .constants import STATUS_DRAFT from .helpers import paper_nr_string, issue_doi_label_from_doi_label from .models import Journal, Issue, Publication, Deposit, DOAJDeposit,\ GenericDOIDeposit, PublicationAuthorsTable -from .forms import FundingInfoForm, InitiatePublicationForm, ValidatePublicationForm,\ +from .forms import FundingInfoForm,\ UnregisteredAuthorForm, CreateMetadataXMLForm, CitationListBibitemsForm,\ - ReferenceFormSet, CreateMetadataDOAJForm + ReferenceFormSet, CreateMetadataDOAJForm, DraftPublicationForm,\ + PublicationGrantsForm, DraftPublicationApprovalForm, PublicationPublishForm +from .mixins import PublicationMixin, ProdSupervisorPublicationPermissionMixin from .utils import JournalUtils from comments.models import Comment -from funders.models import Funder -from submissions.models import Submission, Report -from scipost.models import Contributor -from production.constants import PROOFS_PUBLISHED -from production.models import ProductionEvent -from production.signals import notify_stream_status_change - from funders.forms import FunderSelectForm, GrantSelectForm +from funders.models import Funder, Grant +from submissions.models import Submission, Report from scipost.forms import ConfirmationForm +from scipost.models import Contributor +from scipost.mixins import PermissionsMixin, RequestViewMixin from guardian.decorators import permission_required @@ -94,7 +97,7 @@ def recent(request, doi_label): Display page for the most recent 20 publications in SciPost Physics. """ journal = get_object_or_404(Journal, doi_label=doi_label) - recent_papers = Publication.objects.published( + recent_papers = Publication.objects.published().filter( in_issue__in_volume__in_journal=journal).order_by('-publication_date', '-paper_nr')[:20] context = { @@ -140,7 +143,7 @@ def issue_detail(request, doi_label): issue = Issue.objects.get_published(doi_label=doi_label) journal = issue.in_volume.in_journal - papers = issue.publication_set.order_by('paper_nr') + papers = issue.publications.published().order_by('paper_nr') next_issue = (Issue.objects.published(in_volume__in_journal=journal, start_date__gt=issue.start_date) .order_by('start_date').first()) @@ -160,155 +163,82 @@ def issue_detail(request, doi_label): ####################### # Publication process # ####################### - -@permission_required('scipost.can_publish_accepted_submission', return_403=True) -@transaction.atomic -def initiate_publication(request): +class PublicationGrantsView(PermissionsMixin, UpdateView): """ - Called by an Editorial Administrator. - Publish the manuscript after proofs have been accepted. - This method prefills a ValidatePublicationForm for further - processing (verification in validate_publication method). + Add/update grants associated to a Publication. """ - 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) + permission_required = 'scipost.can_draft_publication' + queryset = Publication.objects.drafts() + slug_field = slug_url_kwarg = 'doi_label' + form_class = PublicationGrantsForm + template_name = 'journals/grants_form.html' -@permission_required('scipost.can_publish_accepted_submission', return_403=True) -@transaction.atomic -def validate_publication(request): +class PublicationGrantsRemovalView(PermissionsMixin, DetailView): """ - This creates a Publication instance from the ValidatePublicationForm, - pre-filled by the initiate_publication method above. + Remove grant associated to a Publication. """ - # TODO: move from uploads to Journal folder - # TODO: create metadata - # TODO: set DOI, register with Crossref - # TODO: add funding info - context = {} - validate_publication_form = ValidatePublicationForm(request.POST or None, - request.FILES or None) - if validate_publication_form.is_valid(): - publication = validate_publication_form.save() - - # Fill remaining data - submission = publication.accepted_submission - - for submission_author in submission.authors.all(): - PublicationAuthorsTable.objects.create( - publication=publication, contributor=submission_author) - publication.authors_claims.add(*submission.authors_claims.all()) - publication.authors_false_claims.add(*submission.authors_false_claims.all()) - - # Add Institutions to the publication - for author in publication.authors_registered.all(): - for current_affiliation in author.affiliations.active(): - publication.institutions.add(current_affiliation.institution) - - # Save the beast - publication.save() + permission_required = 'scipost.can_draft_publication' + queryset = Publication.objects.drafts() + slug_field = slug_url_kwarg = 'doi_label' - # Move file to final location - initial_path = publication.pdf_file.path - new_dir = (settings.MEDIA_ROOT + publication.in_issue.path + '/' - + publication.get_paper_nr()) - new_path = new_dir + '/' + publication.doi_label.replace('.', '_') + '.pdf' - os.makedirs(new_dir) - os.rename(initial_path, new_path) - publication.pdf_file.name = new_path - publication.save() + def get(self, request, *args, **kwargs): + super().get(request, *args, **kwargs) + grant = get_object_or_404(Grant, id=kwargs.get('grant_id')) + self.object.grants.remove(grant) + return redirect(reverse('journals:update_grants', args=(self.object.doi_label,))) - # Mark the submission as having been published: - submission.published_as = publication - submission.status = 'published' - submission.save() - - # Update ProductionStream - if hasattr(submission, 'production_stream'): - stream = submission.production_stream - stream.status = PROOFS_PUBLISHED - stream.save() - if request.user.production_user: - prodevent = ProductionEvent( - stream=stream, - event='status', - comments=' published the manuscript.', - noted_by=request.user.production_user - ) - prodevent.save() - notify_stream_status_change(request.user, stream, False) - - # TODO: Create a Commentary Page - # Email authors - JournalUtils.load({'publication': publication}) - JournalUtils.send_authors_paper_published_email() - - # 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.' - context['validate_publication_form'] = validate_publication_form - return render(request, 'journals/validate_publication.html', context) +class DraftPublicationUpdateView(PermissionsMixin, UpdateView): + """ + Any Production Officer or Administrator can draft a new publication without publishing here. + The actual publishing is done lin a later stadium, after the draft has been finished. + """ + permission_required = 'scipost.can_draft_publication' + queryset = Publication.objects.unpublished() + slug_url_kwarg = 'arxiv_identifier_w_vn_nr' + slug_field = 'accepted_submission__arxiv_identifier_w_vn_nr' + form_class = DraftPublicationForm + template_name = 'journals/publication_form.html' + + def get_object(self, queryset=None): + try: + publication = Publication.objects.get( + accepted_submission__arxiv_identifier_w_vn_nr=self.kwargs.get( + 'arxiv_identifier_w_vn_nr')) + except Publication.DoesNotExist: + if Submission.objects.accepted().filter(arxiv_identifier_w_vn_nr=self.kwargs.get( + 'arxiv_identifier_w_vn_nr')).exists(): + return None + raise Http404('No accepted Submission found') + if publication.status == STATUS_DRAFT: + return publication + if self.request.user.has_perm('scipost.can_publish_accepted_submission'): + return publication + raise Http404('Found Publication is not in draft') + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs['arxiv_identifier_w_vn_nr'] = self.kwargs.get('arxiv_identifier_w_vn_nr') + kwargs['issue_id'] = self.request.GET.get('issue') + return kwargs + + +class DraftPublicationApprovalView(PermissionsMixin, UpdateView): + permission_required = 'scipost.can_draft_publication' + queryset = Publication.objects.drafts() + slug_field = slug_url_kwarg = 'doi_label' + form_class = DraftPublicationApprovalForm + template_name = 'journals/publication_approval_form.html' + + +@method_decorator(transaction.atomic, name='dispatch') +class PublicationPublishView(PermissionsMixin, RequestViewMixin, UpdateView): + permission_required = 'scipost.can_publish_accepted_submission' + queryset = Publication.objects.unpublished() + slug_field = slug_url_kwarg = 'doi_label' + form_class = PublicationPublishForm + template_name = 'journals/publication_publish_form.html' @permission_required('scipost.can_publish_accepted_submission', return_403=True) @@ -351,7 +281,7 @@ def mark_first_author(request, publication_id, author_object_id): kwargs={'doi_label': publication.doi_label})) -@permission_required('scipost.can_publish_accepted_submission', return_403=True) +@permission_required('scipost.can_draft_publication', return_403=True) @transaction.atomic def add_author(request, doi_label, contributor_id=None, unregistered_author_id=None): """ @@ -361,6 +291,9 @@ def add_author(request, doi_label, contributor_id=None, unregistered_author_id=N This is important for the Crossref metadata, in which all authors must appear. """ publication = get_object_or_404(Publication, doi_label=doi_label) + if not publication.is_draft and not request.user.has_perm('can_publish_accepted_submission'): + raise Http404('You do not have permission to edit this non-draft Publication') + if contributor_id: contributor = get_object_or_404(Contributor, id=contributor_id) PublicationAuthorsTable.objects.create(contributor=contributor, publication=publication) @@ -393,38 +326,16 @@ def add_author(request, doi_label, contributor_id=None, unregistered_author_id=N return render(request, 'journals/add_author.html', context) -@permission_required('scipost.can_publish_accepted_submission', return_403=True) -@transaction.atomic -def create_citation_list_metadata(request, doi_label): - """ - Called by an Editorial Administrator. - This populates the citation_list dictionary entry - in the metadata field in a Publication instance. - """ - publication = get_object_or_404(Publication, doi_label=doi_label) - bibitems_form = CitationListBibitemsForm(request.POST or None, request.FILES or None) - if bibitems_form.is_valid(): - publication.metadata['citation_list'] = bibitems_form.extract_dois() - publication.save() - messages.success(request, 'Updated citation list') - return redirect(reverse('journals:create_citation_list_metadata', - kwargs={'doi_label': publication.doi_label})) - context = { - 'publication': publication, - 'bibitems_form': bibitems_form, - 'citation_list': publication.metadata.get('citation_list', '') - } - return render(request, 'journals/create_citation_list_metadata.html', context) - - -@permission_required('scipost.can_publish_accepted_submission', return_403=True) +@permission_required('scipost.can_draft_publication', return_403=True) def update_references(request, doi_label): """ Update the References for a certain Publication. """ publication = get_object_or_404(Publication, doi_label=doi_label) - references = publication.references.all() + if not publication.is_draft and not request.user.has_perm('can_publish_accepted_submission'): + raise Http404('You do not have permission to edit this non-draft Publication') + references = publication.references.all() formset = ReferenceFormSet(request.POST or None, queryset=references, publication=publication, extra=request.GET.get('extra')) @@ -443,34 +354,20 @@ def update_references(request, doi_label): return render(request, 'journals/update_references.html', context) -@permission_required('scipost.can_publish_accepted_submission', return_403=True) -@transaction.atomic -def create_funding_info_metadata(request, doi_label): +class CitationUpdateView(PublicationMixin, ProdSupervisorPublicationPermissionMixin, UpdateView): """ - Called by an Editorial Administrator. - This populates the funding_info dictionary entry - in the metadata field in a Publication instance. + Populates the citation_list dictionary entry in the metadata field in a Publication instance. """ - publication = get_object_or_404(Publication, doi_label=doi_label) + form_class = CitationListBibitemsForm + template_name = 'journals/create_citation_list_metadata.html' - funding_statement = publication.metadata.get('funding_statement', '') - initial = { - 'funding_statement': funding_statement, - } - form = FundingInfoForm(request.POST or None, instance=publication, initial=initial) - if form.is_valid(): - form.save() - messages.success(request, 'Updated funding info') - return redirect(reverse('journals:create_funding_info_metadata', - kwargs={'doi_label': publication.doi_label})) - - context = { - 'publication': publication, - 'funding_info_form': form, - 'funding_statement': funding_statement, - } - return render(request, 'journals/create_funding_info_metadata.html', context) +class FundingInfoView(PublicationMixin, ProdSupervisorPublicationPermissionMixin, UpdateView): + """ + Add/update funding statement to the xml_metadata + """ + form_class = FundingInfoForm + template_name = 'journals/create_funding_info_metadata.html' @permission_required('scipost.can_publish_accepted_submission', return_403=True) @@ -507,195 +404,19 @@ def add_generic_funder(request, doi_label): kwargs={'doi_label': doi_label})) -@permission_required('scipost.can_publish_accepted_submission', return_403=True) -@transaction.atomic -def create_metadata_xml(request, doi_label): +class CreateMetadataXMLView(PublicationMixin, + ProdSupervisorPublicationPermissionMixin, + UpdateView): """ - To be called by an EdAdmin after the citation_list, - funding_info entries have been filled. - Populates the metadata_xml field of a Publication instance. + To be called by an EdAdmin (or Production Supervisor) after the citation_list, funding_info + entries have been filled. Populates the metadata_xml field of a Publication instance. The contents can then be sent to Crossref for registration. """ - publication = get_object_or_404(Publication, doi_label=doi_label) - - # create a doi_batch_id - salt = "" - for i in range(5): - salt = salt + random.choice(string.ascii_letters) - salt = salt.encode('utf8') - idsalt = publication.title[:10] - idsalt = idsalt.encode('utf8') - doi_batch_id = hashlib.sha1(salt+idsalt).hexdigest() - - initial = {'metadata_xml': ''} - initial['metadata_xml'] += ( - '<?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" ' - 'xmlns:ai="http://www.crossref.org/AccessIndicators.xsd">\n' - '<head>\n' - '<doi_batch_id>' + str(doi_batch_id) + '</doi_batch_id>\n' - '<timestamp>' + timezone.now().strftime('%Y%m%d%H%M%S') + '</timestamp>\n' - '<depositor>\n' - '<depositor_name>scipost</depositor_name>\n' - '<email_address>' + settings.CROSSREF_DEPOSIT_EMAIL + '</email_address>\n' - '</depositor>\n' - '<registrant>scipost</registrant>\n' - '</head>\n' - '<body>\n' - '<journal>\n' - '<journal_metadata>\n' - '<full_title>' + publication.in_issue.in_volume.in_journal.get_name_display() - + '</full_title>\n' - '<abbrev_title>' - + publication.in_issue.in_volume.in_journal.get_abbreviation_citation() + - '</abbrev_title>\n' - '<issn media_type=\'electronic\'>' + publication.in_issue.in_volume.in_journal.issn - + '</issn>\n' - '<doi_data>\n' - '<doi>' + publication.in_issue.in_volume.in_journal.doi_string + '</doi>\n' - '<resource>https://scipost.org/' - + publication.in_issue.in_volume.in_journal.doi_string + '</resource>\n' - '</doi_data>\n' - '</journal_metadata>\n' - '<journal_issue>\n' - '<publication_date media_type=\'online\'>\n' - '<year>' + publication.publication_date.strftime('%Y') + '</year>\n' - '</publication_date>\n' - '<journal_volume>\n' - '<volume>' + str(publication.in_issue.in_volume.number) + '</volume>\n' - '</journal_volume>\n' - '<issue>' + str(publication.in_issue.number) + '</issue>\n' - '</journal_issue>\n' - '<journal_article publication_type=\'full_text\'>\n' - '<titles><title>' + publication.title + '</title></titles>\n' - ) - - # Precondition: all authors MUST be listed in authors field of publication instance, - # this to be checked by EdAdmin before publishing. - initial['metadata_xml'] += '<contributors>\n' - for author_object in publication.authors.all(): - if author_object.order == 1: - initial['metadata_xml'] += ( - '<person_name sequence=\'first\' contributor_role=\'author\'> ' - '<given_name>' + author_object.first_name + '</given_name> ' - '<surname>' + author_object.last_name + '</surname> ' - ) - else: - initial['metadata_xml'] += ( - '<person_name sequence=\'additional\' contributor_role=\'author\'> ' - '<given_name>' + author_object.first_name + '</given_name> ' - '<surname>' + author_object.last_name + '</surname> ' - ) - if author_object.contributor and author_object.contributor.orcid_id: - initial['metadata_xml'] += ( - '<ORCID>http://orcid.org/' + author_object.contributor.orcid_id + '</ORCID>' - ) - initial['metadata_xml'] += '</person_name>\n' - initial['metadata_xml'] += '</contributors>\n' - - initial['metadata_xml'] += ( - '<publication_date media_type=\'online\'>\n' - '<month>' + publication.publication_date.strftime('%m') + '</month>' - '<day>' + publication.publication_date.strftime('%d') + '</day>' - '<year>' + publication.publication_date.strftime('%Y') + '</year>' - '</publication_date>\n' - '<publisher_item><item_number item_number_type="article_number">' - + paper_nr_string(publication.paper_nr) + - '</item_number></publisher_item>\n' - '<crossmark>\n' - '<crossmark_policy>10.21468/SciPost.CrossmarkPolicy</crossmark_policy>\n' - '<crossmark_domains>\n' - '<crossmark_domain><domain>scipost.org</domain></crossmark_domain>\n' - '</crossmark_domains>\n' - '<crossmark_domain_exclusive>false</crossmark_domain_exclusive>\n' - ) - funders = (Funder.objects.filter(grant__in=publication.grants.all()) - | publication.funders_generic.all()).distinct() - nr_funders = funders.count() - initial['metadata_xml'] += '<custom_metadata>\n' - 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'] += ( - '<ai:program name="AccessIndicators">\n' - '<ai:license_ref>' + publication.get_cc_license_URI() + - '</ai:license_ref>\n' - '</ai:program>\n' - ) - initial['metadata_xml'] += '</custom_metadata>\n' - initial['metadata_xml'] += ( - '</crossmark>\n' - '<archive_locations><archive name="CLOCKSS"></archive></archive_locations>\n' - '<doi_data>\n' - '<doi>' + publication.doi_string + '</doi>\n' - '<resource>https://scipost.org/' + publication.doi_string + '</resource>\n' - '<collection property="crawler-based">\n' - '<item crawler="iParadigms">\n' - '<resource>https://scipost.org/' - + publication.doi_string + '/pdf</resource>\n' - '</item></collection>\n' - '<collection property="text-mining">\n' - '<item><resource mime_type="application/pdf">' - 'https://scipost.org/' + publication.doi_string + '/pdf</resource></item>\n' - '</collection>' - '</doi_data>\n' - ) - try: - if publication.metadata['citation_list']: - initial['metadata_xml'] += '<citation_list>\n' - for ref in publication.metadata['citation_list']: - initial['metadata_xml'] += ( - '<citation key="' + ref['key'] + '">' - '<doi>' + ref['doi'] + '</doi>' - '</citation>\n' - ) - initial['metadata_xml'] += '</citation_list>\n' - except KeyError: - pass - initial['metadata_xml'] += ( - '</journal_article>\n' - '</journal>\n' - ) - initial['metadata_xml'] += '</body>\n</doi_batch>' - - create_metadata_xml_form = CreateMetadataXMLForm(request.POST or None, - instance=publication, - initial=initial) - if create_metadata_xml_form.is_valid(): - create_metadata_xml_form.save() - messages.success(request, 'Metadata XML saved') - return redirect(reverse('journals:manage_metadata', - kwargs={'doi_label': doi_label})) - - publication.latest_metadata_update = timezone.now() - publication.save() - context = { - 'publication': publication, - 'create_metadata_xml_form': create_metadata_xml_form, - } - return render(request, 'journals/create_metadata_xml.html', context) + form_class = CreateMetadataXMLForm + template_name = 'journals/create_metadata_xml.html' -@permission_required('scipost.can_publish_accepted_submission', return_403=True) +@permission_required('scipost.can_draft_publication', return_403=True) @transaction.atomic def metadata_xml_deposit(request, doi_label, option='test'): """ @@ -704,6 +425,10 @@ def metadata_xml_deposit(request, doi_label, option='test'): Makes use of the python requests module. """ publication = get_object_or_404(Publication, doi_label=doi_label) + if not publication.is_draft and not request.user.has_perm('can_publish_accepted_submission'): + raise Http404('You do not have permission to access this non-draft Publication') + if not request.user.has_perm('can_publish_accepted_submission') and option != 'test': + raise PermissionDenied('You do not have permission to do real Crossref deposits') if publication.metadata_xml is None: messages.warning( @@ -741,7 +466,9 @@ def metadata_xml_deposit(request, doi_label, option='test'): 'login_passwd': settings.CROSSREF_LOGIN_PASSWORD, } files = { - 'fname': ('metadata.xml', publication.metadata_xml.encode('utf-8'), 'multipart/form-data') + 'fname': ('metadata.xml', + publication.metadata_xml.encode('utf-8'), + 'multipart/form-data') } r = requests.post(url, params=params, files=files) response_headers = r.headers @@ -1299,7 +1026,14 @@ def author_reply_detail(request, doi_label): def publication_detail(request, doi_label): - publication = Publication.objects.get_published(doi_label=doi_label) + """ + The actual Publication detail page. This is visible for everyone if published or + visible for Production Supervisors and Administrators if in draft. + """ + publication = get_object_or_404(Publication, doi_label=doi_label) + if not publication.is_published and not request.user.has_perm('scipost.can_draft_publication'): + raise Http404('Publication is not publicly visible') + journal = publication.in_issue.in_volume.in_journal context = { @@ -1310,7 +1044,14 @@ def publication_detail(request, doi_label): def publication_detail_pdf(request, doi_label): - publication = Publication.objects.get_published(doi_label=doi_label) + """ + The actual Publication pdf. This is visible for everyone if published or + visible for Production Supervisors and Administrators if in draft. + """ + publication = get_object_or_404(Publication, doi_label=doi_label) + if not publication.is_published and not request.user.has_perm('scipost.can_draft_publication'): + raise Http404('Publication is not publicly visible') + response = HttpResponse(publication.pdf_file.read(), content_type='application/pdf') response['Content-Disposition'] = ('filename=' + publication.doi_label.replace('.', '_') + '.pdf') diff --git a/mails/mixins.py b/mails/mixins.py index e34dd97123125c66c502413ace9248fba4a3d93e..8b88a62e291e80b7d640ff13ffd5ec8d3175721e 100644 --- a/mails/mixins.py +++ b/mails/mixins.py @@ -4,7 +4,6 @@ import inspect from html2text import HTML2Text from django.core.mail import EmailMultiAlternatives -from django.contrib import messages from django.contrib.auth import get_user_model from django.conf import settings from django.template import loader @@ -12,79 +11,6 @@ from django.template import loader from scipost.models import Contributor -from . import forms - - -class MailEditorMixin: - """ - Use MailEditorMixin in edit CBVs to automatically implement the mail editor as - a post-form_valid hook. - - The view must specify the `mail_code` variable. - """ - object = None - mail_form = None - has_permission_to_send_mail = True - alternative_from_address = None # Tuple: ('from_name', 'from_address') - - def __init__(self, *args, **kwargs): - if not self.mail_code: - raise AttributeError(self.__class__.__name__ + ' object has no attribute `mail_code`') - super().__init__(*args, **kwargs) - - def get_template_names(self): - """ - The mail editor form has its own template. - """ - if self.mail_form and not self.mail_form.is_valid(): - return ['mails/mail_form.html'] - return super().get_template_names() - - def post(self, request, *args, **kwargs): - """ - Handle POST requests, but interpect the data if the mail form data isn't valid. - """ - if not self.has_permission_to_send_mail: - # Don't use the mail form; don't send out the mail. - return super().post(request, *args, **kwargs) - self.object = self.get_object() - form = self.get_form() - if form.is_valid(): - self.mail_form = forms.EmailTemplateForm(request.POST or None, - mail_code=self.mail_code, - instance=self.object) - if self.mail_form.is_valid(): - return self.form_valid(form) - - return self.render_to_response( - self.get_context_data(form=self.mail_form, - transfer_data_form=forms.HiddenDataForm(form))) - else: - return self.form_invalid(form) - - def form_valid(self, form): - """ - If both the regular form and mailing form are valid, save the form and run the mail form. - """ - # Don't use the mail form; don't send out the mail. - if not self.has_permission_to_send_mail: - return super().form_valid(form) - - if self.alternative_from_address: - # Set different from address if given. - self.mail_form.set_alternative_sender( - self.alternative_from_address[0], self.alternative_from_address[1]) - - response = super().form_valid(form) - try: - self.mail_form.send() - except AttributeError: - # self.mail_form is None - raise AttributeError('Did you check the order in which MailEditorMixin is used?') - messages.success(self.request, 'Mail sent') - return response - - class MailUtilsMixin: """ This mixin takes care of inserting the default data into the Utils or Form. @@ -126,14 +52,7 @@ class MailUtilsMixin: self.mail_template = mail_template.render(kwargs) # Gather Recipients data - self.original_recipient = '' - if self.object: - recipient = self.object - for attr in self.mail_data.get('to_address').split('.'): - recipient = getattr(recipient, attr) - if inspect.ismethod(recipient): - recipient = recipient() - self.original_recipient = recipient + self.original_recipient = self._validate_single_entry(self.mail_data.get('to_address'))[0] self.subject = self.mail_data['subject'] @@ -148,18 +67,20 @@ class MailUtilsMixin: # Email string return [entry] else: - bcc_to = self.object + mail_to = self.object for attr in entry.split('.'): try: - bcc_to = getattr(bcc_to, attr) + mail_to = getattr(mail_to, attr) + if inspect.ismethod(mail_to): + mail_to = mail_to() except AttributeError: - # Invalid property, don't use bcc + # Invalid property/mail return [] - if not isinstance(bcc_to, list): - return [bcc_to] + if not isinstance(mail_to, list): + return [mail_to] else: - return bcc_to + return mail_to elif re.match("[^@]+@[^@]+\.[^@]+", entry): return [entry] @@ -170,8 +91,9 @@ class MailUtilsMixin: """ # Get recipients list. Try to send through BCC to prevent privacy issues! self.bcc_list = [] - for bcc_entry in self.mail_data.get('bcc_to', '').split(','): - self.bcc_list += self._validate_single_entry(bcc_entry) + if self.mail_data.get('bcc_to'): + for bcc_entry in self.mail_data['bcc_to'].split(','): + self.bcc_list += self._validate_single_entry(bcc_entry) def validate_recipients(self): # Check the send list diff --git a/mails/templates/mail_templates/publication_ready.html b/mails/templates/mail_templates/publication_ready.html new file mode 100644 index 0000000000000000000000000000000000000000..a3ebebbd5a05a8bd1103ead6d5db36f23ee1d4ca --- /dev/null +++ b/mails/templates/mail_templates/publication_ready.html @@ -0,0 +1,14 @@ +<p> + The following Publication is drafted and ready for publication. +</p> +<p> + <a href="https://scipost.org/{{ publication.get_absolute_url }}">{{ publication.title }}</a><br> + by {{ publication.author_list }} +</p> +<p> + Please review the Publication and proceed with the publication process. +</p> + +<p> + <em>This mail is automatically generated from the SciPost platform</em>. +</p> diff --git a/mails/templates/mail_templates/publication_ready.json b/mails/templates/mail_templates/publication_ready.json new file mode 100644 index 0000000000000000000000000000000000000000..fed59bff8e9546c56590e69f485cb82051b81119 --- /dev/null +++ b/mails/templates/mail_templates/publication_ready.json @@ -0,0 +1,5 @@ +{ + "subject": "SciPost: manuscript ready for publication", + "to_address": "admin@scipost.org", + "context_object": "publication" +} diff --git a/mails/utils.py b/mails/utils.py index ee0d910d627e623f9c64064bb1894ce615aec123..f03191e05c44552b442dff0ad3c999c966cd4bb3 100644 --- a/mails/utils.py +++ b/mails/utils.py @@ -1,7 +1,7 @@ -from . import mixins +from .mixins import MailUtilsMixin -class DirectMailUtil(mixins.MailUtilsMixin): +class DirectMailUtil(MailUtilsMixin): """ Same templates and json files as the form EmailTemplateForm, but this will directly send the mails out, without intercepting and showing the mail editor to the user. diff --git a/mails/views.py b/mails/views.py index 61e4e3ccc9b79ca7e5221092335e3df649aa853c..51f68766cfd89afbaeaf25d94803fb2a094bda7a 100644 --- a/mails/views.py +++ b/mails/views.py @@ -26,3 +26,72 @@ class MailEditingSubView(object): def return_render(self): self.context['form'] = self.mail_form return render(self.request, self.template_name, self.context) + + +class MailEditorMixin: + """ + Use MailEditorMixin in edit CBVs to automatically implement the mail editor as + a post-form_valid hook. + + The view must specify the `mail_code` variable. + """ + object = None + mail_form = None + has_permission_to_send_mail = True + alternative_from_address = None # Tuple: ('from_name', 'from_address') + + def __init__(self, *args, **kwargs): + if not self.mail_code: + raise AttributeError(self.__class__.__name__ + ' object has no attribute `mail_code`') + super().__init__(*args, **kwargs) + + def get_template_names(self): + """ + The mail editor form has its own template. + """ + if self.mail_form and not self.mail_form.is_valid(): + return ['mails/mail_form.html'] + return super().get_template_names() + + def post(self, request, *args, **kwargs): + """ + Handle POST requests, but interpect the data if the mail form data isn't valid. + """ + if not self.has_permission_to_send_mail: + # Don't use the mail form; don't send out the mail. + return super().post(request, *args, **kwargs) + self.object = self.get_object() + form = self.get_form() + if form.is_valid(): + self.mail_form = EmailTemplateForm(request.POST or None, mail_code=self.mail_code, + instance=self.object) + if self.mail_form.is_valid(): + return self.form_valid(form) + + return self.render_to_response( + self.get_context_data(form=self.mail_form, + transfer_data_form=HiddenDataForm(form))) + else: + return self.form_invalid(form) + + def form_valid(self, form): + """ + If both the regular form and mailing form are valid, save the form and run the mail form. + """ + # Don't use the mail form; don't send out the mail. + if not self.has_permission_to_send_mail: + return super().form_valid(form) + + if self.alternative_from_address: + # Set different from address if given. + self.mail_form.set_alternative_sender( + self.alternative_from_address[0], self.alternative_from_address[1]) + + response = super().form_valid(form) + try: + self.mail_form.send() + except AttributeError: + # self.mail_form is None + raise AttributeError('Did you check the order in which MailEditorMixin is used?') + messages.success(self.request, 'Mail sent') + return response diff --git a/production/templates/production/partials/production_stream_card.html b/production/templates/production/partials/production_stream_card.html index 1637b4bfe06682c252a2b3e733a99d74db397785..3cd23ca4f56947a8e666ef733d1999446c75bb76 100644 --- a/production/templates/production/partials/production_stream_card.html +++ b/production/templates/production/partials/production_stream_card.html @@ -99,11 +99,11 @@ {% endif %} {% if perms.scipost.can_publish_accepted_submission %} - {% if not stream.submission.publication %} - <li><a href="{% url 'journals:initiate_publication' %}">Initiate the publication process</a></li> - {% endif %} <li><a href="{% url 'production:mark_as_completed' stream_id=stream.id %}">Mark this stream as completed</a></li> {% endif %} + {% if perms.scipost.can_draft_publication and stream.status == 'accepted' %} + <li><a href="{% url 'journals:update_publication' stream.submission.arxiv_identifier_w_vn_nr %}">Draft Publication</a></li> + {% endif %} </ul> {% endif %} {% endblock %} diff --git a/production/templates/production/partials/production_stream_card_completed.html b/production/templates/production/partials/production_stream_card_completed.html index 38f1c7fa637e5fafee9117caf225492c8dad3abb..555799b9e3d39eb0c5b124c9664f98351774fe0c 100644 --- a/production/templates/production/partials/production_stream_card_completed.html +++ b/production/templates/production/partials/production_stream_card_completed.html @@ -8,6 +8,9 @@ {% include 'partials/submissions/submission_card_content.html' with submission=stream.submission %} </div> <div class="card-body"> + {% if perms.scipost.can_draft_publication and stream.status == 'accepted' %} + <p>The proofs have been accepted. Please start <a href="{% url 'journals:update_publication' stream.submission.arxiv_identifier_w_vn_nr %}">drafting the Publication here</a>.</p> + {% endif %} <h3>Stream details</h3> <ul> <li>Status: <span class="label label-secondary label-sm">{{ stream.get_status_display }}</span></li> diff --git a/scipost/feeds.py b/scipost/feeds.py index 738a1c9567a60b8004ad3fba2f543edec9056c6e..e58758d2fe56a7b4aa24b1e0083541e757515824 100644 --- a/scipost/feeds.py +++ b/scipost/feeds.py @@ -1,6 +1,7 @@ import datetime from django.contrib.syndication.views import Feed +from django.http import Http404 from django.utils.feedgenerator import Atom1Feed from django.core.urlresolvers import reverse from django.db.models import Q @@ -34,7 +35,7 @@ class LatestCommentsFeedRSS(Feed): elif item.submission: return reverse('submissions:submission', kwargs={'arxiv_identifier_w_vn_nr': - item.submission.arxiv_identifier_w_vn_nr,}) + item.submission.arxiv_identifier_w_vn_nr}) elif item.thesislink: return reverse('theses:thesis', kwargs={'thesislink_id': item.thesislink.id}) @@ -142,27 +143,26 @@ class LatestPublicationsFeedRSS(Feed): link = "/journals/" def get_object(self, request, subject_area=''): - if subject_area != '': - queryset = Publication.objects.filter( - Q(subject_area=subject_area) | Q(secondary_areas__contains=[subject_area]) - ).order_by('-publication_date')[:10] - queryset.subject_area = subject_area - else: - queryset = Publication.objects.order_by('-publication_date')[:10] - queryset.subject_area = None - return queryset + if subject_area and subject_area not in subject_areas_dict: + raise Http404('Invalid subject area') + qs = Publication.objects.published() + if subject_area: + qs = qs.filter( + Q(subject_area=subject_area) | Q(secondary_areas__contains=[subject_area])) + self.subject_area = subject_area + return qs.order_by('-publication_date')[:10] def title(self, obj): title_text = 'SciPost: Latest Publications' - if obj.subject_area: - title_text += ' in %s' % subject_areas_dict[obj.subject_area] + if self.subject_area: + title_text += ' in %s' % subject_areas_dict.get(self.subject_area) return title_text def description(self, obj): desc = 'SciPost: most recent publications' try: - if obj.subject_area: - desc += ' in %s' % subject_areas_dict[obj.subject_area] + if self.subject_area: + desc += ' in %s' % subject_areas_dict.get(self.subject_area) except KeyError: pass return desc diff --git a/scipost/forms.py b/scipost/forms.py index 9f68b07836b683bf59804cdf3fb3e37647dd66d0..61da0a666a8247b176858890bdb2b2662e05c494 100644 --- a/scipost/forms.py +++ b/scipost/forms.py @@ -42,6 +42,27 @@ REGISTRATION_REFUSAL_CHOICES = ( reg_ref_dict = dict(REGISTRATION_REFUSAL_CHOICES) +class RequestFormMixin: + """ + This mixin lets the Form accept `request` as an argument. + """ + def __init__(self, *args, **kwargs): + self.request = kwargs.pop('request') + super().__init__(*args, **kwargs) + + +class HttpRefererFormMixin(RequestFormMixin): + """ + This mixin adds a HiddenInput to the form which tracks the previous url, which can + be used to redirect to. + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['http_referer'] = forms.URLField(widget=forms.HiddenInput(), required=False) + if self.request: + self.fields['http_referer'].initial = self.request.META.get('HTTP_REFERER') + + class RegistrationForm(forms.Form): """ Use this form to process the registration of new accounts. diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py index 9a85a5165eb7b6cec44d41b2af231ea5701e3ef2..aa4d6d5ce525bbbdf5dcef55708ae01cedae68a3 100644 --- a/scipost/management/commands/add_groups_and_permissions.py +++ b/scipost/management/commands/add_groups_and_permissions.py @@ -248,6 +248,14 @@ class Command(BaseCommand): codename='can_view_all_funding_info', name='Can view all Funders info', content_type=content_type) + can_create_grants, created = Permission.objects.get_or_create( + codename='can_create_grants', + name='Can create Grant', + content_type=content_type) + can_draft_publication, created = Permission.objects.get_or_create( + codename='can_draft_publication', + name='Can draft Publication', + content_type=content_type) # Documentation can_view_docs_scipost, created = Permission.objects.get_or_create( @@ -325,7 +333,9 @@ class Command(BaseCommand): can_view_production, can_view_timesheets, can_publish_accepted_submission, + can_draft_publication, can_view_all_funding_info, + can_create_grants, can_attend_VGMs, can_manage_reports, can_assign_production_supervisor, @@ -376,6 +386,8 @@ class Command(BaseCommand): ProductionSupervisors.permissions.set([ can_assign_production_officer, can_take_decisions_related_to_proofs, + # can_draft_publication, + # can_create_grants, can_view_all_production_streams, can_run_proofs_by_authors, can_view_docs_scipost, diff --git a/scipost/management/commands/models.py b/scipost/management/commands/models.py new file mode 100644 index 0000000000000000000000000000000000000000..0447660e2a189556e389b610e0c5d6bc797218aa --- /dev/null +++ b/scipost/management/commands/models.py @@ -0,0 +1,188 @@ +import datetime +import hashlib +import random +import string + +from django.db import models, IntegrityError +from django.conf import settings +from django.utils import timezone + +from . import constants +from .managers import RegistrationInvitationQuerySet, CitationNotificationQuerySet + +from scipost.constants import TITLE_CHOICES + + +class RegistrationInvitation(models.Model): + """ + Invitation to particular persons for registration + """ + title = models.CharField(max_length=4, choices=TITLE_CHOICES) + first_name = models.CharField(max_length=30) + last_name = models.CharField(max_length=150) + email = models.EmailField() + status = models.CharField(max_length=8, choices=constants.REGISTATION_INVITATION_STATUSES, + default=constants.STATUS_DRAFT) + + # Text content + message_style = models.CharField(max_length=1, choices=constants.INVITATION_STYLE, + default=constants.INVITATION_FORMAL) + personal_message = models.TextField(blank=True) + invited_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, + blank=True, null=True, related_name='invitations_sent') + created_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='invitations_created') + + # Related to objects + invitation_type = models.CharField(max_length=2, choices=constants.INVITATION_TYPE, + default=constants.INVITATION_CONTRIBUTOR) + + # Response keys + invitation_key = models.CharField(max_length=40, unique=True) + key_expires = models.DateTimeField(default=timezone.now) + + # Timestamps + date_sent_first = models.DateTimeField(null=True, blank=True) + date_sent_last = models.DateTimeField(null=True, blank=True) + times_sent = models.PositiveSmallIntegerField(default=0) + created = models.DateTimeField(auto_now_add=True) + modified = models.DateTimeField(auto_now=True) + + objects = RegistrationInvitationQuerySet.as_manager() + + class Meta: + ordering = ['last_name'] + + def __str__(self): + return '{} {} on {}'.format(self.first_name, self.last_name, + self.created.strftime("%Y-%m-%d")) + + def save(self, *args, **kwargs): + self.refresh_keys(commit=False) + return super().save(*args, **kwargs) + + def refresh_keys(self, force_new_key=False, commit=True): + # Generate email activation key and link + if not self.invitation_key or force_new_key: + # TODO: Replace this all by the `secrets` package available from python 3.6(!) + salt = '' + for i in range(5): + salt += random.choice(string.ascii_letters) + salt = salt.encode('utf8') + invitationsalt = self.last_name.encode('utf8') + self.invitation_key = hashlib.sha1(salt + invitationsalt).hexdigest() + self.key_expires = timezone.now() + datetime.timedelta(days=365) + if commit: + self.save() + + def mail_sent(self, user=None): + """ + Update instance fields as if a new invitation mail has been sent out. + """ + if self.status == constants.STATUS_DRAFT: + self.status = constants.STATUS_SENT + if not self.date_sent_first: + self.date_sent_first = timezone.now() + self.date_sent_last = timezone.now() + self.invited_by = user or self.created_by + self.times_sent += 1 + self.citation_notifications.update(processed=True) + self.save() + + @property + def has_responded(self): + return self.status in [constants.STATUS_DECLINED, constants.STATUS_REGISTERED] + + +class CitationNotification(models.Model): + invitation = models.ForeignKey('invitations.RegistrationInvitation', + on_delete=models.SET_NULL, + null=True, blank=True) + contributor = models.ForeignKey('scipost.Contributor', + on_delete=models.CASCADE, + null=True, blank=True, + related_name='+') + + # Content + submission = models.ForeignKey('submissions.Submission', null=True, blank=True, + related_name='+') + publication = models.ForeignKey('journals.Publication', null=True, blank=True, + related_name='+') + processed = models.BooleanField(default=False) + + # Meta info + created_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='notifications_created') + date_sent = models.DateTimeField(null=True, blank=True) + created = models.DateTimeField(auto_now_add=True) + modified = models.DateTimeField(auto_now=True) + + objects = CitationNotificationQuerySet.as_manager() + + class Meta: + default_related_name = 'citation_notifications' + unique_together = ( + ('invitation', 'submission'), + ('invitation', 'publication'), + ('contributor', 'submission'), + ('contributor', 'publication'), + ) + + def __str__(self): + _str = 'Citation for ' + if self.invitation: + _str += ' Invitation ({} {})'.format( + self.invitation.first_name, + self.invitation.last_name, + ) + elif self.contributor: + _str += ' Contributor ({})'.format(self.contributor) + + _str += ' on ' + if self.submission: + _str += 'Submission ({})'.format(self.submission.arxiv_identifier_w_vn_nr) + elif self.publication: + _str += 'Publication ({})'.format(self.publication.doi_label) + return _str + + def save(self, *args, **kwargs): + if not self.submission and not self.publication: + raise IntegrityError(('CitationNotification needs to be related to either a ' + 'Submission or Publication object.')) + return super().save(*args, **kwargs) + + def mail_sent(self): + """ + Update instance fields as if a new citation notification mail has been sent out. + """ + self.processed = True + if not self.date_sent: + # Don't overwrite by accident... + self.date_sent = timezone.now() + self.save() + + def related_notifications(self): + return CitationNotification.objects.unprocessed().filter( + models.Q(contributor=self.contributor) | models.Q(invitation=self.invitation)) + + def get_first_related_contributor(self): + return self.related_notifications().filter(contributor__isnull=False).first() + + @property + def email(self): + if self.invitation: + return self.invitation.email + elif self.contributor: + return self.contributor.user.email + + @property + def last_name(self): + if self.invitation: + return self.invitation.last_name + elif self.contributor: + return self.contributor.user.last_name + + @property + def get_title(self): + if self.invitation: + return self.invitation.get_title_display() + elif self.contributor: + return self.contributor.get_title_display() diff --git a/scipost/mixins.py b/scipost/mixins.py index 0cdb24844cca643934797c665b3357a01caabf82..b7d76dc854dfffc2ed458f0cc0b49ace0d2a66fd 100644 --- a/scipost/mixins.py +++ b/scipost/mixins.py @@ -1,15 +1,21 @@ +from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin + from .paginator import SciPostPaginator +class PermissionsMixin(LoginRequiredMixin, PermissionRequiredMixin): + pass + + class PaginationMixin: """ Mixin for generic class-based views (e.g. django.views.generic.ListView) """ paginator_class = SciPostPaginator - # def get_paginator(self, queryset, per_page, orphans=0, allow_empty_first_page=True): - # # Pass the request object to the paginator to keep the parameters in the - # # url querystring ("?page=2&old_param=...") - # request = self.request - # return self.paginator_class(queryset, per_page, orphans=orphans, - # allow_empty_first_page=allow_empty_first_page, request=request) + +class RequestViewMixin: + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs['request'] = self.request + return kwargs diff --git a/scipost/static/scipost/assets/js/scripts.js b/scipost/static/scipost/assets/js/scripts.js index 4e96974ec3df7596e5d8cdc9f2fbafa8daedad99..6a451fe5e5b1bed6004c85e7da4fb15275f42a5f 100644 --- a/scipost/static/scipost/assets/js/scripts.js +++ b/scipost/static/scipost/assets/js/scripts.js @@ -1,7 +1,7 @@ import notifications from './notifications.js'; function hide_all_alerts() { - $(".alert").fadeOut(300); + $(".alert").remove('.no-dismiss').fadeOut(300); } var activate_tooltip = function() { diff --git a/scipost/views.py b/scipost/views.py index 3097897d4be736bc225a97543b6cc026cebd921c..bf965b1f1d9323209cfcd6c94e997c279de75d3d 100644 --- a/scipost/views.py +++ b/scipost/views.py @@ -511,7 +511,7 @@ def _personal_page_publications(request): contributor = request.user.contributor context = { 'contributor': contributor, - 'own_publications': contributor.publications.order_by('-publication_date') + 'own_publications': contributor.publications.published().order_by('-publication_date') } context['nr_publication_authorships_to_claim'] = Publication.objects.filter( author_list__contains=request.user.last_name).exclude( diff --git a/submissions/forms.py b/submissions/forms.py index 7dc9bc305a3a998a3c345678da02edf64172fe09..850fcbc52a7d743d9eb4747012ab219cf081e4f5 100644 --- a/submissions/forms.py +++ b/submissions/forms.py @@ -495,11 +495,8 @@ class VotingEligibilityForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - # Do we need this discipline filter still with the new Pool construction??? - # -- JdW; Oct 20th, 2017 self.fields['eligible_fellows'].queryset = Contributor.objects.filter( fellowships__pool=self.instance.submission, - discipline=self.instance.submission.discipline, expertises__contains=[self.instance.submission.subject_area] ).order_by('user__last_name') diff --git a/submissions/models.py b/submissions/models.py index 61d1f8555517f1afd33a60a1a6dd7b0a5c1f589b..f9cdcfd6f731c28c9ef43b839e2b76e04d9e12ba 100644 --- a/submissions/models.py +++ b/submissions/models.py @@ -139,11 +139,9 @@ class Submission(models.Model): header += ' (current version)' else: header += ' (deprecated version ' + str(self.arxiv_vn_nr) + ')' - try: + if hasattr(self, 'publication') and self.publication.is_published: header += ' (published as %s (%s))' % ( self.publication.doi_string, self.publication.publication_date.strftime('%Y')) - except Publication.DoesNotExist: - pass return header def touch(self): diff --git a/submissions/templates/partials/submissions/pool/submission_details.html b/submissions/templates/partials/submissions/pool/submission_details.html index d8b17662f5918fb642d1aa70ac453b794fcfd5ad..bc0e006926e75d812324d4f9a9f33afdd495c099 100644 --- a/submissions/templates/partials/submissions/pool/submission_details.html +++ b/submissions/templates/partials/submissions/pool/submission_details.html @@ -94,7 +94,7 @@ {# Accepted submission actions #} {% if submission.status == 'accepted' %} <li><a href="{% url 'submissions:treated_submission_pdf_compile' submission.arxiv_identifier_w_vn_nr %}">Update the Refereeing Package pdf</a></li> - <li>After proofs have been accepted, you can <a href="{% url 'journals:initiate_publication' %}">initiate the publication process</a> (leads to the validation page)</li> + <li><a href="{% url 'journals:update_publication' submission.arxiv_identifier_w_vn_nr %}">Draft Publication</a></li> {% endif %} </ul> diff --git a/submissions/templates/partials/submissions/submission_card_content.html b/submissions/templates/partials/submissions/submission_card_content.html index aa9128c75d3e9ac2f5ff0e87f181f13e5c4bcceb..80ca856c5ff151077fdc79ce337ed439706983da 100644 --- a/submissions/templates/partials/submissions/submission_card_content.html +++ b/submissions/templates/partials/submissions/submission_card_content.html @@ -4,7 +4,7 @@ <p class="text-muted mb-0"> Version {{ submission.arxiv_vn_nr }} ({% if submission.is_current %}current version{% else %}deprecated version {{ submission.arxiv_vn_nr }}{% endif %}) <br> - {% if submission.publication %} + {% if submission.publication and submission.publication.is_published %} Published as <a href="{{ submission.publication.get_absolute_url }}">{{ submission.publication.in_issue.in_volume.in_journal.get_abbreviation_citation }} <strong>{{ submission.publication.in_issue.in_volume.number }}</strong>, {{ submission.publication.get_paper_nr }} ({{ submission.publication.publication_date|date:'Y' }})</a> {% else %} Submitted {{ submission.submission_date }} to {{ submission.get_submitted_to_journal_display }} diff --git a/submissions/templates/partials/submissions/submission_status.html b/submissions/templates/partials/submissions/submission_status.html index f4812d1d3a9a41215a6f8d8c637707088e4d4e41..60a391c36da52871975d2ae6efb0c428d73238e2 100644 --- a/submissions/templates/partials/submissions/submission_status.html +++ b/submissions/templates/partials/submissions/submission_status.html @@ -1,7 +1,7 @@ <h4 class="d-inline-block">Current status:</h4> <div class="d-inline"> <span class="label label-secondary">{{submission.get_status_display}}</span> - {% if submission.publication %} + {% if submission.publication and submission.publication.is_published %} as <a href="{{submission.publication.get_absolute_url}}">{{submission.publication.in_issue.in_volume.in_journal.get_abbreviation_citation}} <strong>{{submission.publication.in_issue.in_volume.number}}</strong>, {{submission.publication.get_paper_nr}} ({{submission.publication.publication_date|date:'Y'}})</a> {% endif %} </div> diff --git a/submissions/templates/submissions/submission_detail.html b/submissions/templates/submissions/submission_detail.html index 0d6dd800738d0cec86087a939f8efd0969f93639..b43d69f9553777e50f70a918299e59e5f6872a41 100644 --- a/submissions/templates/submissions/submission_detail.html +++ b/submissions/templates/submissions/submission_detail.html @@ -26,7 +26,7 @@ <h3 class="mb-3">by {{submission.author_list}}</h3> <div class="pl-2"> - {% if submission.publication %} + {% if submission.publication and submission.publication.is_published %} <h3>- Published as <a href="{{submission.publication.get_absolute_url}}">{{submission.publication.in_issue.in_volume.in_journal.get_abbreviation_citation}} <strong>{{submission.publication.in_issue.in_volume.number}}</strong>, {{submission.publication.get_paper_nr}} ({{submission.publication.publication_date|date:'Y'}})</a></h3> {% endif %} @@ -42,25 +42,6 @@ {% if not submission.is_current %} <h3><span class="text-danger">- This is not the current version.</span></h3> {% endif %} - - {% comment %} - {% if submission.other_versions or not submission.is_current %} - <ul class="mt-3 mb-1 list-unstyled pl-4"> - {% if not submission.is_current %} - <li><h3 class="text-danger">This is not the current version.</h3></li> - {% endif %} - - {% if submission.other_versions %} - <li>Other versions of this Submission (with Reports) exist:</li> - <ul class="list-unstyled"> - {% for vn in submission.other_versions %} - <li>{% include 'partials/submissions/submission_version.html' with submission=vn %}</li> - {% endfor %} - </ul> - {% endif %} - </ul> - {% endif %} - {% endcomment %} </div> <h3 class="mt-2">Submission summary</h3>