Newer
Older
from django.utils import timezone
from django.utils.safestring import mark_safe
from django.db import models
from django.contrib.auth.models import User

Jean-Sébastien Caux
committed
from django.contrib.postgres.fields import ArrayField, JSONField

Jean-Sébastien Caux
committed
from django.template import Template, Context
from .models import *
from scipost.models import ChoiceArrayField, Contributor, title_dict, Remark
from scipost.models import SCIPOST_DISCIPLINES, SCIPOST_SUBJECT_AREAS
from scipost.models import subject_areas_dict, TITLE_CHOICES
from journals.models import SCIPOST_JOURNALS_SUBMIT, SCIPOST_JOURNALS_DOMAINS
from journals.models import SCIPOST_JOURNALS_SPECIALIZATIONS

Jean-Sébastien Caux
committed
from journals.models import journals_submit_dict, journals_domains_dict, journals_spec_dict
from journals.models import Publication
###############
# Submissions:
###############
SUBMISSION_STATUS = (
('unassigned', 'Unassigned, undergoing pre-screening'),
('assignment_failed', 'Failed to assign Editor-in-charge; manuscript rejected'),
('EICassigned', 'Editor-in-charge assigned, manuscript under review'),
('review_closed', 'Review period closed, editorial recommendation pending'),
# If revisions required: resubmission creates a new Submission object
('revision_requested', 'Editor-in-charge has requested revision'),
('resubmitted', 'Has been resubmitted'),
('resubmitted_and_rejected', 'Has been resubmitted and subsequently rejected'),
('resubmitted_and_rejected_visible',
'Has been resubmitted and subsequently rejected (still publicly visible)'),
# If acceptance/rejection:
('voting_in_preparation', 'Voting in preparation (eligible Fellows being selected)'),
('put_to_EC_voting', 'Undergoing voting at the Editorial College'),
('EC_vote_completed', 'Editorial College voting rounded up'),
('accepted', 'Publication decision taken: accept'),
('rejected', 'Publication decision taken: reject'),
('rejected_visible', 'Publication decision taken: reject (still publicly visible)'),
# If withdrawn:

Jean-Sébastien Caux
committed
('withdrawn', 'Withdrawn by the Authors'),
submission_status_dict = dict(SUBMISSION_STATUS)
SUBMISSION_STATUS_OUT_OF_POOL = [
'assignment_failed',
'resubmitted',
'published',
'withdrawn',
]
# Submissions which should not appear in search lists
SUBMISSION_STATUS_PUBLICLY_UNLISTED = [
'unassigned',
'assignment_failed',
'resubmitted',
'resubmitted_rejected',
'resubmitted_rejected_visible',
'rejected',
'published',
'withdrawn',
]
# Submissions which should not be viewable (except by admins, Fellows and authors)
SUBMISSION_STATUS_PUBLICLY_INVISIBLE = [
'unassigned',
'assignment_failed',
'resubmitted_rejected',
'rejected',
'published',
'withdrawn',
]
# Submissions for which voting on a related recommendation is deprecated:
SUBMISSION_STATUS_VOTING_DEPRECATED = [
'rejected',
'published',
'withdrawn',
]
# SUBMISSION_ACTION_REQUIRED = (
# ('assign_EIC', 'Editor-in-charge to be assigned'),
# # ('Fellow_accepts_or_refuse_assignment', 'Fellow must accept or refuse assignment'),
# ('EIC_runs_refereeing_round', 'Editor-in-charge to run refereeing round (inviting referees)'),
# ('EIC_closes_refereeing_round', 'Editor-in-charge to close refereeing round'),
# ('EIC_invites_author_response', 'Editor-in-charge invites authors to complete their replies'),
# ('EIC_formulates_editorial_recommendation',
# 'Editor-in-charge to formulate editorial recommendation'),
# ('EC_ratification', 'Editorial College ratifies editorial recommendation'),
# ('Decision_to_authors', 'Editor-in-charge forwards decision to authors'),
# )

Jean-Sébastien Caux
committed
SUBMISSION_TYPE = (
('Letter', 'Letter (broad-interest breakthrough results)'),
('Article', 'Article (in-depth reports on specialized research)'),
('Review', 'Review (candid snapshot of current research in a given area)'),

Jean-Sébastien Caux
committed
)
submission_type_dict = dict(SUBMISSION_TYPE)
class Submission(models.Model):
is_current = models.BooleanField(default=True)
is_resubmission = models.BooleanField(default=False)
submitted_by = models.ForeignKey(Contributor, on_delete=models.CASCADE)
editor_in_charge = models.ForeignKey(Contributor, related_name='EIC', blank=True, null=True,
on_delete=models.CASCADE)
submitted_to_journal = models.CharField(max_length=30, choices=SCIPOST_JOURNALS_SUBMIT,

Jean-Sébastien Caux
committed
verbose_name="Journal to be submitted to")
submission_type = models.CharField(max_length=10, choices=SUBMISSION_TYPE,

Jean-Sébastien Caux
committed
blank=True, null=True, default=None)
discipline = models.CharField(max_length=20, choices=SCIPOST_DISCIPLINES, default='physics')
domain = models.CharField(max_length=3, choices=SCIPOST_JOURNALS_DOMAINS)

Jean-Sébastien Caux
committed
# specialization = models.CharField(max_length=1, choices=SCIPOST_JOURNALS_SPECIALIZATIONS)
subject_area = models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS,

Jean-Sébastien Caux
committed
verbose_name='Primary subject area', default='Phys:QP')
secondary_areas = ChoiceArrayField(
models.CharField(max_length=10, choices=SCIPOST_SUBJECT_AREAS),
blank=True, null=True)
status = models.CharField(max_length=30, choices=SUBMISSION_STATUS) # set by Editors
author_comments = models.TextField(blank=True, null=True)
list_of_changes = models.TextField(blank=True, null=True)
remarks_for_editors = models.TextField(blank=True, null=True)
referees_suggested = models.TextField(blank=True, null=True)
referees_flagged = models.TextField(blank=True, null=True)
open_for_reporting = models.BooleanField(default=False)
reporting_deadline = models.DateTimeField(default=timezone.now)
open_for_commenting = models.BooleanField(default=False)
title = models.CharField(max_length=300)
author_list = models.CharField(max_length=1000, verbose_name="author list")
# Authors which have been mapped to contributors:
authors = models.ManyToManyField (Contributor, blank=True, related_name='authors_sub')
authors_claims = models.ManyToManyField (Contributor, blank=True,
related_name='authors_sub_claims')
authors_false_claims = models.ManyToManyField (Contributor, blank=True,
related_name='authors_sub_false_claims')
abstract = models.TextField()
arxiv_identifier_w_vn_nr = models.CharField(max_length=15, default='0000.00000v0')
arxiv_identifier_wo_vn_nr = models.CharField(max_length=10, default='0000.00000')
arxiv_vn_nr = models.PositiveSmallIntegerField(default=1)
arxiv_link = models.URLField(verbose_name='arXiv link (including version nr)')
metadata = JSONField(default={}, blank=True, null=True)
submission_date = models.DateField(verbose_name='submission date')
latest_activity = models.DateTimeField(default=timezone.now)
class Meta:
permissions = (
('can_take_editorial_actions', 'Can take editorial actions'),
)
def __str__ (self):
header = (self.arxiv_identifier_w_vn_nr + ', '
+ self.title[:30] + ' by ' + self.author_list[:30])
if self.is_current:
header += ' (current version)'
else:
header += ' (deprecated version ' + str(self.arxiv_vn_nr) + ')'
try:
header += ' (published as ' + self.publication.citation() + ')'
except Publication.DoesNotExist:
pass
return header
@property
def reporting_deadline_has_passed(self):
if timezone.now() > self.reporting_deadline:
return True
return False

