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.template import Template, Context
from .models import *

Jean-Sébastien Caux
committed
from scipost.models import Contributor
from scipost.models import SCIPOST_DISCIPLINES, TITLE_CHOICES

Jean-Sébastien Caux
committed
from journals.models import SCIPOST_JOURNALS_SUBMIT, SCIPOST_JOURNALS_DOMAINS, SCIPOST_JOURNALS_SPECIALIZATIONS
from journals.models import journals_submit_dict, journals_domains_dict, journals_spec_dict
###############
# Submissions:
###############
SUBMISSION_STATUS = (
('unassigned', 'Unassigned'),
('assigned', 'Assigned to a specialty editor (response pending)'),
('EICassigned', 'Editor-in-charge assigned, manuscript under review'),
('review_closed', 'Review period closed, editorial recommendation pending'),
('EIC_has_recommended', 'Editor-in-charge has provided recommendation'),
('put_to_EC_voting', 'Undergoing voting at the Editorial College'),
('EC_vote_completed', 'Editorial College voting rounded up'),
('decided', 'Publication decision taken'),
submission_status_dict = dict(SUBMISSION_STATUS)
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'),
)
class Submission(models.Model):
submitted_by = models.ForeignKey(Contributor)
assigned = models.BooleanField(default=False)
editor_in_charge = models.ForeignKey(Contributor, related_name='EIC', blank=True, null=True)
submitted_to_journal = models.CharField(max_length=30, choices=SCIPOST_JOURNALS_SUBMIT, verbose_name="Journal to be submitted to")
discipline = models.CharField(max_length=20, choices=SCIPOST_DISCIPLINES, default='physics')
domain = models.CharField(max_length=3, choices=SCIPOST_JOURNALS_DOMAINS)
specialization = models.CharField(max_length=1, choices=SCIPOST_JOURNALS_SPECIALIZATIONS)
status = models.CharField(max_length=30, choices=SUBMISSION_STATUS) # set by Editors
open_for_reporting = models.BooleanField(default=True)
reporting_deadline = models.DateTimeField(default=timezone.now)
open_for_commenting = models.BooleanField(default=True)
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_link = models.URLField(verbose_name='arXiv link (including version nr)')
submission_date = models.DateField(verbose_name='submission date')
latest_activity = models.DateTimeField(default=timezone.now)
def __str__ (self):
return self.title[:30] + ' by ' + self.author_list[:30]
@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():
for auth in self.authors.all():
header += '<td><a href="/contributor/' + str(auth.id) + '">' + auth.user.first_name + ' ' + auth.user.last_name + '</a></td>'
else:
header += '<td>(none claimed)</td>'
header += '</tr>'

Jean-Sébastien Caux
committed
header += '<tr><td>arxiv Link: </td><td> </td><td><a href="{{ arxiv_link }}" target="_blank">{{ arxiv_link }}</a></td></tr>'
header += '<tr><td>Date submitted: </td><td> </td><td>{{ submission_date }}</td></tr>'
header += '<tr><td>Submitted by: </td><td> </td><td>{{ submitted_by }}</td></tr>'
header += '<tr><td>Submitted to: </td><td> </td><td>{{ to_journal }}</td></tr>'
header += '<tr><td>Domain(s): </td><td> </td><td>{{ domain }}</td></tr>'
header += '<tr><td>Specialization: </td><td> </td><td>{{ spec }}</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], 'spec': journals_spec_dict[self.specialization]})
return template.render(context)
# for search lists
header = '<li><div class="flex-container">'

Jean-Sébastien Caux
committed
header += '<div class="flex-whitebox0"><p><a href="/submission/{{ id }}" class="pubtitleli">{{ title }}</a></p>'
header += ('<p>by {{ author_list }}</p><p> (submitted {{ submission_date }} to {{ to_journal }})' +
' - latest activity: {{ latest_activity }}</p></div></div></li>')
context = Context({'id': self.id, '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')})
template = Template(header)
return template.render(context)
def status_info_as_table (self):
header += '<tr><td>Current status: </td><td> </td><td>' + submission_status_dict[self.status] + '</td></tr>'
return mark_safe(header)
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
######################
# 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)
to = models.ForeignKey(Contributor)
accepted = models.NullBooleanField(choices=ASSIGNMENT_NULLBOOL, default=None)
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] +
', assigned on ' + self.date_created.strftime('%Y-%m-%d'))
def header_as_li(self):
header = '<li><div class="flex-container">'

