SciPost Code Repository

Skip to content
Snippets Groups Projects
Commit 2ed1699d authored by George Katsikas's avatar George Katsikas :goat:
Browse files

add proofs repo object and tests

parent e2a76bc2
No related branches found
No related tags found
1 merge request!43Polish up new production page
...@@ -12,6 +12,7 @@ from .models import ( ...@@ -12,6 +12,7 @@ from .models import (
ProductionUser, ProductionUser,
Proofs, Proofs,
ProductionEventAttachment, ProductionEventAttachment,
ProofsRepository,
) )
...@@ -95,3 +96,17 @@ admin.site.register(Proofs, ProductionProofsAdmin) ...@@ -95,3 +96,17 @@ admin.site.register(Proofs, ProductionProofsAdmin)
admin.site.register(ProductionEventAttachment) admin.site.register(ProductionEventAttachment)
class ProofsRepositoryAdmin(GuardedModelAdmin):
search_fields = [
"stream__submission__author_list",
"stream__submission__title",
"stream__submission__preprint__identifier_w_vn_nr",
]
list_filter = ["status"]
list_display = ["stream", "status", "git_path"]
readonly_fields = ["template_path", "git_path"]
admin.site.register(ProofsRepository, ProofsRepositoryAdmin)
...@@ -74,3 +74,16 @@ PRODUCTION_ALL_WORK_LOG_TYPES = ( ...@@ -74,3 +74,16 @@ PRODUCTION_ALL_WORK_LOG_TYPES = (
"Cited people have been notified/invited to SciPost", "Cited people have been notified/invited to SciPost",
), ),
) )
PROOFS_REPO_UNINITIALIZED = "uninitialized"
PROOFS_REPO_CREATED = "created"
PROOFS_REPO_TEMPLATE_ONLY = "template_only"
PROOFS_REPO_TEMPLATE_FORMATTED = "template_formatted"
PROOFS_REPO_PRODUCTION_READY = "production_ready"
PROOFS_REPO_STATUSES = (
(PROOFS_REPO_UNINITIALIZED, "The repository does not exist"),
(PROOFS_REPO_CREATED, "The repository exists but is empty"),
(PROOFS_REPO_TEMPLATE_ONLY, "The repository contains the bare template"),
(PROOFS_REPO_TEMPLATE_FORMATTED, "The repository contains the automatically formatted template"),
(PROOFS_REPO_PRODUCTION_READY, "The repository is ready for production"),
)
# Generated by Django 3.2.18 on 2023-05-15 14:25
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('production', '0005_auto_20190511_1141'),
]
operations = [
migrations.CreateModel(
name='ProofsRepository',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('uninitialized', 'The repository does not exist'), ('created', 'The repository exists but is empty'), ('template_only', 'The repository contains the bare template'), ('template_formatted', 'The repository contains the automatically formatted template'), ('production_ready', 'The repository is ready for production')], default='uninitialized', max_length=32)),
('stream', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='proofs_repository', to='production.productionstream')),
],
options={
'verbose_name_plural': 'proofs repositories',
},
),
]
...@@ -6,8 +6,14 @@ from django.db import models ...@@ -6,8 +6,14 @@ from django.db import models
from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.fields import GenericRelation
from django.urls import reverse from django.urls import reverse
from django.contrib.auth.models import User from django.contrib.auth.models import User
from profiles.models import Profile
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.db.models import Value
from django.db.models.functions import Concat
from .constants import ( from .constants import (
PRODUCTION_STREAM_STATUS, PRODUCTION_STREAM_STATUS,
...@@ -18,6 +24,8 @@ from .constants import ( ...@@ -18,6 +24,8 @@ from .constants import (
PRODUCTION_STREAM_COMPLETED, PRODUCTION_STREAM_COMPLETED,
PROOFS_STATUSES, PROOFS_STATUSES,
PROOFS_UPLOADED, PROOFS_UPLOADED,
PROOFS_REPO_STATUSES,
PROOFS_REPO_UNINITIALIZED,
) )
from .managers import ( from .managers import (
ProductionStreamQuerySet, ProductionStreamQuerySet,
...@@ -126,17 +134,11 @@ class ProductionStream(models.Model): ...@@ -126,17 +134,11 @@ class ProductionStream(models.Model):
class ProductionEvent(models.Model): class ProductionEvent(models.Model):
stream = models.ForeignKey( stream = models.ForeignKey(ProductionStream, on_delete=models.CASCADE, related_name="events")
ProductionStream, on_delete=models.CASCADE, related_name="events" event = models.CharField(max_length=64, choices=PRODUCTION_EVENTS, default=EVENT_MESSAGE)
)
event = models.CharField(
max_length=64, choices=PRODUCTION_EVENTS, default=EVENT_MESSAGE
)
comments = models.TextField(blank=True, null=True) comments = models.TextField(blank=True, null=True)
noted_on = models.DateTimeField(default=timezone.now) noted_on = models.DateTimeField(default=timezone.now)
noted_by = models.ForeignKey( noted_by = models.ForeignKey("production.ProductionUser", on_delete=models.CASCADE, related_name="events")
"production.ProductionUser", on_delete=models.CASCADE, related_name="events"
)
noted_to = models.ForeignKey( noted_to = models.ForeignKey(
"production.ProductionUser", "production.ProductionUser",
on_delete=models.CASCADE, on_delete=models.CASCADE,
...@@ -159,10 +161,7 @@ class ProductionEvent(models.Model): ...@@ -159,10 +161,7 @@ class ProductionEvent(models.Model):
@cached_property @cached_property
def editable(self): def editable(self):
return ( return self.event in [EVENT_MESSAGE, EVENT_HOUR_REGISTRATION] and not self.stream.completed
self.event in [EVENT_MESSAGE, EVENT_HOUR_REGISTRATION]
and not self.stream.completed
)
def production_event_upload_location(instance, filename): def production_event_upload_location(instance, filename):
...@@ -185,9 +184,7 @@ class ProductionEventAttachment(models.Model): ...@@ -185,9 +184,7 @@ class ProductionEventAttachment(models.Model):
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name="attachments", related_name="attachments",
) )
attachment = models.FileField( attachment = models.FileField(upload_to=production_event_upload_location, storage=SecureFileStorage())
upload_to=production_event_upload_location, storage=SecureFileStorage()
)
def get_absolute_url(self): def get_absolute_url(self):
return reverse( return reverse(
...@@ -213,20 +210,12 @@ class Proofs(models.Model): ...@@ -213,20 +210,12 @@ class Proofs(models.Model):
Proofs are directly related to a ProductionStream and Submission in SciPost. Proofs are directly related to a ProductionStream and Submission in SciPost.
""" """
attachment = models.FileField( attachment = models.FileField(upload_to=proofs_upload_location, storage=SecureFileStorage())
upload_to=proofs_upload_location, storage=SecureFileStorage()
)
version = models.PositiveSmallIntegerField(default=0) version = models.PositiveSmallIntegerField(default=0)
stream = models.ForeignKey( stream = models.ForeignKey("production.ProductionStream", on_delete=models.CASCADE, related_name="proofs")
"production.ProductionStream", on_delete=models.CASCADE, related_name="proofs" uploaded_by = models.ForeignKey("production.ProductionUser", on_delete=models.CASCADE, related_name="+")
)
uploaded_by = models.ForeignKey(
"production.ProductionUser", on_delete=models.CASCADE, related_name="+"
)
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
status = models.CharField( status = models.CharField(max_length=16, choices=PROOFS_STATUSES, default=PROOFS_UPLOADED)
max_length=16, choices=PROOFS_STATUSES, default=PROOFS_UPLOADED
)
accessible_for_authors = models.BooleanField(default=False) accessible_for_authors = models.BooleanField(default=False)
objects = ProofsQuerySet.as_manager() objects = ProofsQuerySet.as_manager()
...@@ -239,9 +228,7 @@ class Proofs(models.Model): ...@@ -239,9 +228,7 @@ class Proofs(models.Model):
return reverse("production:proofs_pdf", kwargs={"slug": self.slug}) return reverse("production:proofs_pdf", kwargs={"slug": self.slug})
def __str__(self): def __str__(self):
return "Proofs {version} for Stream {stream}".format( return "Proofs {version} for Stream {stream}".format(version=self.version, stream=self.stream.submission.title)
version=self.version, stream=self.stream.submission.title
)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# Control Report count per Submission. # Control Report count per Submission.
...@@ -252,3 +239,96 @@ class Proofs(models.Model): ...@@ -252,3 +239,96 @@ class Proofs(models.Model):
@property @property
def slug(self): def slug(self):
return proofs_id_to_slug(self.id) return proofs_id_to_slug(self.id)
class ProofsRepository(models.Model):
"""
ProofsRepository is a GitLab repository of Proofs for a Submission.
"""
stream = models.OneToOneField(
ProductionStream,
on_delete=models.CASCADE,
related_name="proofs_repository",
)
status = models.CharField(
max_length=32,
choices=PROOFS_REPO_STATUSES,
default=PROOFS_REPO_UNINITIALIZED,
)
@property
def name(self) -> str:
"""
Return the name of the repository in the form of "id_lastname".
"""
# Get the last name of the first author by getting the first author string from the submission
first_author_str = self.stream.submission.authors_as_list[0]
first_author_profile = (
Profile.objects.annotate(
full_name=Concat("first_name", Value(" "), "last_name")
)
.filter(full_name=first_author_str)
.first()
)
if first_author_profile is None:
first_author_last_name = first_author_str.split(" ")[-1]
else:
first_author_last_name = first_author_profile.last_name
# Keep only the last of the last names
first_author_last_name = first_author_last_name.split(" ")[-1]
return "{preprint_id}_{last_name}".format(
preprint_id=self.stream.submission.preprint.identifier_w_vn_nr,
last_name=first_author_last_name,
)
@property
def journal_path_abbrev(self) -> str:
# The DOI label is used to determine the path of the repository and template
journal_abbrev = (
self.stream.submission.editorial_decision.for_journal.doi_label
)
return journal_abbrev
@property
def git_path(self) -> str:
# Get creation date of the stream
# Warning: The month grouping of streams was done using the tasked date,
# but should now instead be the creation (opened) date.
creation_year, creation_month = self.stream.opened.strftime(
"%Y-%m"
).split("-")
return "/Proofs/{journal}/{year}/{month}/{repo_name}".format(
journal=self.journal_path_abbrev,
year=creation_year,
month=creation_month,
repo_name=self.name,
)
@property
def template_path(self) -> str:
return "Templates/{journal}".format(journal=self.journal_path_abbrev)
def __str__(self) -> str:
return f"Proofs repo for {self.stream}"
class Meta:
verbose_name_plural = "proofs repositories"
@receiver(post_save, sender=ProductionStream)
def production_stream_create_proofs_repo(sender, instance, created, **kwargs):
"""
If a ProductionStream instance is created, a Proofs Repository instance is created
and linked to it.
"""
if created:
ProofsRepository.objects.create(
stream=instance,
status=PROOFS_REPO_UNINITIALIZED,
)
post_save.connect(production_stream_create_proofs_repo, sender=ProductionStream)
__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)" __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3" __license__ = "AGPL v3"
import datetime
from django.test import TestCase from django.test import TestCase
# Create your tests here. # Create your tests here.
from submissions.constants import EIC_REC_PUBLISH
from journals.models import Journal
from submissions.models import Submission, EditorialDecision
from production.models import ProductionStream, ProofsRepository
from preprints.models import Preprint
from ontology.models import AcademicField, Branch, Specialty
from colleges.models import College
from scipost.models import Contributor
from profiles.models import Profile
from django.contrib.auth.models import User
class TestProofRepository(TestCase):
def _create_submitter_contributor(self):
random_user = User.objects.create_user(
username="testuser",
password="testpassword",
)
user_profile = Profile.objects.create(
title="DR",
first_name="Test",
last_name="User",
)
Contributor.objects.create(user=random_user, profile=user_profile)
def _create_college(self):
College.objects.create(
name="College of Quantum Physics",
acad_field=AcademicField.objects.get(name="Quantum Physics"),
slug="college-of-quantum-physics",
order=10,
)
def _create_journal(self):
Journal.objects.create(
college=College.objects.get(name="College of Quantum Physics"),
name="SciPost Physics",
name_abbrev="SciPost Phys.",
doi_label="SciPostPhys",
cf_metrics='{"":""}',
)
def _create_editorial_decision(self):
EditorialDecision.objects.create(
submission=Submission.objects.get(
preprint__identifier_w_vn_nr="scipost_202101_00001v1"
),
for_journal=Journal.objects.get(name="SciPost Physics"),
decision=EIC_REC_PUBLISH,
status=EditorialDecision.FIXED_AND_ACCEPTED,
)
def _create_specialty(self):
Specialty.objects.create(
acad_field=AcademicField.objects.get(name="Quantum Physics"),
name="Quantum Information",
slug="quantum-information",
order=10,
)
def _create_academic_field(self):
AcademicField.objects.create(
branch=Branch.objects.get(name="Physics"),
name="Quantum Physics",
slug="quantum-physics",
order=10,
)
def _create_branch(self):
Branch.objects.create(
name="Physics",
slug="physics",
order=10,
)
def _create_preprint(self):
Preprint.objects.create(identifier_w_vn_nr="scipost_202101_00001v1")
def _create_submission(self):
submission = Submission.objects.create(
preprint=Preprint.objects.get(
identifier_w_vn_nr="scipost_202101_00001v1"
),
submitted_to=Journal.objects.get(name="SciPost Physics"),
title="Test submission",
abstract="Test abstract",
author_list="Test User",
acad_field=AcademicField.objects.get(name="Quantum Physics"),
# specialties=Specialty.objects.filter(name="Quantum Information"),
submitted_by=Contributor.objects.get(user__username="testuser"),
)
submission.authors.add(
Contributor.objects.get(user__username="testuser")
)
submission.save()
def _create_production_stream(self):
stream = ProductionStream.objects.create(
submission=Submission.objects.get(
preprint__identifier_w_vn_nr="scipost_202101_00001v1"
),
)
stream.opened = datetime.datetime(
2021, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc
)
stream.save()
def setUp(self):
self._create_submitter_contributor()
self._create_branch()
self._create_academic_field()
self._create_specialty()
self._create_college()
self._create_journal()
self._create_preprint()
self._create_submission()
self._create_editorial_decision()
self._create_production_stream()
def test_repo_scipostphys_existing_profile(self):
proofs_repo = ProofsRepository.objects.get(
stream__submission__preprint__identifier_w_vn_nr="scipost_202101_00001v1"
)
self.assertEqual(
proofs_repo.git_path,
"Proofs/SciPostPhys/2021/01/scipost_202101_00001v1_User",
)
self.assertEqual(proofs_repo.template_path, "Templates/SciPostPhys")
def test_repo_scipostphys_nonexisting_profile(self):
proofs_repo = ProofsRepository.objects.get(
stream__submission__preprint__identifier_w_vn_nr="scipost_202101_00001v1"
)
# delete profile
Contributor.objects.get(user__username="testuser").profile.delete()
self.assertEqual(
proofs_repo.git_path,
"Proofs/SciPostPhys/2021/01/scipost_202101_00001v1_User",
)
self.assertEqual(proofs_repo.template_path, "Templates/SciPostPhys")
def test_repo_scipostphys_double_last_name_profile(self):
proofs_repo = ProofsRepository.objects.get(
stream__submission__preprint__identifier_w_vn_nr="scipost_202101_00001v1"
)
proofs_repo.stream.submission.author_list = "Test Usable User"
user_profile = Contributor.objects.get(
user__username="testuser"
).profile
user_profile.last_name = "Usable User"
user_profile.save()
self.assertEqual(
proofs_repo.git_path,
"Proofs/SciPostPhys/2021/01/scipost_202101_00001v1_User",
)
self.assertEqual(proofs_repo.template_path, "Templates/SciPostPhys")
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment