From 664729cb3876cbe00af298c29149cfc19811fb3d Mon Sep 17 00:00:00 2001 From: Kevin McCurley Date: Mon, 9 Mar 2026 14:51:09 -0700 Subject: [PATCH] Add revised date. --- webapp/admin.py | 3 +++ webapp/export.py | 1 + webapp/forms.py | 20 ++++++++++++++++- webapp/metadata/compilation.py | 3 +++ webapp/metadata/db_models.py | 1 + webapp/metadata/tests/metadata_test.py | 12 +++++++++- webapp/routes.py | 31 ++++++++++++++++++++++---- webapp/templates/admin/metadata.html | 4 ++++ webapp/templates/compilation.html | 1 + webapp/templates/message.html | 5 +++++ webapp/templates/submit.html | 6 +++++ webapp/templates/view.html | 4 ++++ 12 files changed, 85 insertions(+), 6 deletions(-) diff --git a/webapp/admin.py b/webapp/admin.py index 7584c1b..b393a31 100644 --- a/webapp/admin.py +++ b/webapp/admin.py @@ -613,6 +613,7 @@ def request_more_changes(): 'email': last_compilation.email, 'submitted': last_compilation.submitted, 'accepted': last_compilation.accepted, + 'revised': last_compilation.revised, 'pubtype': last_compilation.pubtype, 'errata_doi': last_compilation.errata_doi, 'compiled': now, @@ -821,6 +822,8 @@ def change_issue(): metadata += '\\def\\IACR@Published{' + publishedDate + '}\n' metadata += '\\def\\IACR@vol{' + str(volume.name) + '}\n' metadata += '\\def\\IACR@no{' + str(issue.name) + '}\n' + if compilation.revised: + metadata += '\\def\\IACR@Revised{' + compilation.revised[:10] + '}\n' metadata += '\\def\\IACR@CROSSMARKURL{https://crossmark.crossref.org/dialog/?doi=' + doi + '\&domain=pdf\&date\_stamp=' + publishedDate + '}\n' metadata_file = input_dir / Path('main.iacrmetadata') metadata_file.write_text(metadata) diff --git a/webapp/export.py b/webapp/export.py index df5b566..52512e9 100644 --- a/webapp/export.py +++ b/webapp/export.py @@ -104,6 +104,7 @@ def export_issue(data_path: Path, output_path: Path, issue: Issue) -> datetime: data = comp.meta.model_dump(exclude={'version': True}, exclude_none=True) data['submitted'] = comp.submitted data['accepted'] = comp.accepted + data['revised'] = comp.revised data['compiled'] = comp.compiled.strftime('%Y-%m-%d %H:%M:%S') data['errata_doi'] = comp.errata_doi data['paperid'] = comp.paperid diff --git a/webapp/forms.py b/webapp/forms.py index 4a81f3b..8ed20a4 100644 --- a/webapp/forms.py +++ b/webapp/forms.py @@ -3,7 +3,7 @@ from flask import current_app as app from flask_wtf import FlaskForm from flask_wtf.file import FileAllowed, FileRequired, FileField -from wtforms.validators import InputRequired, Email, EqualTo, Length, Regexp, NumberRange, AnyOf +from wtforms.validators import InputRequired, Optional, Email, EqualTo, Length, Regexp, NumberRange, AnyOf from wtforms import EmailField, PasswordField, SubmitField, BooleanField, HiddenField, SelectField, StringField, ValidationError, IntegerField, FieldList, Form, FormField from .metadata.db_models import Role, validate_version, Version from .metadata import validate_paperid @@ -11,6 +11,7 @@ import random, string # TODO - remove this from . import create_hmac, validate_hmac import logging +import re import time class LoginForm(FlaskForm): @@ -182,6 +183,15 @@ def __init__(self, *args, **kwargs): validators=[InputRequired('Accepted date is required'), Regexp(dt_regex, message='Format of accepted is YYYY-mm-dd HH:MM:SS')], default='') + revised = HiddenField(id='revised', + name='revised', + default='', + validators=[Optional('Revised date could be empty')]) + def validate_revised(form, field): + """Revised field is optional, but must match a regex if specified.""" + if field.data: + if not re.match(dt_regex, field.data): + raise ValidationError('Format of revised is YYYY-mm-dd HH:MM:SS') pubtype = HiddenField(id='pubtype', name='pubtype', validators=[InputRequired('pubtype field is required'), @@ -218,6 +228,7 @@ def check_auth(self): self.version.data, self.submitted.data, self.accepted.data, + self.revised.data, self.journal.data, self.volume.data, self.issue.data, @@ -232,6 +243,7 @@ def generate_auth(self): self.version.data, self.submitted.data, self.accepted.data, + self.revised.data, self.journal.data, self.volume.data, self.issue.data, @@ -241,6 +253,12 @@ def validate(self, extra_validators=None): if not super(FlaskForm, self).validate(): logging.warning('failed to validate: ' + str(self.errors)) return False + if self.revised.data: + if (self.revised.data < self.submitted.data or + self.revised.data > self.accepted.data): + logging.warning('revised must be between submitted and accepted') + self.revised.errors.append('revised must be between submitted and accepted') + return False return self.check_auth() class NotifyFinalForm(FlaskForm): diff --git a/webapp/metadata/compilation.py b/webapp/metadata/compilation.py index d1ff69d..64ed9c6 100644 --- a/webapp/metadata/compilation.py +++ b/webapp/metadata/compilation.py @@ -349,6 +349,9 @@ class Compilation(BaseModel): accepted: Annotated[str, StringConstraints(pattern=dt_regex)] = Field(..., title='When the paper was accepted for publication', description = 'Authenticated upon acceptance.') + revised: Annotated[str, StringConstraints(pattern='|' + dt_regex)] = Field('', + title='When the paper was last revised after submission', + description = 'Authenticated upon acceptance.') pubtype: PubType = Field(default=PubType.RESEARCH, title='Type of publication', description='Originates in review system') diff --git a/webapp/metadata/db_models.py b/webapp/metadata/db_models.py index 67b8ca2..e3620cf 100644 --- a/webapp/metadata/db_models.py +++ b/webapp/metadata/db_models.py @@ -144,6 +144,7 @@ class PaperStatus(Base): email: Mapped[str] = mapped_column(String(50), nullable=False) submitted: Mapped[str] = mapped_column(String(32), nullable=False) accepted: Mapped[str] = mapped_column(String(32), nullable=False) + revised: Mapped[str] = mapped_column(String(32), nullable=False, default='') pubtype: Mapped[PubType] = mapped_column(default=PubType.RESEARCH) status: Mapped[PaperStatusEnum] = mapped_column(default=PaperStatusEnum.PENDING) hotcrp: Mapped[str] = mapped_column(String(32), diff --git a/webapp/metadata/tests/metadata_test.py b/webapp/metadata/tests/metadata_test.py index 7d15d3c..df92cdb 100644 --- a/webapp/metadata/tests/metadata_test.py +++ b/webapp/metadata/tests/metadata_test.py @@ -341,4 +341,14 @@ def test_pubtype(): assert comp.pubtype == PubType.ERRATA assert comp.errata_doi == '10.1791/foobar' - +def test_revised(): + data = _compile_data + data['revised'] = '2022-10-09 13:10:04' + comp = Compilation(**data) + assert comp.revised == '2022-10-09 13:10:04' + +def test_empty_revised(): + data = _compile_data + data['revised'] = '' + comp = Compilation(**data) + assert comp.revised == '' diff --git a/webapp/routes.py b/webapp/routes.py index 99364bc..83e527d 100644 --- a/webapp/routes.py +++ b/webapp/routes.py @@ -1,7 +1,7 @@ import datetime import time from io import BytesIO -from flask import json, Blueprint, render_template, request, jsonify, send_file, redirect, url_for +from flask import json, Blueprint, render_template, request, jsonify, send_file, redirect, url_for, flash from flask import current_app as app from flask_mail import Message import hmac @@ -66,14 +66,20 @@ def show_submit_version(): # In this case the submission doesn't come from hotcrp, so we make up some fields. random.seed() form.paperid.data = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8)) + paperid = form.paperid.data form.hotcrp.data = NO_HOTCRP form.hotcrp_id.data = NO_HOTCRP form.version.data = 'candidate' now = datetime.datetime.now() - submitted = now - datetime.timedelta(days=10) + submitted = now - datetime.timedelta(days=32) accepted = now - datetime.timedelta(days=5) form.accepted.data = accepted.strftime('%Y-%m-%d %H:%M:%S') form.submitted.data = submitted.strftime('%Y-%m-%d %H:%M:%S') + if random.randint(0, 1) == 0: + revised = now - datetime.timedelta(days=7) + form.revised.data = submitted.strftime('%Y-%m-%d %H:%M:%S') + else: + form.revised.data = '' form.volume.data = '9999' form.issue.data = '1' form.generate_auth() @@ -91,7 +97,7 @@ def show_submit_version(): error='The token for this request is invalid') paperid = form.paperid.data # Submission form is limited to papers that are not in copy editing mode and not already - # accepted for publication. + # accepted for publication. sql = select(PaperStatus).filter_by(paperid=paperid) paper_status = db.session.execute(sql).scalar_one_or_none() if paper_status: @@ -137,6 +143,9 @@ def submit_version(): logging.critical('{}:{}:{} submission not authenticated'.format(form.paperid.data, form.version.data, form.auth.data)) + for fieldname, errors in form.errors.items(): + for error in errors: + flash('{}:{}'.format(fieldname,error)) return render_template('message.html', title='The form data is invalid.', error='The form data is invalid. This is a bug') @@ -145,6 +154,7 @@ def submit_version(): version = args.get('version', Version.CANDIDATE.value) accepted = args.get('accepted', '') submitted = args.get('submitted', '') + revised = args.get('revised', '') hotcrp = args.get('hotcrp', '') hotcrp_id = args.get('hotcrp_id', '') task_key = paper_key(paperid, version) @@ -200,6 +210,7 @@ def submit_version(): email=args.get('email'), submitted=submitted, accepted=accepted, + revised=revised, pubtype=pubtype, journal_key=args.get('journal'), volume_key=args.get('volume'), @@ -295,6 +306,7 @@ def submit_version(): 'venue': args.get('journal'), 'submitted': submitted, 'accepted': accepted, + 'revised': revised, 'pubtype': pubtype.name, 'errata_doi': form.errata_doi.data, 'compiled': now, @@ -326,10 +338,15 @@ def submit_version(): metadata += '\\def\\IACR@Received{' + receivedDate.strftime('%Y-%m-%d') + '}\n' metadata += '\\def\\IACR@Accepted{' + acceptedDate.strftime('%Y-%m-%d') + '}\n' metadata += '\\def\\IACR@Published{' + publishedDate + '}\n' + if revised: + revisedDate = datetime.datetime.strptime(revised[:10], '%Y-%m-%d') + metadata += '\\def\IACR@Revised{' + revisedDate.strftime('%Y-%m-%d') + '}\n' if paper_status.issue: metadata += '\\def\\IACR@vol{' + str(paper_status.issue.volume.name) + '}\n' metadata += '\\def\\IACR@no{' + str(paper_status.issue.name) + '}\n' metadata += '\\def\\IACR@CROSSMARKURL{https://crossmark.crossref.org/dialog/?doi=' + doi + r'\&domain=pdf\&date\_stamp=' + publishedDate + '}\n' + # We now check for version=final in iacrj. + metadata += '\\ifcsstring{@IACRversion}{final}{}{\\ClassError{iacrj}{This production system requires using version=final in \\string\documentclass}{}}' metadata_file = input_dir / Path('main.iacrmetadata') metadata_file.write_text(metadata) output_dir = version_dir / Path('output') @@ -483,6 +500,7 @@ def compile_for_copyedit(): 'email': version_compilation.email, 'submitted': version_compilation.submitted, 'accepted': version_compilation.accepted, + 'revised': version_compilation.revised, 'pubtype': version_compilation.pubtype, 'errata_doi': version_compilation.errata_doi, 'compiled': now, @@ -615,6 +633,7 @@ def view_copyedit(paperid, auth): volume=paper_status.volume_key, submitted=paper_status.submitted, accepted=paper_status.accepted, + revised=paper_status.revised, email=paper_status.email, journal=paper_status.journal_key, pubtype=paper_status.pubtype.name, @@ -624,6 +643,7 @@ def view_copyedit(paperid, auth): Version.FINAL.value, paper_status.submitted, paper_status.accepted, + paper_status.revised, paper_status.journal_key, paper_status.volume_key, paper_status.issue_key, @@ -699,6 +719,7 @@ def respond_to_comment(paperid, itemid, auth): volume=paper_status.volume_key, submitted=paper_status.submitted, accepted=paper_status.accepted, + revised=paper_status.revised, email=paper_status.email, journal=paper_status.journal_key, auth=create_hmac([paperid, # TODO: authenticate other fields @@ -707,6 +728,7 @@ def respond_to_comment(paperid, itemid, auth): Version.FINAL.value, paper_status.submitted, paper_status.accepted, + paper_status.revised, paper_status.journal_key, paper_status.volume_key, paper_status.issue_key, @@ -850,7 +872,6 @@ def view_results(paperid, version, auth): json_file = paper_path / Path('compilation.json') comp = Compilation.model_validate_json(json_file.read_text(encoding='UTF-8')) data['comp'] = comp - data['auth'] = create_hmac([paperid, version, comp.submitted, comp.accepted]) except Exception as e: return render_template('message.html', title='Unable to parse compilation', @@ -901,12 +922,14 @@ def view_results(paperid, version, auth): issue=pstatus.issue_key, submitted=pstatus.submitted, accepted=pstatus.accepted, + revised=pstatus.revised, auth=create_hmac([paperid, pstatus.hotcrp, pstatus.hotcrp_id, version, comp.submitted, comp.accepted, + comp.revised, pstatus.journal_key, pstatus.volume_key, pstatus.issue_key, diff --git a/webapp/templates/admin/metadata.html b/webapp/templates/admin/metadata.html index 2689419..30e495a 100644 --- a/webapp/templates/admin/metadata.html +++ b/webapp/templates/admin/metadata.html @@ -117,6 +117,10 @@ Accepted {{comp.accepted}} + + Revised + {{comp.revised}} + Compiled {{comp.compiled.strftime('%Y-%m-%d %H:%M:%S')}} diff --git a/webapp/templates/compilation.html b/webapp/templates/compilation.html index dda1a81..67ef495 100644 --- a/webapp/templates/compilation.html +++ b/webapp/templates/compilation.html @@ -66,6 +66,7 @@
Authors
email:
{{comp.email}}
submitted:
{{comp.submitted}}
accepted:
{{comp.accepted}}
+
revised:
{{comp.revised}}
compiled:
{{comp.compiled}}
compile_time:
{{comp.compile_time}} seconds
DOI:
{{comp.meta.doi}}
diff --git a/webapp/templates/message.html b/webapp/templates/message.html index 0b057ad..cd2d8e8 100644 --- a/webapp/templates/message.html +++ b/webapp/templates/message.html @@ -10,6 +10,11 @@

{{title}}

+{% for msg in get_flashed_messages() %} +
+ {{ msg }} +
+{% endfor %} {% if message %}
{{message|safe}} diff --git a/webapp/templates/submit.html b/webapp/templates/submit.html index 39c6230..8684d1f 100644 --- a/webapp/templates/submit.html +++ b/webapp/templates/submit.html @@ -89,6 +89,12 @@ {% endfor %} {% endif %} {{ form.accepted }} + {% if form.revised.errors %} + {% for msg in form.revised.errors %} +
{{ msg }}
+ {% endfor %} + {% endif %} + {{ form.revised }} {% if form.volume.errors %} {% for msg in form.volume.errors %}
{{msg}}
diff --git a/webapp/templates/view.html b/webapp/templates/view.html index 472b03b..630521e 100644 --- a/webapp/templates/view.html +++ b/webapp/templates/view.html @@ -308,6 +308,10 @@

Metadata

Accepted {{comp.accepted}} + + Revised + {{comp.revised}} + Date compiled: {{comp.compiled.strftime('%Y-%m-%d %H:%M:%S')}}