Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions consts.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
__author__ = "Rosetta Reatherford"
__license__ = "AGPL v3"
__maintainer__ = "The Public Library of Science (PLOS)"

import os

from django.conf import settings
Expand Down
3 changes: 2 additions & 1 deletion dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
hypothesis==6.138.7
pytest==8.4.1
pytest==8.4.1
python-magic==0.4.27
Empty file added enums/__init__.py
Empty file.
13 changes: 13 additions & 0 deletions enums/transfer_log_message_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""
A file for tracking the transfer log message types.
"""
__author__ = "Rosetta Reatherford"
__license__ = "AGPL v3"
__maintainer__ = "The Public Library of Science (PLOS)"

import django.db.models as models
from django.utils.translation import gettext_lazy as _

class TransferLogMessageType(models.TextChoices):
EXPORT = "EX", _("Export Message")
IMPORT = "IM", _("Import Message")
106 changes: 76 additions & 30 deletions file_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import plugins.editorial_manager_transfer_service.logger_messages as logger_messages
from core.models import File
from journal.models import Journal
from plugins.editorial_manager_transfer_service.enums.transfer_log_message_type import TransferLogMessageType
from plugins.editorial_manager_transfer_service.models import TransferLogs
from submission.models import Article
from utils import setting_handler
from utils.logger import get_logger
Expand All @@ -42,47 +44,32 @@ class ExportFileCreation:
A class for managing the export file creation process.
"""

def __init__(self, journal_code: str, article_id: str):
def __init__(self, janeway_journal_code: str, article_id: int | None) -> None:
self.zip_filepath: str | None = None
self.go_filepath: str | None = None
self.in_error_state: bool = False
self.__license_code: str | None = None
self.__journal_code: str | None = None
self.__submission_partner_code: str | None = None
self.article_id: str | None = article_id.strip() if article_id else None
self.article_id: int | None = article_id
self.article: Article | None = None
self.journal: Journal | None = None
self.export_folder: str | None = None

# If no article ID, return an error.
if not self.article_id or len(self.article_id) <= 0:
logger.error(logger_messages.process_failed_no_article_id_provided())
self.in_error_state = True
return

# Attempt to get the journal.
try:
self.journal: Journal = Journal.objects.get(code=journal_code)
except Journal.DoesNotExist:
logger.error(logger_messages.process_failed_fetching_journal(article_id))
self.in_error_state = True
# Gets the journal
self.journal: Journal | None = self.__fetch_journal(janeway_journal_code)
if self.in_error_state:
return

# Get the article based upon the given article ID.
logger.info(logger_messages.process_fetching_article(article_id))
try:
self.article: Article = self.__fetch_article(self.journal, article_id)
if not self.article:
raise Article.DoesNotExist
except Article.DoesNotExist:
logger.error(logger_messages.process_failed_fetching_article(article_id))
self.in_error_state = True
self.article: Article | None = self.__fetch_article(self.journal, article_id)
if self.in_error_state:
return

# Get the export folder.
export_folders: str = get_article_export_folders()
if len(export_folders) <= 0:
logger.error(logger_messages.export_process_failed_no_export_folder())
self.log_error(logger_messages.export_process_failed_no_export_folder())
self.in_error_state = True
return
self.export_folder = export_folders
Expand Down Expand Up @@ -131,7 +118,7 @@ def __create_export_file(self):
# Attempt to fetch the article files.
article_files: Sequence[File] = self.__fetch_article_files(self.article)
if len(article_files) <= 0:
logger.error(logger_messages.process_failed_fetching_article_files(self.article_id))
self.log_error(logger_messages.process_failed_fetching_article_files(self.article_id))
self.in_error_state = True
return

Expand Down Expand Up @@ -198,8 +185,8 @@ def get_setting(self, setting_name: str) -> str:
try:
return setting_handler.get_setting(setting_group_name=consts.PLUGIN_SETTINGS_GROUP_NAME,
setting_name=setting_name, journal=self.journal, ).processed_value
except ObjectDoesNotExist:
logger.error("Could not get the following setting, '{0}'".format(setting_name))
except ObjectDoesNotExist as e:
self.log_error("Could not get the following setting, '{0}'".format(setting_name), e)
self.in_error_state = True
return ""

