Source code for submissions.models.report

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


from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
from django.urls import reverse
from django.utils.functional import cached_property

from scipost.storage import SecureFileStorage
from comments.behaviors import validate_file_extension, validate_max_file_size
from journals.models import Publication

from ..behaviors import SubmissionRelatedObjectMixin
from ..constants import (
    REPORT_TYPES, REPORT_NORMAL,
    REPORT_STATUSES, STATUS_DRAFT, STATUS_UNVETTED, STATUS_VETTED,
    STATUS_INCORRECT, STATUS_UNCLEAR, STATUS_NOT_USEFUL, STATUS_NOT_ACADEMIC,
    REFEREE_QUALIFICATION, RANKING_CHOICES, QUALITY_SPEC,
    REPORT_REC)
from ..managers import ReportQuerySet


[docs]class Report(SubmissionRelatedObjectMixin, models.Model): """Report on a Submission, written by a Contributor.""" status = models.CharField(max_length=16, choices=REPORT_STATUSES, default=STATUS_UNVETTED) report_type = models.CharField(max_length=32, choices=REPORT_TYPES, default=REPORT_NORMAL) submission = models.ForeignKey('submissions.Submission', related_name='reports', on_delete=models.CASCADE) report_nr = models.PositiveSmallIntegerField(default=0, help_text='This number is a unique number ' 'refeering to the Report nr. of ' 'the Submission') vetted_by = models.ForeignKey('scipost.Contributor', related_name="report_vetted_by", blank=True, null=True, on_delete=models.CASCADE) # `invited' filled from RefereeInvitation objects at moment of report submission invited = models.BooleanField(default=False) # `flagged' if author of report has been flagged by submission authors (surname check only) flagged = models.BooleanField(default=False) author = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE, related_name='reports') qualification = models.PositiveSmallIntegerField( null=True, blank=True, choices=REFEREE_QUALIFICATION, verbose_name="Qualification to referee this: I am") # Text-based reporting strengths = models.TextField(blank=True) weaknesses = models.TextField(blank=True) report = models.TextField(blank=True) requested_changes = models.TextField(verbose_name="requested changes", blank=True) # Comments can be added to a Submission comments = GenericRelation('comments.Comment', related_query_name='reports') # Qualities: validity = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, null=True, blank=True) significance = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, null=True, blank=True) originality = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, null=True, blank=True) clarity = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, null=True, blank=True) formatting = models.SmallIntegerField(choices=QUALITY_SPEC, null=True, blank=True, verbose_name="Quality of paper formatting") grammar = models.SmallIntegerField(choices=QUALITY_SPEC, null=True, blank=True, verbose_name="Quality of English grammar") recommendation = models.SmallIntegerField(null=True, blank=True, choices=REPORT_REC) remarks_for_editors = models.TextField(blank=True, verbose_name='optional remarks for the Editors only') needs_doi = models.NullBooleanField(default=None) doideposit_needs_updating = models.BooleanField(default=False) genericdoideposit = GenericRelation('journals.GenericDOIDeposit', related_query_name='genericdoideposit') doi_label = models.CharField(max_length=200, blank=True) anonymous = models.BooleanField(default=True, verbose_name='Publish anonymously') pdf_report = models.FileField(upload_to='UPLOADS/REPORTS/%Y/%m/', max_length=200, blank=True) date_submitted = models.DateTimeField('date submitted') created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) # Attachment file_attachment = models.FileField( upload_to='uploads/reports/%Y/%m/%d/', blank=True, validators=[validate_file_extension, validate_max_file_size], storage=SecureFileStorage()) objects = ReportQuerySet.as_manager() class Meta: unique_together = ('submission', 'report_nr') default_related_name = 'reports' ordering = ['-date_submitted'] def __str__(self): """Summarize the RefereeInvitation's basic information.""" return (self.author.user.first_name + ' ' + self.author.user.last_name + ' on ' + self.submission.title[:50] + ' by ' + self.submission.author_list[:50])
[docs] def save(self, *args, **kwargs): """Update report number before saving on creation.""" if not self.report_nr: new_report_nr = self.submission.reports.aggregate( models.Max('report_nr')).get('report_nr__max') if new_report_nr: new_report_nr += 1 else: new_report_nr = 1 self.report_nr = new_report_nr return super().save(*args, **kwargs)
[docs] def get_absolute_url(self): """Return url of the Report on the Submission detail page.""" return self.submission.get_absolute_url() + '#report_' + str(self.report_nr)
[docs] def get_notification_url(self, url_code): """Return url related to the Report by the `url_code` meant for Notifications.""" if url_code == 'report_form': return reverse( 'submissions:submit_report', args=(self.submission.preprint.identifier_w_vn_nr,)) elif url_code == 'editorial_page': return reverse( 'submissions:editorial_page', args=(self.submission.preprint.identifier_w_vn_nr,)) return self.get_absolute_url()
[docs] def get_attachment_url(self): """Return url of the Report its attachment if exists.""" return reverse('submissions:report_attachment', kwargs={ 'identifier_w_vn_nr': self.submission.preprint.identifier_w_vn_nr, 'report_nr': self.report_nr})
@property def is_in_draft(self): """Return if Report is in draft.""" return self.status == STATUS_DRAFT @property def is_vetted(self): """Return if Report is publicly available.""" return self.status == STATUS_VETTED @property def is_unvetted(self): """Return if Report is awaiting vetting.""" return self.status == STATUS_UNVETTED @property def is_rejected(self): """Return if Report is rejected.""" return self.status in [ STATUS_INCORRECT, STATUS_UNCLEAR, STATUS_NOT_USEFUL, STATUS_NOT_ACADEMIC] @property def notification_name(self): """Return string representation of this Report as shown in Notifications.""" return self.submission.preprint.identifier_w_vn_nr @property def doi_string(self): """Return the doi with the registrant identifier prefix.""" if self.doi_label: return '10.21468/' + self.doi_label return '' @cached_property def title(self): """Return the submission's title. This property is (mainly) used to let Comments get the title of the Submission without overcomplicated logic. """ return self.submission.title @property def is_followup_report(self): """Return if Report is a follow-up Report instead of a regular Report. This property is used in the ReportForm, but will be candidate to become a database field if this information becomes necessary in more general information representation. """ return (self.author.reports.accepted().filter( submission__preprint__identifier_wo_vn_nr=self.submission.preprint.identifier_wo_vn_nr, submission__preprint__vn_nr__lt=self.submission.preprint.vn_nr).exists()) @property def associated_published_doi(self): """Return the related Publication doi. Check if the Report relates to a SciPost-published object. If it does, return the doi of the published object. """ try: publication = Publication.objects.get( accepted_submission__preprint__identifier_wo_vn_nr=self.submission.preprint.identifier_wo_vn_nr) except Publication.DoesNotExist: return None return publication.doi_string @property def relation_to_published(self): """Return dictionary with published object information. Check if the Report relates to a SciPost-published object. If it does, return a dict with info on relation to the published object, based on Crossref's peer review content type. """ try: publication = Publication.objects.get( accepted_submission__preprint__identifier_wo_vn_nr=self.submission.preprint.identifier_wo_vn_nr) except Publication.DoesNotExist: return None relation = { 'isReviewOfDOI': publication.doi_string, 'stage': 'pre-publication', 'type': 'referee-report', 'title': 'Report on ' + self.submission.preprint.identifier_w_vn_nr, 'contributor_role': 'reviewer', } return relation @property def citation(self): """Return the proper citation format for this Report.""" citation = '' if self.doi_string: if self.anonymous: citation += 'Anonymous, ' else: citation += '%s %s, ' % (self.author.user.first_name, self.author.user.last_name) citation += 'Report on arXiv:%s, ' % self.submission.preprint.identifier_w_vn_nr citation += 'delivered %s, ' % self.date_submitted.strftime('%Y-%m-%d') citation += 'doi: %s' % self.doi_string return citation
[docs] def create_doi_label(self): """Create a doi in the default format.""" Report.objects.filter(id=self.id).update(doi_label='SciPost.Report.{}'.format(self.id))
[docs] def latest_report_from_thread(self): """Get latest Report of this Report's author for the Submission thread.""" return self.author.reports.accepted().filter( submission__preprint__identifier_wo_vn_nr=self.submission.preprint.identifier_wo_vn_nr ).order_by('submission__preprint__identifier_wo_vn_nr').last()