Jean-Sébastien Caux
committed
header += '<div class="flex-whitebox0"><p><a href="/submission/{{ id }}" class="pubtitleli">{{ title }}</a></p>'
header += '<p>by {{ author_list }}</p><p> (submitted {{ date }} to {{ to_journal }})</p>'
header += '<p>Status: {{ status }}</p><p>Manage this Submission from its '
header += '<a href="/submissions/editorial_page/{{ id }}">Editorial Page</a>.</p></div></div></li>'
template = Template(header)
context = Context({'id': self.submission.id, '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]})
return template.render(context)
class RefereeInvitation(models.Model):
submission = models.ForeignKey(Submission)
referee = models.ForeignKey(Contributor, related_name='referee', blank=True, null=True)
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()
invitation_key = models.CharField(max_length=40, default='') # if Contributor not found, person is invited to register
date_invited = models.DateTimeField(default=timezone.now)
invited_by = models.ForeignKey(Contributor, related_name='referee_invited_by', 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
def __str__(self):
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):

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 = '<li>{{ first_name }} {{ last_name }}, invited {{ date_invited }}, '
if self.accepted is not None:
if self.accepted:
output += 'task accepted '
output += 'task declined '

Jean-Sébastien Caux
committed
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'
output += '; (no Report yet)'

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)
REPORT_REC = (
(1, 'Publish as Tier I (top 10% of papers in this journal)'),
(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:
# 1: vetted
# 0: unvetted
# -1: rejected (unclear)
# -2: rejected (incorrect)
# -3: rejected (not useful)
status = models.SmallIntegerField(default=0)
submission = models.ForeignKey(Submission)
# date_invited = models.DateTimeField('date invited', blank=True, null=True)
# invited_by = models.ForeignKey(Contributor, blank=True, null=True, related_name='invited_by')
invited = models.BooleanField(default=False) # filled from RefereeInvitation objects at moment of report submission
date_submitted = models.DateTimeField('date submitted')
author = models.ForeignKey(Contributor)
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 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>')
output += '<div class="reportRatings"><ul>'
output += '<li>validity: ' + ranking_choices_dict[self.validity] + '</li>'
output += '<li>significance: ' + ranking_choices_dict[self.significance] + '</li>'
output += '<li>originality: ' + ranking_choices_dict[self.originality] + '</li>'
output += '<li>clarity: ' + ranking_choices_dict[self.clarity] + '</li>'
output += '<li>formatting: ' + quality_spec_dict[self.formatting] + '</li>'
output += '<li>grammar: ' + quality_spec_dict[self.grammar] + '</li>'
output += '</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")})
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) '

Jean-Sébastien Caux
committed
output += '<a href="/contributor/{{ author_id }}">{{ author_first_name }} {{ author_last_name }}</a>'
output += ' on {{ date_submitted }}</h3></div>'
output += ('<div class="row"><div class="col-2">Qualification:</p></div><div class="col-10"><p>' +
ref_qualif_dict[self.qualification] + '</p></div></div>')
output += self.print_contents()
output += '<h3>Recommendation: ' + report_rec_dict[self.recommendation] + '</h3>'

Jean-Sébastien Caux
committed
template = Template(output)
return template.render(context)
##########################
# EditorialCommunication #
##########################
ED_CORR_CHOICES = (
('EtoA', 'Editor-in-charge to Author'),
('AtoE', 'Author to Editor-in-charge'),
('EtoR', 'Editor-in-charge to Referee'),
('RtoE', 'Referee to Editor-in-Charge'),
('EtoS', 'Editor-in-charge to SciPost Editorial Administration'),
('StoE', 'SciPost Editorial Administration to Editor-in-charge'),
)
ed_corr_choices_dict = dict(ED_CORR_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)
referee = models.ForeignKey(Contributor, related_name='referee_in_correspondence', blank=True, null=True)
type = models.CharField(max_length=4, choices=ED_CORR_CHOICES)
timestamp = models.DateTimeField(default=timezone.now)
text = models.TextField()
def __str__ (self):
output = self.type
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):

Jean-Sébastien Caux
committed
context = Context({'timestamp': self.timestamp.strftime("%Y-%m-%d %H:%M"), 'text': self.text})
output = '<li><p>'
if self.type == 'EtoA':
output += 'From you to Authors'
elif self.type == 'EtoR':
output += 'From you to Referee '
try:
output += self.referee.user.first_name + ' ' + self.referee.user.last_name
except AttributeError:
pass
elif self.type == 'EtoS':
output += 'From you to SciPost Ed Admin'
elif self.type == 'AtoE':
output += 'From Authors to you'
elif self.type == 'RtoE':
output += 'From Referee '
try:
output += self.referee.user.first_name + ' ' + self.referee.user.last_name + ' to you'
except AttributeError:
pass
elif self.type == 'StoE':
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)
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
############################
# Editorial Recommendation #
############################
# From the Editor-in-charge of a Submission
class EICRecommendation(models.Model):
submission = models.ForeignKey(Submission)
date_submitted = models.DateTimeField('date submitted')
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:
voted_for = models.ManyToManyField (Contributor, blank=True, related_name='voted_for')
voted_against = models.ManyToManyField (Contributor, blank=True, related_name='voted_against')
voting_deadline = models.DateTimeField('date submitted', default=timezone.now)
@property
def nr_for(self):
return self.voted_for.count()
@property
def nr_against(self):
return self.voted_against.count()