Jean-Sébastien Caux
committed
# for Submission page

Jean-Sébastien Caux
committed
header += '<tr><td>Title: </td><td> </td><td>{{ title }}</td></tr>'
header += '<tr><td>Author(s): </td><td> </td><td>{{ author_list }}</td></tr>'
header += '<tr><td>As Contributors: </td><td> </td>'
if self.authors.all():
header += '<td>'
for auth in self.authors.all():
header += ('<a href="/contributor/' + str(auth.id) + '">' + auth.user.first_name
+ ' ' + auth.user.last_name + '</a> ')
header += '</td>'
else:
header += '<td>(none claimed)</td>'
header += ('</tr>'
'<tr><td>arxiv Link: </td><td> </td>'
'<td><a href="{{ arxiv_link }}" target="_blank">{{ arxiv_link }}</a></td></tr>'
'<tr><td>Date submitted: </td><td> </td><td>{{ submission_date }}</td></tr>'
'<tr><td>Submitted by: </td><td> </td><td>{{ submitted_by }}</td></tr>'
'<tr><td>Submitted to: </td><td> </td><td>{{ to_journal }}</td></tr>'
'<tr><td>Domain(s): </td><td> </td><td>{{ domain }}</td></tr>'

Jean-Sébastien Caux
committed
# '<tr><td>Specialization: </td><td> </td><td>{{ spec }}</td></tr>'
'<tr><td>Subject area: </td><td> </td><td>{{ subject_area }}</td></tr>'

Jean-Sébastien Caux
committed
template = Template(header)
context = Context({'title': self.title, 'author_list': self.author_list,
'arxiv_link': self.arxiv_link, 'submission_date': self.submission_date,
'submitted_by': self.submitted_by,
'to_journal': journals_submit_dict[self.submitted_to_journal],
'domain': journals_domains_dict[self.domain],

Jean-Sébastien Caux
committed
# 'spec': journals_spec_dict[self.specialization],

Jean-Sébastien Caux
committed
'subject_area': subject_areas_dict[self.subject_area],
})

Jean-Sébastien Caux
committed
return template.render(context)
# for search lists
header = ('<li>'
#'<div class="flex-container">'
#'<div class="flex-whitebox0">'
'<p>'
'<a href="/submission/{{ arxiv_identifier_w_vn_nr }}" '
'class="pubtitleli">{{ title }}</a></p>'
'<p>by {{ author_list }}</p>'
'<p>Version {{ arxiv_vn_nr }}')
if self.is_current:
header += ' (current version)'
else:
header += ' (deprecated version {{ arxiv_vn_nr }})'
header += ('</p><p> Submitted {{ submission_date }} to {{ to_journal }}'
' - latest activity: {{ latest_activity }}</p>'
#'</div></div>'
'</li>')
context = Context({'arxiv_identifier_w_vn_nr': self.arxiv_identifier_w_vn_nr,
'arxiv_vn_nr': self.arxiv_vn_nr,
'title': self.title, 'author_list': self.author_list,
'submission_date': self.submission_date,

Jean-Sébastien Caux
committed
'to_journal': journals_submit_dict[self.submitted_to_journal],
'latest_activity': self.latest_activity.strftime('%Y-%m-%d %H:%M')})
template = Template(header)
return template.render(context)
def header_as_li_for_authors (self):
header = ('<li>'
#'<div class="flex-container">'
#'<div class="flex-whitebox0">'
'<p><a href="/submission/{{ arxiv_identifier_w_vn_nr }}" '
'class="pubtitleli">{{ title }}</a></p>'
'<p>by {{ author_list }}</p>'
'<p>Version {{ arxiv_vn_nr }}')
if self.is_current:
header += ' (current version)'
else:
header += ' (deprecated version {{ arxiv_vn_nr }})'
header += ('</p><p>Submitted {{ submission_date }} to {{ to_journal }}'
' - latest activity: {{ latest_activity }}</p>'
'<p>Status: {{ status }}</p>'
#'</div></div>'
'</li>')
context = Context({'arxiv_identifier_w_vn_nr': self.arxiv_identifier_w_vn_nr,
'arxiv_vn_nr': self.arxiv_vn_nr,
'title': self.title, 'author_list': self.author_list,
'submission_date': self.submission_date,
'to_journal': journals_submit_dict[self.submitted_to_journal],
'latest_activity': self.latest_activity.strftime('%Y-%m-%d %H:%M'),
'status': submission_status_dict[self.status]})
template = Template(header)
return template.render(context)
def refereeing_status_as_p (self):
nr_ref_invited = RefereeInvitation.objects.filter(submission=self).count()
nr_ref_accepted = RefereeInvitation.objects.filter(submission=self, accepted=True).count()
nr_ref_declined = RefereeInvitation.objects.filter(submission=self, accepted=False).count()
nr_invited_reports_in = Report.objects.filter(submission=self,
status=1, invited=True).count()
nr_contrib_reports_in = Report.objects.filter(submission=self,
status=1, invited=False).count()
nr_reports_awaiting_vetting = Report.objects.filter(submission=self, status=0).count()
nr_reports_refused = Report.objects.filter(submission=self, status__lte=-1).count()
header = ('<p>Nr referees invited: ' + str(nr_ref_invited) +
' [' + str(nr_ref_accepted) + ' accepted/ ' +
str(nr_ref_declined) + ' declined/ ' +
str(nr_ref_invited - nr_ref_accepted - nr_ref_declined) +
' response pending]</p>' +
'<p>Nr reports obtained: ' +
str(nr_invited_reports_in + nr_contrib_reports_in) + ' [' +
str(nr_invited_reports_in) + ' invited/ ' + str(nr_contrib_reports_in) +
' contributed], nr refused: ' + str(nr_reports_refused) +
', nr awaiting vetting: ' + str(nr_reports_awaiting_vetting) + '</p>')
template = Template(header)
context = Context({})
return template.render(context)
# for submissions pool
header = ('<li>'
#'<div class="flex-container">'
#'<div class="flex-whitebox0">'
'<p><a href="/submission/{{ arxiv_identifier_w_vn_nr }}" '
'class="pubtitleli">{{ title }}</a></p>'
'<p>by {{ author_list }}</p>'
'<p>Version {{ arxiv_vn_nr }}')
if self.is_current:
header += ' (current version)'
else:
header += ' (deprecated version {{ arxiv_vn_nr }})'
header += ('</p><p> Submitted {{ submission_date }} to {{ to_journal }}'
' - latest activity: {{ latest_activity }}</p>')
if self.status == 'unassigned':
header += ('<p style="color: red">Status: {{ status }}.'
' You can volunteer to become Editor-in-charge by '
'<a href="/submissions/volunteer_as_EIC/{{ arxiv_identifier_w_vn_nr }}">'
'clicking here</a>.</p>')
header += '<p>Editor-in-charge: {{ EIC }}</p><p>Status: {{ status }}</p>'
header += self.refereeing_status_as_p()
header += (#'</div></div>'
'</li>')
context = Context({'arxiv_identifier_w_vn_nr': self.arxiv_identifier_w_vn_nr,
'arxiv_vn_nr': self.arxiv_vn_nr,
'title': self.title, 'author_list': self.author_list,
'submission_date': self.submission_date,
'to_journal': journals_submit_dict[self.submitted_to_journal],
'latest_activity': self.latest_activity.strftime('%Y-%m-%d %H:%M'),
'EIC': str(self.editor_in_charge),
'status': submission_status_dict[self.status]})
template = Template(header)
return template.render(context)
def simple_header_as_li (self):
# for Lists
header = ('<li>'
#'<div class="flex-container">'
#'<div class="flex-whitebox0">'
'<p>'
'<a href="/submission/{{ arxiv_identifier_w_vn_nr }}" '
'class="pubtitleli">{{ title }}</a></p>'
'<p>by {{ author_list }}</p>'
'<p>Version {{ arxiv_vn_nr }}')
if self.is_current:
header += ' (current version)'
else:
header += ' (deprecated version {{ arxiv_vn_nr }})'
header += ('</p>'
#'</div></div>'
'</li>')
context = Context({'arxiv_identifier_w_vn_nr': self.arxiv_identifier_w_vn_nr,
'arxiv_vn_nr': self.arxiv_vn_nr,
'title': self.title, 'author_list': self.author_list})
template = Template(header)
return template.render(context)
def version_info_as_li (self):
# for listing all versions of a Submission
header = ('<li>'
#'<div class="flex-whitebox0">'
'<p>'
'<a href="/submission/{{ arxiv_identifier_w_vn_nr }}" '
'class="pubtitleli">version {{ arxiv_vn_nr }}</a>')
if self.is_current:
header += ' (current version)'
else:
header += ' (deprecated version {{ arxiv_vn_nr }})'
header += ('</p>'
#'</div>'
'</li>')
context = Context({'arxiv_identifier_w_vn_nr': self.arxiv_identifier_w_vn_nr,
'arxiv_vn_nr': self.arxiv_vn_nr,})
template = Template(header)
return template.render(context)
def status_info_as_table (self):
header = '<table><tr><td>Current status: </td><td> </td><td>{{ status }}'
context = Context({'status': submission_status_dict[self.status],})
try:
context['citation'] = self.publication.citation_for_web_linked()
header += ' as {{ citation }}'
except Publication.DoesNotExist:
pass
header += '</td></tr></table>'
template = Template(header)
return template.render(context)
######################
# Editorial workflow #
######################
ASSIGNMENT_BOOL = ((True, 'Accept'), (False, 'Decline'))
ASSIGNMENT_NULLBOOL = ((None, 'Response pending'), (True, 'Accept'), (False, 'Decline'))
ASSIGNMENT_REFUSAL_REASONS = (
('BUS', 'Too busy'),
('COI', 'Conflict of interest: coauthor in last 5 years'),
('CCC', 'Conflict of interest: close colleague'),
('NIR', 'Cannot give an impartial assessment'),
('NIE', 'Not interested enough'),
('DNP', 'SciPost should not even consider this paper'),
)
assignment_refusal_reasons_dict = dict(ASSIGNMENT_REFUSAL_REASONS)
class EditorialAssignment(models.Model):
submission = models.ForeignKey(Submission, on_delete=models.CASCADE)
to = models.ForeignKey(Contributor, on_delete=models.CASCADE)
accepted = models.NullBooleanField(choices=ASSIGNMENT_NULLBOOL, default=None)
# attribute `deprecated' becomes True if another Fellow becomes Editor-in-charge
deprecated = models.BooleanField(default=False)
completed = models.BooleanField(default=False)
refusal_reason = models.CharField(max_length=3, choices=ASSIGNMENT_REFUSAL_REASONS,
blank=True, null=True)
date_created = models.DateTimeField(default=timezone.now)
date_answered = models.DateTimeField(blank=True, null=True)
def __str__(self):
return (self.to.user.first_name + ' ' + self.to.user.last_name + ' to become EIC of ' +
self.submission.title[:30] + ' by ' + self.submission.author_list[:30] +
', requested on ' + self.date_created.strftime('%Y-%m-%d'))
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
def info_as_li(self):
context = Context({'first_name': self.to.user.first_name,
'last_name': self.to.user.last_name,
'date_created': self.date_created.strftime('%Y-%m-%d %H:%M')})
info = '<li'
if self.accepted:
info += ' style="color: green"'
elif self.deprecated:
info += ' style="color: purple"'
elif self.accepted == False:
if self.refusal_reason == 'NIE' or self.refusal_reason == 'DNP':
info += ' style="color: #CC0000"'
else:
info += ' style="color: #FF7700"'
info += '>{{ first_name }} {{ last_name }}, requested {{ date_created }}'
if self.accepted:
info += ', accepted {{ date_answered }}'
context['date_answered'] = self.date_answered.strftime('%Y-%m-%d %H:%M')
if self.deprecated:
info += ', deprecated'
if self.refusal_reason:
info += ', declined {{ date_answered }}, reason: {{ reason }}'
context['date_answered'] = self.date_answered.strftime('%Y-%m-%d %H:%M')
context['reason'] = assignment_refusal_reasons_dict[self.refusal_reason]
info += '</li>'
template = Template(info)
return template.render(context)
header = ('<li>'
#'<div class="flex-container">'
#'<div class="flex-whitebox0">'
'<p><a href="/submission/{{ arxiv_identifier_w_vn_nr }}" '
'class="pubtitleli">{{ title }}</a></p>'
'<p>by {{ author_list }}</p>'
'<p> (submitted {{ date }} to {{ to_journal }})</p>'
'<p>Status: {{ status }}</p><p>Manage this Submission from its '
'<a href="/submissions/editorial_page/{{ arxiv_identifier_w_vn_nr }}">'
'Editorial Page</a>.'
'</p>'
#'</div></div>'
'</li>')

Jean-Sébastien Caux
committed
template = Template(header)
context = Context({'arxiv_identifier_w_vn_nr': self.submission.arxiv_identifier_w_vn_nr,
'author_list': self.submission.author_list,
'date': self.submission.submission_date,
'to_journal': journals_submit_dict[self.submission.submitted_to_journal],
'status': submission_status_dict[self.submission.status]})
return template.render(context)
def header_as_li(self):
""" Same as above, but without link to Editorial Page. """
header = ('<li>'
#'<div class="flex-container">'
#'<div class="flex-whitebox0">'
'<p><a href="/submission/{{ arxiv_identifier_w_vn_nr }}" '
'class="pubtitleli">{{ title }}</a></p>'
'<p>by {{ author_list }}</p>'
'<p> (submitted {{ date }} to {{ to_journal }})</p>'
'<p>Status: {{ status }}</p>'
#'</div></div>'
'</li>'
)
context = Context({'arxiv_identifier_w_vn_nr': self.submission.arxiv_identifier_w_vn_nr,
'title': self.submission.title,
'author_list': self.submission.author_list,
'date': self.submission.submission_date,
'to_journal': journals_submit_dict[self.submission.submitted_to_journal],
'status': submission_status_dict[self.submission.status]})

Jean-Sébastien Caux
committed
return template.render(context)
class RefereeInvitation(models.Model):
submission = models.ForeignKey(Submission, on_delete=models.CASCADE)
referee = models.ForeignKey(Contributor, related_name='referee', blank=True, null=True,
on_delete=models.CASCADE)
title = models.CharField(max_length=4, choices=TITLE_CHOICES)
first_name = models.CharField(max_length=30, default='')
last_name = models.CharField(max_length=30, default='')
email_address = models.EmailField()
# if Contributor not found, person is invited to register
invitation_key = models.CharField(max_length=40, default='')
date_invited = models.DateTimeField(default=timezone.now)
invited_by = models.ForeignKey(Contributor, related_name='referee_invited_by',
blank=True, null=True, on_delete=models.CASCADE)
nr_reminders = models.PositiveSmallIntegerField(default=0)
date_last_reminded = models.DateTimeField(blank=True, null=True)
accepted = models.NullBooleanField(choices=ASSIGNMENT_NULLBOOL, default=None)
date_responded = models.DateTimeField(blank=True, null=True)
refusal_reason = models.CharField(max_length=3, choices=ASSIGNMENT_REFUSAL_REASONS,
blank=True, null=True)
fulfilled = models.BooleanField(default=False) # True if a Report has been submitted
cancelled = models.BooleanField(default=False) # True if EIC has deactivated invitation
return (self.first_name + ' ' + self.last_name + ' to referee ' +
self.submission.title[:30] + ' by ' + self.submission.author_list[:30] +
', invited on ' + self.date_invited.strftime('%Y-%m-%d'))
# def summary_as_li(self):
# context = Context({'first_name': self.first_name, 'last_name': self.last_name,
# 'date_invited': self.date_invited.strftime('%Y-%m-%d %H:%M')})
# output = '<li>{{ first_name }} {{ last_name }}, invited {{ date_invited }}, '
# if self.accepted is not None:
# if self.accepted:
# output += '<strong style="color: green">task accepted</strong> '
# else:
# output += '<strong style="color: red">task declined</strong> '
# output += '{{ date_responded }}'
# context['date_responded'] = self.date_responded.strftime('%Y-%m-%d %H:%M')
# else:
# output += 'response pending'
# if self.fulfilled:
# output += '; Report has been delivered'
# template = Template(output)
# return template.render(context)
def summary_as_tds(self):

Jean-Sébastien Caux
committed
context = Context({'first_name': self.first_name, 'last_name': self.last_name,
'date_invited': self.date_invited.strftime('%Y-%m-%d %H:%M')})
output = ('<td>{{ first_name }} {{ last_name }}</td><td>invited <br/>'
'{{ date_invited }}</td><td>')
if self.cancelled:
output += '<strong style="color: red;">cancelled</strong>'
elif self.accepted is not None:
output += '<strong style="color: green">task accepted</strong> '
output += '<strong style="color: red">task declined</strong> '
output += '<br/>{{ date_responded }}'

Jean-Sébastien Caux
committed
context['date_responded'] = self.date_responded.strftime('%Y-%m-%d %H:%M')
else:
output += 'response pending'
if self.fulfilled:

Jean-Sébastien Caux
committed
output += '<br/><strong style="color: green">task fulfilled</strong>'
output += '</td>'

Jean-Sébastien Caux
committed
template = Template(output)
return template.render(context)
###########
# Reports:
###########
REFEREE_QUALIFICATION = (
(4, 'expert in this subject'),
(3, 'very knowledgeable in this subject'),
(2, 'knowledgeable in this subject'),
(1, 'generally qualified'),
(0, 'not qualified'),
)
ref_qualif_dict = dict(REFEREE_QUALIFICATION)
QUALITY_SPEC = (
(6, 'perfect'),
(5, 'excellent'),
(4, 'good'),
(3, 'reasonable'),
(2, 'acceptable'),
(1, 'below threshold'),
(0, 'mediocre'),
quality_spec_dict = dict(QUALITY_SPEC)
RANKING_CHOICES = (
(101, '-'), # Only values between 0 and 100 are kept, anything outside those limits is discarded.
(100, 'top'), (80, 'high'), (60, 'good'), (40, 'ok'), (20, 'low'), (0, 'poor')
)
ranking_choices_dict = dict(RANKING_CHOICES)
(1, 'Publish as Tier I (top 10% of papers in this journal, qualifies as Select) NOTE: SELECT NOT YET OPEN, STARTS EARLY 2017'),
(2, 'Publish as Tier II (top 50% of papers in this journal)'),
(3, 'Publish as Tier III (meets the criteria of this journal)'),
(-1, 'Ask for minor revision'),
(-2, 'Ask for major revision'),
(-3, 'Reject')
)
report_rec_dict = dict(REPORT_REC)
class Report(models.Model):
""" Both types of reports, invited or contributed. """
# status: see forms.py:REPORT_REFUSAL_CHOICES
# 1: vetted
# 0: unvetted
# -1: rejected (unclear)
# -2: rejected (incorrect)
# -3: rejected (not useful)
# -4: rejected (not academic in style)
status = models.SmallIntegerField(default=0)
submission = models.ForeignKey(Submission, on_delete=models.CASCADE)
vetted_by = models.ForeignKey(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)
date_submitted = models.DateTimeField('date submitted')
author = models.ForeignKey(Contributor, on_delete=models.CASCADE)
qualification = models.PositiveSmallIntegerField(
choices=REFEREE_QUALIFICATION,
verbose_name="Qualification to referee this: I am ")
# Text-based reporting
strengths = models.TextField()
weaknesses = models.TextField()
report = models.TextField()
requested_changes = models.TextField(verbose_name="requested changes")
# Qualities:
validity = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, default=101)
significance = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, default=101)
originality = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, default=101)
clarity = models.PositiveSmallIntegerField(choices=RANKING_CHOICES, default=101)
formatting = models.SmallIntegerField(choices=QUALITY_SPEC,
verbose_name="Quality of paper formatting")
grammar = models.SmallIntegerField(choices=QUALITY_SPEC,
verbose_name="Quality of English grammar")
recommendation = models.SmallIntegerField(choices=REPORT_REC)
remarks_for_editors = models.TextField(default='', blank=True,
verbose_name='optional remarks for the Editors only')
anonymous = models.BooleanField(default=True, verbose_name='Publish anonymously')
def __str__(self):
return (self.author.user.first_name + ' ' + self.author.user.last_name + ' on ' +
self.submission.title[:50] + ' by ' + self.submission.author_list[:50])
def print_identifier(self):

