Source code for journals.models.issue

__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3"


from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Avg, F
from django.utils import timezone
from django.urls import reverse

from proceedings.models import Proceedings

from ..constants import ISSUES_ONLY, ISSUE_STATUSES, STATUS_DRAFT, STATUS_PUBLISHED,\
    STATUS_PUBLICLY_OPEN
from ..managers import IssueQuerySet
from ..validators import doi_issue_validator



[docs]class Issue(models.Model): """ An Issue is related to a specific Journal, either indirectly via a Volume container, or directly. It is a container for multiple Publications. """ in_journal = models.ForeignKey( 'journals.Journal', on_delete=models.CASCADE, null=True, blank=True, limit_choices_to={'structure': ISSUES_ONLY}, help_text='Assign either a Volume or Journal to the Issue') in_volume = models.ForeignKey( 'journals.Volume', on_delete=models.CASCADE, null=True, blank=True, help_text='Assign either a Volume or Journal to the Issue') number = models.PositiveIntegerField() 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) 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 = IssueQuerySet.as_manager() class Meta: default_related_name = 'issues' ordering = ('-until_date',) unique_together = ('number', 'in_volume') def __str__(self): text = self.issue_string if hasattr(self, 'proceedings'): return text text += ' (%s)' % self.period_as_string if self.status == STATUS_DRAFT: text += ' (In draft)' return text
[docs] def clean(self): """Check if either a Journal or Volume is assigned to the Issue.""" 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'), })
[docs] def get_absolute_url(self): return reverse('scipost:issue_detail', args=[self.doi_label])
@property def doi_string(self): return '10.21468/' + self.doi_label @property def issue_string(self): if self.in_volume: return '%s issue %s' % (self.in_volume, self.number) elif self.status == STATUS_PUBLICLY_OPEN: try: return '%s (open): %s (%s)' % (self.in_journal, self.proceedings.event_name, self.number) except Proceedings.DoesNotExist: pass return '%s (open): %s' % (self.in_journal, self.number) return '%s issue %s' % (self.in_journal, self.number) @property def short_str(self): if self.in_volume: return 'Vol. %s issue %s' % (self.in_volume.number, self.number) return 'Issue %s' % self.doi_label.rpartition('.')[2] @property def period_as_string(self): if self.start_date.month == self.until_date.month: return '%s %s' % (self.until_date.strftime('%B'), self.until_date.strftime('%Y')) return '%s - %s' % (self.start_date.strftime('%B'), self.until_date.strftime('%B %Y'))
[docs] def get_journal(self): if self.in_journal: return self.in_journal return self.in_volume.in_journal
[docs] def is_current(self): today = timezone.now().date() return self.start_date <= today and self.until_date >= today
[docs] def nr_publications(self, tier=None): from journals.models import Publication publications = Publication.objects.filter(in_issue=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=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=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)