__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3"
import factory
import pytz
import random
from comments.factories import SubmissionCommentFactory
from scipost.constants import SCIPOST_APPROACHES
from scipost.models import Contributor
from journals.models import Journal
from common.helpers import random_scipost_report_doi_label
from ontology.models import Specialty, AcademicField
from .constants import (
STATUS_UNASSIGNED,
STATUS_EIC_ASSIGNED,
STATUS_INCOMING,
STATUS_PUBLISHED,
STATUS_RESUBMITTED,
STATUS_VETTED,
REFEREE_QUALIFICATION,
RANKING_CHOICES,
QUALITY_SPEC,
REPORT_REC,
REPORT_STATUSES,
STATUS_UNVETTED,
STATUS_DRAFT,
ASSIGNMENT_STATUSES,
STATUS_COMPLETED,
)
from .models import (
Submission,
Report,
RefereeInvitation,
EICRecommendation,
EditorialAssignment,
)
from faker import Faker
[docs]class SubmissionFactory(factory.django.DjangoModelFactory):
"""
Generate random basic Submission instances.
"""
author_list = factory.Faker("name")
submitted_by = factory.Iterator(Contributor.objects.all())
submitted_to = factory.Iterator(Journal.objects.all())
title = factory.Faker("sentence")
abstract = factory.Faker("paragraph", nb_sentences=10)
list_of_changes = factory.Faker("paragraph", nb_sentences=10)
acad_field = factory.Iterator(AcademicField.objects.all())
approaches = factory.Iterator(
SCIPOST_APPROACHES,
getter=lambda c: [
c[0],
],
)
abstract = factory.Faker("paragraph")
author_comments = factory.Faker("paragraph")
remarks_for_editors = factory.Faker("paragraph")
thread_hash = factory.Faker("uuid4")
is_current = True
submission_date = factory.Faker("date_time_this_decade", tzinfo=pytz.utc)
latest_activity = factory.LazyAttribute(
lambda o: Faker().date_time_between(
start_date=o.submission_date, end_date="now", tzinfo=pytz.UTC
)
)
preprint = factory.SubFactory("preprints.factories.PreprintFactory")
visible_public = True
visible_pool = False
class Meta:
model = Submission
[docs] @classmethod
def create(cls, **kwargs):
if Contributor.objects.count() < 5:
from scipost.factories import ContributorFactory
ContributorFactory.create_batch(5)
if Journal.objects.count() < 3:
from journals.factories import JournalFactory
JournalFactory.create_batch(3)
if AcademicField.objects.count() < 10:
from ontology.factories import AcademicFieldFactory
AcademicFieldFactory.create_batch(10)
if Specialty.objects.count() < 5:
from ontology.factories import SpecialtyFactory
SpecialtyFactory.create_batch(5)
return super().create(**kwargs)
@factory.post_generation
def add_specialties(self, create, extracted, **kwargs):
if create:
self.specialties.set(Specialty.objects.order_by("?")[:3])
@factory.post_generation
def contributors(self, create, extracted, **kwargs):
contribs = Contributor.objects.all()
if self.editor_in_charge:
contribs = contribs.exclude(id=self.editor_in_charge.id)
contribs = contribs.order_by("?")[: random.randint(1, 6)]
# Auto-add the submitter as an author
self.submitted_by = contribs[0]
self.author_list = ", ".join(
["%s %s" % (c.user.first_name, c.user.last_name) for c in contribs]
)
if not create:
return
# Add three random authors
self.authors.add(*contribs)
[docs]class UnassignedSubmissionFactory(SubmissionFactory):
"""
A new incoming Submission without any EIC assigned.
"""
status = STATUS_UNASSIGNED
visible_public = False
visible_pool = True
[docs]class EICassignedSubmissionFactory(SubmissionFactory):
"""
A Submission with an EIC assigned, visible in the pool and refereeing in process.
"""
status = STATUS_EIC_ASSIGNED
open_for_commenting = True
open_for_reporting = True
visible_public = True
visible_pool = True
editor_in_charge = factory.Iterator(Contributor.objects.all())
# @factory.lazy_attribute
# def editor_in_charge(self):
# return Contributor.objects.order_by('?').first()
@factory.post_generation
def eic_assignment(self, create, extracted, **kwargs):
if create:
EditorialAssignmentFactory(submission=self, to=self.editor_in_charge)
@factory.post_generation
def referee_invites(self, create, extracted, **kwargs):
for i in range(random.randint(1, 3)):
RefereeInvitationFactory(submission=self)
for i in range(random.randint(0, 2)):
AcceptedRefereeInvitationFactory(submission=self)
for i in range(random.randint(0, 2)):
FulfilledRefereeInvitationFactory(submission=self)
@factory.post_generation
def comments(self, create, extracted, **kwargs):
if create:
for i in range(random.randint(0, 3)):
SubmissionCommentFactory(content_object=self)
@factory.post_generation
def eic_recommendation(self, create, extracted, **kwargs):
if create:
EICRecommendationFactory(submission=self)
[docs]class ResubmittedSubmissionFactory(EICassignedSubmissionFactory):
"""
A Submission that has a newer Submission version in the database
with a successive version number.
"""
status = STATUS_RESUBMITTED
open_for_commenting = False
open_for_reporting = False
is_current = False
visible_public = True
visible_pool = False
@factory.post_generation
def successive_submission(self, create, extracted, **kwargs):
"""
Generate a second Submission that's the successive version of the resubmitted Submission
"""
if create and extracted is not False:
# Prevent infinite loops by checking the extracted argument
ResubmissionFactory(
thread_hash=self.thread_hash,
previous_submission=False,
visible_pool=True,
)
@factory.post_generation
def gather_successor_data(self, create, extracted, **kwargs):
"""
Gather some data from Submission with same arxiv id such that this Submission
more or less looks like any regular real resubmission.
"""
submission = (
Submission.objects.filter(thread_hash=self.thread_hash)
.exclude(pk=self.id)
.first()
)
if not submission:
return
self.author_list = submission.author_list
self.submitted_by = submission.submitted_by
self.editor_in_charge = submission.editor_in_charge
self.submitted_to = submission.submitted_to
self.title = submission.title
self.acad_field = submission.acad_field
self.specialties.add(*submission.specialties.all())
self.approaches = submission.approaches
self.title = submission.title
self.authors.set(self.authors.all())
@factory.post_generation
def referee_invites(self, create, extracted, **kwargs):
"""
This Submission is deactivated for refereeing.
"""
for i in range(random.randint(0, 2)):
FulfilledRefereeInvitationFactory(submission=self)
for i in range(random.randint(1, 3)):
CancelledRefereeInvitationFactory(submission=self)
[docs]class ResubmissionFactory(EICassignedSubmissionFactory):
"""
This Submission is a newer version of a Submission which is
already known by the SciPost database.
"""
status = STATUS_INCOMING
open_for_commenting = True
open_for_reporting = True
visible_public = False
visible_pool = True
@factory.post_generation
def previous_submission(self, create, extracted, **kwargs):
if create and extracted is not False:
# Prevent infinite loops by checking the extracted argument
ResubmittedSubmissionFactory(
thread_hash=self.thread_hash,
successive_submission=False,
visible_pool=False,
visible_public=True,
)
@factory.post_generation
def gather_predecessor_data(self, create, extracted, **kwargs):
"""
Gather some data from Submission with same arxiv id such that this Submission
more or less looks like any regular real resubmission.
"""
submission = (
Submission.objects.filter(thread_hash=self.thread_hash)
.exclude(pk=self.id)
.first()
)
if not submission:
return
self.author_list = submission.author_list
self.submitted_by = submission.submitted_by
self.editor_in_charge = submission.editor_in_charge
self.submitted_to = submission.submitted_to
self.title = submission.title
self.acad_field = submission.acad_field
self.specialties.add(*submission.specialties.all())
self.approaches = submission.approaches
self.title = submission.title
self.authors.set(self.authors.all())
@factory.post_generation
def referee_invites(self, create, extracted, **kwargs):
"""
Referees for resubmissions are invited once the cycle has been chosen.
"""
pass
[docs]class PublishedSubmissionFactory(EICassignedSubmissionFactory):
status = STATUS_PUBLISHED
open_for_commenting = False
open_for_reporting = False
visible_public = True
visible_pool = False
@factory.post_generation
def generate_publication(self, create, extracted, **kwargs):
if create and extracted is not False:
from journals.factories import PublicationFactory
PublicationFactory(
journal=self.submitted_to.doi_label,
accepted_submission=self,
title=self.title,
author_list=self.author_list,
)
@factory.post_generation
def eic_assignment(self, create, extracted, **kwargs):
if create:
EditorialAssignmentFactory(
submission=self, to=self.editor_in_charge, status=STATUS_COMPLETED
)
@factory.post_generation
def referee_invites(self, create, extracted, **kwargs):
for i in range(random.randint(2, 4)):
FulfilledRefereeInvitationFactory(submission=self)
for i in range(random.randint(0, 2)):
CancelledRefereeInvitationFactory(submission=self)
[docs]class ReportFactory(factory.django.DjangoModelFactory):
status = factory.Iterator(REPORT_STATUSES, getter=lambda c: c[0])
submission = factory.SubFactory("submissions.factories.SubmissionFactory")
report_nr = factory.LazyAttribute(lambda o: o.submission.reports.count() + 1)
date_submitted = factory.Faker("date_time_this_decade", tzinfo=pytz.utc)
vetted_by = factory.Iterator(Contributor.objects.all())
author = factory.Iterator(Contributor.objects.all())
strengths = factory.Faker("paragraph")
weaknesses = factory.Faker("paragraph")
report = factory.Faker("paragraph")
requested_changes = factory.Faker("paragraph")
qualification = factory.Iterator(REFEREE_QUALIFICATION[1:], getter=lambda c: c[0])
validity = factory.Iterator(RANKING_CHOICES[1:], getter=lambda c: c[0])
significance = factory.Iterator(RANKING_CHOICES[1:], getter=lambda c: c[0])
originality = factory.Iterator(RANKING_CHOICES[1:], getter=lambda c: c[0])
clarity = factory.Iterator(RANKING_CHOICES[1:], getter=lambda c: c[0])
formatting = factory.Iterator(QUALITY_SPEC[1:], getter=lambda c: c[0])
grammar = factory.Iterator(QUALITY_SPEC[1:], getter=lambda c: c[0])
recommendation = factory.Iterator(REPORT_REC[1:], getter=lambda c: c[0])
remarks_for_editors = factory.Faker("paragraph")
flagged = factory.Faker("boolean", chance_of_getting_true=10)
anonymous = factory.Faker("boolean", chance_of_getting_true=75)
class Meta:
model = Report
[docs] @classmethod
def create(cls, **kwargs):
if Contributor.objects.count() < 5:
from scipost.factories import ContributorFactory
ContributorFactory.create_batch(5)
return super().create(**kwargs)
[docs]class DraftReportFactory(ReportFactory):
status = STATUS_DRAFT
vetted_by = None
[docs]class UnVettedReportFactory(ReportFactory):
status = STATUS_UNVETTED
vetted_by = None
[docs]class VettedReportFactory(ReportFactory):
status = STATUS_VETTED
needs_doi = True
doideposit_needs_updating = factory.Faker("boolean")
doi_label = factory.lazy_attribute(lambda n: random_scipost_report_doi_label())
pdf_report = factory.Faker("file_name", extension="pdf")
[docs]class RefereeInvitationFactory(factory.django.DjangoModelFactory):
submission = factory.SubFactory("submissions.factories.SubmissionFactory")
referee = factory.lazy_attribute(
lambda o: Contributor.objects.exclude(id__in=o.submission.authors.all())
.order_by("?")
.first()
)
title = factory.lazy_attribute(lambda o: o.referee.profile.title)
first_name = factory.lazy_attribute(lambda o: o.referee.user.first_name)
last_name = factory.lazy_attribute(lambda o: o.referee.user.last_name)
email_address = factory.lazy_attribute(lambda o: o.referee.user.email)
date_invited = factory.lazy_attribute(lambda o: o.submission.latest_activity)
nr_reminders = factory.lazy_attribute(lambda o: random.randint(0, 4))
date_last_reminded = factory.lazy_attribute(lambda o: o.submission.latest_activity)
invitation_key = factory.Faker("md5")
invited_by = factory.lazy_attribute(lambda o: o.submission.editor_in_charge)
class Meta:
model = RefereeInvitation
[docs]class AcceptedRefereeInvitationFactory(RefereeInvitationFactory):
accepted = True
date_responded = factory.lazy_attribute(
lambda o: Faker().date_time_between(
start_date=o.date_invited, end_date="now", tzinfo=pytz.UTC
)
)
@factory.post_generation
def report(self, create, extracted, **kwargs):
if create:
VettedReportFactory(submission=self.submission, author=self.referee)
[docs]class FulfilledRefereeInvitationFactory(AcceptedRefereeInvitationFactory):
fulfilled = True
date_responded = factory.lazy_attribute(
lambda o: Faker().date_time_between(
start_date=o.date_invited, end_date="now", tzinfo=pytz.UTC
)
)
@factory.post_generation
def report(self, create, extracted, **kwargs):
if create:
VettedReportFactory(submission=self.submission, author=self.referee)
[docs]class CancelledRefereeInvitationFactory(AcceptedRefereeInvitationFactory):
fulfilled = False
cancelled = True
date_responded = factory.lazy_attribute(
lambda o: Faker().date_time_between(
start_date=o.date_invited, end_date="now", tzinfo=pytz.UTC
)
)
[docs]class EICRecommendationFactory(factory.django.DjangoModelFactory):
submission = factory.Iterator(Submission.objects.all())
date_submitted = factory.lazy_attribute(
lambda o: Faker().date_time_between(
start_date=o.submission.submission_date, end_date="now", tzinfo=pytz.UTC
)
)
remarks_for_authors = factory.Faker("paragraph")
requested_changes = factory.Faker("paragraph")
remarks_for_editorial_college = factory.Faker("paragraph")
recommendation = factory.Iterator(REPORT_REC[1:], getter=lambda c: c[0])
version = 1
active = True
class Meta:
model = EICRecommendation
[docs]class EditorialAssignmentFactory(factory.django.DjangoModelFactory):
"""
An EditorialAssignmentFactory should always have a `submission` explicitly assigned. This will
mostly be done using the post_generation hook in any SubmissionFactory.
"""
submission = None
to = factory.Iterator(Contributor.objects.all())
status = factory.Iterator(ASSIGNMENT_STATUSES, getter=lambda c: c[0])
date_created = factory.lazy_attribute(lambda o: o.submission.latest_activity)
date_answered = factory.lazy_attribute(lambda o: o.submission.latest_activity)
class Meta:
model = EditorialAssignment