Jean-Sébastien Caux
committed
context = Context({'id': self.id, 'author_id': self.author.id,
'first_name': self.author.user.first_name,
'last_name': self.author.user.last_name,
'date_submitted': self.date_submitted.strftime("%Y-%m-%d")})
output = '<div class="reportid">\n'

Jean-Sébastien Caux
committed
output += '<h3><a id="report_id{{ id }}"></a>'

Jean-Sébastien Caux
committed
output += 'Anonymous Report {{ id }}'

Jean-Sébastien Caux
committed
output += '<a href="/contributor/{{ author_id }}">{{ first_name }} {{ last_name }}</a>'
output += ' on {{ date_submitted }}</h3></div>'
template = Template(output)
return template.render(context)
def print_contents(self):

Jean-Sébastien Caux
committed
context = Context({'strengths': self.strengths, 'weaknesses': self.weaknesses,
'report': self.report, 'requested_changes': self.requested_changes})
output = ('<div class="row"><div class="col-2">'
'<p>Strengths:</p></div><div class="col-10"><p>{{ strengths }}</p></div></div>'
'<div class="row"><div class="col-2">'
'<p>Weaknesses:</p></div><div class="col-10"><p>{{ weaknesses }}</p></div></div>'
'<div class="row"><div class="col-2">'
'<p>Report:</p></div><div class="col-10"><p>{{ report }}</p></div></div>'
'<div class="row"><div class="col-2">'
'<p>Requested changes:</p></div>'
'<div class="col-10"><p>{{ requested_changes }}</p></div></div>'
'<div class="reportRatings"><ul>'
'<li>validity: ' + ranking_choices_dict[self.validity] + '</li>'
'<li>significance: ' + ranking_choices_dict[self.significance] + '</li>'
'<li>originality: ' + ranking_choices_dict[self.originality] + '</li>'
'<li>clarity: ' + ranking_choices_dict[self.clarity] + '</li>'
'<li>formatting: ' + quality_spec_dict[self.formatting] + '</li>'
'<li>grammar: ' + quality_spec_dict[self.grammar] + '</li>'
'</ul></div>')

