diff --git a/commentaries/templates/commentaries/_commentary_summary.html b/commentaries/templates/commentaries/_commentary_summary.html index 7567fea0d99cbf2d19e9d17abae4c852be517e06..8c817cca6a218f64e0473e78980e909e5a220ff0 100644 --- a/commentaries/templates/commentaries/_commentary_summary.html +++ b/commentaries/templates/commentaries/_commentary_summary.html @@ -46,5 +46,5 @@ {% if commentary.scipost_publication %} <br> - <p class="my-0">Published in {{commentary.scipost_publication.in_issue.in_volume.in_journal.get_name_display}}: <a href="{{commentary.scipost_publication.get_absolute_url}}">{{commentary.scipost_publication.in_issue.in_volume.in_journal.get_abbreviation_citation}} <strong>{{commentary.scipost_publication.in_issue.in_volume.number}}</strong>, {{commentary.scipost_publication.get_paper_nr}} ({{commentary.scipost_publication.publication_date|date:'Y'}})</a></p> + <p class="my-0">Published in {{commentary.scipost_publication.in_issue.in_volume.in_journal.get_name_display}}: <a href="{{commentary.scipost_publication.get_absolute_url}}">{{commentary.scipost_publication.in_issue.in_volume.in_journal.abbreviation_citation}} <strong>{{commentary.scipost_publication.in_issue.in_volume.number}}</strong>, {{commentary.scipost_publication.get_paper_nr}} ({{commentary.scipost_publication.publication_date|date:'Y'}})</a></p> {% endif %} diff --git a/funders/forms.py b/funders/forms.py index a5c7d8d29a2f363cf7f8adb125e5b9eff9ad782f..a88322618dda52734dd9e9418a89706f45660061 100644 --- a/funders/forms.py +++ b/funders/forms.py @@ -28,9 +28,9 @@ class GrantForm(HttpRefererFormMixin, forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['recipient'] = forms.ModelChoiceField( - queryset=Contributor.objects.all().order_by('user__last_name'), + queryset=Contributor.objects.select_related('user').order_by('user__last_name'), required=False) class GrantSelectForm(forms.Form): - grant = forms.ModelChoiceField(queryset=Grant.objects.all()) + grant = forms.ModelChoiceField(queryset=Grant.objects.all().select_related('funder')) diff --git a/journals/behaviors.py b/journals/behaviors.py index eca874354c8a6458dd3fa6804c38f7770c65e2a7..8079e19c758b972d28c1dafc4869fd62f90f9cdf 100644 --- a/journals/behaviors.py +++ b/journals/behaviors.py @@ -1,5 +1,8 @@ from django.core.validators import RegexValidator +from .constants import PUBLICATION_DOI_VALIDATION_REGEX + + doi_journal_validator = RegexValidator(r'^[a-zA-Z]+$', 'Only valid DOI expressions are allowed ([a-zA-Z]+).') doi_volume_validator = RegexValidator(r'^[a-zA-Z]+.[0-9]+$', @@ -7,6 +10,7 @@ doi_volume_validator = RegexValidator(r'^[a-zA-Z]+.[0-9]+$', doi_issue_validator = RegexValidator(r'^[a-zA-Z]+.[0-9]+.[0-9]+$', ('Only valid DOI expressions are allowed ' '([a-zA-Z]+.[0-9]+.[0-9]+).')) -doi_publication_validator = RegexValidator(r'^[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,}$', - ('Only valid DOI expressions are allowed ' - '([a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,}).')) +doi_publication_validator = RegexValidator( + r'^{regex}$'.format(regex=PUBLICATION_DOI_VALIDATION_REGEX), + ('Only valid DOI expressions are allowed ' + '(`[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,}` or `[a-zA-Z]+.[0-9]+`)')) diff --git a/journals/constants.py b/journals/constants.py index 8bbd0497025de767f66a792dcc78ddcf0baeea26..60a5440145aa2ac1acbb33326f38151c3b398dc0 100644 --- a/journals/constants.py +++ b/journals/constants.py @@ -26,6 +26,7 @@ REGEX_CHOICES = '|'.join([ SCIPOST_JOURNAL_PHYSICS ]) +PUBLICATION_DOI_REGEX = PUBLICATION_DOI_VALIDATION_REGEX = '[a-zA-Z]+.[0-9]+(.[0-9]+.[0-9]{3,})?' SCIPOST_JOURNALS_DOMAINS = ( ('E', 'Experimental'), @@ -78,3 +79,13 @@ CC_LICENSES_URI = ( (CCBYSA4, 'https://creativecommons.org/licenses/by-sa/4.0'), (CCBYNC4, 'https://creativecommons.org/licenses/by-nc/4.0'), ) + + +ISSUES_AND_VOLUMES = 'IV' +ISSUES_ONLY = 'IO' +INDIVIDUAL_PUBLCATIONS = 'IP' +JOURNAL_STRUCTURE = ( + (ISSUES_AND_VOLUMES, 'Issues and Volumes'), + # (ISSUES_ONLY, 'Issues only'), # This option complies with Crossref's rules, but is not implemented (yet). + (INDIVIDUAL_PUBLCATIONS, 'Individual Publications'), +) diff --git a/journals/forms.py b/journals/forms.py index 5bbddf4ab3502e1fb62c95322d7b5d3027a4476e..4464d567a65e5ad49d7d0870b78f1f807c35e853 100644 --- a/journals/forms.py +++ b/journals/forms.py @@ -19,6 +19,7 @@ from .utils import JournalUtils from funders.models import Grant, Funder +from journals.models import Journal from mails.utils import DirectMailUtil from production.constants import PROOFS_PUBLISHED from production.models import ProductionEvent @@ -127,17 +128,20 @@ class CreateMetadataXMLForm(forms.ModelForm): class CreateMetadataDOAJForm(forms.ModelForm): class Meta: model = Publication - fields = () + fields = ['metadata_DOAJ'] def __init__(self, *args, **kwargs): self.request = kwargs.pop('request') + kwargs['initial'] = { + 'metadata_DOAJ': self.generate(kwargs.get('instance')) + } super().__init__(*args, **kwargs) - def save(self, *args, **kwargs): - self.instance.metadata_DOAJ = self.generate(self.instance) - return super().save(*args, **kwargs) - def generate(self, publication): + if publication.in_issue: + issn = str(publication.in_issue.in_volume.in_journal.issn) + else: + issn = str(publication.in_journal.issn) md = { 'bibjson': { 'author': [{'name': publication.author_list}], @@ -149,7 +153,7 @@ class CreateMetadataDOAJForm(forms.ModelForm): 'identifier': [ { 'type': 'eissn', - 'id': str(publication.in_issue.in_volume.in_journal.issn) + 'id': issn }, { 'type': 'doi', @@ -162,28 +166,48 @@ class CreateMetadataDOAJForm(forms.ModelForm): 'type': 'fulltext', } ], - 'journal': { - 'publisher': 'SciPost', - 'volume': str(publication.in_issue.in_volume.number), - 'number': str(publication.in_issue.number), - 'identifier': [{ - 'type': 'eissn', - 'id': str(publication.in_issue.in_volume.in_journal.issn) - }], - 'license': [ - { - 'url': self.request.build_absolute_uri( - publication.in_issue.in_volume.in_journal.get_absolute_url()), - 'open_access': 'true', - 'type': publication.get_cc_license_display(), - 'title': publication.get_cc_license_display(), - } - ], - 'language': ['EN'], - 'title': publication.in_issue.in_volume.in_journal.get_name_display(), - } } } + if publication.in_issue: + md['journal'] = { + 'publisher': 'SciPost', + 'volume': str(publication.in_issue.in_volume.number), + 'number': str(publication.in_issue.number), + 'identifier': [{ + 'type': 'eissn', + 'id': issn + }], + 'license': [ + { + 'url': self.request.build_absolute_uri( + publication.in_issue.in_volume.in_journal.get_absolute_url()), + 'open_access': 'true', + 'type': publication.get_cc_license_display(), + 'title': publication.get_cc_license_display(), + } + ], + 'language': ['EN'], + 'title': publication.in_issue.in_volume.in_journal.get_name_display(), + } + else: + md['journal'] = { + 'publisher': 'SciPost', + 'identifier': [{ + 'type': 'eissn', + 'id': issn + }], + 'license': [ + { + 'url': self.request.build_absolute_uri( + publication.in_journal.get_absolute_url()), + 'open_access': 'true', + 'type': publication.get_cc_license_display(), + 'title': publication.get_cc_license_display(), + } + ], + 'language': ['EN'], + 'title': publication.in_journal.get_name_display(), + } return md @@ -306,20 +330,32 @@ class DraftPublicationForm(forms.ModelForm): # Use separate instance to be able to prefill the form without any existing Publication self.submission = None self.issue = None + self.to_journal = 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: + + # Check if the Submission is related to a Journal with individual Publications only + if self.submission: + try: + self.to_journal = Journal.objects.has_individual_publications().get( + name=self.submission.submitted_to_journal) + except Journal.DoesNotExist: + self.to_journal = None + + # If the Journal is not for individual publications, choose a Issue for Publication + if issue_id and not self.to_journal: 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: + + if kwargs.get('instance') or self.issue or self.to_journal: # When updating: fix in_issue, because many fields are directly related to the issue. del self.fields['in_issue'] self.prefill_fields() @@ -328,7 +364,10 @@ class DraftPublicationForm(forms.ModelForm): self.delete_secondary_fields() def get_possible_issues(self): - return Issue.objects.filter(until_date__gte=timezone.now()) + issues = Issue.objects.filter(until_date__gte=timezone.now()) + if self.submission: + issues = issues.for_journal(self.submission.submitted_to_journal) + return issues def delete_secondary_fields(self): """ @@ -351,6 +390,17 @@ class DraftPublicationForm(forms.ModelForm): del self.fields['acceptance_date'] del self.fields['publication_date'] + def clean(self): + data = super().clean() + if not self.instance.id: + if self.submission: + self.instance.accepted_submission = self.submission + if self.issue: + self.instance.in_issue = self.issue + if self.to_journal: + self.instance.in_journal = self.to_journal + return data + def save(self, *args, **kwargs): """ Save the Publication object always as a draft and prefill the Publication with @@ -359,10 +409,7 @@ class DraftPublicationForm(forms.ModelForm): 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) + super().save(*args, **kwargs) if do_prefill: self.first_time_fill() return self.instance @@ -398,51 +445,101 @@ class DraftPublicationForm(forms.ModelForm): 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 + self.fields['publication_date'].initial = timezone.now() # Fill data that may be derived from the issue data - issue = self.instance.in_issue if hasattr(self.instance, 'in_issue') else self.issue + issue = None + if hasattr(self.instance, 'in_issue') and self.instance.in_issue: + issue = self.instance.in_issue + elif self.issue: + issue = 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 + self.prefill_with_issue(issue) + + # Fill data that may be derived from the issue data + journal = None + if hasattr(self.instance, 'in_journal') and self.instance.in_journal: + journal = self.instance.in_issue + elif self.to_journal: + journal = self.to_journal + if journal: + self.prefill_with_journal(journal) + + def prefill_with_issue(self, issue): + # Determine next available paper number: + paper_nr = Publication.objects.filter(in_issue__in_volume=issue.in_volume).count() + 1 + 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.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 + + def prefill_with_journal(self, journal): + # Determine next available paper number: + paper_nr = journal.publications.count() + 1 + self.fields['paper_nr'].initial = str(paper_nr) + doi_label = '{journal}.{paper}'.format( + journal=journal.name, + paper=paper_nr) + 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' + '\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'), + journal.abbreviation_citation, + paper_nr, + timezone.now().year, + 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): @@ -483,14 +580,24 @@ class PublicationPublishForm(RequestFormMixin, forms.ModelForm): fields = [] def move_pdf(self): - # Move file to final location + """ + To keep the Publication pdfs organized we move the pdfs to their own folder + organized by journal and optional issue folder. + """ 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 + + new_dir = '' + if self.instance.in_issue: + new_dir += self.instance.in_issue.path + elif self.instance.in_journal: + new_dir += 'SCIPOST_JOURNALS/{name}'.format(name=self.instance.in_journal.name) + + new_dir += '/{paper_nr}'.format(paper_nr=self.instance.get_paper_nr()) + os.makedirs(settings.MEDIA_ROOT + new_dir) + + new_dir += '/{doi}.pdf'.format(doi=self.instance.doi_label.replace('.', '_')) + os.rename(initial_path, settings.MEDIA_ROOT + new_dir) + self.instance.pdf_file.name = new_dir self.instance.status = PUBLICATION_PUBLISHED self.instance.save() diff --git a/journals/managers.py b/journals/managers.py index 9d9bb31a1e25049162b5c0737079c214ec400723..8bddbf37b2692c8149c5ecfa33147344491029bb 100644 --- a/journals/managers.py +++ b/journals/managers.py @@ -1,59 +1,53 @@ from django.db import models -from django.http import Http404 from django.utils import timezone -from .constants import STATUS_PUBLISHED, STATUS_DRAFT, PUBLICATION_PUBLISHED +from .constants import STATUS_PUBLISHED, STATUS_DRAFT, PUBLICATION_PUBLISHED, ISSUES_AND_VOLUMES,\ + ISSUES_ONLY, INDIVIDUAL_PUBLCATIONS -class JournalManager(models.Manager): +class JournalQuerySet(models.QuerySet): def active(self): return self.filter(active=True) + def has_issues(self): + return self.filter(structure__in=(ISSUES_AND_VOLUMES, ISSUES_ONLY)) -class IssueManager(models.Manager): - def get_published(self, *args, **kwargs): - try: - return self.published(*args, **kwargs)[0] - except IndexError: - raise Http404 + def has_individual_publications(self): + return self.filter(structure=INDIVIDUAL_PUBLCATIONS) - def published(self, journal=None, **kwargs): - issues = self.filter(status=STATUS_PUBLISHED, **kwargs) - if journal: - issues.filter(in_volume__in_journal=journal) - return issues - def in_draft(self, journal=None, **kwargs): - issues = self.filter(status=STATUS_DRAFT, **kwargs) - if journal: - issues.filter(in_volume__in_journal=journal) - return issues +class IssueQuerySet(models.QuerySet): + def published(self): + return self.filter(status=STATUS_PUBLISHED) - def get_current_issue(self, *args, **kwargs): - return self.published(start_date__lte=timezone.now(), - until_date__gte=timezone.now(), - **kwargs).order_by('-until_date').first() + def in_draft(self): + return self.filter(status=STATUS_DRAFT) - def get_last_filled_issue(self, *args, **kwargs): - return self.published(publication__isnull=False, - **kwargs).order_by('-until_date').first() + def for_journal(self, journal_name): + return self.filter( + models.Q(in_volume__in_journal__name=journal_name) | + models.Q(in_journal__name=journal_name)) + def get_current_issue(self): + return self.published( + start_date__lte=timezone.now(), until_date__gte=timezone.now()).first() -class PublicationQuerySet(models.QuerySet): - def get_published(self, *args, **kwargs): - try: - return self.published().filter(*args, **kwargs)[0] - except IndexError: - raise Http404 - def published(self, **kwargs): - return self.filter(status=PUBLICATION_PUBLISHED, in_issue__status=STATUS_PUBLISHED) +class PublicationQuerySet(models.QuerySet): + def published(self): + return self.filter(status=PUBLICATION_PUBLISHED).filter( + models.Q(in_issue__status=STATUS_PUBLISHED) | models.Q(in_journal__active=True)) def unpublished(self): return self.exclude(status=PUBLICATION_PUBLISHED) - def in_draft(self, **kwargs): - return self.filter(in_issue__status=STATUS_DRAFT, **kwargs) + def in_draft(self): + return self.filter(in_issue__status=STATUS_DRAFT) def drafts(self): return self.filter(status=STATUS_DRAFT) + + def for_journal(self, journal_name): + return self.filter( + models.Q(in_issue__in_volume__in_journal__name=journal_name) | + models.Q(in_journal__name=journal_name)) diff --git a/journals/migrations/0017_auto_20180310_1103.py b/journals/migrations/0017_auto_20180310_1103.py new file mode 100644 index 0000000000000000000000000000000000000000..b5ba40965f1d7c49a56cedc586a25357f5b8bd9b --- /dev/null +++ b/journals/migrations/0017_auto_20180310_1103.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-03-10 10:03 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0016_auto_20180303_0918'), + ] + + operations = [ + migrations.AddField( + model_name='journal', + name='_has_issues', + field=models.BooleanField(default=True, verbose_name='Use Issues to group Publications'), + ), + migrations.AddField( + model_name='journal', + name='_has_volumes', + field=models.BooleanField(default=True, verbose_name='Use Issues to group Publications (if True, the use of Issues is required)'), + ), + migrations.AlterField( + model_name='publication', + name='in_issue', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='publications', to='journals.Issue'), + ), + ] diff --git a/journals/migrations/0018_auto_20180310_1112.py b/journals/migrations/0018_auto_20180310_1112.py new file mode 100644 index 0000000000000000000000000000000000000000..028c046820795fa14e37b882febfced9ab715ba5 --- /dev/null +++ b/journals/migrations/0018_auto_20180310_1112.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-03-10 10:12 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0017_auto_20180310_1103'), + ] + + operations = [ + migrations.AddField( + model_name='issue', + name='in_journal', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='journals.Journal'), + ), + migrations.AlterField( + model_name='issue', + name='in_volume', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='journals.Volume'), + ), + ] diff --git a/journals/migrations/0019_auto_20180310_1115.py b/journals/migrations/0019_auto_20180310_1115.py new file mode 100644 index 0000000000000000000000000000000000000000..71ac22ab4b8851c23c3db53b9e151429a6481334 --- /dev/null +++ b/journals/migrations/0019_auto_20180310_1115.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-03-10 10:15 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0018_auto_20180310_1112'), + ] + + operations = [ + migrations.AddField( + model_name='publication', + name='in_journal', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='publications', to='journals.Journal'), + ), + migrations.AlterField( + model_name='publication', + name='in_issue', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='publications', to='journals.Issue'), + ), + ] diff --git a/journals/migrations/0020_auto_20180310_1137.py b/journals/migrations/0020_auto_20180310_1137.py new file mode 100644 index 0000000000000000000000000000000000000000..3e800f5de4a291096dfd7f1a302503a578bfe937 --- /dev/null +++ b/journals/migrations/0020_auto_20180310_1137.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-03-10 10:37 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0019_auto_20180310_1115'), + ] + + operations = [ + migrations.RenameField( + model_name='journal', + old_name='_has_issues', + new_name='has_issues', + ), + migrations.RenameField( + model_name='journal', + old_name='_has_volumes', + new_name='has_volumes', + ), + migrations.AlterField( + model_name='issue', + name='in_journal', + field=models.ForeignKey(blank=True, help_text='Assign either an Volume or Journal to the Issue', null=True, on_delete=django.db.models.deletion.PROTECT, to='journals.Journal'), + ), + migrations.AlterField( + model_name='issue', + name='in_volume', + field=models.ForeignKey(blank=True, help_text='Assign either an Volume or Journal to the Issue', null=True, on_delete=django.db.models.deletion.PROTECT, to='journals.Volume'), + ), + migrations.AlterField( + model_name='publication', + name='in_issue', + field=models.ForeignKey(blank=True, help_text='Assign either an Issue or Journal to the Publication', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='publications', to='journals.Issue'), + ), + migrations.AlterField( + model_name='publication', + name='in_journal', + field=models.ForeignKey(blank=True, help_text='Assign either an Issue or Journal to the Publication', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='publications', to='journals.Journal'), + ), + ] diff --git a/journals/migrations/0021_auto_20180310_1137.py b/journals/migrations/0021_auto_20180310_1137.py new file mode 100644 index 0000000000000000000000000000000000000000..70c8a6fd2ebcc3628790bf1de87db5743ac33bd8 --- /dev/null +++ b/journals/migrations/0021_auto_20180310_1137.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-03-10 10:37 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0020_auto_20180310_1137'), + ] + + operations = [ + migrations.AlterField( + model_name='journal', + name='has_volumes', + field=models.BooleanField(default=True, verbose_name='Use Volumes to group Publications'), + ), + ] diff --git a/journals/migrations/0022_auto_20180310_1154.py b/journals/migrations/0022_auto_20180310_1154.py new file mode 100644 index 0000000000000000000000000000000000000000..94624cc27b658c23a24d5cc75362314c9686ffd9 --- /dev/null +++ b/journals/migrations/0022_auto_20180310_1154.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-03-10 10:54 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0021_auto_20180310_1137'), + ] + + operations = [ + migrations.RemoveField( + model_name='journal', + name='has_issues', + ), + migrations.RemoveField( + model_name='journal', + name='has_volumes', + ), + ] diff --git a/journals/migrations/0023_journal_structure.py b/journals/migrations/0023_journal_structure.py new file mode 100644 index 0000000000000000000000000000000000000000..0bfb0012cc2548ce1d1db0a428b2ac810d4fcb9b --- /dev/null +++ b/journals/migrations/0023_journal_structure.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-03-10 11:05 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0022_auto_20180310_1154'), + ] + + operations = [ + migrations.AddField( + model_name='journal', + name='structure', + field=models.CharField(choices=[('IV', 'Issues and Volumes'), ('IO', 'Issues only'), ('IP', 'Individual Publications')], default='IV', max_length=2), + ), + ] diff --git a/journals/models.py b/journals/models.py index b0d64fbeca8a2bb2c703b5c9e669562e89505235..5b755195e44ad710d2d3390d6c209ba0d51bf775 100644 --- a/journals/models.py +++ b/journals/models.py @@ -1,6 +1,7 @@ from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.fields import JSONField +from django.core.exceptions import ValidationError from django.db import models from django.db.models import Avg, F from django.utils import timezone @@ -10,9 +11,10 @@ from .behaviors import doi_journal_validator, doi_volume_validator,\ doi_issue_validator, doi_publication_validator from .constants import SCIPOST_JOURNALS, SCIPOST_JOURNALS_DOMAINS,\ STATUS_DRAFT, STATUS_PUBLISHED, ISSUE_STATUSES, PUBLICATION_PUBLISHED,\ - CCBY4, CC_LICENSES, CC_LICENSES_URI, PUBLICATION_STATUSES + CCBY4, CC_LICENSES, CC_LICENSES_URI, PUBLICATION_STATUSES,\ + JOURNAL_STRUCTURE, ISSUES_AND_VOLUMES, ISSUES_ONLY from .helpers import paper_nr_string, journal_name_abbrev_citation -from .managers import IssueManager, PublicationQuerySet, JournalManager +from .managers import IssueQuerySet, PublicationQuerySet, JournalQuerySet from scipost.constants import SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS from scipost.fields import ChoiceArrayField @@ -71,27 +73,56 @@ class PublicationAuthorsTable(models.Model): class Journal(models.Model): + """ + Journal is a container of Publications with a unique issn and doi_label. + Publications may be categorized into issues or issues and volumes. + """ name = models.CharField(max_length=100, choices=SCIPOST_JOURNALS, unique=True) doi_label = models.CharField(max_length=200, unique=True, db_index=True, validators=[doi_journal_validator]) issn = models.CharField(max_length=16, default='2542-4653', blank=True) active = models.BooleanField(default=True) + structure = models.CharField(max_length=2, + choices=JOURNAL_STRUCTURE, default=ISSUES_AND_VOLUMES) - objects = JournalManager() + objects = JournalQuerySet.as_manager() def __str__(self): return self.get_name_display() + def get_absolute_url(self): + return reverse('scipost:landing_page', args=(self.doi_label,)) + @property def doi_string(self): return '10.21468/' + self.doi_label - def get_absolute_url(self): - return reverse('scipost:landing_page', args=[self.doi_label]) + @property + def has_issues(self): + return self.structure in (ISSUES_AND_VOLUMES, ISSUES_ONLY) + + @property + def has_volumes(self): + return self.structure in (ISSUES_AND_VOLUMES) - def get_abbreviation_citation(self): + @property + def abbreviation_citation(self): return journal_name_abbrev_citation(self.name) + def get_issues(self): + if self.structure == ISSUES_AND_VOLUMES: + return Issue.objects.filter(in_volume__in_journal=self) + elif self.structure == ISSUES_ONLY: + return self.issues.all() + return Issue.objects.none() + + def get_publications(self): + if self.structure == ISSUES_AND_VOLUMES: + return Publication.objects.filter(in_issue__in_volume__in_journal=self) + elif self.structure == ISSUES_ONLY: + return Publication.objects.filter(in_issue__in_journal=self) + return self.publications.all() + def nr_publications(self, tier=None): publications = Publication.objects.filter(in_issue__in_volume__in_journal=self) if tier: @@ -125,6 +156,9 @@ class Journal(models.Model): class Volume(models.Model): + """ + A Volume may be used as a subgroup of Publications related to a specific Issue object. + """ in_journal = models.ForeignKey('journals.Journal', on_delete=models.CASCADE) number = models.PositiveSmallIntegerField() start_date = models.DateField(default=timezone.now) @@ -133,11 +167,22 @@ class Volume(models.Model): validators=[doi_volume_validator]) class Meta: + default_related_name = 'volumes' unique_together = ('number', 'in_journal') def __str__(self): return str(self.in_journal) + ' Vol. ' + str(self.number) + def clean(self): + """ + Check if the Volume is assigned to a valid Journal. + """ + if not self.in_journal.has_volumes: + raise ValidationError({ + 'in_journal': ValidationError('This journal does not allow for the use of Volumes', + code='invalid'), + }) + @property def doi_string(self): return '10.21468/' + self.doi_label @@ -175,19 +220,30 @@ class Volume(models.Model): class Issue(models.Model): - in_volume = models.ForeignKey('journals.Volume', on_delete=models.CASCADE) + """ + An Issue may be used as a subgroup of Publications related to a specific Journal object. + """ + in_journal = models.ForeignKey( + 'journals.Journal', on_delete=models.PROTECT, null=True, blank=True, + help_text='Assign either an Volume or Journal to the Issue') + in_volume = models.ForeignKey( + 'journals.Volume', on_delete=models.PROTECT, null=True, blank=True, + help_text='Assign either an Volume or Journal to the Issue') number = models.PositiveSmallIntegerField() start_date = models.DateField(default=timezone.now) until_date = models.DateField(default=timezone.now) status = models.CharField(max_length=20, choices=ISSUE_STATUSES, default=STATUS_PUBLISHED) doi_label = models.CharField(max_length=200, unique=True, db_index=True, validators=[doi_issue_validator]) + # absolute path on filesystem: (JOURNALS_DIR)/journal/vol/issue/ path = models.CharField(max_length=200) - objects = IssueManager() + objects = IssueQuerySet.as_manager() class Meta: + default_related_name = 'issues' + ordering = ('-until_date',) unique_together = ('number', 'in_volume') def __str__(self): @@ -199,6 +255,24 @@ class Issue(models.Model): text += ' (In draft)' return text + def clean(self): + """ + Check if either a Journal or Volume is assigned to the Issue, else the Issue be floating + like Musk's red Roadster. + """ + if not (self.in_journal or self.in_volume): + raise ValidationError({ + 'in_journal': ValidationError('Either assign a Journal or Volume to this Issue', + code='required'), + 'in_volume': ValidationError('Either assign a Journal or Volume to this Issue', + code='required'), + }) + if self.in_journal and not self.in_journal.has_issues: + raise ValidationError({ + 'in_journal': ValidationError('This journal does not allow for the use of Issues', + code='invalid'), + }) + def get_absolute_url(self): return reverse('scipost:issue_detail', args=[self.doi_label]) @@ -260,12 +334,18 @@ class Publication(models.Model): """ A Publication is an object directly related to an accepted Submission. It contains metadata, the actual publication file, author data, etc. etc. + + It may be directly related to a Journal or to an Issue. """ # Publication data accepted_submission = models.OneToOneField('submissions.Submission', on_delete=models.CASCADE, related_name='publication') - in_issue = models.ForeignKey('journals.Issue', on_delete=models.CASCADE, - related_name='publications') + in_issue = models.ForeignKey( + 'journals.Issue', on_delete=models.PROTECT, null=True, blank=True, + help_text='Assign either an Issue or Journal to the Publication') + in_journal = models.ForeignKey( + 'journals.Journal', on_delete=models.PROTECT, null=True, blank=True, + help_text='Assign either an Issue or Journal to the Publication') paper_nr = models.PositiveSmallIntegerField() status = models.CharField(max_length=8, choices=PUBLICATION_STATUSES, default=STATUS_DRAFT) @@ -285,14 +365,12 @@ class Publication(models.Model): # Authors authors_registered = models.ManyToManyField('scipost.Contributor', blank=True, through='PublicationAuthorsTable', - through_fields=('publication', 'contributor'), - related_name='publications') + through_fields=('publication', 'contributor')) authors_unregistered = models.ManyToManyField('journals.UnregisteredAuthor', blank=True, through='PublicationAuthorsTable', through_fields=( 'publication', - 'unregistered_author'), - related_name='publications') + 'unregistered_author')) authors_claims = models.ManyToManyField('scipost.Contributor', blank=True, related_name='claimed_publications') authors_false_claims = models.ManyToManyField('scipost.Contributor', blank=True, @@ -301,11 +379,9 @@ class Publication(models.Model): cc_license = models.CharField(max_length=32, choices=CC_LICENSES, default=CCBY4) # Funders - grants = models.ManyToManyField('funders.Grant', blank=True, related_name="publications") - funders_generic = models.ManyToManyField( - 'funders.Funder', blank=True, related_name="publications") # not linked to a grant - institutions = models.ManyToManyField('affiliations.Institution', - blank=True, related_name="publications") + grants = models.ManyToManyField('funders.Grant', blank=True) + funders_generic = models.ManyToManyField('funders.Funder', blank=True) # not linked to a grant + institutions = models.ManyToManyField('affiliations.Institution', blank=True) # Metadata metadata = JSONField(default={}, blank=True, null=True) @@ -327,14 +403,53 @@ class Publication(models.Model): objects = PublicationQuerySet.as_manager() + class Meta: + default_related_name = 'publications' + ordering = ('-publication_date', '-paper_nr') + def __str__(self): - header = (self.citation() + ', ' - + self.title[:30] + ' by ' + self.author_list[:30] - + ', published ' + self.publication_date.strftime('%Y-%m-%d')) - return header + return '{cite}, {title} by {authors}, {date}'.format( + cite=self.citation, + title=self.title[:30], + authors=self.author_list[:30], + date=self.publication_date.strftime('%Y-%m-%d')) + + def clean(self): + """ + Check if either a valid Journal or Issue is assigned to the Publication. + """ + if not (self.in_journal or self.in_issue): + raise ValidationError({ + 'in_journal': ValidationError( + 'Either assign a Journal or Issue to this Publication', code='required'), + 'in_issue': ValidationError( + 'Either assign a Journal or Issue to this Publication', code='required'), + }) + if self.in_journal and self.in_issue: + # Assigning both a Journal and an Issue will screw up the database + raise ValidationError({ + 'in_journal': ValidationError( + 'Either assign only a Journal or Issue to this Publication', code='invalid'), + 'in_issue': ValidationError( + 'Either assign only a Journal or Issue to this Publication', code='invalid'), + }) + if self.in_issue and not self.in_issue.in_volume.in_journal.has_issues: + # Assigning both a Journal and an Issue will screw up the database + raise ValidationError({ + 'in_issue': ValidationError( + 'This journal does not allow the use of Issues', + code='invalid'), + }) + if self.in_journal and self.in_journal.has_issues: + # Assigning both a Journal and an Issue will screw up the database + raise ValidationError({ + 'in_journal': ValidationError( + 'This journal does not allow the use of individual Publications', + code='invalid'), + }) def get_absolute_url(self): - return reverse('scipost:publication_detail', args=[self.doi_label]) + return reverse('scipost:publication_detail', args=(self.doi_label,)) def get_cc_license_URI(self): for (key, val) in CC_LICENSES_URI: @@ -352,7 +467,14 @@ class Publication(models.Model): @property def is_published(self): - return self.status == PUBLICATION_PUBLISHED and self.in_issue.status == STATUS_PUBLISHED + if self.status != PUBLICATION_PUBLISHED: + return False + + if self.in_issue: + return self.in_issue.status == STATUS_PUBLISHED + elif self.in_journal: + return self.in_journal.active + return False @property def has_xml_metadata(self): @@ -370,15 +492,31 @@ class Publication(models.Model): def has_funding_statement(self): return 'funding_statement' in self.metadata and self.metadata['funding_statement'] + @property + def citation(self): + """ + Return Publication name in the preferred citation format. + """ + if self.in_issue: + txt = '{journal} {volume}'.format( + journal=self.in_issue.in_volume.in_journal.abbreviation_citation, + volume=self.in_issue.in_volume.number) + elif self.in_journal: + txt = self.in_journal.abbreviation_citation + else: + txt = 'Invalid' + + txt += ', {paper_nr} ({year})'.format( + paper_nr=self.get_paper_nr(), + year=self.publication_date.strftime('%Y')) + return txt + + def get_journal(self): + return self.in_journal or self.in_issue.in_volume.in_journal + def get_paper_nr(self): return paper_nr_string(self.paper_nr) - def citation(self): - return (self.in_issue.in_volume.in_journal.get_abbreviation_citation() - + ' ' + str(self.in_issue.in_volume.number) - + ', ' + self.get_paper_nr() - + ' (' + self.publication_date.strftime('%Y') + ')') - def citation_rate(self): """ Returns the citation rate in units of nr citations per article per year. diff --git a/journals/templates/journals/_base.html b/journals/templates/journals/_base.html index bd692680e7e564e05329bdc90c1314e1ac27c0b5..e34dfe309b1be6a58efb59172be166bd88fb298d 100644 --- a/journals/templates/journals/_base.html +++ b/journals/templates/journals/_base.html @@ -2,8 +2,8 @@ {% load staticfiles %} -{% block pagetitle %}: {{journal}}{% endblock pagetitle %} -{% block body_class %}{{block.super}} journals{% endblock %} +{% block pagetitle %}: {{ journal }}{% endblock pagetitle %} +{% block body_class %}{{ block.super }} journals{% endblock %} {% block breadcrumb %} <div class="container-outside breadcrumb-nav"> @@ -21,11 +21,15 @@ <div class="container mt-3"> <div class="row"> <div class="col journal"> - <h2 class="banner d-inline-block mr-2"><a href="{% url 'scipost:landing_page' journal.doi_label %}">{{journal}}</a></h2> + <h2 class="banner d-inline-block mr-2"><a href="{{ journal.get_absolute_url }}">{{journal}}</a></h2> <ul class="links"> {% if journal.active or request.user.is_staff %} - <li><a class="{% block link_class_physics_issues %}{% endblock %}" href="{% url 'journal:issues' journal.doi_label %}">Issues</a></li> - <li><a class="{% block link_class_physics_recent %}{% endblock %}" href="{% url 'journal:recent' journal.doi_label %}">Recent</a></li> + {% if journal.has_issues %} + <li><a class="{% block link_class_physics_issues %}{% endblock %}" href="{% url 'journal:issues' journal.doi_label %}">Issues</a></li> + <li><a class="recent {% block link_class_physics_recent %}{% endblock %}" href="{% url 'journal:recent' journal.doi_label %}">Recent</a></li> + {% else %} + <li><a class="recent" href="{{ journal.get_absolute_url }}">Recent</a></li> + {% endif %} <li><a class="{% block link_class_physics_accepted %}{% endblock %}" href="{% url 'journal:accepted' journal.doi_label %}">Accepted</a></li> <li><a href="{% url 'submissions:submissions' %}?to_journal={{ journal.name }}">Submissions</a></li> <li><a class="{% block link_class_physics_info %}{% endblock %}" href="{% url 'journal:info_for_authors' journal.doi_label %}">Info for authors</a></li> diff --git a/journals/templates/journals/add_author.html b/journals/templates/journals/add_author.html index 668919333f224cee185775fc77edd28e4510821e..627dd1a06baf3690764cdcf7ec3ab42063044c62 100644 --- a/journals/templates/journals/add_author.html +++ b/journals/templates/journals/add_author.html @@ -7,8 +7,6 @@ <div class="container"> <nav class="breadcrumb hidden-sm-down"> <a href="{% url 'journals:journals' %}" class="breadcrumb-item">Journals</a> - <a href="{{publication.in_issue.in_volume.in_journal.get_absolute_url}}" class="breadcrumb-item">{{publication.in_issue.in_volume.in_journal}}</a> - <a href="{{publication.in_issue.get_absolute_url}}" class="breadcrumb-item">{{publication.in_issue.short_str}}</a> <a href="{{publication.get_absolute_url}}" class="breadcrumb-item">{{publication.citation}}</a> <span class="breadcrumb-item active">Add author to publication</span> diff --git a/journals/templates/journals/journal_accepted.html b/journals/templates/journals/journal_accepted.html index 2eb16483da99aa3932b753af4b69e555a40c39af..d5944bf769b67b5b121d54bf598176827ca56b11 100644 --- a/journals/templates/journals/journal_accepted.html +++ b/journals/templates/journals/journal_accepted.html @@ -19,7 +19,7 @@ <div class="row"> <div class="col-12"> <ul class="list-group list-group-flush"> - {% for submission in accepted_SP_submissions %} + {% for submission in accepted_submissions %} <li class="list-group-item"> <div class="card-body px-0"> {% include 'partials/submissions/submission_card_content.html' with submission=submission %} diff --git a/journals/templates/journals/journal_issue_detail.html b/journals/templates/journals/journal_issue_detail.html index 6b646613d4d4de29b67a170ad2c1d32a6e20dbf2..5b22c11f7d028b9f06f49c3351e72c7f33dd9193 100644 --- a/journals/templates/journals/journal_issue_detail.html +++ b/journals/templates/journals/journal_issue_detail.html @@ -14,10 +14,10 @@ {% block journal_header_block %} {% if prev_issue %} - <h4 class="d-inline-block"><a href="{{prev_issue.get_absolute_url}}">< Previous issue | {{prev_issue.short_str}}</a></h4> + <h4 class="d-inline-block"><a href="{{prev_issue.get_absolute_url}}"><i class="fa fa-long-arrow-left"></i> Previous issue | {{prev_issue.short_str}}</a></h4> {% endif %} {% if next_issue %} - <h4 class="float-right d-inline-block"><a href="{{next_issue.get_absolute_url}}">{{next_issue.short_str}} | Next issue ></a></h4> + <h4 class="float-right d-inline-block"><a href="{{next_issue.get_absolute_url}}">{{next_issue.short_str}} | Next issue <i class="fa fa-long-arrow-right"></i></a></h4> {% endif %} {% endblock %} diff --git a/journals/templates/journals/journal_issues.html b/journals/templates/journals/journal_issues.html index 601a8c550639617c5f3d130bbeaba8a83af987e0..e69b81f25d844551d18d925c6733a703ba2bb29a 100644 --- a/journals/templates/journals/journal_issues.html +++ b/journals/templates/journals/journal_issues.html @@ -18,7 +18,7 @@ <div class="row"> <div class="col-12"> <ul> - {% for issue in issues %} + {% for issue in journal.get_issues %} <li> <a href="{{issue.get_absolute_url}}">{{issue}}</a> {% if issue.proceedings %} diff --git a/journals/templates/journals/journal_landing_page.html b/journals/templates/journals/journal_landing_page.html index c7c7f3fa13c4ea8156d6f11d2d1ab29a2796884e..98bae00dcc384a1fb6ab18f1d927f24e23eb15c0 100644 --- a/journals/templates/journals/journal_landing_page.html +++ b/journals/templates/journals/journal_landing_page.html @@ -7,62 +7,92 @@ {% block content %} - {% if current_issue %} - <div class="row"> - <div class="col-12"> - <h2 class="highlight-empty text-blue m-0 p-0 pt-2">Current issue: Vol. {{ current_issue.in_volume.number }} issue {{ current_issue.number }} (in progress)</h2> - {% if prev_issue %} - <h4 class="d-inline-block"><a href="{{prev_issue.get_absolute_url}}">< Previous issue | Vol. {{prev_issue.in_volume.number}} issue {{prev_issue.number}}</a></h4> - {% endif %} + {% if journal.has_issues %} + {% if current_issue %} + <div class="row"> + <div class="col-12"> + <h2 class="highlight-empty text-blue m-0 p-0 pt-2">Current issue: Vol. {{ current_issue.in_volume.number }} issue {{ current_issue.number }} (in progress)</h2> + {% if prev_issue %} + <h4 class="d-inline-block"><a href="{{prev_issue.get_absolute_url}}"><i class="fa fa-long-arrow-left"></i> Previous issue | Vol. {{prev_issue.in_volume.number}} issue {{prev_issue.number}}</a></h4> + {% endif %} + </div> </div> - </div> - <div class="row"> - <div class="col-12"> - <ul class="list-unstyled"> - {% for paper in current_issue.publications.published|dictsort:"paper_nr" %} - <li> - {% include 'partials/journals/publication_card.html' with publication=paper %} - </li> - {% empty %} - <li> - <h3>No publications found for this issue</h3> - </li> - {% endfor %} - </ul> + <div class="row"> + <div class="col-12"> + <ul class="list-unstyled"> + {% for paper in current_issue.publications.published|dictsort:"paper_nr" %} + <li> + {% include 'partials/journals/publication_card.html' with publication=paper %} + </li> + {% empty %} + <li> + <h3>No publications found for this issue</h3> + </li> + {% endfor %} + </ul> + </div> </div> - </div> - {% endif %} + {% endif %} + + {% if latest_issue %} + <div class="row"> + <div class="col-12"> + <h2 class="highlight-empty text-blue m-0 p-0">Latest issue: Vol. {{ latest_issue.in_volume.number }} issue {{ latest_issue.number }}</h2> + </div> + </div> + <div class="row"> + <div class="col-12"> + <ul class="list-unstyled"> + {% for paper in latest_issue.publications.published|dictsort:"paper_nr" %} + <li> + {% include 'partials/journals/publication_card.html' with publication=paper %} + </li> + {% empty %} + <li> + <h3>No publications found for this issue</h3> + </li> + {% endfor %} + </ul> + </div> + </div> + {% endif %} - {% if latest_issue %} + {% if not current_issue and not latest_issue %} + <div class="row"> + <div class="col-12"> + <h2 class="text-blue">Coming soon!</h2> + </div> + </div> + {% endif %} + {% elif page_object %} <div class="row"> <div class="col-12"> - <h2 class="highlight-empty text-blue m-0 p-0">Latest issue: Vol. {{ latest_issue.in_volume.number }} issue {{ latest_issue.number }}</h2> + <h2 class="text-blue">{{ journal.get_name_display }} Publications</h2> </div> </div> <div class="row"> <div class="col-12"> <ul class="list-unstyled"> - {% for paper in latest_issue.publications.published|dictsort:"paper_nr" %} + {% for publication in page_object.object_list %} <li> - {% include 'partials/journals/publication_card.html' with publication=paper %} + {% include 'partials/journals/publication_card.html' with publication=publication %} </li> {% empty %} <li> - <h3>No publications found for this issue</h3> + <h3>First Publication coming soon!</h3> </li> {% endfor %} </ul> </div> </div> - {% endif %} - - {% if not current_issue and not latest_issue %} <div class="row"> <div class="col-12"> - <h2 class="text-blue">Coming soon!</h2> + {% include 'partials/pagination.html' with page_obj=page_object %} </div> </div> + {% else %} + <h3 class="none-found">First Publication coming soon!</h3> {% endif %} {% endblock %} diff --git a/journals/templates/journals/journal_recent.html b/journals/templates/journals/journal_recent.html index d12a180904f8c57ace7aa15c2996930e7e708544..66c96e5019a0a7e72cedac9c31ae546c51953f74 100644 --- a/journals/templates/journals/journal_recent.html +++ b/journals/templates/journals/journal_recent.html @@ -19,7 +19,7 @@ <div class="row"> <div class="col-12"> <ul class="list-unstyled"> - {% for paper in recent_papers %} + {% for paper in journal.get_publications.published|slice:':20' %} <li> {% include 'partials/journals/publication_card.html' with publication=paper %} </li> diff --git a/journals/templates/journals/journals.html b/journals/templates/journals/journals.html index de1e80066fb283e867d99346edbe3e9e2879f199..a696b5cd0184ce4be0100e0afae9a8c1dbcf23cd 100644 --- a/journals/templates/journals/journals.html +++ b/journals/templates/journals/journals.html @@ -77,9 +77,13 @@ </div> </div> <div class="col-md-6"> - <h1 class="banner">SciPost Physics Lecture Notes</h1> + <h1 class="banner"><a href="{% url 'scipost:landing_page' 'SciPostPhysLectNotes' %}">SciPost Physics Lecture Notes</a></h1> <div class="py-2"> - <p>Research-level didactic material in all domains and subject areas of Physics.</p> + <p> + <a href="{% url 'journal:about' 'SciPostPhysLectNotes' %}">Go directly to SciPost Physics Lecture Notes</a> + <br><br> + Research-level didactic material in all domains and subject areas of Physics. + </p> </div> </div> </div> diff --git a/journals/templates/journals/manage_metadata.html b/journals/templates/journals/manage_metadata.html index d77c5d434fc2d2de50eccbccec42be9fe947d996..08ae88c8079ddfdbc104961abab2b010b75adc7d 100644 --- a/journals/templates/journals/manage_metadata.html +++ b/journals/templates/journals/manage_metadata.html @@ -19,23 +19,47 @@ event: "focusin" <span class="breadcrumb-item">Manage metadata</span> {% endblock %} +{% block body_class %}{{ block.super }} manage_metadata{% endblock %} + {% block content %} <div class="row"> <div class="col-12"> - <h1 class="highlight">Manage Publications Metadata{% if issue_doi_label %} for issue {{ issue_doi_label }}{% endif %}</h1> - {% if issue_doi_label %} - <h3>Return to the <a href="{% url 'journals:manage_metadata' %}">Main manage metadata page</a></h3> - {% endif %} - <h3>Manage metadata per issue:</h3> - <ul> - {% for issue in issues %} - <li> - <a href="{% url 'journals:manage_metadata' issue_doi_label=issue.doi_label %}">{{ issue }}</a> - </li> - {% endfor %} + <h1 class="highlight">Manage Publications Metadata{% if issue_doi_label %} for issue {{ issue_doi_label }}{% elif journal %} for {{ journal }}{% endif %}</h1> + + </div> +</div> +<div class="row"> + <div class="col-md-6"> + <h3>Available journals</h3> + <ul class="links"> + {% for journal in journals %} + {% url 'journals:manage_metadata' journal_doi_label=journal.doi_label as url %} + <li class="{% if url == request.path %}active{% endif %}"> + <a href="{{ url }}">{{ journal }}</a> + </li> + {% endfor %} </ul> </div> + <div class="col-md-6"> + <h3>Manage metadata per issue:</h3> + {% if journal.has_issues %} + <ul class="links"> + {% for volume in journal.volumes.all %} + {% for issue in volume.issues.all %} + {% url 'journals:manage_metadata' issue_doi_label=journal.doi_label as url %} + <li class="{% if url == request.path %}active{% endif %}"> + <a href="{{ url }}">{{ issue }}</a> + </li> + {% endfor %} + {% empty %} + <li><em>There are no Volumes and Issues for this Journal</em></li> + {% endfor %} + </ul> + {% else %} + <em>This Journal does not have Issues, but individual Publications.</em> + {% endif %} + </div> </div> diff --git a/journals/templates/journals/publication_detail.html b/journals/templates/journals/publication_detail.html index f8e1d9ecf2c8156e4fee4b90ee7e2b66f06f79e6..20f08ba694ec77c6fc6d9d491eaf3333a274596e 100644 --- a/journals/templates/journals/publication_detail.html +++ b/journals/templates/journals/publication_detail.html @@ -12,8 +12,10 @@ {% block breadcrumb_items %} {{block.super}} - <a href="{{journal.get_absolute_url}}" class="breadcrumb-item">{{journal}}</a> - <a href="{{publication.in_issue.get_absolute_url}}" class="breadcrumb-item">{{publication.in_issue.short_str}}</a> + <a href="{{ journal.get_absolute_url }}" class="breadcrumb-item">{{ journal }}</a> + {% if publication.in_issue %} + <a href="{{ publication.in_issue.get_absolute_url }}" class="breadcrumb-item">{{ publication.in_issue.short_str }}</a> + {% endif %} <span class="breadcrumb-item active">{{publication.title}}</span> {% endblock %} @@ -31,8 +33,10 @@ <meta name="citation_publication_date" content="{{ publication.publication_date|date:'Y/m/d' }}"/> <meta name="citation_journal_title" content="{{ journal }}"/> <meta name="citation_issn" content="{{ journal.issn }}"/> - <meta name="citation_volume" content="{{ publication.in_issue.in_volume.number }}"/> - <meta name="citation_issue" content="{{ publication.in_issue.number }}"/> + {% if publication.in_issue %} + <meta name="citation_volume" content="{{ publication.in_issue.in_volume.number }}"/> + <meta name="citation_issue" content="{{ publication.in_issue.number }}"/> + {% endif %} <meta name="citation_firstpage" content="{{ publication.paper_nr|paper_nr_string_filter }}"/> <meta name="citation_pdf_url" content="https://scipost.org/{{ publication.doi_string }}/pdf"/> <meta name="dc.identifier" content="doi:{{ publication.doi_string }}"/> @@ -185,7 +189,7 @@ <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 %}">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 %} diff --git a/journals/templates/journals/publication_form.html b/journals/templates/journals/publication_form.html index 6217eef9d55b80a892301f5dc2cc4f45310b2702..8d46616c3d1fbc955e347d6034a773420a9ec485 100644 --- a/journals/templates/journals/publication_form.html +++ b/journals/templates/journals/publication_form.html @@ -9,7 +9,7 @@ <h1>Draft publication</h1> -{% if request.GET.issue or form.instance.id %} +{% if request.GET.issue or form.instance.id or form.to_journal %} <form method="post" enctype="multipart/form-data"> {% csrf_token %} {{ form|bootstrap }} diff --git a/journals/templates/xml/publication_crossref.html b/journals/templates/xml/publication_crossref.html index 99d744783788e9cf739c5ee08bc568c14b6b54be..f4fb350348d65e592d805133ea4abad061bffaa8 100644 --- a/journals/templates/xml/publication_crossref.html +++ b/journals/templates/xml/publication_crossref.html @@ -11,24 +11,36 @@ </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> + {% if publication.in_issue %} + <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.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> + {% else %} + <journal_metadata> + <full_title>{{ publication.in_journal.get_name_display }}</full_title> + <abbrev_title>{{ publication.in_journal.abbreviation_citation }}</abbrev_title> + <issn media_type='electronic'>{{ publication.in_journal.issn }}</issn> + <doi_data> + <doi>{{ publication.in_journal.doi_string }}</doi> + <resource>https://scipost.org/{{ publication.in_journal.doi_string }}</resource> + </doi_data> + </journal_metadata> + {% endif %} <journal_article publication_type='full_text'> <titles> <title>{{ publication.title }}</title> diff --git a/journals/urls/general.py b/journals/urls/general.py index 0293ca0430c050edf854f57ed0956195414ce73e..bf2d672b48b39132d3487756e08b41323de97649 100644 --- a/journals/urls/general.py +++ b/journals/urls/general.py @@ -4,8 +4,10 @@ from django.views.generic import TemplateView, RedirectView from submissions.constants import SUBMISSIONS_COMPLETE_REGEX +from journals.constants import PUBLICATION_DOI_REGEX, REGEX_CHOICES from journals import views as journals_views + urlpatterns = [ # Journals url(r'^$', journals_views.journals, name='journals'), @@ -22,64 +24,76 @@ urlpatterns = [ 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$', + url(r'^admin/publications/(?P<doi_label>{regex})/publish$'.format(regex=PUBLICATION_DOI_REGEX), 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$', + url(r'^admin/publications/(?P<doi_label>{regex})/approval$'.format( + regex=PUBLICATION_DOI_REGEX), 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$', + url(r'^admin/publications/(?P<doi_label>{regex})/grants$'.format(regex=PUBLICATION_DOI_REGEX), 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$', + url(r'^admin/publications/(?P<doi_label>{regex})/grants/(?P<grant_id>[0-9]+)/remove$'.format( + regex=PUBLICATION_DOI_REGEX), journals_views.PublicationGrantsRemovalView.as_view(), name='remove_grant'), # Editorial and Administrative Workflow - url(r'^admin/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/authors/add/(?P<contributor_id>[0-9]+)$', + url(r'^admin/(?P<doi_label>{regex})/authors/add/(?P<contributor_id>[0-9]+)$'.format( + regex=PUBLICATION_DOI_REGEX), journals_views.add_author, name='add_author'), - url(r'^admin/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/authors/add$', + url(r'^admin/(?P<doi_label>{regex})/authors/add$'.format(regex=PUBLICATION_DOI_REGEX), journals_views.add_author, name='add_author'), - url(r'^admin/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/authors/mark_first/(?P<author_object_id>[0-9]+)$', + url(r'^admin/(?P<doi_label>{regex})/authors/mark_first/(?P<author_object_id>[0-9]+)$'.format( + regex=PUBLICATION_DOI_REGEX), journals_views.mark_first_author, name='mark_first_author'), - url(r'^admin/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/manage_metadata$', + url(r'^admin/(?P<doi_label>{regex})/manage_metadata$'.format(regex=PUBLICATION_DOI_REGEX), journals_views.manage_metadata, name='manage_metadata'), url(r'^admin/(?P<issue_doi_label>[a-zA-Z]+.[0-9]+.[0-9]+)/manage_metadata$', journals_views.manage_metadata, name='manage_metadata'), + url(r'^admin/(?P<journal_doi_label>{regex})/manage_metadata$'.format(regex=REGEX_CHOICES), + journals_views.manage_metadata, + name='manage_metadata'), url(r'^admin/manage_metadata/$', 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$', + url(r'^admin/(?P<doi_label>{regex})/citation_list_metadata$'.format( + regex=PUBLICATION_DOI_REGEX), 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$', + url(r'^admin/(?P<doi_label>{regex})/update_references$'.format(regex=PUBLICATION_DOI_REGEX), 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$', + url(r'^admin/(?P<doi_label>{regex})/funders/create_metadata$'.format( + regex=PUBLICATION_DOI_REGEX), 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$', + url(r'^admin/(?P<doi_label>{regex})/funders/add_generic$'.format(regex=PUBLICATION_DOI_REGEX), journals_views.add_generic_funder, name='add_generic_funder'), - url(r'^admin/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/grants/add$', + url(r'^admin/(?P<doi_label>{regex})/grants/add$'.format(regex=PUBLICATION_DOI_REGEX), journals_views.add_associated_grant, name='add_associated_grant'), # Metadata handling - url(r'^admin/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/metadata/crossref/create$', + url(r'^admin/(?P<doi_label>{regex})/metadata/crossref/create$'.format( + regex=PUBLICATION_DOI_REGEX), 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]+)$', + url(r'^admin/(?P<doi_label>{regex})/metadata/crossref/deposit/(?P<option>[a-z]+)$'.format( + regex=PUBLICATION_DOI_REGEX), journals_views.metadata_xml_deposit, name='metadata_xml_deposit'), - url(r'^admin/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/metadata/DOAJ$', + url(r'^admin/(?P<doi_label>{regex})/metadata/DOAJ$'.format(regex=PUBLICATION_DOI_REGEX), journals_views.produce_metadata_DOAJ, name='produce_metadata_DOAJ'), - url(r'^admin/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/metadata/DOAJ/deposit$', + url(r'^admin/(?P<doi_label>{regex})/metadata/DOAJ/deposit$'.format( + regex=PUBLICATION_DOI_REGEX), journals_views.metadata_DOAJ_deposit, name='metadata_DOAJ_deposit'), url(r'^admin/metadata/crossref/(?P<deposit_id>[0-9]+)/mark/(?P<success>[0-1])$', @@ -102,7 +116,7 @@ urlpatterns = [ url(r'^admin/citedby/$', journals_views.harvest_citedby_list, name='harvest_citedby_list'), - url(r'^admin/citedby/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/harvest$', + url(r'^admin/citedby/(?P<doi_label>{regex})/harvest$'.format(regex=PUBLICATION_DOI_REGEX), journals_views.harvest_citedby_links, name='harvest_citedby_links'), diff --git a/journals/urls/journal.py b/journals/urls/journal.py index 5cffb7d42d425137fe5e89fe136f45fa2e71472b..e8526ee4dfef73b5f1a49b7f14aea97fe859be9b 100644 --- a/journals/urls/journal.py +++ b/journals/urls/journal.py @@ -4,9 +4,9 @@ from journals import views as journals_views urlpatterns = [ # Journal routes - url(r'^issues$', journals_views.issues, name='issues'), - url(r'^recent$', journals_views.recent, name='recent'), - url(r'^accepted$', journals_views.accepted, name='accepted'), + url(r'^issues$', journals_views.IssuesView.as_view(), name='issues'), + url(r'^recent$', journals_views.RecentView.as_view(), name='recent'), + url(r'^accepted$', journals_views.AcceptedView.as_view(), name='accepted'), url(r'^info_for_authors$', journals_views.info_for_authors, name='info_for_authors'), url(r'^about$', journals_views.about, name='about'), ] diff --git a/journals/utils.py b/journals/utils.py index 9ae86f9461d9c4784b9f8f2e16e89b524e7212c7..c5788610701b1f31b61c83a6a46554adf2fdab7d 100644 --- a/journals/utils.py +++ b/journals/utils.py @@ -18,7 +18,7 @@ class JournalUtils(BaseMailUtil): cls.publication.accepted_submission.title + ' by ' + cls.publication.accepted_submission.author_list + '\n\nhas been published online with reference ' - + cls.publication.citation() + '.' + + cls.publication.citation + '.' '\n\nThe publication page is located at the permanent link ' 'https://scipost.org/' + cls.publication.doi_label + '.' '\n\nThe permanent DOI for your publication is 10.21468/' diff --git a/journals/views.py b/journals/views.py index 5b237c52282bca6040b796f4b9e77dbb098c3b58..5f2571efa2306730e77c545d9ab488b4ad304951 100644 --- a/journals/views.py +++ b/journals/views.py @@ -21,10 +21,10 @@ 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 django.shortcuts import get_object_or_404, get_list_or_404, render, redirect from .constants import STATUS_DRAFT -from .helpers import paper_nr_string, issue_doi_label_from_doi_label +from .helpers import issue_doi_label_from_doi_label from .models import Journal, Issue, Publication, Deposit, DOAJDeposit,\ GenericDOIDeposit, PublicationAuthorsTable from .forms import FundingInfoForm,\ @@ -36,7 +36,7 @@ from .utils import JournalUtils from comments.models import Comment from funders.forms import FunderSelectForm, GrantSelectForm -from funders.models import Funder, Grant +from funders.models import Grant from submissions.models import Submission, Report from scipost.forms import ConfirmationForm from scipost.models import Contributor @@ -56,100 +56,100 @@ def journals(request): def landing_page(request, doi_label): + """ + The landing page of a Journal lists either the latest and the current issue of a Journal + of paginates the individual Publications. + """ journal = get_object_or_404(Journal, doi_label=doi_label) - - current_issue = Issue.objects.published( - in_volume__in_journal=journal, - start_date__lte=timezone.now(), - until_date__gte=timezone.now()).order_by('-until_date').first() - latest_issue = Issue.objects.published( - in_volume__in_journal=journal, - until_date__lte=timezone.now()).order_by('-until_date').first() - - prev_issue = None - if current_issue: - prev_issue = (Issue.objects.published(in_volume__in_journal=journal, - start_date__lt=current_issue.start_date) - .order_by('start_date').last()) - context = { - 'current_issue': current_issue, - 'latest_issue': latest_issue, - 'prev_issue': prev_issue, 'journal': journal } - return render(request, 'journals/journal_landing_page.html', context) + if journal.has_issues: + current_issue = Issue.objects.published().filter( + in_volume__in_journal=journal, + start_date__lte=timezone.now(), + until_date__gte=timezone.now()).first() + latest_issue = Issue.objects.published().filter( + in_volume__in_journal=journal, + until_date__lte=timezone.now()).first() + prev_issue = None + if current_issue: + prev_issue = Issue.objects.published().filter( + in_volume__in_journal=journal, start_date__lt=current_issue.start_date + ).order_by('start_date').last() + + context.update({ + 'current_issue': current_issue, + 'latest_issue': latest_issue, + 'prev_issue': prev_issue, + }) + else: + paginator = Paginator(journal.publications.published(), 10) + context.update({ + 'page_object': paginator.page(request.GET.get('page', 1)), + }) + return render(request, 'journals/journal_landing_page.html', context) -def issues(request, doi_label): - journal = get_object_or_404(Journal, doi_label=doi_label) - issues = Issue.objects.published(in_volume__in_journal=journal).order_by('-until_date') - context = { - 'issues': issues, - 'journal': journal - } - return render(request, 'journals/journal_issues.html', context) +class IssuesView(DetailView): + """ + List all Issues sorted per Journal. + """ + queryset = Journal.objects.has_issues() + slug_field = slug_url_kwarg = 'doi_label' + template_name = 'journals/journal_issues.html' -def recent(request, doi_label): +class RecentView(DetailView): """ - Display page for the most recent 20 publications in SciPost Physics. + List all recent Publications for a specific Journal. """ - journal = get_object_or_404(Journal, doi_label=doi_label) - recent_papers = Publication.objects.published().filter( - in_issue__in_volume__in_journal=journal).order_by('-publication_date', - '-paper_nr')[:20] - context = { - 'recent_papers': recent_papers, - 'journal': journal, - } - return render(request, 'journals/journal_recent.html', context) + queryset = Journal.objects.active() + slug_field = slug_url_kwarg = 'doi_label' + template_name = 'journals/journal_recent.html' -def accepted(request, doi_label): +class AcceptedView(DetailView): """ - Display page for submissions to SciPost Physics which - have been accepted but are not yet published. + List all Submissions for a specific Journal which have been accepted but are not + yet published. """ - journal = get_object_or_404(Journal, doi_label=doi_label) - accepted_SP_submissions = (Submission.objects.accepted() - .filter(submitted_to_journal=journal.name) - .order_by('-latest_activity')) - context = { - 'accepted_SP_submissions': accepted_SP_submissions, - 'journal': journal - } - return render(request, 'journals/journal_accepted.html', context) + queryset = Journal.objects.active() + slug_field = slug_url_kwarg = 'doi_label' + template_name = 'journals/journal_accepted.html' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['accepted_submissions'] = Submission.objects.accepted().filter( + submitted_to_journal=context['journal'].name).order_by('-latest_activity') + return context def info_for_authors(request, doi_label): journal = get_object_or_404(Journal, doi_label=doi_label) - context = { - 'journal': journal - } + context = {'journal': journal} return render(request, 'journals/%s_info_for_authors.html' % doi_label, context) def about(request, doi_label): journal = get_object_or_404(Journal, doi_label=doi_label) - context = { - 'journal': journal - } + context = {'journal': journal} return render(request, 'journals/%s_about.html' % doi_label, context) def issue_detail(request, doi_label): - issue = Issue.objects.get_published(doi_label=doi_label) + issue = get_object_or_404(Issue.objects.published(), doi_label=doi_label) journal = issue.in_volume.in_journal 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()) - prev_issue = (Issue.objects.published(in_volume__in_journal=journal, - start_date__lt=issue.start_date) - .order_by('start_date').last()) + next_issue = Issue.objects.published().filter( + in_volume__in_journal=journal, start_date__gt=issue.start_date + ).order_by('start_date').first() + prev_issue = Issue.objects.published().filter( + in_volume__in_journal=journal, start_date__lt=issue.start_date + ).order_by('start_date').last() + context = { 'issue': issue, 'prev_issue': prev_issue, @@ -242,19 +242,40 @@ class PublicationPublishView(PermissionsMixin, RequestViewMixin, UpdateView): @permission_required('scipost.can_publish_accepted_submission', return_403=True) -def manage_metadata(request, issue_doi_label=None, doi_label=None): - issues = Issue.objects.all().order_by('-until_date') - publications = Publication.objects.all() +def manage_metadata(request, doi_label=None, issue_doi_label=None, journal_doi_label=None): + journal = None + journals = Journal.objects.all() + issue = None + if doi_label: - issue_doi_label = issue_doi_label_from_doi_label(doi_label) - if issue_doi_label: - publications = publications.filter(in_issue__doi_label=issue_doi_label) - publications = publications.order_by('-publication_date', '-paper_nr') + publications = get_list_or_404(Publication, doi_label=doi_label) + journal = publications[0].get_journal() + elif issue_doi_label: + issue = get_object_or_404(Issue, doi_label=issue_doi_label) + if issue.in_volume: + journal = issue.in_volume.in_journal + else: + journal = issue.in_journal + publications = issue.publications.all() + elif journal_doi_label: + journal = get_object_or_404(Journal, doi_label=journal_doi_label) + publications = Publication.objects.for_journal(journal.name) + else: + # Limit the amount of Publications to still an idiot size + publications = Publication.objects.all()[:50] + + # Speeds up operations by reducing the number of queries + if not isinstance(publications, list): + publications = publications.prefetch_related( + 'authors', 'funders_generic', 'deposit_set', 'doajdeposit_set') + associate_grant_form = GrantSelectForm() associate_generic_funder_form = FunderSelectForm() context = { - 'issues': issues, + 'journal': journal, + 'journals': journals, 'issue_doi_label': issue_doi_label, + 'journal_doi_label': journal_doi_label, 'publications': publications, 'associate_grant_form': associate_grant_form, 'associate_generic_funder_form': associate_generic_funder_form, @@ -437,13 +458,27 @@ def metadata_xml_deposit(request, doi_label, option='test'): return redirect(reverse('journals:create_metadata_xml', kwargs={'doi_label': publication.doi_label})) - timestamp = (publication.metadata_xml.partition( - '<timestamp>'))[2].partition('</timestamp>')[0] - doi_batch_id = (publication.metadata_xml.partition( - '<doi_batch_id>'))[2].partition('</doi_batch_id>')[0] - path = (settings.MEDIA_ROOT + publication.in_issue.path + '/' - + publication.get_paper_nr() + '/' + publication.doi_label.replace('.', '_') - + '_Crossref_' + timestamp + '.xml') + timestamp = publication.metadata_xml.partition( + '<timestamp>')[2].partition('</timestamp>')[0] + doi_batch_id = publication.metadata_xml.partition( + '<doi_batch_id>')[2].partition('</doi_batch_id>')[0] + + # Find Crossref xml files + path = settings.MEDIA_ROOT + if publication.in_issue: + path += '{issue_path}/{paper_nr}/{doi_label}_Crossref'.format( + issue_path=publication.in_issue.path, + paper_nr=publication.get_paper_nr(), + doi_label=publication.doi_label.replace('.', '_')) + + if publication.in_journal: + path += 'SCIPOST_JOURNALS/{journal_name}/{paper_nr}/{doi_label}_Crossref'.format( + journal_name=publication.in_journal.name, + paper_nr=publication.get_paper_nr(), + doi_label=publication.doi_label.replace('.', '_')) + + path_wo_timestamp = path + '.xml' + path += '_{timestamp}.xml'.format(timestamp=timestamp) valid = True response_headers = None @@ -454,7 +489,7 @@ def metadata_xml_deposit(request, doi_label, option='test'): else: # New deposit, go for it. if option == 'deposit' and not settings.DEBUG: - # CAUTION: Real deposit only on production (non-debug-mode) + # CAUTION: Real deposit only on production! url = 'http://doi.crossref.org/servlet/deposit' else: url = 'http://test.crossref.org/servlet/deposit' @@ -484,24 +519,14 @@ def metadata_xml_deposit(request, doi_label, option='test'): deposit.response_text = r.text # Save the filename with timestamp - path_with_timestamp = '{issue}/{paper}/{doi}_Crossref_{timestamp}.xml'.format( - issue=publication.in_issue.path, - paper=publication.get_paper_nr(), - doi=publication.doi_label.replace('.', '_'), - timestamp=timestamp) - f = open(settings.MEDIA_ROOT + path_with_timestamp, 'w', encoding='utf-8') + f = open(settings.MEDIA_ROOT + path, 'w', encoding='utf-8') f.write(publication.metadata_xml) f.close() - # Copy file - path_without_timestamp = '{issue}/{paper}/{doi}_Crossref.xml'.format( - issue=publication.in_issue.path, - paper=publication.get_paper_nr(), - doi=publication.doi_label.replace('.', '_')) - shutil.copyfile(settings.MEDIA_ROOT + path_with_timestamp, - settings.MEDIA_ROOT + path_without_timestamp) - - deposit.metadata_xml_file = path_with_timestamp + # Update Crossref timestamp-free file to latest deposit + shutil.copyfile(settings.MEDIA_ROOT + path, + settings.MEDIA_ROOT + path_wo_timestamp) + deposit.metadata_xml_file = path deposit.save() publication.latest_crossref_deposit = timezone.now() publication.save() @@ -558,11 +583,24 @@ def metadata_DOAJ_deposit(request, doi_label): 'DOAJ metadata before depositing.' % publication.doi_label) return redirect(reverse('journals:manage_metadata')) - timestamp = (publication.metadata_xml.partition( - '<timestamp>'))[2].partition('</timestamp>')[0] - path = (settings.MEDIA_ROOT + publication.in_issue.path + '/' - + publication.get_paper_nr() + '/' + publication.doi_label.replace('.', '_') - + '_DOAJ_' + timestamp + '.json') + timestamp = publication.metadata_xml.partition('<timestamp>')[2].partition('</timestamp>')[0] + + # Find DOAJ xml files + path = settings.MEDIA_ROOT + if publication.in_issue: + path += '{issue_path}/{paper_nr}/{doi_label}_DOAJ'.format( + issue_path=publication.in_issue.path, + paper_nr=publication.get_paper_nr(), + doi_label=publication.doi_label.replace('.', '_')) + elif publication.in_journal: + path += 'SCIPOST_JOURNALS/{journal_name}/{paper_nr}/{doi_label}_DOAJ'.format( + journal_name=publication.in_journal.name, + paper_nr=publication.get_paper_nr(), + doi_label=publication.doi_label.replace('.', '_')) + + path_wo_timestamp = path + '.json' + path += '_{timestamp}.json'.format(timestamp=timestamp) + if os.path.isfile(path): errormessage = 'The metadata file for this metadata timestamp already exists' return render(request, 'scipost/error.html', context={'errormessage': errormessage}) @@ -584,25 +622,16 @@ def metadata_DOAJ_deposit(request, doi_label): deposit.response_text = r.text # Save a copy to the filename with and without timestamp - path_with_timestamp = '{issue}/{paper}/{doi}_DOAJ_{timestamp}.json'.format( - issue=publication.in_issue.path, - paper=publication.get_paper_nr(), - doi=publication.doi_label.replace('.', '_'), - timestamp=timestamp) - f = open(settings.MEDIA_ROOT + path_with_timestamp, 'w') + f = open(settings.MEDIA_ROOT + path, 'w') f.write(json.dumps(publication.metadata_DOAJ)) f.close() # Copy file - path_without_timestamp = '{issue}/{paper}/{doi}_DOAJ.json'.format( - issue=publication.in_issue.path, - paper=publication.get_paper_nr(), - doi=publication.doi_label.replace('.', '_')) - shutil.copyfile(settings.MEDIA_ROOT + path_with_timestamp, - settings.MEDIA_ROOT + path_without_timestamp) + shutil.copyfile(settings.MEDIA_ROOT + path, + settings.MEDIA_ROOT + path_wo_timestamp) # Save the database entry - deposit.metadata_DOAJ_file = path_with_timestamp + deposit.metadata_DOAJ_file = path deposit.save() messages.success(request, '<h3>%s</h3>Successfull deposit of metadata DOAJ.' @@ -980,7 +1009,7 @@ def email_object_made_citable(request, **kwargs): try: publication = Publication.objects.get( accepted_submission__arxiv_identifier_wo_vn_nr=_object.submission.arxiv_identifier_wo_vn_nr) - publication_citation = publication.citation() + publication_citation = publication.citation publication_doi = publication.doi_string except Publication.DoesNotExist: pass @@ -1034,7 +1063,12 @@ def publication_detail(request, 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 + if publication.in_issue: + journal = publication.in_issue.in_volume.in_journal + elif publication.in_journal: + journal = publication.in_journal + else: + raise Http404('Publication configuration is valid') context = { 'publication': publication, diff --git a/scipost/static/scipost/assets/css/_list_group.scss b/scipost/static/scipost/assets/css/_list_group.scss index b598898a08caa9faf14f2f59088a2c2f1045e000..c2313938bd328c3d3fa2c09ed704792f1f706da6 100644 --- a/scipost/static/scipost/assets/css/_list_group.scss +++ b/scipost/static/scipost/assets/css/_list_group.scss @@ -45,3 +45,7 @@ ul.references { margin: 0 0 0.2rem 2rem; } } + +ul.links > li.active a { + font-weight: 700; +} diff --git a/scipost/templates/feeds/latest_publications_title.html b/scipost/templates/feeds/latest_publications_title.html index 6558e2da81b6a1766d69447f71d5d776a4f81485..388e7a3dfc6642a5bbf9408c14da16be46d8e0f6 100644 --- a/scipost/templates/feeds/latest_publications_title.html +++ b/scipost/templates/feeds/latest_publications_title.html @@ -1 +1 @@ -{{obj.in_issue.in_volume.in_journal.get_abbreviation_citation}} {{obj.in_issue.in_volume.number}}, {{obj.get_paper_nr}} ({{obj.publication_date|date:'Y'}}), by {{ obj.author_list }} +{{obj.in_issue.in_volume.in_journal.abbreviation_citation}} {{obj.in_issue.in_volume.number}}, {{obj.get_paper_nr}} ({{obj.publication_date|date:'Y'}}), by {{ obj.author_list }} diff --git a/scipost/urls.py b/scipost/urls.py index 9da0a19f216c11051b08f1b2064ccb6774057ea9..862b316352d7ac06ad36185dedd0392de56b7671 100644 --- a/scipost/urls.py +++ b/scipost/urls.py @@ -7,7 +7,7 @@ from .feeds import LatestNewsFeedRSS, LatestNewsFeedAtom, LatestCommentsFeedRSS, LatestPublicationsFeedRSS, LatestPublicationsFeedAtom from journals import views as journals_views -from journals.constants import REGEX_CHOICES +from journals.constants import REGEX_CHOICES, PUBLICATION_DOI_REGEX from submissions import views as submission_views JOURNAL_REGEX = '(?P<doi_label>%s)' % REGEX_CHOICES @@ -185,16 +185,16 @@ urlpatterns = [ name='author_reply_detail'), # Publication detail (+pdf) - url(r'^10.21468/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})$', + url(r'^10.21468/(?P<doi_label>{regex})$'.format(regex=PUBLICATION_DOI_REGEX), journals_views.publication_detail, name='publication_detail'), - url(r'^(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})$', + url(r'^(?P<doi_label>{regex})$'.format(regex=PUBLICATION_DOI_REGEX), journals_views.publication_detail, name='publication_detail'), - url(r'^10.21468/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/pdf$', + url(r'^10.21468/(?P<doi_label>{regex})/pdf$'.format(regex=PUBLICATION_DOI_REGEX), journals_views.publication_detail_pdf, name='publication_pdf'), - url(r'^(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9]+.[0-9]{3,})/pdf$', + url(r'^(?P<doi_label>{regex})/pdf$'.format(regex=PUBLICATION_DOI_REGEX), journals_views.publication_detail_pdf, name='publication_pdf'), diff --git a/submissions/templates/partials/submissions/submission_card_content.html b/submissions/templates/partials/submissions/submission_card_content.html index 80ca856c5ff151077fdc79ce337ed439706983da..6d8877f262e8aea19ae4c9e14e557378ce913db2 100644 --- a/submissions/templates/partials/submissions/submission_card_content.html +++ b/submissions/templates/partials/submissions/submission_card_content.html @@ -5,7 +5,7 @@ Version {{ submission.arxiv_vn_nr }} ({% if submission.is_current %}current version{% else %}deprecated version {{ submission.arxiv_vn_nr }}{% endif %}) <br> {% 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> + Published as <a href="{{ submission.publication.get_absolute_url }}">{{ submission.publication.in_issue.in_volume.in_journal.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 }} {% endif %} · latest activity: {{ submission.latest_activity }} diff --git a/submissions/templates/partials/submissions/submission_status.html b/submissions/templates/partials/submissions/submission_status.html index 60a391c36da52871975d2ae6efb0c428d73238e2..82438028e92314b6c417d505f29cc6b90da17efc 100644 --- a/submissions/templates/partials/submissions/submission_status.html +++ b/submissions/templates/partials/submissions/submission_status.html @@ -2,6 +2,6 @@ <div class="d-inline"> <span class="label label-secondary">{{submission.get_status_display}}</span> {% if submission.publication and submission.publication.is_published %} - as <a href="{{submission.publication.get_absolute_url}}">{{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> + as <a href="{{submission.publication.get_absolute_url}}">{{submission.publication.in_issue.in_volume.in_journal.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 b43d69f9553777e50f70a918299e59e5f6872a41..d0b5e25d9e619074b6716aba5b4e4f3119f8ac9b 100644 --- a/submissions/templates/submissions/submission_detail.html +++ b/submissions/templates/submissions/submission_detail.html @@ -27,7 +27,7 @@ <div class="pl-2"> {% 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> + <h3>- Published as <a href="{{submission.publication.get_absolute_url}}">{{submission.publication.in_issue.in_volume.in_journal.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 %} diff --git a/submissions/templates/submissions/submission_list.html b/submissions/templates/submissions/submission_list.html index 05e114e3f2e255678c6c9b80c6ce870f309e436f..ca2cefaf756537a3d59c03e7639e7d4077c1bac1 100644 --- a/submissions/templates/submissions/submission_list.html +++ b/submissions/templates/submissions/submission_list.html @@ -44,7 +44,7 @@ <div class="row"> <div class="col-12"> {% if recent %} - <h2>Recent Submissions:</h2> + <h2>Recent Submissions{% if to_journal %} to {{ to_journal }}{% endif %}:</h2> {% elif browse %} <h2>Submissions in {{ discipline }} in the last {{ nrweeksback }} week{% if nrweeksback == '1' %}{% else %}s{% endif %}:</h2> {% else %} diff --git a/submissions/views.py b/submissions/views.py index 382fa621324a8a87fcf27089e76503a435768ce9..3df758bca45b6ff60eeed2fce5ed7112228d29ea 100644 --- a/submissions/views.py +++ b/submissions/views.py @@ -1,5 +1,6 @@ import datetime import feedparser +import strings from django.contrib import messages from django.contrib.auth.decorators import login_required, permission_required @@ -36,19 +37,17 @@ from .forms import SubmissionIdentifierForm, RequestSubmissionForm, SubmissionSe from .utils import SubmissionUtils from colleges.permissions import fellowship_required, fellowship_or_admin_required -from mails.views import MailEditingSubView -from scipost.forms import ModifyPersonalMessageForm, RemarkForm -from scipost.mixins import PaginationMixin -from scipost.models import Contributor, Remark - from comments.forms import CommentForm from invitations.constants import INVITATION_REFEREEING from invitations.models import RegistrationInvitation +from journals.models import Journal from mails.utils import DirectMailUtil +from mails.views import MailEditingSubView from production.forms import ProofsDecisionForm from production.models import ProductionStream - -import strings +from scipost.forms import ModifyPersonalMessageForm, RemarkForm +from scipost.mixins import PaginationMixin +from scipost.models import Contributor, Remark ############### @@ -136,10 +135,10 @@ class SubmissionListView(PaginationMixin, ListView): def get_queryset(self): queryset = Submission.objects.public_newest() self.form = self.form(self.request.GET) - if 'to_journal' in self.kwargs: + if 'to_journal' in self.request.GET: queryset = queryset.filter( latest_activity__gte=timezone.now() + datetime.timedelta(days=-60), - submitted_to_journal=self.kwargs['to_journal'] + submitted_to_journal=self.request.GET['to_journal'] ) elif 'discipline' in self.kwargs and 'nrweeksback' in self.kwargs: discipline = self.kwargs['discipline'] @@ -161,8 +160,12 @@ class SubmissionListView(PaginationMixin, ListView): context['form'] = self.form # To customize display in the template - if 'to_journal' in self.kwargs: - context['to_journal'] = self.kwargs['to_journal'] + if 'to_journal' in self.request.GET: + try: + context['to_journal'] = Journal.objects.filter( + name=self.request.GET['to_journal']).first().get_name_display() + except (Journal.DoesNotExist, AttributeError): + context['to_journal'] = self.request.GET['to_journal'] if 'discipline' in self.kwargs: context['discipline'] = self.kwargs['discipline'] context['nrweeksback'] = self.kwargs['nrweeksback']