diff --git a/SciPost_v1/settings/local_jorran.py b/SciPost_v1/settings/local_jorran.py index 6b4b12e24cea7c7d0747cf4a948c0a5d03091146..6db72e8b3e4cc6d3d5f0393f45fc99ab32049d97 100644 --- a/SciPost_v1/settings/local_jorran.py +++ b/SciPost_v1/settings/local_jorran.py @@ -13,10 +13,10 @@ MIDDLEWARE += ( INTERNAL_IPS = ['127.0.0.1', '::1'] # Static and media -STATIC_ROOT = '/Users/jorrandewit/Documents/Develop/SciPost/scipost_v1/local_files/static/' -MEDIA_ROOT = '/Users/jorrandewit/Documents/Develop/SciPost/scipost_v1/local_files/media/' +STATIC_ROOT = '/Users/jorrandewit/Documents/SciPost/codebase/local_files/static/' +MEDIA_ROOT = '/Users/jorrandewit/Documents/SciPost/codebase/local_files/media/' WEBPACK_LOADER['DEFAULT']['BUNDLE_DIR_NAME'] =\ - '/Users/jorrandewit/Documents/Develop/SciPost/scipost_v1/local_files/static/bundles/' + '/Users/jorrandewit/Documents/SciPost/codebase/local_files/static/bundles/' MAILCHIMP_API_USER = get_secret("MAILCHIMP_API_USER") MAILCHIMP_API_KEY = get_secret("MAILCHIMP_API_KEY") @@ -27,8 +27,8 @@ ITHENTICATE_USERNAME = get_secret('ITHENTICATE_USERNAME') ITHENTICATE_PASSWORD = get_secret('ITHENTICATE_PASSWORD') # Logging -LOGGING['handlers']['scipost_file_arxiv']['filename'] = '/Users/jorrandewit/Documents/Develop/SciPost/scipost_v1/logs/arxiv.log' -LOGGING['handlers']['scipost_file_doi']['filename'] = '/Users/jorrandewit/Documents/Develop/SciPost/scipost_v1/logs/doi.log' +LOGGING['handlers']['scipost_file_arxiv']['filename'] = '/Users/jorrandewit/Documents/SciPost/codebase/logs/arxiv.log' +LOGGING['handlers']['scipost_file_doi']['filename'] = '/Users/jorrandewit/Documents/SciPost/codebase/logs/doi.log' # Other CROSSREF_LOGIN_ID = get_secret("CROSSREF_LOGIN_ID") diff --git a/journals/behaviors.py b/journals/behaviors.py index dd9e5f253b3391d7c19672fbbe9b4978a2c7ae27..ce61585abd8761eea51ea03f31e3d9393cdf3910 100644 --- a/journals/behaviors.py +++ b/journals/behaviors.py @@ -7,14 +7,13 @@ 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]+$', - 'Only valid DOI expressions are allowed ([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_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]+$', 'Only valid DOI expressions are allowed ([a-zA-Z]+.[0-9]+).') +doi_issue_validator = RegexValidator( + r'^[a-zA-Z]+.\w+(.[0-9]+)?$', + 'Only valid DOI expressions are allowed ([a-zA-Z]+.\w+(.[0-9]+)?)') 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]+`)')) + 'Only valid DOI expressions are allowed: `[a-zA-Z]+(.\w+(.[0-9]+(.[0-9]{3,})?)?)?`') diff --git a/journals/constants.py b/journals/constants.py index 529c204ef05a223473c915cee4cca47717b6fc6f..d8d394a5c1c70eaba9b0dd14311e18d26a40f07a 100644 --- a/journals/constants.py +++ b/journals/constants.py @@ -33,8 +33,12 @@ REGEX_CHOICES = '|'.join([ # Regex used for URLs of specific Publications and for # doi validation during the publication process. PUBLICATION_DOI_REGEX = '({})'.format(REGEX_CHOICES) -PUBLICATION_DOI_REGEX += '.[0-9]+(.[0-9]+.[0-9]{3,})?' +PUBLICATION_DOI_REGEX += '(.\w+(.[0-9]+(.[0-9]{3,})?)?)?' PUBLICATION_DOI_VALIDATION_REGEX = PUBLICATION_DOI_REGEX +DOI_DISPATCH_REGEX = '(?P<journal_tag>{})'.format(REGEX_CHOICES) +DOI_DISPATCH_REGEX += '(.(?P<part_1>\w+)(.(?P<part_2>[0-9]+)(.(?P<part_3>[0-9]{3,}))?)?)?' + +DOI_ISSUE_REGEX = '(?P<doi_label>({}).\w+(.[0-9]+)?)'.format(REGEX_CHOICES) SCIPOST_JOURNALS_DOMAINS = ( ('E', 'Experimental'), @@ -90,9 +94,9 @@ CC_LICENSES_URI = ( ISSUES_AND_VOLUMES = 'IV' ISSUES_ONLY = 'IO' -INDIVIDUAL_PUBLCATIONS = 'IP' +INDIVIDUAL_PUBLICATIONS = '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'), + (ISSUES_ONLY, 'Issues only'), + (INDIVIDUAL_PUBLICATIONS, 'Individual Publications'), ) diff --git a/journals/exceptions.py b/journals/exceptions.py index 202f40aca57188a04f558297094c390266900b1c..d7d1b2642e5818ea410c6deeeba4f25e90e76955 100644 --- a/journals/exceptions.py +++ b/journals/exceptions.py @@ -24,3 +24,11 @@ class PaperNumberingError(Exception): def __str__(self): return self.nr + + +class InvalidDOIError(Exception): + def __init__(self, name): + self.name = name + + def __str__(self): + return self.name diff --git a/journals/factories.py b/journals/factories.py index 3bb8bc26fca3c41ecdab65b873c95bea583b9b44..5f74ef19e736a38089e47086058f4e9bfafbab81 100644 --- a/journals/factories.py +++ b/journals/factories.py @@ -9,7 +9,7 @@ import random from common.helpers import random_digits, random_external_doi, random_external_journal_abbrev from journals.constants import SCIPOST_JOURNALS, SCIPOST_JOURNAL_PHYSICS_LECTURE_NOTES,\ - ISSUES_AND_VOLUMES, INDIVIDUAL_PUBLCATIONS, PUBLICATION_PUBLISHED + ISSUES_AND_VOLUMES, INDIVIDUAL_PUBLICATIONS, PUBLICATION_PUBLISHED from submissions.factories import PublishedSubmissionFactory from .models import Journal, Volume, Issue, Publication, Reference @@ -48,7 +48,7 @@ class JournalFactory(factory.django.DjangoModelFactory): @factory.lazy_attribute def structure(self): if self.name == SCIPOST_JOURNAL_PHYSICS_LECTURE_NOTES: - return INDIVIDUAL_PUBLCATIONS + return INDIVIDUAL_PUBLICATIONS return ISSUES_AND_VOLUMES diff --git a/journals/forms.py b/journals/forms.py index 58d59866ef25cbd9da0d8dc4b7133b046cf936ff..ab03f21092d8215c77826480521cacf721057723 100644 --- a/journals/forms.py +++ b/journals/forms.py @@ -497,44 +497,61 @@ class DraftPublicationForm(forms.ModelForm): 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 = None + # Fill data for Publications grouped by Issues (or Issue+Volume). if hasattr(self.instance, 'in_issue') and self.instance.in_issue: - issue = self.instance.in_issue - elif self.issue: - issue = self.issue - if issue: - self.prefill_with_issue(issue) - - # Fill data that may be derived from the issue data - journal = None + self.issue = self.instance.in_issue + if self.issue: + self.prefill_with_issue(self.issue) + + # Fill data for Publications ungrouped; directly linked to a Journal. 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) + self.to_journal = self.instance.in_issue + if self.to_journal: + self.prefill_with_journal(self.to_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 issue.in_volume: + # Issue/Volume + paper_nr = Publication.objects.filter(in_issue__in_volume=issue.in_volume).count() + 1 + elif issue.in_journal: + # Issue only + paper_nr = Publication.objects.filter(in_issue=issue).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')) + if issue.in_volume: + 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')) + elif issue.in_journal: + doi_label = '{journal}.{issue}.{paper}'.format( + journal=issue.in_journal.name, + 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) + + # Initiate a BibTex entry bibtex_entry = ( '@Article{%s,\n' - '\ttitle={{%s},\n' + '\ttitle={{%s}},\n' '\tauthor={%s},\n' - '\tjournal={%s},\n' - '\tvolume={%i},\n' + ) % ( + doi_string, + self.submission.title, + self.submission.author_list.replace(',', ' and')) + + if issue.in_volume: + bibtex_entry += '\tjournal={%s},\n\tvolume={%i},\n' % ( + issue.in_volume.in_journal.abbreviation_citation, issue.in_volume.number) + elif issue.in_journal: + bibtex_entry += '\tjournal={%s},\n' % (issue.in_journal.abbreviation_citation) + + bibtex_entry += ( '\tissue={%i},\n' '\tpages={%i},\n' '\tyear={%s},\n' @@ -543,16 +560,12 @@ class DraftPublicationForm(forms.ModelForm): '\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 @@ -569,7 +582,7 @@ class DraftPublicationForm(forms.ModelForm): doi_string = '10.21468/{doi}'.format(doi=doi_label) bibtex_entry = ( '@Article{%s,\n' - '\ttitle={{%s},\n' + '\ttitle={{%s}},\n' '\tauthor={%s},\n' '\tjournal={%s},\n' '\tpages={%i},\n' diff --git a/journals/managers.py b/journals/managers.py index ca106578a5a8f02eba9fa2e271d7bac68209473d..f4ead6cac827c5a23cf13705fe8bc674b1dd5c2c 100644 --- a/journals/managers.py +++ b/journals/managers.py @@ -6,7 +6,7 @@ from django.db import models from django.utils import timezone from .constants import STATUS_PUBLISHED, STATUS_DRAFT, PUBLICATION_PUBLISHED, ISSUES_AND_VOLUMES,\ - ISSUES_ONLY, INDIVIDUAL_PUBLCATIONS + ISSUES_ONLY, INDIVIDUAL_PUBLICATIONS class JournalQuerySet(models.QuerySet): @@ -17,7 +17,7 @@ class JournalQuerySet(models.QuerySet): return self.filter(structure__in=(ISSUES_AND_VOLUMES, ISSUES_ONLY)) def has_individual_publications(self): - return self.filter(structure=INDIVIDUAL_PUBLCATIONS) + return self.filter(structure=INDIVIDUAL_PUBLICATIONS) class IssueQuerySet(models.QuerySet): diff --git a/journals/migrations/0042_auto_20180923_2130.py b/journals/migrations/0042_auto_20180923_2130.py new file mode 100644 index 0000000000000000000000000000000000000000..70e6f49568399481e69c1b15c9dd0fab5addaa31 --- /dev/null +++ b/journals/migrations/0042_auto_20180923_2130.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-09-23 19:30 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0041_auto_20180922_1609'), + ] + + operations = [ + migrations.AlterField( + model_name='journal', + name='structure', + field=models.CharField(choices=[('IV', 'Issues and Volumes'), ('IO', 'Issues only'), ('IP', 'Individual Publications')], default='IV', max_length=2), + ), + migrations.AlterField( + model_name='publication', + name='doi_label', + field=models.CharField(db_index=True, max_length=200, unique=True, validators=[django.core.validators.RegexValidator('^(SciPostPhysProc|SciPostPhysSel|SciPostPhysLectNotes|SciPostPhys).[0-9]+((.[0-9]+)?.[0-9]{3,})?$', '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/migrations/0043_auto_20180927_2014.py b/journals/migrations/0043_auto_20180927_2014.py new file mode 100644 index 0000000000000000000000000000000000000000..7d2f820e30ebfac90d7e8f30c897372310682d45 --- /dev/null +++ b/journals/migrations/0043_auto_20180927_2014.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-09-27 18:14 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0042_auto_20180923_2130'), + ] + + operations = [ + migrations.AddField( + model_name='issue', + name='slug', + field=models.SlugField(null=True), + ), + migrations.AlterField( + model_name='issue', + name='doi_label', + field=models.CharField(db_index=True, max_length=200, unique=True, validators=[django.core.validators.RegexValidator('^[a-zA-Z]+(.[0-9]+)?.[0-9]+$', 'Only valid DOI expressions are allowed ([a-zA-Z]+.[0-9]+.[0-9]+).')]), + ), + ] diff --git a/journals/migrations/0044_auto_20180927_2050.py b/journals/migrations/0044_auto_20180927_2050.py new file mode 100644 index 0000000000000000000000000000000000000000..480657815b90971c7b5880d1f1ab676201106be1 --- /dev/null +++ b/journals/migrations/0044_auto_20180927_2050.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-09-27 18:50 +from __future__ import unicode_literals + +from django.db import migrations + + +def update_issue_slugs(apps, schema_editor): + """Fill all slug fields for existing Issue instances.""" + Issue = apps.get_model('journals', 'Issue') + + for issue in Issue.objects.all(): + Issue.objects.filter(id=issue.id).update(slug=issue.number) + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0043_auto_20180927_2014'), + ] + + operations = [ + migrations.RunPython(update_issue_slugs), + ] diff --git a/journals/migrations/0045_auto_20180927_2055.py b/journals/migrations/0045_auto_20180927_2055.py new file mode 100644 index 0000000000000000000000000000000000000000..75588da9e0c49ff393176555c70afbb3289215f6 --- /dev/null +++ b/journals/migrations/0045_auto_20180927_2055.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-09-27 18:55 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('journals', '0044_auto_20180927_2050'), + ] + + operations = [ + migrations.AlterField( + model_name='issue', + name='doi_label', + field=models.CharField(db_index=True, max_length=200, unique=True, validators=[django.core.validators.RegexValidator('^[a-zA-Z]+.\\w+(.[0-9]+)?$', 'Only valid DOI expressions are allowed ([a-zA-Z]+.\\w+(.[0-9]+)?)')]), + ), + migrations.AlterField( + model_name='issue', + name='slug', + field=models.SlugField(), + ), + migrations.AlterField( + model_name='publication', + name='doi_label', + field=models.CharField(db_index=True, max_length=200, unique=True, validators=[django.core.validators.RegexValidator('^(SciPostPhysProc|SciPostPhysSel|SciPostPhysLectNotes|SciPostPhys)(.\\w+(.[0-9]+(.[0-9]{3,})?)?)?$', 'Only valid DOI expressions are allowed: `[a-zA-Z]+(.\\w+(.[0-9]+(.[0-9]{3,})?)?)?`')]), + ), + ] diff --git a/journals/models.py b/journals/models.py index 95ffaac4bc4ad643d53d1c9f5d0e3b1929d5e44b..2e13ad41a8761f4dff24cb2243357312fb1ee8fd 100644 --- a/journals/models.py +++ b/journals/models.py @@ -256,6 +256,7 @@ class Issue(models.Model): 'journals.Volume', on_delete=models.CASCADE, null=True, blank=True, help_text='Assign either an Volume or Journal to the Issue') number = models.PositiveSmallIntegerField() + slug = models.SlugField() 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) @@ -308,11 +309,15 @@ class Issue(models.Model): @property def issue_number(self): - return '%s issue %s' % (self.in_volume, self.number) + if self.in_volume: + return '%s issue %s' % (self.in_volume, self.number) + return '%s issue %s' % (self.in_journal, self.number) @property def short_str(self): - return 'Vol. %s issue %s' % (self.in_volume.number, self.number) + if self.in_volume: + return 'Vol. %s issue %s' % (self.in_volume.number, self.number) + return 'Issue %s' % self.number @property def period_as_string(self): @@ -321,8 +326,8 @@ class Issue(models.Model): return '%s - %s' % (self.start_date.strftime('%B'), self.until_date.strftime('%B %Y')) def is_current(self): - return self.start_date <= timezone.now().date() and\ - self.until_date >= timezone.now().date() + today = timezone.now().date() + return self.start_date <= today and self.until_date >= today def nr_publications(self, tier=None): publications = Publication.objects.filter(in_issue=self) @@ -463,14 +468,14 @@ class Publication(models.Model): '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: + if self.in_issue and not self.get_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: + if self.in_journal and self.get_journal().has_issues: # Assigning both a Journal and an Issue will screw up the database raise ValidationError({ 'in_journal': ValidationError( @@ -548,12 +553,18 @@ class Publication(models.Model): """ Return Publication name in the preferred citation format. """ - if self.in_issue: + if self.in_issue and self.in_issue.in_volume: return '{journal} {volume}, {paper_nr} ({year})'.format( journal=self.in_issue.in_volume.in_journal.abbreviation_citation, volume=self.in_issue.in_volume.number, paper_nr=self.get_paper_nr(), year=self.publication_date.strftime('%Y')) + elif self.in_issue and self.in_issue.in_journal: + return '{journal} {issue}, {paper_nr} ({year})'.format( + journal=self.in_issue.in_journal.abbreviation_citation, + issue=self.in_issue.number, + paper_nr=self.get_paper_nr(), + year=self.publication_date.strftime('%Y')) elif self.in_journal: return '{journal} {paper_nr} ({year})'.format( journal=self.in_journal.abbreviation_citation, @@ -564,7 +575,11 @@ class Publication(models.Model): year=self.publication_date.strftime('%Y')) def get_journal(self): - return self.in_journal or self.in_issue.in_volume.in_journal + if self.in_journal: + return self.in_journal + elif self.in_issue.in_journal: + return self.in_issue.in_journal + return self.in_issue.in_volume.in_journal def journal_issn(self): return self.get_journal().issn @@ -581,7 +596,7 @@ class Publication(models.Model): if self.citedby and self.latest_citedby_update: ncites = len(self.citedby) deltat = (self.latest_citedby_update.date() - self.publication_date).days - return (ncites * 365.25/deltat) + return (ncites * 365.25 / deltat) else: return 0 diff --git a/journals/templates/xml/publication_crossref.html b/journals/templates/xml/publication_crossref.html index 10f70218493076a53c3ac4683a8f67678b2cec0d..26e49c9c2dadf0e8289493cc40de4dfe8bee10b6 100644 --- a/journals/templates/xml/publication_crossref.html +++ b/journals/templates/xml/publication_crossref.html @@ -16,7 +16,7 @@ <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> + {% if publication.in_journal.issn %}<issn media_type='electronic'>{{ publication.in_journal.issn }}</issn>{% endif %} <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> diff --git a/journals/views.py b/journals/views.py index d4a59d242916b45a98b900e59cedcd3f66ff4706..e660f9e2830cefe73995f8c4743f84d126f49ead 100644 --- a/journals/views.py +++ b/journals/views.py @@ -24,13 +24,13 @@ from django.db.models import Q from django.http import Http404, HttpResponse from django.utils import timezone from django.utils.decorators import method_decorator -from django.views.generic.base import TemplateView from django.views.generic.detail import DetailView from django.views.generic.edit import UpdateView from django.views.generic.list import ListView from django.shortcuts import get_object_or_404, get_list_or_404, render, redirect -from .constants import STATUS_DRAFT, PUBLICATION_PREPUBLISHED +from .constants import STATUS_DRAFT, ISSUES_AND_VOLUMES, ISSUES_ONLY, INDIVIDUAL_PUBLICATIONS +from .exceptions import InvalidDOIError from .models import Journal, Issue, Publication, Deposit, DOAJDeposit,\ GenericDOIDeposit, PublicationAuthorsTable, OrgPubFraction from .forms import AbstractJATSForm, FundingInfoForm,\ @@ -57,12 +57,59 @@ from scipost.mixins import PermissionsMixin, RequestViewMixin, PaginationMixin from guardian.decorators import permission_required +def doi_dispatch(request, journal_tag, part_1=None, part_2=None, part_3=None): + """ + Dispatch given DOI route to the appropriate view according to the Journal's settings. + + journal_tag -- Part of the DOI right before the first period. + part_1 (optional) -- Part of the DOI after the first period. + part_2 (optional) -- Part of the DOI after the second period. + part_3 (optional) -- Part of the DOI after the third period. + """ + journal = get_object_or_404(Journal, doi_label=journal_tag) + if part_1 is None: + # This DOI refers to a Journal landing page. + return landing_page(request, journal_tag) + elif part_2 is None: + doi_label = '{0}.{1}'.format(journal_tag, part_1) + + if journal.structure == INDIVIDUAL_PUBLICATIONS: + # Publication DOI for invidivual publication Journals. + return publication_detail(request, doi_label) + elif journal.structure == ISSUES_ONLY: + # Issue DOI for Issue only Journals. + return issue_detail(request, doi_label) + + # The third option: a `Issue and Volume Journal DOI` would lead to a "volume detail page", + # but that does not exist. Redirect to the Journal landing page instead. + return landing_page(request, journal_tag) + elif part_3 is None: + doi_label = '{0}.{1}.{2}'.format(journal_tag, part_1, part_2) + + if journal.structure == ISSUES_AND_VOLUMES: + # Issue DOI for Issue+Volumes Journals. + return issue_detail(request, doi_label) + elif journal.structure == ISSUES_ONLY: + # Publication DOI for Issue only Journals. + return publication_detail(request, doi_label) + else: + doi_label = '{0}.{1}.{2}.{3}'.format(journal_tag, part_1, part_2, part_3) + return publication_detail(request, doi_label) + + # Invalid db configure + raise InvalidDOIError({ + 'journal_tag': journal_tag, + 'part_1': part_1, + 'part_2': part_2, + 'part_3': part_3}) + + ############ # Journals ############ def journals(request): - '''Main landing page for Journals application.''' + """Main landing page for Journals application.""" context = {'journals': Journal.objects.order_by('name')} return render(request, 'journals/journals.html', context) @@ -149,15 +196,15 @@ def about(request, doi_label): def issue_detail(request, doi_label): issue = get_object_or_404(Issue.objects.published(), doi_label=doi_label) - journal = issue.in_volume.in_journal + journal = issue.in_journal or issue.in_volume.in_journal papers = issue.publications.published().order_by('paper_nr') - 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() + next_issue = Issue.objects.published().filter(start_date__gt=issue.start_date).filter( + Q(in_volume__in_journal=journal) | Q(in_journal=journal), + ).distinct().order_by('start_date').first() + prev_issue = Issue.objects.published().filter(start_date__lt=issue.start_date).filter( + Q(in_volume__in_journal=journal) | Q(in_journal=journal) + ).distinct().order_by('start_date').last() context = { 'issue': issue, @@ -365,24 +412,6 @@ class AuthorAffiliationView(PublicationMixin, PermissionsMixin, DetailView): context['add_affiliation_form'] = AuthorsTableOrganizationSelectForm() return context -# NOT WORKING, using the FBV below instead -# class AddAffiliationView(PermissionsMixin, UpdateView): -# """ -# Add an affiliation to a PublicationAuthorsTable instance. -# """ -# permission_required = 'scipost.can_draft_publication' -# model = PublicationAuthorsTable -# form_class = AuthorsTableOrganizationSelectForm -# template_name = 'journals/author_affiliations.html' - -# def form_valid(self, form): -# self.object.affiliations.add(form.cleaned_data['organization']) -# return super().form_valid(form) - -# def get_success_url(self): -# return reverse_lazy('journals:author_affiliations', -# kwargs={'doi_label': self.kwargs['.doi_label']}) - @permission_required('scipost.can_draft_publication', return_403=True) @transaction.atomic @@ -755,7 +784,7 @@ def allocate_orgpubfractions(request, doi_label): elif not (request.user == publication.accepted_submission.submitted_by.user or request.user.has_perm('scipost.can_publish_accepted_submission')): raise Http404 - initial = [] + if not publication.pubfractions.all().exists(): # Create new OrgPubFraction objects from existing data, spreading weight evenly for org in publication.get_organizations(): @@ -1232,20 +1261,12 @@ def publication_detail(request, doi_label): 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 publication.status == PUBLICATION_PREPUBLISHED: - if not request.user.has_perm('scipost.can_draft_publication'): - raise Http404('Publication is not publicly visible') - - 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') + if not publication.is_published and not request.user.has_perm('scipost.can_draft_publication'): + raise Http404('Publication is not publicly visible') context = { 'publication': publication, - 'journal': journal + 'journal': publication.get_journal(), } return render(request, 'journals/publication_detail.html', context) diff --git a/package.json b/package.json index f2c64c62424bf4e46e3382a94e50c3c2f4ea1266..d1b19598bf1e9ce5f936f6408157a529a47d3e6b 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,7 @@ "homepage": "https://www.scipost.org", "devDependencies": { "ajv": "^5.2.2", - "bootstrap": "^4.1.0", - "bootstrap-loader": "^2.1.0", + "bootstrap": "^4.1.3", "clean-webpack-plugin": "^0.1.15", "css-loader": "^0.28.4", "enhanced-resolve": "^3.4.1", @@ -41,13 +40,15 @@ "style-loader": "^0.13.1", "tapable": "^0.2.8", "tether": "^1.4.0", - "url-loader": "^0.5.7", + "url-loader": "^1.1.1", "webpack": "^3.5.4", - "webpack-bundle-tracker": "^0.2.0", + "webpack-bundle-tracker": "^0.3.0", "webpack-glob-entry": "^2.1.1" }, "dependencies": { - "npm": "^5.10.0", + "bootstrap-loader": "^2.2.0", + "npm": "^6.4.1", + "npm-install-peers": "^1.2.1", "schema-utils": "^0.3.0" } } diff --git a/proceedings/models.py b/proceedings/models.py index abff3df6b1c8749c04ae2845c884b7fcf10ee919..cf5d45b61e420def2a9a74868e7b89063b16e46c 100644 --- a/proceedings/models.py +++ b/proceedings/models.py @@ -20,7 +20,7 @@ class Proceedings(TimeStampedModel): # Link to the actual Journal platform issue = models.OneToOneField( 'journals.Issue', related_name='proceedings', - limit_choices_to={'in_volume__in_journal__name': 'SciPostPhysProc'}) + limit_choices_to=models.Q(in_volume__in_journal__name='SciPostPhysProc') | models.Q(in_journal__name='SciPostPhysProc')) minimum_referees = models.PositiveSmallIntegerField( help_text='Require an explicit minimum number of referees for the default ref cycle.', blank=True, null=True) diff --git a/requirements.txt b/requirements.txt index 176e10765b2e5252f3879571ba27bc11e07e7cb3..388e33d5cfc3018c8b67c0976ab9684b9fcceb5d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,7 +16,7 @@ django-debug-toolbar==1.8 django-extensions==1.7.6 django-filter==1.0.4 django-guardian==1.4.9 -django-mathjax==0.0.5 # This thing looks dead as well +django-mathjax==0.0.8 django-mptt==0.8.6 # Dead django-sphinxdoc==1.5.1 django-silk==2.0.0 @@ -34,7 +34,7 @@ sphinx-rtd-theme==0.1.9 # Sphinx theme # Testing factory-boy==2.10.0 -Faker==0.8.12 +Faker==0.8.1 # Django Utils diff --git a/scipost/urls.py b/scipost/urls.py index e6e4183ba01daeb61f30a4510e5a6186a8c7f0fc..0aeb24a4e3b11cef3d315d86f86a96ee006d5c05 100644 --- a/scipost/urls.py +++ b/scipost/urls.py @@ -11,7 +11,7 @@ from .feeds import LatestNewsFeedRSS, LatestNewsFeedAtom, LatestCommentsFeedRSS, LatestPublicationsFeedRSS, LatestPublicationsFeedAtom from journals import views as journals_views -from journals.constants import REGEX_CHOICES, PUBLICATION_DOI_REGEX +from journals.constants import REGEX_CHOICES, PUBLICATION_DOI_REGEX, DOI_DISPATCH_REGEX, DOI_ISSUE_REGEX from submissions import views as submission_views JOURNAL_REGEX = '(?P<doi_label>%s)' % REGEX_CHOICES @@ -193,6 +193,10 @@ urlpatterns = [ name='author_reply_detail'), # Publication detail (+pdf) + url(r'^10.21468/{regex}$'.format(regex=DOI_DISPATCH_REGEX), + journals_views.doi_dispatch, name='doi_dispatch'), + url(r'^{regex}$'.format(regex=DOI_DISPATCH_REGEX), + journals_views.doi_dispatch, name='doi_dispatch'), url(r'^10.21468/(?P<doi_label>{regex})$'.format(regex=PUBLICATION_DOI_REGEX), journals_views.publication_detail, name='publication_detail'), @@ -207,9 +211,9 @@ urlpatterns = [ name='publication_pdf'), # Journal issue - url(r'^10.21468/(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9])$', + url(r'^10.21468/{regex}$'.format(regex=DOI_ISSUE_REGEX), journals_views.issue_detail, name='issue_detail'), - url(r'^(?P<doi_label>[a-zA-Z]+.[0-9]+.[0-9])$', + url(r'^{regex}$'.format(regex=DOI_ISSUE_REGEX), journals_views.issue_detail, name='issue_detail'), # Journal landing page