Jean-Sébastien Caux
committed
template = Template(output)
return template.render(context)
def print_contents_for_editors(self):

Jean-Sébastien Caux
committed
context = Context({'id': self.id, 'author_id': self.author.id,
'author_first_name': self.author.user.first_name,
'author_last_name': self.author.user.last_name,
'date_submitted': self.date_submitted.strftime("%Y-%m-%d"),
'remarks_for_editors': self.remarks_for_editors,
})
output = '<div class="reportid">\n'

Jean-Sébastien Caux
committed
output += '<h3><a id="report_id{{ id }}"></a>'
if self.anonymous:
output += '(chose public anonymity) '
output += ('<a href="/contributor/{{ author_id }}">'
'{{ author_first_name }} {{ author_last_name }}</a>'
' on {{ date_submitted }}</h3></div>'
'<div class="row"><div class="col-2">Qualification:</p></div>'
+ ref_qualif_dict[self.qualification] + '</p></div></div>')
output += self.print_contents()
output += '<h3>Remarks for editors</h3><p>{{ remarks_for_editors }}</p>'
output += '<h3>Recommendation: ' + report_rec_dict[self.recommendation] + '</h3>'

Jean-Sébastien Caux
committed
template = Template(output)
return template.render(context)
##########################
# EditorialCommunication #
##########################
ED_COMM_CHOICES = (
('EtoA', 'Editor-in-charge to Author'),
('EtoR', 'Editor-in-charge to Referee'),
('EtoS', 'Editor-in-charge to SciPost Editorial Administration'),
('AtoE', 'Author to Editor-in-charge'),
('RtoE', 'Referee to Editor-in-Charge'),
('StoE', 'SciPost Editorial Administration to Editor-in-charge'),
)
ed_comm_choices_dict = dict(ED_COMM_CHOICES)
class EditorialCommunication(models.Model):
"""
Each individual communication between Editor-in-charge
to and from Referees and Authors becomes an instance of this class.
"""
submission = models.ForeignKey(Submission, on_delete=models.CASCADE)
referee = models.ForeignKey(Contributor, related_name='referee_in_correspondence',
blank=True, null=True, on_delete=models.CASCADE)
comtype = models.CharField(max_length=4, choices=ED_COMM_CHOICES)
timestamp = models.DateTimeField(default=timezone.now)
text = models.TextField()
def __str__ (self):
if self.referee is not None:
output += ' ' + self.referee.user.first_name + ' ' + self.referee.user.last_name
output += (' for submission ' + self.submission.title[:30] + ' by '
+ self.submission.author_list[:30])
return output
def print_contents_as_li(self):
context = Context({'timestamp': self.timestamp.strftime("%Y-%m-%d %H:%M"),
'text': self.text})
output = '<li><p>'
output += 'From you to Authors'
output += 'From you to Referee '
try:
output += self.referee.user.first_name + ' ' + self.referee.user.last_name
except AttributeError:
pass
output += 'From you to SciPost Ed Admin'
output += 'From Authors to you'
output += 'From Referee '
try:
output += (self.referee.user.first_name + ' ' +
self.referee.user.last_name + ' to you')
except AttributeError:
pass
output += 'From SciPost Ed Admin to you'

