__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3"
import datetime
from django.db import models
from django.db.models import Avg, F
from django.urls import reverse
from scipost.constants import SCIPOST_DISCIPLINES
from ..constants import JOURNAL_STRUCTURE, ISSUES_AND_VOLUMES, ISSUES_ONLY
from ..managers import JournalQuerySet
from ..validators import doi_journal_validator
[docs]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.
"""
discipline = models.CharField(max_length=20, choices=SCIPOST_DISCIPLINES, default='physics')
name = models.CharField(max_length=256, unique=True)
name_abbrev = models.CharField(max_length=128, default='SciPost [abbrev]',
help_text='Abbreviated name (for use in citations)')
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)
refereeing_period = models.DurationField(default=datetime.timedelta(days=28))
style = models.TextField(blank=True, null=True,
help_text=('CSS styling for the journal; the Journal\'s DOI '
'should be used as class'))
# For Journals list page
blurb = models.TextField(default='[To be filled in; you can use markup]')
list_order = models.PositiveSmallIntegerField(default=100)
# For about page:
description = models.TextField(default='[To be filled in; you can use markup]')
scope = models.TextField(default='[To be filled in; you can use markup]')
content = models.TextField(default='[To be filled in; you can use markup]')
acceptance_criteria = models.TextField(default='[To be filled in; you can use markup]')
minimal_nr_of_reports = models.PositiveSmallIntegerField(
help_text=('Minimal number of substantial Reports required '
'before an acceptance motion can be formulated'),
default=1)
has_DOAJ_Seal = models.BooleanField(default=False)
# Templates
template_latex_tgz = models.FileField(
verbose_name='Template (LaTeX, gzipped tarball)',
help_text='Gzipped tarball of the LaTeX template package',
upload_to='UPLOADS/TEMPLATES/latex/%Y/', max_length=256, blank=True)
objects = JournalQuerySet.as_manager()
class Meta:
ordering = ['discipline', 'list_order']
def __str__(self):
return self.name
[docs] def get_absolute_url(self):
"""Return Journal's homepage url."""
return reverse('scipost:landing_page', args=(self.doi_label,))
@property
def doi_string(self):
"""Return DOI including the SciPost registrant prefix."""
return '10.21468/' + 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)
[docs] def get_issues(self):
from journals.models import Issue
if self.structure == ISSUES_AND_VOLUMES:
return Issue.objects.filter(in_volume__in_journal=self).published()
elif self.structure == ISSUES_ONLY:
return self.issues.open_or_published()
return Issue.objects.none()
[docs] def get_latest_issue(self):
"""Get latest existing Issue in database irrespective of its status."""
from journals.models import Issue
if self.structure == ISSUES_ONLY:
return self.issues.order_by('-until_date').first()
if self.structure == ISSUES_AND_VOLUMES:
return Issue.objects.filter(in_volume__in_journal=self).order_by('-until_date').first()
return None
[docs] def get_latest_volume(self):
"""Get latest existing Volume in database irrespective of its status."""
if self.structure == ISSUES_AND_VOLUMES:
return self.volumes.order_by('-until_date').first()
return None
[docs] def get_publications(self):
from journals.models import Publication
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()
[docs] def nr_publications(self, tier=None):
from journals.models import Publication
publications = Publication.objects.filter(in_issue__in_volume__in_journal=self)
if tier:
publications = publications.filter(
accepted_submission__eicrecommendations__recommendation=tier)
return publications.count()
[docs] def avg_processing_duration(self):
from journals.models import Publication
duration = Publication.objects.filter(
in_issue__in_volume__in_journal=self).aggregate(
avg=Avg(F('publication_date') - F('submission_date')))['avg']
if duration:
return duration.total_seconds() / 86400
return 0
[docs] def citation_rate(self, tier=None):
"""Return the citation rate in units of nr citations per article per year."""
from journals.models import Publication
publications = Publication.objects.filter(in_issue__in_volume__in_journal=self)
if tier:
publications = publications.filter(
accepted_submission__eicrecommendations__recommendation=tier)
ncites = 0
deltat = 1 # to avoid division by zero
for pub in publications:
if pub.citedby and pub.latest_citedby_update:
ncites += len(pub.citedby)
deltat += (pub.latest_citedby_update.date() - pub.publication_date).days
return (ncites * 365.25/deltat)
[docs] def citedby_impact_factor(self, year):
"""Compute the impact factor for a given year YYYY, from Crossref cited-by data.
This is defined as the total number of citations in year YYYY
for all papers published in years YYYY-1 and YYYY-2, divided
by the number of papers published in year YYYY.
"""
publications = self.get_publications().filter(
models.Q(publication_date__year=int(year)-1) |
models.Q(publication_date__year=int(year)-2))
nrpub = publications.count()
if nrpub == 0:
return 0
ncites = 0
for pub in publications:
if pub.citedby and pub.latest_citedby_update:
for citation in pub.citedby:
if citation['year'] == year:
ncites += 1
return ncites / nrpub