Expand All @@ -225,7 +212,7 @@ def __create_go_xml_file(self, metadata_filename: str, article_filenames: Sequen
version.set(consts.GO_FILE_VERSION_ELEMENT_ATTRIBUTE_NUMBER_KEY,
consts.GO_FILE_VERSION_ELEMENT_ATTRIBUTE_NUMBER_VALUE)
journal: ETree.Element = ETree.SubElement(header, consts.GO_FILE_ELEMENT_TAG_JOURNAL)
journal.set(consts.GO_FILE_JOURNAL_ELEMENT_ATTRIBUTE_CODE_KEY, self.get_license_code())
journal.set(consts.GO_FILE_JOURNAL_ELEMENT_ATTRIBUTE_CODE_KEY, self.get_journal_code())
import_type: ETree.Element = ETree.SubElement(header, consts.GO_FILE_ELEMENT_TAG_IMPORT_TYPE)
import_type.set(consts.GO_FILE_IMPORT_TYPE_ELEMENT_ATTRIBUTE_ID_KEY,
consts.GO_FILE_IMPORT_TYPE_ELEMENT_ATTRIBUTE_ID_VALUE)
Expand Down Expand Up @@ -260,15 +247,74 @@ def __create_metadata_file(self, article: Article) -> File | None:
"""
pass

@staticmethod
def __fetch_article(journal: Journal, article_id: str) -> Article:
def __fetch_article(self, journal: Journal | None, article_id: int | None) -> Article | None:
"""
Gets the article object for the given article ID.
:param journal: The journal to fetch the article from.
:param article_id: The ID of the article.
:return: The article object with the given article ID.
"""
return Article.get_article(journal, "id", article_id)
# If no article ID or journal, return an error.
if not article_id or article_id <= 0:
self.log_error(logger_messages.process_failed_no_article_id_provided())
self.in_error_state = True
return None

article: Article | None = None

logger.debug(logger_messages.process_fetching_article(article_id))
try:
article = Article.get_article(journal, "id", article_id)
logger.debug(logger_messages.process_finished_fetching_article(article_id))
except Article.DoesNotExist as e:
self.log_error(logger_messages.process_failed_fetching_article(article_id), e)
self.in_error_state = True

return article

def __fetch_journal(self, janeway_journal_code: str | None) -> Journal | None:
"""
Gets the journal from the database given the Janeway journal code.
:param janeway_journal_code: The code of the Janeway journal to fetch.
:return: The journal object with the given Janeway journal code, if there is one. None otherwise.
"""
# If no journal code, return an error.
if not janeway_journal_code or len(janeway_journal_code) <= 0:
self.log_error(logger_messages.process_failed_no_janeway_journal_code_provided())
self.in_error_state = True
return None

journal: Journal | None = None

# Attempt to get the journal.
logger.debug(logger_messages.process_fetching_journal(janeway_journal_code))
try:
journal = Journal.objects.get(code=janeway_journal_code)
logger.debug(logger_messages.process_finished_fetching_journal(janeway_journal_code))
except Journal.DoesNotExist as e:
self.log_error(logger_messages.process_failed_fetching_journal(janeway_journal_code), e)
self.in_error_state = True

return journal

def log_error(self, message: str, error: Exception = None) -> None:
"""
Logs the given error message in both the database and plaintext logs.
:param message: The message to log.
:param error: The exception, if there is one.
"""
logger.exception(error)
logger.error(message)
TransferLogs.objects.create(journal=self.journal, article=self.article, message=message,
message_type=TransferLogMessageType.EXPORT, success=False)

def log_success(self) -> None:
"""
Logs a success message in both the database and plaintext logs.
"""
TransferLogs.objects.create(journal=self.journal, article=self.article,
message=logger_messages.export_process_succeeded(self.article_id),
message_type=TransferLogMessageType.EXPORT, success=True)

@staticmethod
def __fetch_article_files(article: Article) -> List[File]:
Expand Down
53 changes: 51 additions & 2 deletions file_transfer_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import os
from typing import List

from plugins.editorial_manager_transfer_service import logger_messages
from plugins.editorial_manager_transfer_service.file_exporter import ExportFileCreation
from utils.logger import get_logger

Expand Down Expand Up @@ -53,6 +54,12 @@ def get_export_file_creator(self, journal_code: str, article_id: str) -> ExportF

@staticmethod
def __get_dictionary_identifier(journal_code: str, article_id: str) -> str:
"""
Gets the dictionary identifier for the given article.
:param journal_code: The journal code of the journal where the article lives.
:param article_id: The article id.
:return: The dictionary identifier.
"""
return f"{journal_code}-{article_id}"

def get_export_zip_filepath(self, journal_code: str, article_id: str) -> str | None:
Expand All @@ -75,7 +82,38 @@ def get_export_go_filepath(self, journal_code: str, article_id: str) -> str | No
file_export_creator = self.get_export_file_creator(journal_code, article_id)
return file_export_creator.get_go_filepath() if file_export_creator else None

def log_export_error(self, journal_code: str,
article_id: str,
error_message: str = None,
error: Exception = None) -> None:
"""
Logs an error message in both the database and plaintext logs.
:param error: The exception, if there is one.
:param error_message: The error message to print out.
:param journal_code: The journal code of the journal where the article lives.
:param article_id: The article id.
"""
file_export_creator = self.get_export_file_creator(journal_code, article_id)
if file_export_creator:
file_export_creator.log_error(logger_messages.export_process_failed_ingest(article_id, error_message), error)

def log_export_success(self, journal_code: str,
article_id: str) -> None:
"""
Logs the success message for when an article has completed a journey to Editorial Manager.
:param journal_code: The journal code of the journal where the article lives.
:param article_id: The article id.
"""
file_export_creator = self.get_export_file_creator(journal_code, article_id)
if file_export_creator:
file_export_creator.log_success()

def delete_export_files(self, journal_code: str, article_id: str) -> None:
"""
Deletes the export files for the given article.
:param journal_code: The journal code of the journal the article lives in.
:param article_id: The article id.
"""
dictionary_identifier: str = self.__get_dictionary_identifier(journal_code, article_id)
if dictionary_identifier not in self.exports:
return
Expand All @@ -93,11 +131,18 @@ def __delete_files(self) -> None:

@staticmethod
def __delete_file(filepath: str) -> bool:
"""
Deletes the given file.
:param filepath: The file path of the file to delete.
:return: True if the file was deleted, false otherwise.
"""
if not os.path.exists(filepath):
return True
try:
os.remove(filepath)
except OSError:
except OSError as e:
logger.exception(e)
logger.error(logger_messages.excport_process_failed_delete_file(filepath))
return False

return True
Expand Down Expand Up @@ -129,13 +174,17 @@ def export_success_callback(journal_code: str, article_id: str) -> None:
:param journal_code: The journal code of the journal the article lives in.
:param article_id: The article id.
"""
FileTransferService().log_export_success(journal_code, article_id)
FileTransferService().delete_export_files(journal_code, article_id)


def export_failure_callback(journal_code: str, article_id: str) -> None:
def export_failure_callback(journal_code: str, article_id: str, error_message: str = None, error: Exception = None) -> None:
"""
The callback in case of a failed export.
:param error: The exception, if there is one.
:param error_message: The error message to print.
:param journal_code: The journal code of the journal the article lives in.
:param article_id: The article id.
"""
FileTransferService().log_export_error(journal_code, article_id, error_message, error)
FileTransferService().delete_export_files(journal_code, article_id)
4 changes: 4 additions & 0 deletions forms.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
__author__ = "Rosetta Reatherford"
__license__ = "AGPL v3"
__maintainer__ = "The Public Library of Science (PLOS)"

from django import forms


Expand Down
Loading