Jean-Sébastien Caux
committed
output += ' on {{ timestamp }}</p><p>{{ text }}</p>'
template = Template(output)
return template.render(context)
############################
# Editorial Recommendation #
############################
# From the Editor-in-charge of a Submission
class EICRecommendation(models.Model):
submission = models.ForeignKey(Submission, on_delete=models.CASCADE)

Jean-Sébastien Caux
committed
date_submitted = models.DateTimeField('date submitted', default=timezone.now)
remarks_for_authors = models.TextField(blank=True, null=True)
requested_changes = models.TextField(verbose_name="requested changes", blank=True, null=True)
remarks_for_editorial_college = models.TextField(
default='', blank=True, null=True,
verbose_name='optional remarks for the Editorial College')
recommendation = models.SmallIntegerField(choices=REPORT_REC)
# Editorial Fellows who have assessed this recommendation:
eligible_to_vote = models.ManyToManyField (Contributor, blank=True,
related_name='eligible_to_vote')
voted_for = models.ManyToManyField (Contributor, blank=True, related_name='voted_for')
voted_against = models.ManyToManyField (Contributor, blank=True, related_name='voted_against')
voted_abstain = models.ManyToManyField (Contributor, blank=True, related_name='voted_abstain')
voting_deadline = models.DateTimeField('date submitted', default=timezone.now)
def __str__(self):
return (self.submission.title[:20] + ' by ' + self.submission.author_list[:30] +
', ' + report_rec_dict[self.recommendation])
@property
def nr_for(self):
return self.voted_for.count()
@property
def nr_against(self):
return self.voted_against.count()
@property
def nr_abstained(self):
return self.voted_abstain.count()
def print_for_authors(self):
output = ('<h3>Date: {{ date_submitted }}</h3>'
'<h3>Remarks for authors</h3>'
'<p>{{ remarks_for_authors }}</p>'
'<h3>Requested changes</h3>'
'<p>{{ requested_changes }}</p>'
'<h3>Recommendation</h3>'
'<p>{{ recommendation }}</p>')
context = Context({'date_submitted': self.date_submitted.strftime('%Y-%m-%d %H:%M'),
'remarks_for_authors': self.remarks_for_authors,
'requested_changes': self.requested_changes,
'recommendation': report_rec_dict[self.recommendation],})
template = Template(output)
return template.render(context)
def print_for_Fellows(self):
output = ('<h3>By {{ Fellow }}, formulated on {{ date_submitted }}</h3>'
'<h3>Remarks for authors</h3>'
'<p>{{ remarks_for_authors }}</p>'
'<h3>Requested changes</h3>'
'<p>{{ requested_changes }}</p>'
'<h3>Remarks for Editorial College</h3>'
'<p>{{ remarks_for_editorial_college }}</p>'
'<h3>Recommendation</h3>'
'<p>{{ recommendation }}</p>')
'Fellow': (title_dict[self.submission.editor_in_charge.title] +
' ' + self.submission.editor_in_charge.user.first_name +
' ' + self.submission.editor_in_charge.user.last_name),
'date_submitted': self.date_submitted.strftime('%Y-%m-%d %H:%M'),
'remarks_for_authors': self.remarks_for_authors,
'requested_changes': self.requested_changes,
'remarks_for_editorial_college': self.remarks_for_editorial_college,
'recommendation': report_rec_dict[self.recommendation],})
template = Template(output)
return template.render(context)