Source code for comments.models

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


from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django.utils.functional import cached_property
from django.urls import reverse

from guardian.shortcuts import assign_perm

from scipost.behaviors import TimeStampedModel
from scipost.models import Contributor
from commentaries.constants import COMMENTARY_PUBLISHED

from .behaviors import validate_file_extension, validate_max_file_size
from .constants import (
    COMMENT_STATUS, STATUS_PENDING, STATUS_UNCLEAR, STATUS_INCORRECT, STATUS_NOT_USEFUL,
    STATUS_VETTED)
from .managers import CommentQuerySet


WARNING_TEXT = ('Warning: Rather use/edit `content_object` instead or be 100% sure you'
                ' know what you are doing!')
US_NOTICE = 'Warning: This field is out of service and will be removed in the future.'


[docs]class Comment(TimeStampedModel): """ A Comment is an unsollicited note, submitted by a Contributor. A Comment is pointed to a particular Publication, Report or in reply to an earlier Comment. """ status = models.SmallIntegerField(default=STATUS_PENDING, choices=COMMENT_STATUS) vetted_by = models.ForeignKey('scipost.Contributor', blank=True, null=True, on_delete=models.CASCADE, related_name='comment_vetted_by') file_attachment = models.FileField( upload_to='uploads/comments/%Y/%m/%d/', blank=True, validators=[validate_file_extension, validate_max_file_size]) # A Comment is always related to another model content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, help_text=WARNING_TEXT) object_id = models.PositiveIntegerField(help_text=WARNING_TEXT) content_object = GenericForeignKey() nested_comments = GenericRelation('comments.Comment', related_query_name='comments') # Author info is_author_reply = models.BooleanField(default=False) author = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE, related_name='comments') anonymous = models.BooleanField(default=False, verbose_name='Publish anonymously') # Categories: is_cor = models.BooleanField(default=False, verbose_name='correction/erratum') is_rem = models.BooleanField(default=False, verbose_name='remark') is_que = models.BooleanField(default=False, verbose_name='question') is_ans = models.BooleanField(default=False, verbose_name='answer to question') is_obj = models.BooleanField(default=False, verbose_name='objection') is_rep = models.BooleanField(default=False, verbose_name='reply to objection') is_val = models.BooleanField(default=False, verbose_name='validation or rederivation') is_lit = models.BooleanField(default=False, verbose_name='pointer to related literature') is_sug = models.BooleanField(default=False, verbose_name='suggestion for further work') comment_text = models.TextField() remarks_for_editors = models.TextField(blank=True, verbose_name='optional remarks for the Editors only') date_submitted = models.DateTimeField('date submitted', default=timezone.now) 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) objects = CommentQuerySet.as_manager() class Meta: permissions = ( ('can_vet_comments', 'Can vet submitted Comments'), ) def __str__(self): return ('by ' + self.author.user.first_name + ' ' + self.author.user.last_name + ' on ' + self.date_submitted.strftime('%Y-%m-%d') + ', ' + self.comment_text[:30]) @property def title(self): """Get title of Submission if Comment is pointed to Submission.""" try: return self.content_object.title except: return self.content_type @cached_property def core_content_object(self): """Return object Comment is pointed to.""" from commentaries.models import Commentary from submissions.models import Submission, Report from theses.models import ThesisLink to_object = self.content_object while True: # Loop because of possible nested relations if (isinstance(to_object, Submission) or isinstance(to_object, Commentary) or isinstance(to_object, ThesisLink)): return to_object elif isinstance(to_object, Report): return to_object.submission elif isinstance(to_object, Comment): # Nested Comment. to_object = to_object.content_object else: raise Exception @property def is_vetted(self): """Check if Comment is vetted.""" return self.status == STATUS_VETTED @property def is_unvetted(self): """Check if Comment is awaiting vetting.""" return self.status == STATUS_PENDING @property def is_rejected(self): """Check if Comment is rejected.""" return self.status in [STATUS_UNCLEAR, STATUS_INCORRECT, STATUS_NOT_USEFUL] @property def doi_string(self): if self.doi_label: return '10.21468/' + self.doi_label else: return None
[docs] def create_doi_label(self): self.doi_label = 'SciPost.Comment.' + str(self.id) self.save()
[docs] def get_absolute_url(self): return self.content_object.get_absolute_url().split('#')[0] + '#comment_id' + str(self.id)
[docs] def get_attachment_url(self): return reverse('comments:attachment', args=(self.id,))
[docs] def grant_permissions(self): # Import here due to circular import errors from submissions.models import Submission to_object = self.core_content_object if isinstance(to_object, Submission): # Add permissions for EIC only, the Vetting-group already has it! assign_perm('comments.can_vet_comments', to_object.editor_in_charge.user, self)
[docs] def get_author(self): """Return Contributor instance of object if not anonymous.""" if not self.anonymous: return self.author return None
[docs] def get_author_str(self): """Return author string if not anonymous.""" author = self.get_author() if author: return '{} {}'.format(author.get_title_display(), author.user.last_name) return 'Anonymous'
@property def relation_to_published(self): """ Check if the Comment relates to a SciPost-published object. If it is, return a dict with info on relation to the published object, based on Crossref's peer review content type. """ # Import here due to circular import errors from submissions.models import Submission from journals.models import Publication from commentaries.models import Commentary to_object = self.core_content_object if isinstance(to_object, Submission): publication = Publication.objects.filter( accepted_submission__preprint__identifier_wo_vn_nr=to_object.preprint.identifier_wo_vn_nr) if publication: relation = { 'isReviewOfDOI': publication.doi_string, 'stage': 'pre-publication', 'title': 'Comment on ' + to_object.preprint.identifier_w_vn_nr, } if self.is_author_reply: relation['type'] = 'author-comment' else: relation['type'] = 'community-comment' return relation if isinstance(to_object, Commentary): if to_object.type == COMMENTARY_PUBLISHED: relation = { 'isReviewOfDOI': to_object.pub_doi, 'stage': 'post-publication', 'title': 'Comment on ' + to_object.pub_doi, } if self.is_author_reply: relation['type'] = 'author-comment' relation['contributor_role'] = 'author' else: relation['type'] = 'community-comment' relation['contributor_role'] = 'reviewer-external' return relation return None @property def citation(self): citation = '' if self.doi_string: if self.anonymous: citation += 'Anonymous, ' else: citation += '%s %s, ' % (self.author.user.first_name, self.author.user.last_name) if self.is_author_reply: citation += 'SciPost Author Replies, ' else: citation += 'SciPost Comments, ' citation += 'Delivered %s, ' % self.date_submitted.strftime('%Y-%m-%d') citation += 'doi: %s' % self.doi_string return citation