Source code for arxiv.submission.services.classic.models

"""SQLAlchemy ORM classes for the classic database."""

import json
from typing import Optional, List, Any
from datetime import datetime
from pytz import UTC
from sqlalchemy import Column, Date, DateTime, Enum, ForeignKey, Text, text, \
    ForeignKeyConstraint, Index, Integer, SmallInteger, String, Table
from sqlalchemy.orm import relationship, joinedload, backref
from sqlalchemy.ext.declarative import declarative_base

from arxiv.base import logging
from arxiv.license import LICENSES
from arxiv import taxonomy

from ... import domain
from .util import transaction

Base = declarative_base()

logger = logging.getLogger(__name__)


[docs]class Submission(Base): # type: ignore """Represents an arXiv submission.""" __tablename__ = 'arXiv_submissions' # Pre-moderation stages; these are tied to the classic submission UI. NEW = 0 STARTED = 1 FILES_ADDED = 2 PROCESSED = 3 METADATA_ADDED = 4 SUBMITTED = 5 STAGES = [NEW, STARTED, FILES_ADDED, PROCESSED, METADATA_ADDED, SUBMITTED] # Submission status; this describes where the submission is in the # publication workflow. NOT_SUBMITTED = 0 # Working. SUBMITTED = 1 # Enqueued for moderation, to be scheduled. ON_HOLD = 2 UNUSED = 3 NEXT_PUBLISH_DAY = 4 """Scheduled for the next publication cycle.""" PROCESSING = 5 """Scheduled for today.""" NEEDS_EMAIL = 6 """Announced, not yet announced.""" ANNOUNCED = 7 DELETED_ANNOUNCED = 27 """Announced and files expired.""" PROCESSING_SUBMISSION = 8 REMOVED = 9 # This is "rejected". USER_DELETED = 10 ERROR_STATE = 19 """There was a problem validating the submission during publication.""" DELETED_EXPIRED = 20 """Was working but expired.""" DELETED_ON_HOLD = 22 DELETED_PROCESSING = 25 DELETED_REMOVED = 29 DELETED_USER_EXPIRED = 30 """User deleted and files expired.""" DELETED = ( USER_DELETED, DELETED_ON_HOLD, DELETED_PROCESSING, DELETED_REMOVED, DELETED_USER_EXPIRED, DELETED_EXPIRED ) NEW_SUBMISSION = 'new' REPLACEMENT = 'rep' JOURNAL_REFERENCE = 'jref' WITHDRAWAL = 'wdr' CROSS_LIST = 'cross' WITHDRAWN_FORMAT = 'withdrawn' submission_id = Column(Integer, primary_key=True) type = Column(String(8), index=True) """Submission type (e.g. ``new``, ``jref``, ``cross``).""" document_id = Column( ForeignKey('arXiv_documents.document_id', ondelete='CASCADE', onupdate='CASCADE'), index=True ) doc_paper_id = Column(String(20), index=True) sword_id = Column(ForeignKey('arXiv_tracking.sword_id'), index=True) userinfo = Column(Integer, server_default=text("'0'")) is_author = Column(Integer, nullable=False, server_default=text("'0'")) agree_policy = Column(Integer, server_default=text("'0'")) viewed = Column(Integer, server_default=text("'0'")) stage = Column(Integer, server_default=text("'0'")) submitter_id = Column( ForeignKey('tapir_users.user_id', ondelete='CASCADE', onupdate='CASCADE'), index=True ) submitter_name = Column(String(64)) submitter_email = Column(String(64)) created = Column(DateTime, default=lambda: datetime.now(UTC)) updated = Column(DateTime, onupdate=lambda: datetime.now(UTC)) status = Column(Integer, nullable=False, index=True, server_default=text("'0'")) sticky_status = Column(Integer) """ If the submission goes out of queue (e.g. submitter makes changes), this status should be applied when the submission is re-finalized (goes back into queue, comes out of working status). """ must_process = Column(Integer, server_default=text("'1'")) submit_time = Column(DateTime) release_time = Column(DateTime) source_size = Column(Integer, server_default=text("'0'")) source_format = Column(String(12)) """Submission content type (e.g. ``pdf``, ``tex``, ``pdftex``).""" source_flags = Column(String(12)) allow_tex_produced = Column(Integer, server_default=text("'0'")) """Whether to allow a TeX-produced PDF.""" package = Column(String(255), nullable=False, server_default=text("''")) """Path (on disk) to the submission package (tarball, PDF).""" is_oversize = Column(Integer, server_default=text("'0'")) has_pilot_data = Column(Integer) is_withdrawn = Column(Integer, nullable=False, server_default=text("'0'")) title = Column(Text) authors = Column(Text) comments = Column(Text) proxy = Column(String(255)) report_num = Column(Text) msc_class = Column(String(255)) acm_class = Column(String(255)) journal_ref = Column(Text) doi = Column(String(255)) abstract = Column(Text) license = Column(ForeignKey('arXiv_licenses.name', onupdate='CASCADE'), index=True) version = Column(Integer, nullable=False, server_default=text("'1'")) is_ok = Column(Integer, index=True) admin_ok = Column(Integer) """Used by administrators for reporting/bookkeeping.""" remote_addr = Column(String(16), nullable=False, server_default=text("''")) remote_host = Column(String(255), nullable=False, server_default=text("''")) rt_ticket_id = Column(Integer, index=True) auto_hold = Column(Integer, server_default=text("'0'")) """Should be placed on hold when submission comes out of working status.""" document = relationship('Document') arXiv_license = relationship('License') submitter = relationship('User') sword = relationship('Tracking') categories = relationship('SubmissionCategory', back_populates='submission', lazy='joined', cascade="all, delete-orphan")
[docs] def get_submitter(self) -> domain.User: """Generate a :class:`.User` representing the submitter.""" extra = {} if self.submitter: extra.update(dict(forename=self.submitter.first_name, surname=self.submitter.last_name, suffix=self.submitter.suffix_name)) return domain.User(native_id=self.submitter_id, email=self.submitter_email, **extra)
WDR_DELIMETER = '. Withdrawn: '
[docs] def get_withdrawal_reason(self) -> Optional[str]: """Extract the withdrawal reason from the comments field.""" if Submission.WDR_DELIMETER not in self.comments: return return self.comments.split(Submission.WDR_DELIMETER, 1)[1]
[docs] def update_withdrawal(self, submission: domain.Submission, reason: str, paper_id: str, version: int, created: datetime) -> None: """Update withdrawal request information in the database.""" self.update_from_submission(submission) self.created = created self.updated = created self.doc_paper_id = paper_id self.status = Submission.PROCESSING_SUBMISSION reason = f"{Submission.WDR_DELIMETER}{reason}" self.comments = self.comments.rstrip('. ') + reason
[docs] def update_cross(self, submission: domain.Submission, categories: List[str], paper_id: str, version: int, created: datetime) -> None: """Update cross-list request information in the database.""" self.update_from_submission(submission) self.created = created self.updated = created self.doc_paper_id = paper_id self.status = Submission.PROCESSING_SUBMISSION for category in categories: self.categories.append( SubmissionCategory(submission_id=self.submission_id, category=category, is_primary=0))
[docs] def update_from_submission(self, submission: domain.Submission) -> None: """Update this database object from a :class:`.domain.submission.Submission`.""" if self.is_announced(): # Avoid doing anything. to be safe. return self.submitter_id = submission.creator.native_id self.submitter_name = submission.creator.name self.submitter_email = submission.creator.email self.is_author = 1 if submission.submitter_is_author else 0 self.agree_policy = 1 if submission.submitter_accepts_policy else 0 self.userinfo = 1 if submission.submitter_contact_verified else 0 self.viewed = 1 if submission.submitter_confirmed_preview else 0 self.updated = submission.updated self.title = submission.metadata.title self.abstract = submission.metadata.abstract self.authors = submission.metadata.authors_display self.comments = submission.metadata.comments self.report_num = submission.metadata.report_num self.doi = submission.metadata.doi self.msc_class = submission.metadata.msc_class self.acm_class = submission.metadata.acm_class self.journal_ref = submission.metadata.journal_ref self.version = submission.version # Numeric version. self.doc_paper_id = submission.arxiv_id # arXiv canonical ID. # The document ID is a legacy concept, and not replicated in the NG # data model. So we need to grab it from the arXiv_documents table # using the doc_paper_id. if self.doc_paper_id and not self.document_id: doc = _load_document(paper_id=self.doc_paper_id) self.document_id = doc.document_id if submission.license: self.license = submission.license.uri if submission.source_content is not None: self.source_size = submission.source_content.uncompressed_size if submission.source_content.source_format is not None: self.source_format = \ submission.source_content.source_format.value else: self.source_format = None self.package = (f'fm://{submission.source_content.identifier}' f'@{submission.source_content.checksum}') if submission.submitter_compiled_preview: self.must_process = 0 else: self.must_process = 1 # Not submitted -> Submitted. if submission.is_finalized \ and self.status in [Submission.NOT_SUBMITTED, None]: self.status = Submission.SUBMITTED self.submit_time = submission.updated # Delete. elif submission.is_deleted: self.status = Submission.USER_DELETED elif submission.is_on_hold: self.status = Submission.ON_HOLD # Unsubmit. elif self.status is None or self.status <= Submission.ON_HOLD: if not submission.is_finalized: self.status = Submission.NOT_SUBMITTED if submission.primary_classification: self._update_primary(submission) self._update_secondaries(submission) self._update_submitter(submission) # We only want to set the creation datetime on the initial row. if self.version == 1 and self.type == Submission.NEW_SUBMISSION: self.created = submission.created
@property def primary_classification(self): """Get the primary classification for this submission.""" categories = [ db_cat for db_cat in self.categories if db_cat.is_primary == 1 ] try: return categories[0] except IndexError: return
[docs] def get_arxiv_id(self) -> Optional[str]: """Get the arXiv identifier for this submission.""" if not self.document: return return self.document.paper_id
[docs] def get_created(self) -> datetime: """Get the UTC-localized creation datetime.""" return self.created.replace(tzinfo=UTC)
[docs] def get_updated(self) -> datetime: """Get the UTC-localized updated datetime.""" return self.updated.replace(tzinfo=UTC)
[docs] def is_working(self) -> bool: return self.status == self.NOT_SUBMITTED
[docs] def is_announced(self) -> bool: return self.status in [self.ANNOUNCED, self.DELETED_ANNOUNCED]
[docs] def is_active(self) -> bool: return not self.is_announced() and not self.is_deleted()
[docs] def is_rejected(self) -> bool: return self.status == self.REMOVED
[docs] def is_finalized(self) -> bool: return self.status > self.WORKING and not self.is_deleted()
[docs] def is_deleted(self) -> bool: return self.status in self.DELETED
[docs] def is_on_hold(self) -> bool: return self.status == self.ON_HOLD
[docs] def is_new_version(self) -> bool: """Indicate whether this row represents a new version.""" return self.type in [self.NEW_SUBMISSION, self.REPLACEMENT]
[docs] def is_withdrawal(self) -> bool: return self.type == self.WITHDRAWAL
[docs] def is_crosslist(self) -> bool: return self.type == self.CROSS_LIST
[docs] def is_jref(self) -> bool: return self.type == self.JOURNAL_REFERENCE
@property def secondary_categories(self) -> List[str]: """Category names from this submission's secondary classifications.""" return [c.category for c in self.categories if c.is_primary == 0] def _update_submitter(self, submission: domain.Submission) -> None: """Update submitter information on this row.""" self.submitter_id = submission.creator.native_id self.submitter_email = submission.creator.email def _update_primary(self, submission: domain.Submission) -> None: """Update primary classification on this row.""" primary_category = submission.primary_classification.category cur_primary = self.primary_classification if cur_primary and cur_primary.category != primary_category: self.categories.remove(cur_primary) self.categories.append( SubmissionCategory(submission_id=self.submission_id, category=primary_category) ) elif cur_primary is None and primary_category: self.categories.append( SubmissionCategory( submission_id=self.submission_id, category=primary_category, is_primary=1 ) ) def _update_secondaries(self, submission: domain.Submission) -> None: """Update secondary classifications on this row.""" # Remove any categories that have been removed from the Submission. for db_cat in self.categories: if db_cat.is_primary == 1: continue if db_cat.category not in submission.secondary_categories: self.categories.remove(db_cat) # Add any new secondaries for cat in submission.secondary_classification: if cat.category not in self.secondary_categories: self.categories.append( SubmissionCategory( submission_id=self.submission_id, category=cat.category, is_primary=0 ) )
[docs]class License(Base): # type: ignore """Licenses available for submissions.""" __tablename__ = 'arXiv_licenses' name = Column(String(255), primary_key=True) """This is the URI of the license.""" label = Column(String(255)) """Display label for the license.""" active = Column(Integer, server_default=text("'1'")) """Only offer licenses with active=1.""" note = Column(String(255)) sequence = Column(Integer)
[docs]class CategoryDef(Base): # type: ignore """Classification categories available for submissions.""" __tablename__ = 'arXiv_category_def' category = Column(String(32), primary_key=True) name = Column(String(255)) active = Column(Integer, server_default=text("'1'"))
[docs]class SubmissionCategory(Base): # type: ignore """Classification relation for submissions.""" __tablename__ = 'arXiv_submission_category' submission_id = Column( ForeignKey('arXiv_submissions.submission_id', ondelete='CASCADE', onupdate='CASCADE'), primary_key=True, nullable=False, index=True ) category = Column( ForeignKey('arXiv_category_def.category'), primary_key=True, nullable=False, index=True, server_default=text("''") ) is_primary = Column(Integer, nullable=False, index=True, server_default=text("'0'")) is_published = Column(Integer, index=True, server_default=text("'0'")) # category_def = relationship('CategoryDef') submission = relationship('Submission', back_populates='categories')
[docs]class Document(Base): # type: ignore """ Represents an announced arXiv paper. This is here so that we can look up the arXiv ID after a submission is announced. """ __tablename__ = 'arXiv_documents' document_id = Column(Integer, primary_key=True) paper_id = Column(String(20), nullable=False, unique=True, server_default=text("''")) title = Column(String(255), nullable=False, index=True, server_default=text("''")) authors = Column(Text) """Canonical author string.""" dated = Column(Integer, nullable=False, index=True, server_default=text("'0'")) primary_subject_class = Column(String(16)) created = Column(DateTime) submitter_email = Column(String(64), nullable=False, index=True, server_default=text("''")) submitter_id = Column(ForeignKey('tapir_users.user_id'), index=True) submitter = relationship('User') @property def dated_datetime(self) -> datetime: """Return the created time as a datetime.""" return datetime.utcfromtimestamp(self.dated).replace(tzinfo=UTC)
[docs]class DocumentCategory(Base): # type: ignore """Relation between announced arXiv papers and their classifications.""" __tablename__ = 'arXiv_document_category' document_id = Column( ForeignKey('arXiv_documents.document_id', ondelete='CASCADE'), primary_key=True, nullable=False, index=True, server_default=text("'0'") ) category = Column( ForeignKey('arXiv_category_def.category'), primary_key=True, nullable=False, index=True ) """E.g. cs.CG, cond-mat.dis-nn, etc.""" is_primary = Column(Integer, nullable=False, server_default=text("'0'")) category_def = relationship('CategoryDef') document = relationship('Document')
[docs]class User(Base): # type: ignore """Represents an arXiv user.""" __tablename__ = 'tapir_users' user_id = Column(Integer, primary_key=True) first_name = Column(String(50), index=True) last_name = Column(String(50), index=True) suffix_name = Column(String(50)) share_first_name = Column(Integer, nullable=False, server_default=text("'1'")) share_last_name = Column(Integer, nullable=False, server_default=text("'1'")) email = Column(String(255), nullable=False, unique=True, server_default=text("''")) share_email = Column(Integer, nullable=False, server_default=text("'8'")) email_bouncing = Column(Integer, nullable=False, server_default=text("'0'")) policy_class = Column(ForeignKey('tapir_policy_classes.class_id'), nullable=False, index=True, server_default=text("'0'")) """ +----------+---------------+ | class_id | name | +----------+---------------+ | 1 | Administrator | | 2 | Public user | | 3 | Legacy user | +----------+---------------+ """ joined_date = Column(Integer, nullable=False, index=True, server_default=text("'0'")) joined_ip_num = Column(String(16), index=True) joined_remote_host = Column(String(255), nullable=False, server_default=text("''")) flag_internal = Column(Integer, nullable=False, index=True, server_default=text("'0'")) flag_edit_users = Column(Integer, nullable=False, index=True, server_default=text("'0'")) flag_edit_system = Column(Integer, nullable=False, server_default=text("'0'")) flag_email_verified = Column(Integer, nullable=False, server_default=text("'0'")) flag_approved = Column(Integer, nullable=False, index=True, server_default=text("'1'")) flag_deleted = Column(Integer, nullable=False, index=True, server_default=text("'0'")) flag_banned = Column(Integer, nullable=False, index=True, server_default=text("'0'")) flag_wants_email = Column(Integer, nullable=False, server_default=text("'0'")) flag_html_email = Column(Integer, nullable=False, server_default=text("'0'")) tracking_cookie = Column(String(255), nullable=False, index=True, server_default=text("''")) flag_allow_tex_produced = Column(Integer, nullable=False, server_default=text("'0'")) tapir_policy_class = relationship('PolicyClass')
[docs] def to_user(self) -> domain.agent.User: return domain.agent.User( self.user_id, self.email, username=self.username, forename=self.first_name, surname=self.last_name, suffix=self.suffix_name )
[docs]class Username(Base): # type: ignore """ Users' usernames (because why not have a separate table). +--------------+------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------------+------------------+------+-----+---------+----------------+ | nick_id | int(10) unsigned | NO | PRI | NULL | autoincrement | | nickname | varchar(20) | NO | UNI | | | | user_id | int(4) unsigned | NO | MUL | 0 | | | user_seq | int(1) unsigned | NO | | 0 | | | flag_valid | int(1) unsigned | NO | MUL | 0 | | | role | int(10) unsigned | NO | MUL | 0 | | | policy | int(10) unsigned | NO | MUL | 0 | | | flag_primary | int(1) unsigned | NO | | 0 | | +--------------+------------------+------+-----+---------+----------------+ """ __tablename__ = 'tapir_nicknames' nick_id = Column(Integer, primary_key=True) nickname = Column(String(20), nullable=False, unique=True, index=True) user_id = Column(ForeignKey('tapir_users.user_id'), nullable=False, server_default=text("'0'")) user = relationship('User') user_seq = Column(Integer, nullable=False, server_default=text("'0'")) flag_valid = Column(Integer, nullable=False, server_default=text("'0'")) role = Column(Integer, nullable=False, server_default=text("'0'")) policy = Column(Integer, nullable=False, server_default=text("'0'")) flag_primary = Column(Integer, nullable=False, server_default=text("'0'")) user = relationship('User')
# TODO: what is this?
[docs]class PolicyClass(Base): # type: ignore """Defines user roles in the system.""" __tablename__ = 'tapir_policy_classes' class_id = Column(SmallInteger, primary_key=True) name = Column(String(64), nullable=False, server_default=text("''")) description = Column(Text, nullable=False) password_storage = Column(Integer, nullable=False, server_default=text("'0'")) recovery_policy = Column(Integer, nullable=False, server_default=text("'0'")) permanent_login = Column(Integer, nullable=False, server_default=text("'0'"))
[docs]class Tracking(Base): # type: ignore """Record of SWORD submissions.""" __tablename__ = 'arXiv_tracking' tracking_id = Column(Integer, primary_key=True) sword_id = Column(Integer, nullable=False, unique=True, server_default=text("'00000000'")) paper_id = Column(String(32), nullable=False) submission_errors = Column(Text) timestamp = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP"))
[docs]class ArchiveCategory(Base): # type: ignore """Maps categories to the archives in which they reside.""" __tablename__ = 'arXiv_archive_category' archive_id = Column(String(16), primary_key=True, nullable=False, server_default=text("''")) category_id = Column(String(32), primary_key=True, nullable=False)
[docs]class ArchiveDef(Base): # type: ignore """Defines the archives in the arXiv classification taxonomy.""" __tablename__ = 'arXiv_archive_def' archive = Column(String(16), primary_key=True, server_default=text("''")) name = Column(String(255))
[docs]class ArchiveGroup(Base): # type: ignore """Maps archives to the groups in which they reside.""" __tablename__ = 'arXiv_archive_group' archive_id = Column(String(16), primary_key=True, nullable=False, server_default=text("''")) group_id = Column(String(16), primary_key=True, nullable=False, server_default=text("''"))
[docs]class Archive(Base): # type: ignore """Supplemental data about archives in the classification hierarchy.""" __tablename__ = 'arXiv_archives' archive_id = Column(String(16), primary_key=True, server_default=text("''")) in_group = Column(ForeignKey('arXiv_groups.group_id'), nullable=False, index=True, server_default=text("''")) archive_name = Column(String(255), nullable=False, server_default=text("''")) start_date = Column(String(4), nullable=False, server_default=text("''")) end_date = Column(String(4), nullable=False, server_default=text("''")) subdivided = Column(Integer, nullable=False, server_default=text("'0'")) arXiv_group = relationship('Group')
[docs]class GroupDef(Base): # type: ignore """Defines the groups in the arXiv classification taxonomy.""" __tablename__ = 'arXiv_group_def' archive_group = Column(String(16), primary_key=True, server_default=text("''")) name = Column(String(255))
[docs]class Group(Base): # type: ignore """Supplemental data about groups in the classification hierarchy.""" __tablename__ = 'arXiv_groups' group_id = Column(String(16), primary_key=True, server_default=text("''")) group_name = Column(String(255), nullable=False, server_default=text("''")) start_year = Column(String(4), nullable=False, server_default=text("''"))
[docs]class EndorsementDomain(Base): # type: ignore """Endorsement configurations.""" __tablename__ = 'arXiv_endorsement_domains' endorsement_domain = Column(String(32), primary_key=True, server_default=text("''")) endorse_all = Column(Enum('y', 'n'), nullable=False, server_default=text("'n'")) mods_endorse_all = Column(Enum('y', 'n'), nullable=False, server_default=text("'n'")) endorse_email = Column(Enum('y', 'n'), nullable=False, server_default=text("'y'")) papers_to_endorse = Column(SmallInteger, nullable=False, server_default=text("'4'"))
[docs]class Category(Base): # type: ignore """Supplemental data about arXiv categories, including endorsement.""" __tablename__ = 'arXiv_categories' arXiv_endorsement_domain = relationship('EndorsementDomain') archive = Column( ForeignKey('arXiv_archives.archive_id'), primary_key=True, nullable=False, server_default=text("''") ) """E.g. cond-mat, astro-ph, cs.""" arXiv_archive = relationship('Archive') subject_class = Column(String(16), primary_key=True, nullable=False, server_default=text("''")) """E.g. AI, spr-con, str-el, CO, EP.""" definitive = Column(Integer, nullable=False, server_default=text("'0'")) active = Column(Integer, nullable=False, server_default=text("'0'")) """Only use rows where active == 1.""" category_name = Column(String(255)) endorse_all = Column( Enum('y', 'n', 'd'), nullable=False, server_default=text("'d'") ) endorse_email = Column( Enum('y', 'n', 'd'), nullable=False, server_default=text("'d'") ) endorsement_domain = Column( ForeignKey('arXiv_endorsement_domains.endorsement_domain'), index=True ) """E.g. astro-ph, acc-phys, chem-ph, cs.""" papers_to_endorse = Column(SmallInteger, nullable=False, server_default=text("'0'"))
[docs]class AdminLogEntry(Base): # type: ignore """ +---------------+-----------------------+------+-----+-------------------+ | Field | Type | Null | Key | Default | +---------------+-----------------------+------+-----+-------------------+ | id | int(11) | NO | PRI | NULL | | logtime | varchar(24) | YES | | NULL | | created | timestamp | NO | | CURRENT_TIMESTAMP | | paper_id | varchar(20) | YES | MUL | NULL | | username | varchar(20) | YES | | NULL | | host | varchar(64) | YES | | NULL | | program | varchar(20) | YES | | NULL | | command | varchar(20) | YES | MUL | NULL | | logtext | text | YES | | NULL | | document_id | mediumint(8) unsigned | YES | | NULL | | submission_id | int(11) | YES | MUL | NULL | | notify | tinyint(1) | YES | | 0 | +---------------+-----------------------+------+-----+-------------------+ """ __tablename__ = 'arXiv_admin_log' id = Column(Integer, primary_key=True) logtime = Column(String(24), nullable=True) created = Column(DateTime, default=lambda: datetime.now(UTC)) paper_id = Column(String(20), nullable=True) username = Column(String(20), nullable=True) host = Column(String(64), nullable=True) program = Column(String(20), nullable=True) command = Column(String(20), nullable=True) logtext = Column(Text, nullable=True) document_id = Column(Integer, nullable=True) submission_id = Column(Integer, nullable=True) notify = Column(Integer, nullable=True, default=0)
[docs]class CategoryProposal(Base): # type: ignore """ Represents a proposal to change the classification of a submission. +---------------------+-----------------+------+-----+---------+ | Field | Type | Null | Key | Default | +---------------------+-----------------+------+-----+---------+ | proposal_id | int(11) | NO | PRI | NULL | | submission_id | int(11) | NO | PRI | NULL | | category | varchar(32) | NO | PRI | NULL | | is_primary | tinyint(1) | NO | PRI | 0 | | proposal_status | int(11) | YES | | 0 | | user_id | int(4) unsigned | NO | MUL | NULL | | updated | datetime | YES | | NULL | | proposal_comment_id | int(11) | YES | MUL | NULL | | response_comment_id | int(11) | YES | MUL | NULL | +---------------------+-----------------+------+-----+---------+ """ __tablename__ = 'arXiv_submission_category_proposal' UNRESOLVED = 0 ACCEPTED_AS_PRIMARY = 1 ACCEPTED_AS_SECONDARY = 2 REJECTED = 3 DOMAIN_STATUS = { UNRESOLVED: domain.proposal.Proposal.Status.PENDING, ACCEPTED_AS_PRIMARY: domain.proposal.Proposal.Status.ACCEPTED, ACCEPTED_AS_SECONDARY: domain.proposal.Proposal.Status.ACCEPTED, REJECTED: domain.proposal.Proposal.Status.REJECTED } proposal_id = Column(Integer, primary_key=True) submission_id = Column(ForeignKey('arXiv_submissions.submission_id')) submission = relationship('Submission') category = Column(String(32)) is_primary = Column(Integer, server_default=text("'0'")) proposal_status = Column(Integer, nullable=True, server_default=text("'0'")) user_id = Column(ForeignKey('tapir_users.user_id')) user = relationship("User") updated = Column(DateTime, default=lambda: datetime.now(UTC)) proposal_comment_id = Column(ForeignKey('arXiv_admin_log.id'), nullable=True) proposal_comment = relationship("AdminLogEntry", foreign_keys=[proposal_comment_id]) response_comment_id = Column(ForeignKey('arXiv_admin_log.id'), nullable=True) response_comment = relationship("AdminLogEntry", foreign_keys=[response_comment_id])
[docs] def status_from_domain(self, proposal: domain.proposal.Proposal) -> int: if proposal.status == domain.proposal.Proposal.Status.PENDING: return self.UNRESOLVED elif proposal.status == domain.proposal.Proposal.Status.REJECTED: return self.REJECTED elif proposal.status == domain.proposal.Proposal.Status.ACCEPTED: if proposal.proposed_event_type \ is domain.event.SetPrimaryClassification: return self.ACCEPTED_AS_PRIMARY else: return self.ACCEPTED_AS_SECONDARY
def _load_document(paper_id: str) -> Document: with transaction() as session: document = session.query(Document) \ .filter(Document.paper_id == paper_id) \ .one() if document is None: raise RuntimeError('No such document') return document def _get_user_by_username(username: str) -> User: with transaction() as session: return (session.query(Username) .filter(Username.nickname == username) .first() .user)