From 3c66cb4b3532296fff1cfa97ee529ce1520db90c Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Tue, 17 Dec 2024 14:11:11 +0900 Subject: [PATCH 01/61] Change weko-records-ui mdoels for URL download - Change columns of FileSecretDownload - Change columns of FileOnetimeDownload - Add check constraints - Add some methods for new APIs - Add a mixin class to manage common methods --- .../weko-records-ui/weko_records_ui/models.py | 294 ++++++++++++------ 1 file changed, 194 insertions(+), 100 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/models.py b/modules/weko-records-ui/weko_records_ui/models.py index 2343c5712c..60d39a75c6 100644 --- a/modules/weko-records-ui/weko_records_ui/models.py +++ b/modules/weko-records-ui/weko_records_ui/models.py @@ -28,7 +28,7 @@ from flask import current_app from invenio_db import db -from sqlalchemy import desc, or_ ,func +from sqlalchemy import CheckConstraint, desc, ForeignKey, func from sqlalchemy.dialects import postgresql from sqlalchemy.dialects.postgresql import INTERVAL from sqlalchemy.sql.functions import concat ,now @@ -289,66 +289,28 @@ def delete_object(cls, permission): db.session.delete(permission) -class FileOnetimeDownload(db.Model, Timestamp): - """File onetime download.""" - - __tablename__ = 'file_onetime_download' - - id = db.Column(db.Integer, primary_key=True, autoincrement=True) - """Identifier""" - - file_name = db.Column(db.String(255), nullable=False) - """File name""" - - user_mail = db.Column(db.String(255), nullable=False) - """User mail""" - - record_id = db.Column(db.String(255), nullable=False) - """Record identifier.""" - - download_count = db.Column(db.Integer, nullable=False, default=0) - """Download count""" - - expiration_date = db.Column(db.Integer, nullable=False, default=0) - """Expiration Date""" - - extra_info = db.Column( - db.JSON().with_variant( - postgresql.JSONB(none_as_null=True), - 'postgresql', - ).with_variant( - JSONType(), - 'sqlite', - ).with_variant( - JSONType(), - 'mysql', - ), - default=lambda: dict(), - nullable=True - ) - """Extra info.""" - - def __init__(self, file_name, user_mail, record_id, download_count=0, - expiration_date=0, extra_info=None): - """Init. - - :param file_name: File name - :param user_mail: User mail - :param record_id: Record identifier - :param download_count: Download count - :param expiration_date: Expiration date - :param extra_info: Extra info want to store - """ - self.file_name = file_name - self.user_mail = user_mail - self.record_id = record_id - self.download_count = download_count - self.expiration_date = expiration_date - self.extra_info = extra_info +class DownloadMixin: + """A mixin class that provides common methods for managing download-related + functionality. + Note: + This mixin class is specifically designed for managing URL-related + downloads, particularly one-time URLs and secret URLs. + """ @classmethod def create(cls, **data): - """Create data.""" + """Create a new instance and save it to the database. + + Args: + **data: The attributes for the new instance. + + Returns: + object: The created instance, or None if an error occurred. + + Raises: + Exception: If the database commit fails, the transaction is rolled + back and the exception is re-raised. + """ try: file_download = cls(**data) db.session.add(file_download) @@ -359,6 +321,129 @@ def create(cls, **data): current_app.logger.error(ex) return None + + def increment_download_count(self): + """Increment the 'download_count' attribute by 1 and commit the change. + + This method increases the download count for the instance by one + and persists the change to the database. + + Returns: + self: The updated instance with 'download_count' incremented. + + Raises: + Exception: If the database commit fails, the transaction is rolled + back and the exception is re-raised. + """ + try: + self.download_count += 1 + db.session.commit() + return self + except Exception as ex: + db.session.rollback() + current_app.logger.error(ex) + raise ex + + def delete_logicaly(self): + """Execute logical deletion by setting the 'is_deleted' flag to True. + + This marks the record as deleted without removing it from the database. + + Returns: + self: The updated instance with 'is_deleted' set to True. + + Raises: + Exception: If the database commit fails, the transaction is rolled + back and the exception is re-raised. + """ + try: + self.is_deleted = True + db.session.commit() + return self + except Exception as ex: + db.session.rollback() + current_app.logger.error(ex) + raise ex + + +class FileOnetimeDownload(db.Model, Timestamp, DownloadMixin): + """A model class for the 'file_onetime_download' table. + + This class stores information about one-time URLs used for file access. + + Note: + Despite being called 'one-time', the download limit can be set to more + than once. + + Attributes: + id (int): The unique identifier of the record. + approver_id (int): The ID of the user who approved the application. + record_id (str): The ID of the associated file record. + file_name (str): The name of the file. + expiration_date (datetime): The date and time when the URL expires. + download_limit (int): The maximum number of downloads allowed. + download_count (int): The number of times the URL has been downloaded. + user_mail (str): The email address of the user who applied. + is_guest (bool): Indicates whether the user is a guest. + is_deleted (bool): Indicates whether the record is deleted. + extra_info (dict): Additional information stored in JSON format. + """ + __tablename__ = 'file_onetime_download' + id = db.Column(db.Integer,primary_key=True,autoincrement=True) + creator_id = db.Column( + db.Integer, + db.ForeignKey( + 'accounts_user.id', + name='fk_file_onetime_download_approver_id'), + nullable=False) + record_id = db.Column(db.String(255),nullable=False) + file_name = db.Column(db.String(255), nullable=False) + expiration_date = db.Column(db.DateTime, nullable=False) + download_limit = db.Column(db.Integer, nullable=False) + download_count = db.Column(db.Integer, nullable=False, default=0) + user_mail = db.Column(db.String(255), nullable=False) + is_guest = db.Column(db.Boolean, nullable=False, default=False) + is_deleted = db.Column(db.Boolean, nullable=False, default=False) + extra_info = db.Column(db.JSON() + .with_variant(postgresql.JSONB(none_as_null=True), 'postgresql') + .with_variant(JSONType(), 'sqlite') + .with_variant(JSONType(), 'mysql'), + default=lambda: dict(), + nullable=True,) + __table_args__ = ( + CheckConstraint('created < expiration_date', name='check_expire_date'), + CheckConstraint('download_limit > 0', name='check_dl_limit_positive'), + ) + + def __init__( + self, approver_id, record_id, file_name, expiration_date, + download_limit, user_mail, is_guest, extra_info + ): + """Initialize the instance. + + Note: + The 'id', 'download_count', and 'is_deleted' fields are not part of + the initialization. + + Args: + approver_id (int): The ID of the user who approved the application. + record_id (str): The ID of the file's associated record. + file_name (str): The name of the file. + expiration_date (datetime): The date and time when the URL expires. + download_limit (int): The download limit of the URL. + user_mail (str): The email address of the user who applied. + is_guest (bool): A flag indicating whether the user is a guest. + extra_info (dict): Additional information stored in JSON format. + """ + self.approver_id = approver_id + self.record_id = record_id + self.file_name = file_name + self.expiration_date = expiration_date + self.download_limit = download_limit + self.user_mail = user_mail + self.is_guest = is_guest + self.extra_info = extra_info + @classmethod def update_download(cls, **data): """Update download count. @@ -418,60 +503,68 @@ def find_downloadable_only(cls, **obj) -> list: now() < cls.created + func.cast( concat( cls.expiration_date , ' days' ) , INTERVAL) ) return query.order_by(desc(cls.id)).all() - - -class FileSecretDownload(db.Model, Timestamp): - """File secret download.""" - - __tablename__ = 'file_secret_download' - id = db.Column(db.Integer, primary_key=True, autoincrement=True) - """Identifier""" - file_name = db.Column(db.String(255), nullable=False) - """File name""" +class FileSecretDownload(db.Model, Timestamp, DownloadMixin): + """A model class for 'file_secret_download' table. - user_mail = db.Column(db.String(255), nullable=False) - """User mail""" + This class stores information about secret URLs, which used for private + file access. - record_id = db.Column(db.String(255), nullable=False) - """Record identifier.""" + Attributes: + id (int): The identifier of the record. + creator_id (int): The ID of the user who issued the secret URL. + record_id (str): The ID of the record that has the file. + file_name (str): The name of the file. + label_name (str): The label of the secret URL. + expiration_date (datetime): The date and time when the URL expires. + download_limit (int): The download limit of the URL. + download_count (int): The number of times the URL has been downloaded. + is_deleted (bool): A flag indicating whether the record is deleted. + """ + __tablename__ = 'file_secret_download' - download_count = db.Column(db.Integer, nullable=False, default=0) - """Download count""" + id = db.Column(db.Integer,primary_key=True,autoincrement=True) + creator_id = db.Column(db.Integer, + db.ForeignKey( + 'accounts_user.id', + name='fk_file_secret_download_creator_id'), + nullable=False) + record_id = db.Column(db.String(255), nullable=False) + file_name = db.Column(db.String(255), nullable=False) + label_name = db.Column(db.String(255), nullable=False) + expiration_date = db.Column(db.DateTime, nullable=False) + download_limit = db.Column(db.Integer, nullable=False) + download_count = db.Column(db.Integer, nullable=False, default=0) + is_deleted = db.Column(db.Boolean, nullable=False, default=False) + __table_args__ = ( + CheckConstraint('created < expiration_date', name='check_expire_date'), + CheckConstraint('download_limit > 0', name='check_dl_limit_positive'), + ) - expiration_date = db.Column(db.Integer, nullable=False, default=0) - """Expiration Date""" + def __init__(self, creator_id, record_id, file_name, label_name, + expiration_date, download_limit): + """Initialize the instance. - def __init__(self, file_name, user_mail, record_id, download_count=0, - expiration_date=0): - """Init. + Note: + The 'id', 'download_count', and 'is_deleted' fields are not part of + the initialization. - :param file_name: File name - :param user_mail: User mail - :param record_id: Record identifier - :param download_count: Download count - :param expiration_date: Expiration date + Args: + creator_id (int): The ID of the user who issued the secret URL. + record_id (str): The ID of the record that has the file. + file_name (str): The name of the file. + label_name (str): The label of the secret URL. + expiration_date (datetime): The date and time when the URL expires. + download_limit (int): The download limit of the URL. """ - self.file_name = file_name - self.user_mail = user_mail + self.creator_id = creator_id self.record_id = record_id - self.download_count = download_count + self.file_name = file_name + self.label_name = label_name self.expiration_date = expiration_date + self.download_limit = download_limit - @classmethod - def create(cls, **data): - """Create data.""" - try: - file_download = cls(**data) - db.session.add(file_download) - db.session.commit() - db.session.flush() - return file_download - except Exception as ex: - db.session.rollback() - current_app.logger.error(ex) - raise ex @classmethod def update_download(cls, **data): @@ -518,4 +611,5 @@ def find(cls, **obj) -> list: ) return query.order_by(desc(cls.id)).all() + __all__ = ('PDFCoverPageSettings', 'FilePermission', 'FileOnetimeDownload' ,'FileSecretDownload') From a787c3c945b942c07cd6143575973703474ce4e6 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Tue, 17 Dec 2024 20:37:31 +0900 Subject: [PATCH 02/61] Fix isssues in weko-records-ui models --- modules/weko-records-ui/weko_records_ui/fd.py | 34 ++---- .../weko-records-ui/weko_records_ui/models.py | 110 ++++++++++++------ .../weko-records-ui/weko_records_ui/utils.py | 22 ---- 3 files changed, 85 insertions(+), 81 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/fd.py b/modules/weko-records-ui/weko_records_ui/fd.py index 5a66280464..b9becb387c 100644 --- a/modules/weko-records-ui/weko_records_ui/fd.py +++ b/modules/weko-records-ui/weko_records_ui/fd.py @@ -50,11 +50,12 @@ from .pdf import make_combined_pdf from .permissions import check_original_pdf_download_permission, \ file_permission_factory, is_owners_or_superusers -from .utils import check_and_send_usage_report, get_billing_file_download_permission, \ - get_groups_price, get_min_price_billing_file_download, \ - get_onetime_download, get_secret_download, is_billing_item, parse_one_time_download_token, parse_secret_download_token, \ - update_onetime_download, update_secret_download, validate_download_record, \ - validate_onetime_download_token, validate_secret_download_token +from .utils import ( + check_and_send_usage_report, get_billing_file_download_permission, + get_groups_price, get_min_price_billing_file_download, get_onetime_download, + get_secret_download, is_billing_item, parse_one_time_download_token, + parse_secret_download_token, validate_download_record, + validate_onetime_download_token, validate_secret_download_token) def weko_view_method(pid, record, template=None, **kwargs): @@ -449,12 +450,6 @@ def file_download_onetime(pid, record, _record_file_factory=None, **kwargs): return render_template(error_template, error="{} does not exist.".format(filename)) - # Create updated data - update_data = dict( - file_name=filename, record_id=record_id, user_mail=user_mail, - download_count=onetime_download.download_count - 1, - ) - # Check and send usage report for Guest User. if onetime_download.extra_info and 'open_restricted' == file_object.get( 'accessrole'): @@ -473,12 +468,9 @@ def file_download_onetime(pid, record, _record_file_factory=None, **kwargs): db.session.rollback() return render_template(error_template, error=_("Unexpected error occurred.")) - update_data['extra_info'] = extra_info - # Update download data - if not update_onetime_download(**update_data): - return render_template(error_template, - error=_("Unexpected error occurred.")) + onetime_download.increment_download_count() + onetime_download.update_extra_info(extra_info) return _download_file(file_object, False, 'en', file_object.obj, pid, record) @@ -565,16 +557,8 @@ def file_download_secret(pid, record, _record_file_factory=None, **kwargs): return render_template(error_template, error="{} does not exist.".format(filename)) - # Create updated data - update_data = dict( - file_name=filename, record_id=record_id, id=id, - download_count=secret_download.download_count - 1,created=str(date) - ) - # Update download data - if not update_secret_download(**update_data): - return render_template(error_template, - error=_("Unexpected error occurred.")) + secret_download.increment_download_count() # Get user's language and defautl language for PDF coverpage. lang = 'en' diff --git a/modules/weko-records-ui/weko_records_ui/models.py b/modules/weko-records-ui/weko_records_ui/models.py index 60d39a75c6..60c75ec930 100644 --- a/modules/weko-records-ui/weko_records_ui/models.py +++ b/modules/weko-records-ui/weko_records_ui/models.py @@ -297,30 +297,6 @@ class DownloadMixin: This mixin class is specifically designed for managing URL-related downloads, particularly one-time URLs and secret URLs. """ - @classmethod - def create(cls, **data): - """Create a new instance and save it to the database. - - Args: - **data: The attributes for the new instance. - - Returns: - object: The created instance, or None if an error occurred. - - Raises: - Exception: If the database commit fails, the transaction is rolled - back and the exception is re-raised. - """ - try: - file_download = cls(**data) - db.session.add(file_download) - db.session.commit() - return file_download - except Exception as ex: - db.session.rollback() - current_app.logger.error(ex) - return None - def increment_download_count(self): """Increment the 'download_count' attribute by 1 and commit the change. @@ -332,19 +308,23 @@ def increment_download_count(self): self: The updated instance with 'download_count' incremented. Raises: + ValueError: If the download count has already reached the limit. Exception: If the database commit fails, the transaction is rolled back and the exception is re-raised. """ - try: - self.download_count += 1 - db.session.commit() - return self - except Exception as ex: - db.session.rollback() - current_app.logger.error(ex) - raise ex + if self.download_count >= self.download_limit: + raise ValueError('Download limit has been reached.') + else: + try: + self.download_count += 1 + db.session.commit() + return self + except Exception as ex: + db.session.rollback() + current_app.logger.error(ex) + raise ex - def delete_logicaly(self): + def delete_logically(self): """Execute logical deletion by setting the 'is_deleted' flag to True. This marks the record as deleted without removing it from the database. @@ -390,7 +370,7 @@ class FileOnetimeDownload(db.Model, Timestamp, DownloadMixin): """ __tablename__ = 'file_onetime_download' id = db.Column(db.Integer,primary_key=True,autoincrement=True) - creator_id = db.Column( + approver_id = db.Column( db.Integer, db.ForeignKey( 'accounts_user.id', @@ -444,6 +424,30 @@ def __init__( self.is_guest = is_guest self.extra_info = extra_info + @classmethod + def create(cls, **data): + """Create a new instance and save it to the database. + + Args: + **data: The attributes for the new instance. + + Returns: + FileOnetimeDownload: The created instance, or None if an error occurred. + + Raises: + Exception: If the database commit fails, the transaction is rolled + back and the exception is re-raised. + """ + try: + file_download = cls(**data) + db.session.add(file_download) + db.session.commit() + return file_download + except Exception as ex: + db.session.rollback() + current_app.logger.error(ex) + return None + @classmethod def update_download(cls, **data): """Update download count. @@ -504,6 +508,30 @@ def find_downloadable_only(cls, **obj) -> list: ) return query.order_by(desc(cls.id)).all() + def update_extra_info(self, new_info: dict): + """Update the 'extra_info' field with the provided new data. + + Args: + new_info (dict): A dictionary containing the new info to update. + + Returns: + self: The updated instance with the new 'extra_info'. + + Raises: + Exception: If the database commit fails, the transaction is rolled + back and the exception is re-raised. + """ + try: + if not self.extra_info: + self.extra_info = {} + self.extra_info.update(new_info) + db.session.commit() + return self + except Exception as ex: + db.session.rollback() + current_app.logger.error(ex) + raise ex + class FileSecretDownload(db.Model, Timestamp, DownloadMixin): """A model class for 'file_secret_download' table. @@ -565,6 +593,20 @@ def __init__(self, creator_id, record_id, file_name, label_name, self.expiration_date = expiration_date self.download_limit = download_limit + @classmethod + def create(cls, **data): + """Create a new instance and save it to the database. + + Args: + **data: The attributes for the new instance. + + Returns: + object: The created instance, or None if an error occurred. + + Raises: + Exception: If the database commit fails, the transaction is rolled + back and the exception is re-raised. + """ @classmethod def update_download(cls, **data): diff --git a/modules/weko-records-ui/weko_records_ui/utils.py b/modules/weko-records-ui/weko_records_ui/utils.py index e69bfd20c7..66f69733ac 100644 --- a/modules/weko-records-ui/weko_records_ui/utils.py +++ b/modules/weko-records-ui/weko_records_ui/utils.py @@ -1325,15 +1325,6 @@ def create_onetime_download_url( return False -def update_onetime_download(**kwargs) -> Optional[List[FileOnetimeDownload]]: - """Update onetime download. - - @param kwargs: - @return: - """ - return FileOnetimeDownload.update_download(**kwargs) - - def get_workflows(): """Get workflow. @@ -1928,16 +1919,3 @@ def _create_secret_download_url(file_name: str, record_id: str, user_mail: str) "download_count": download_limit, }) return file_secret - - - -def update_secret_download(**kwargs) -> Optional[List[FileSecretDownload]]: - """Update secret download. - - Args - kwargs: - Returns - updated List[FileSecretDownload] or None - """ - current_app.logger.debug("update_secret_download:{}".format(kwargs)) - return FileSecretDownload.update_download(**kwargs) \ No newline at end of file From 33a7b77758303dbd807ca492aa3599d5f3c50c84 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Wed, 18 Dec 2024 11:17:56 +0900 Subject: [PATCH 03/61] Add minor changes to models --- .../weko-records-ui/weko_records_ui/models.py | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/models.py b/modules/weko-records-ui/weko_records_ui/models.py index 60c75ec930..bb481c98d2 100644 --- a/modules/weko-records-ui/weko_records_ui/models.py +++ b/modules/weko-records-ui/weko_records_ui/models.py @@ -293,9 +293,14 @@ class DownloadMixin: """A mixin class that provides common methods for managing download-related functionality. + This mixin class is specifically designed for managing URL-related + downloads, particularly one-time URLs and secret URLs. + Note: - This mixin class is specifically designed for managing URL-related - downloads, particularly one-time URLs and secret URLs. + To use this mixin, the model class must have the following attributes: + - download_limit (int): The maximum number of downloads allowed. + - download_count (int): The number the URL has been downloaded. + - is_deleted (bool): Indicates whether the record is deleted. """ def increment_download_count(self): @@ -376,7 +381,7 @@ class FileOnetimeDownload(db.Model, Timestamp, DownloadMixin): 'accounts_user.id', name='fk_file_onetime_download_approver_id'), nullable=False) - record_id = db.Column(db.String(255),nullable=False) + record_id = db.Column(db.String(255), nullable=False) file_name = db.Column(db.String(255), nullable=False) expiration_date = db.Column(db.DateTime, nullable=False) download_limit = db.Column(db.Integer, nullable=False) @@ -432,7 +437,7 @@ def create(cls, **data): **data: The attributes for the new instance. Returns: - FileOnetimeDownload: The created instance, or None if an error occurred. + FileOnetimeDownload: The created instance, or None if error occurs. Raises: Exception: If the database commit fails, the transaction is rolled @@ -601,12 +606,21 @@ def create(cls, **data): **data: The attributes for the new instance. Returns: - object: The created instance, or None if an error occurred. + FileSecretDownload: The created instance, or None if error occurs. Raises: Exception: If the database commit fails, the transaction is rolled back and the exception is re-raised. """ + try: + file_download = cls(**data) + db.session.add(file_download) + db.session.commit() + return file_download + except Exception as ex: + db.session.rollback() + current_app.logger.error(ex) + return None @classmethod def update_download(cls, **data): From af1b36ae2e90aeee4f5cbc1fc3cffaa5243d9bea Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Wed, 18 Dec 2024 11:31:38 +0900 Subject: [PATCH 04/61] Fix update_extra_info method --- modules/weko-records-ui/weko_records_ui/models.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/models.py b/modules/weko-records-ui/weko_records_ui/models.py index bb481c98d2..2107e9144b 100644 --- a/modules/weko-records-ui/weko_records_ui/models.py +++ b/modules/weko-records-ui/weko_records_ui/models.py @@ -527,9 +527,7 @@ def update_extra_info(self, new_info: dict): back and the exception is re-raised. """ try: - if not self.extra_info: - self.extra_info = {} - self.extra_info.update(new_info) + self.extra_info = new_info db.session.commit() return self except Exception as ex: From 15baf77e39fd44ca14da5e85d5cf32c6496a7cbf Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Wed, 18 Dec 2024 11:34:50 +0900 Subject: [PATCH 05/61] Add validation to update method --- .../weko-records-ui/weko_records_ui/models.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/models.py b/modules/weko-records-ui/weko_records_ui/models.py index 2107e9144b..a35b059c5f 100644 --- a/modules/weko-records-ui/weko_records_ui/models.py +++ b/modules/weko-records-ui/weko_records_ui/models.py @@ -526,14 +526,17 @@ def update_extra_info(self, new_info: dict): Exception: If the database commit fails, the transaction is rolled back and the exception is re-raised. """ - try: - self.extra_info = new_info - db.session.commit() - return self - except Exception as ex: - db.session.rollback() - current_app.logger.error(ex) - raise ex + if not isinstance(new_info, dict): + raise ValueError('The new info must be a dictionary.') + else: + try: + self.extra_info = new_info + db.session.commit() + return self + except Exception as ex: + db.session.rollback() + current_app.logger.error(ex) + raise ex class FileSecretDownload(db.Model, Timestamp, DownloadMixin): From 441c9a0ac4efa4590e6cdb62ef361f7be9ee4add Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Fri, 20 Dec 2024 10:27:03 +0900 Subject: [PATCH 06/61] Add showing error template --- modules/weko-records-ui/weko_records_ui/fd.py | 10 ++-- .../weko-records-ui/weko_records_ui/models.py | 51 ++++++------------- 2 files changed, 22 insertions(+), 39 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/fd.py b/modules/weko-records-ui/weko_records_ui/fd.py index b9becb387c..4473498771 100644 --- a/modules/weko-records-ui/weko_records_ui/fd.py +++ b/modules/weko-records-ui/weko_records_ui/fd.py @@ -469,8 +469,10 @@ def file_download_onetime(pid, record, _record_file_factory=None, **kwargs): return render_template(error_template, error=_("Unexpected error occurred.")) # Update download data - onetime_download.increment_download_count() - onetime_download.update_extra_info(extra_info) + if (not onetime_download.increment_download_count() or + not onetime_download.update_extra_info(extra_info)): + return render_template(error_template, + error=_("Unexpected error occurred.")) return _download_file(file_object, False, 'en', file_object.obj, pid, record) @@ -558,7 +560,9 @@ def file_download_secret(pid, record, _record_file_factory=None, **kwargs): error="{} does not exist.".format(filename)) # Update download data - secret_download.increment_download_count() + if not secret_download.increment_download_count(): + return render_template(error_template, + error=_("Unexpected error occurred")) # Get user's language and defautl language for PDF coverpage. lang = 'en' diff --git a/modules/weko-records-ui/weko_records_ui/models.py b/modules/weko-records-ui/weko_records_ui/models.py index a35b059c5f..3d0de108ea 100644 --- a/modules/weko-records-ui/weko_records_ui/models.py +++ b/modules/weko-records-ui/weko_records_ui/models.py @@ -310,12 +310,7 @@ def increment_download_count(self): and persists the change to the database. Returns: - self: The updated instance with 'download_count' incremented. - - Raises: - ValueError: If the download count has already reached the limit. - Exception: If the database commit fails, the transaction is rolled - back and the exception is re-raised. + bool: True if the download count is successfully updated """ if self.download_count >= self.download_limit: raise ValueError('Download limit has been reached.') @@ -323,11 +318,11 @@ def increment_download_count(self): try: self.download_count += 1 db.session.commit() - return self + return True except Exception as ex: db.session.rollback() current_app.logger.error(ex) - raise ex + return False def delete_logically(self): """Execute logical deletion by setting the 'is_deleted' flag to True. @@ -335,20 +330,16 @@ def delete_logically(self): This marks the record as deleted without removing it from the database. Returns: - self: The updated instance with 'is_deleted' set to True. - - Raises: - Exception: If the database commit fails, the transaction is rolled - back and the exception is re-raised. + bool: True if the record is successfully marked as deleted. """ try: self.is_deleted = True db.session.commit() - return self + return True except Exception as ex: db.session.rollback() current_app.logger.error(ex) - raise ex + return False class FileOnetimeDownload(db.Model, Timestamp, DownloadMixin): @@ -375,7 +366,7 @@ class FileOnetimeDownload(db.Model, Timestamp, DownloadMixin): """ __tablename__ = 'file_onetime_download' id = db.Column(db.Integer,primary_key=True,autoincrement=True) - approver_id = db.Column( + approver_id = db.Column( db.Integer, db.ForeignKey( 'accounts_user.id', @@ -432,16 +423,12 @@ def __init__( @classmethod def create(cls, **data): """Create a new instance and save it to the database. - + Args: **data: The attributes for the new instance. - + Returns: FileOnetimeDownload: The created instance, or None if error occurs. - - Raises: - Exception: If the database commit fails, the transaction is rolled - back and the exception is re-raised. """ try: file_download = cls(**data) @@ -496,7 +483,7 @@ def find(cls, **obj) -> list: cls.user_mail == obj.get("user_mail"), ) return query.order_by(desc(cls.id)).all() - + @classmethod def find_downloadable_only(cls, **obj) -> list: """If the user can download ,find file onetime download. @@ -520,11 +507,7 @@ def update_extra_info(self, new_info: dict): new_info (dict): A dictionary containing the new info to update. Returns: - self: The updated instance with the new 'extra_info'. - - Raises: - Exception: If the database commit fails, the transaction is rolled - back and the exception is re-raised. + bool: True if the update is successful, False otherwise. """ if not isinstance(new_info, dict): raise ValueError('The new info must be a dictionary.') @@ -532,11 +515,11 @@ def update_extra_info(self, new_info: dict): try: self.extra_info = new_info db.session.commit() - return self + return True except Exception as ex: db.session.rollback() current_app.logger.error(ex) - raise ex + return False class FileSecretDownload(db.Model, Timestamp, DownloadMixin): @@ -602,16 +585,12 @@ def __init__(self, creator_id, record_id, file_name, label_name, @classmethod def create(cls, **data): """Create a new instance and save it to the database. - + Args: **data: The attributes for the new instance. - + Returns: FileSecretDownload: The created instance, or None if error occurs. - - Raises: - Exception: If the database commit fails, the transaction is rolled - back and the exception is re-raised. """ try: file_download = cls(**data) From a8afcb6e28c4179a055c408ff97866ea3a26e30c Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Tue, 24 Dec 2024 11:03:54 +0900 Subject: [PATCH 07/61] Fix error handlings in models --- modules/weko-records-ui/weko_records_ui/fd.py | 14 ++- .../weko-records-ui/weko_records_ui/models.py | 93 ++++--------------- 2 files changed, 28 insertions(+), 79 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/fd.py b/modules/weko-records-ui/weko_records_ui/fd.py index 4473498771..361601a195 100644 --- a/modules/weko-records-ui/weko_records_ui/fd.py +++ b/modules/weko-records-ui/weko_records_ui/fd.py @@ -469,8 +469,10 @@ def file_download_onetime(pid, record, _record_file_factory=None, **kwargs): return render_template(error_template, error=_("Unexpected error occurred.")) # Update download data - if (not onetime_download.increment_download_count() or - not onetime_download.update_extra_info(extra_info)): + try: + onetime_download.increment_download_count() + onetime_download.update_extra_info(extra_info) + except: return render_template(error_template, error=_("Unexpected error occurred.")) @@ -560,9 +562,11 @@ def file_download_secret(pid, record, _record_file_factory=None, **kwargs): error="{} does not exist.".format(filename)) # Update download data - if not secret_download.increment_download_count(): + try: + secret_download.increment_download_count() + except: return render_template(error_template, - error=_("Unexpected error occurred")) + error=_("Unexpected error occurred.")) # Get user's language and defautl language for PDF coverpage. lang = 'en' @@ -570,4 +574,4 @@ def file_download_secret(pid, record, _record_file_factory=None, **kwargs): user_profile = UserProfile.get_by_userid(current_user.get_id()) lang = user_profile.language if user_profile and user_profile.language \ else 'en' - return _download_file(file_object, False, lang, file_object.obj, pid, record) \ No newline at end of file + return _download_file(file_object, False, lang, file_object.obj, pid, record) diff --git a/modules/weko-records-ui/weko_records_ui/models.py b/modules/weko-records-ui/weko_records_ui/models.py index 3d0de108ea..b719d939fa 100644 --- a/modules/weko-records-ui/weko_records_ui/models.py +++ b/modules/weko-records-ui/weko_records_ui/models.py @@ -309,8 +309,9 @@ def increment_download_count(self): This method increases the download count for the instance by one and persists the change to the database. - Returns: - bool: True if the download count is successfully updated + Raises: + ValueError: If the download limit has been reached. + Exception: If an unexpected error occurs during the update. """ if self.download_count >= self.download_limit: raise ValueError('Download limit has been reached.') @@ -318,28 +319,26 @@ def increment_download_count(self): try: self.download_count += 1 db.session.commit() - return True except Exception as ex: db.session.rollback() current_app.logger.error(ex) - return False + raise ex def delete_logically(self): """Execute logical deletion by setting the 'is_deleted' flag to True. This marks the record as deleted without removing it from the database. - Returns: - bool: True if the record is successfully marked as deleted. + Raises: + Exception: If an unexpected error occurs during the deletion. """ try: self.is_deleted = True db.session.commit() - return True except Exception as ex: db.session.rollback() current_app.logger.error(ex) - return False + raise ex class FileOnetimeDownload(db.Model, Timestamp, DownloadMixin): @@ -428,7 +427,10 @@ def create(cls, **data): **data: The attributes for the new instance. Returns: - FileOnetimeDownload: The created instance, or None if error occurs. + FileOnetimeDownload: The created instance. + + Raises: + Exception: If an unexpected error occurs during the creation. """ try: file_download = cls(**data) @@ -438,37 +440,7 @@ def create(cls, **data): except Exception as ex: db.session.rollback() current_app.logger.error(ex) - return None - - @classmethod - def update_download(cls, **data): - """Update download count. - - :param data: - :return: - """ - try: - file_name = data.get("file_name") - user_mail = data.get("user_mail") - record_id = data.get("record_id") - file_permission = cls.find(file_name=file_name, user_mail=user_mail, - record_id=record_id) - if file_permission and len(file_permission) > 0: - for file in file_permission: - if data.get("download_count") is not None: - file.download_count = data.get("download_count") - if data.get("expiration_date") is not None: - file.expiration_date = data.get("expiration_date") - if data.get("extra_info"): - file.extra_info = data.get("extra_info") - db.session.merge(file) - db.session.commit() - return file_permission - return None - except Exception as ex: - db.session.rollback() - current_app.logger.error(ex) - return None + raise ex @classmethod def find(cls, **obj) -> list: @@ -506,8 +478,9 @@ def update_extra_info(self, new_info: dict): Args: new_info (dict): A dictionary containing the new info to update. - Returns: - bool: True if the update is successful, False otherwise. + Raises: + ValueError: If the new info is not a dictionary. + Exception: If an unexpected error occurs during the update. """ if not isinstance(new_info, dict): raise ValueError('The new info must be a dictionary.') @@ -515,11 +488,10 @@ def update_extra_info(self, new_info: dict): try: self.extra_info = new_info db.session.commit() - return True except Exception as ex: db.session.rollback() current_app.logger.error(ex) - return False + raise ex class FileSecretDownload(db.Model, Timestamp, DownloadMixin): @@ -591,6 +563,9 @@ def create(cls, **data): Returns: FileSecretDownload: The created instance, or None if error occurs. + + Raises: + Exception: If an unexpected error occurs during the creation. """ try: file_download = cls(**data) @@ -600,36 +575,6 @@ def create(cls, **data): except Exception as ex: db.session.rollback() current_app.logger.error(ex) - return None - - @classmethod - def update_download(cls, **data): - """Update download count. - - :param data: - :return: - """ - try: - file_name = data.get("file_name") - id = data.get("id") - record_id = data.get("record_id") - created = data.get("created") - current_app.logger.debug("data: {}".format(data)) - file_permission = cls.find(file_name=file_name, id=id, - record_id=record_id,created=created) - current_app.logger.debug("file_permission: {}".format(file_permission)) - if len(file_permission) == 1: - file = file_permission[0] - if data.get("download_count") is not None: - file.download_count = data.get("download_count") - db.session.merge(file) - db.session.commit() - return file_permission - else: - return None - except Exception as ex: - db.session.rollback() - current_app.logger.error(traceback.format_exc()) raise ex @classmethod From 141bd69dbafc49ce16880fafad493e01e7eacf64 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Wed, 25 Dec 2024 15:06:12 +0900 Subject: [PATCH 08/61] Add validations and constraints --- .../weko-records-ui/weko_records_ui/models.py | 64 +++++++++++-------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/models.py b/modules/weko-records-ui/weko_records_ui/models.py index b719d939fa..843dc120b6 100644 --- a/modules/weko-records-ui/weko_records_ui/models.py +++ b/modules/weko-records-ui/weko_records_ui/models.py @@ -22,13 +22,11 @@ """Database models for weko-admin.""" from datetime import datetime -from datetime import timedelta -import traceback from typing import List from flask import current_app from invenio_db import db -from sqlalchemy import CheckConstraint, desc, ForeignKey, func +from sqlalchemy import CheckConstraint, desc, func from sqlalchemy.dialects import postgresql from sqlalchemy.dialects.postgresql import INTERVAL from sqlalchemy.sql.functions import concat ,now @@ -315,14 +313,13 @@ def increment_download_count(self): """ if self.download_count >= self.download_limit: raise ValueError('Download limit has been reached.') - else: - try: - self.download_count += 1 - db.session.commit() - except Exception as ex: - db.session.rollback() - current_app.logger.error(ex) - raise ex + try: + self.download_count += 1 + db.session.commit() + except Exception as ex: + db.session.rollback() + current_app.logger.error(ex) + raise ex def delete_logically(self): """Execute logical deletion by setting the 'is_deleted' flag to True. @@ -386,8 +383,12 @@ class FileOnetimeDownload(db.Model, Timestamp, DownloadMixin): default=lambda: dict(), nullable=True,) __table_args__ = ( - CheckConstraint('created < expiration_date', name='check_expire_date'), - CheckConstraint('download_limit > 0', name='check_dl_limit_positive'), + CheckConstraint('created < expiration_date', + name='check_expiration_date'), + CheckConstraint('download_limit > 0', + name='check_download_limit_positive'), + CheckConstraint('download_count <= download_limit', + name='check_download_count_limit'), ) def __init__( @@ -432,6 +433,10 @@ def create(cls, **data): Raises: Exception: If an unexpected error occurs during the creation. """ + if data.get('expiration_date') < datetime.now(): + raise ValueError('The expiration date must be in the future.') + if data.get('download_limit') <= 0: + raise ValueError('The download limit must be greater than 0.') try: file_download = cls(**data) db.session.add(file_download) @@ -484,14 +489,13 @@ def update_extra_info(self, new_info: dict): """ if not isinstance(new_info, dict): raise ValueError('The new info must be a dictionary.') - else: - try: - self.extra_info = new_info - db.session.commit() - except Exception as ex: - db.session.rollback() - current_app.logger.error(ex) - raise ex + try: + self.extra_info = new_info + db.session.commit() + except Exception as ex: + db.session.rollback() + current_app.logger.error(ex) + raise ex class FileSecretDownload(db.Model, Timestamp, DownloadMixin): @@ -512,7 +516,6 @@ class FileSecretDownload(db.Model, Timestamp, DownloadMixin): is_deleted (bool): A flag indicating whether the record is deleted. """ __tablename__ = 'file_secret_download' - id = db.Column(db.Integer,primary_key=True,autoincrement=True) creator_id = db.Column(db.Integer, db.ForeignKey( @@ -526,9 +529,13 @@ class FileSecretDownload(db.Model, Timestamp, DownloadMixin): download_limit = db.Column(db.Integer, nullable=False) download_count = db.Column(db.Integer, nullable=False, default=0) is_deleted = db.Column(db.Boolean, nullable=False, default=False) - __table_args__ = ( - CheckConstraint('created < expiration_date', name='check_expire_date'), - CheckConstraint('download_limit > 0', name='check_dl_limit_positive'), + __table_args__ = ( + CheckConstraint('created < expiration_date', + name='check_expiration_date'), + CheckConstraint('download_limit > 0', + name='check_download_limit_positive'), + CheckConstraint('download_count <= download_limit', + name='check_download_count_limit'), ) def __init__(self, creator_id, record_id, file_name, label_name, @@ -562,11 +569,16 @@ def create(cls, **data): **data: The attributes for the new instance. Returns: - FileSecretDownload: The created instance, or None if error occurs. + FileSecretDownload: The created instance. Raises: + ValueError: If the arguments are invalid. Exception: If an unexpected error occurs during the creation. """ + if data.get('expiration_date') < datetime.now(): + raise ValueError('The expiration date must be in the future.') + if data.get('download_limit') <= 0: + raise ValueError('The download limit must be greater than 0.') try: file_download = cls(**data) db.session.add(file_download) From dc6599ccae89725bf209da5a64558be05fdcbca3 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Wed, 25 Dec 2024 15:06:56 +0900 Subject: [PATCH 09/61] Add tests for model changes --- modules/weko-records-ui/tests/test_models.py | 312 ++++++++++++------- 1 file changed, 199 insertions(+), 113 deletions(-) diff --git a/modules/weko-records-ui/tests/test_models.py b/modules/weko-records-ui/tests/test_models.py index c8b72a5140..c554db172e 100644 --- a/modules/weko-records-ui/tests/test_models.py +++ b/modules/weko-records-ui/tests/test_models.py @@ -1,5 +1,6 @@ import io from datetime import datetime, timedelta, timezone +from sqlite3 import IntegrityError from unittest import mock # python3 #from unittest.mock import MagicMock @@ -135,6 +136,204 @@ def test_find_by_activity(db_file_permission): sorted_list = sorted(listpermission, key= lambda x: x.id ,reverse=True) assert listpermission == sorted_list + +class TestFileOnetimeDownload: + expiration_date = datetime.now() + timedelta(hours=24) + base_data = { + 'approver_id': 1, + 'record_id': '1', + 'file_name': 'test file', + 'expiration_date': expiration_date, + 'download_limit': 1, + 'user_mail': 'test@example.org', + 'is_guest': False, + 'extra_info': {'info': 'value'} + } + + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileOnetimeDownload::test_init -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings + def test_init(self, db): + obj = FileOnetimeDownload(**self.base_data) + for key, value in self.base_data.items(): + assert getattr(obj, key) == value + for key in self.base_data.keys(): + bad_data = self.base_data.copy() + bad_data.pop(key) + with pytest.raises(TypeError): + FileOnetimeDownload(**bad_data) + + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileOnetimeDownload::test_create -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings + def test_create(self, users): + assert FileOnetimeDownload.query.count() == 0 + obj = FileOnetimeDownload.create(**self.base_data) + assert isinstance(obj, FileOnetimeDownload) + assert FileOnetimeDownload.query.count() == 1 + rec = FileOnetimeDownload.query.first() + for key, value in self.base_data.items(): + assert getattr(rec, key) == value + + bad_data1 = self.base_data.copy() + bad_data1['expiration_date'] = datetime.now() - timedelta(hours=24) + with pytest.raises(Exception): + FileOnetimeDownload.create(**bad_data1) + assert FileOnetimeDownload.query.count() == 1 + + bad_data2 = self.base_data.copy() + bad_data2['download_limit'] = -1 + with pytest.raises(Exception): + FileOnetimeDownload.create(**bad_data2) + assert FileOnetimeDownload.query.count() == 1 + + with patch('weko_records_ui.models.db.session.commit') as mock_commit: + mock_commit.side_effect = Exception('DB error test') + with pytest.raises(Exception): + FileOnetimeDownload.create(**self.base_data) + mock_commit.assert_called_once() + assert FileOnetimeDownload.query.count() == 1 + + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileOnetimeDownload::test_update_extra_info -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings + def test_update_extra_info(self, users): + obj = FileOnetimeDownload.create(**self.base_data) + obj.update_extra_info({'new': 'value'}) + assert obj.extra_info == {'new': 'value'} + + with pytest.raises(ValueError): + obj.update_extra_info('invalid') + assert obj.extra_info == {'new': 'value'} + + with patch('weko_records_ui.models.db.session.commit') as mock_commit: + mock_commit.side_effect = Exception('DB error test') + with pytest.raises(Exception): + obj.update_extra_info({'new': 'value2'}) + mock_commit.assert_called_once() + assert obj.extra_info == {'new': 'value'} + + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileOnetimeDownload::test_increment_download_count -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings + def test_increment_download_count(self, users): + rec = FileOnetimeDownload.create(**self.base_data) + assert rec.download_count == 0 + rec.increment_download_count() + assert rec.download_count == 1 + + with pytest.raises(ValueError): + rec.increment_download_count() + assert rec.download_count == 1 + + rec2 = FileOnetimeDownload.create(**self.base_data) + with patch('weko_records_ui.models.db.session.commit') as mock_commit: + mock_commit.side_effect = Exception('DB error test') + with pytest.raises(Exception): + rec2.increment_download_count() + mock_commit.assert_called_once() + assert rec2.download_count == 0 + + + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileOnetimeDownload::test_delete_logically -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings + def test_delete_logically(self, users): + rec = FileOnetimeDownload.create(**self.base_data) + assert rec.is_deleted is False + rec.delete_logically() + assert rec.is_deleted is True + + rec.delete_logically() + assert rec.is_deleted is True + + rec2 = FileOnetimeDownload.create(**self.base_data) + with patch('weko_records_ui.models.db.session.commit') as mock_commit: + mock_commit.side_effect = Exception('DB error test') + with pytest.raises(Exception): + rec2.delete_logically() + mock_commit.assert_called_once() + assert rec2.is_deleted is False + +class TestFileSecretDownload: + expiration_date = datetime.now() + timedelta(hours=24) + base_data = { + 'creator_id': 1, + 'record_id': '1', + 'file_name': 'test file', + 'label_name': 'test label', + 'expiration_date': expiration_date, + 'download_limit': 1 + } + + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileSecretDownload::test_init -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings + def test_init(self, db): + obj = FileSecretDownload(**self.base_data) + for key, value in self.base_data.items(): + assert getattr(obj, key) == value + for key in self.base_data.keys(): + bad_data = self.base_data.copy() + bad_data.pop(key) + with pytest.raises(TypeError): + FileSecretDownload(**bad_data) + + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileSecretDownload::test_create -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings + def test_create(self, users): + assert FileSecretDownload.query.count() == 0 + obj = FileSecretDownload.create(**self.base_data) + assert isinstance(obj, FileSecretDownload) + assert FileSecretDownload.query.count() == 1 + rec = FileSecretDownload.query.first() + for key, value in self.base_data.items(): + assert getattr(rec, key) == value + + bad_data1 = self.base_data.copy() + bad_data1['expiration_date'] = datetime.now() - timedelta(hours=24) + with pytest.raises(Exception): + FileSecretDownload.create(**bad_data1) + assert FileSecretDownload.query.count() == 1 + + bad_data2 = self.base_data.copy() + bad_data2['download_limit'] = -1 + with pytest.raises(Exception): + FileSecretDownload.create(**bad_data2) + assert FileSecretDownload.query.count() == 1 + + with patch('weko_records_ui.models.db.session.commit') as mock_commit: + mock_commit.side_effect = Exception('DB error test') + with pytest.raises(Exception): + FileSecretDownload.create(**self.base_data) + mock_commit.assert_called_once() + assert FileSecretDownload.query.count() == 1 + + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileSecretDownload::test_increment_download_count -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings + def test_increment_download_count(self, users): + rec = FileSecretDownload.create(**self.base_data) + assert rec.download_count == 0 + rec.increment_download_count() + assert rec.download_count == 1 + + with pytest.raises(ValueError): + rec.increment_download_count() + assert rec.download_count == 1 + + rec2 = FileSecretDownload.create(**self.base_data) + with patch('weko_records_ui.models.db.session.commit') as mock_commit: + mock_commit.side_effect = Exception('DB error test') + with pytest.raises(Exception): + rec2.increment_download_count() + mock_commit.assert_called_once() + assert rec2.download_count == 0 + + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileSecretDownload::test_delete_logically -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings + def test_delete_logically(self, users): + rec = FileSecretDownload.create(**self.base_data) + assert rec.is_deleted is False + rec.delete_logically() + assert rec.is_deleted is True + + rec.delete_logically() + assert rec.is_deleted is True + + rec2 = FileSecretDownload.create(**self.base_data) + with patch('weko_records_ui.models.db.session.commit') as mock_commit: + mock_commit.side_effect = Exception('DB error test') + with pytest.raises(Exception): + rec2.delete_logically() + mock_commit.assert_called_once() + assert rec2.is_deleted is False + + # def find_downloadable_only(cls, **obj) -> list: # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::test_find_downloadable_only -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp @pytest.mark.skip(reason="'from sqlalchemy.dialects.postgresql import INTERVAL' can't tests on SQLite.") @@ -153,116 +352,3 @@ def test_find_downloadable_only(app,db): recs = FileOnetimeDownload.find_downloadable_only(user_mail=user_mail,record_id=record_id,file_name=file_name) assert len(recs) == 2 - - -# def find_by_activity: -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::test_find_by_activity -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -def test_init(): - # 36 - dl = FileSecretDownload("a","b","c",1,2) - assert dl.file_name == "a" - assert dl.user_mail == "b" - assert dl.record_id == "c" - assert dl.download_count == 1 - assert dl.expiration_date == 2 - -# def create(cls, **data): -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::test_create -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -def test_create(app,db): - # 37 42 - with app.test_request_context(): - dl = FileSecretDownload.create( - file_name = "a" - ,user_mail ="b" - ,record_id = "c" - ,download_count = 1 - ,expiration_date= 2 - ) - assert dl.id - assert dl.created - rec = FileSecretDownload.find( - id = dl.id - ,file_name = "a" - ,record_id = "c" - ,created = dl.created - ) - assert len(rec) == 1 - rec = rec[0] - assert rec.file_name == "a" - assert rec.user_mail == "b" - assert rec.record_id == "c" - assert rec.download_count == 1 - assert rec.expiration_date == 2 - - # 38 - with app.test_request_context(): - with patch("weko_records_ui.models.db.session.add", side_effect=Exception("test_error")): - defaultlength = len(FileSecretDownload.query.filter_by().all()) - with pytest.raises(Exception): - dl = FileSecretDownload.create( - file_name = "a" - ,user_mail ="b" - ,record_id = "c" - ) - assert defaultlength == len(FileSecretDownload.query.filter_by().all()) - -# def update_download(cls, **data): -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::test_update_download -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -def test_update_download(app,db): - with app.test_request_context(): - # 39 - dl = FileSecretDownload.create( - file_name = "a" - ,user_mail ="b" - ,record_id = "c" - ,download_count = 1 - ,expiration_date= 2 - ) - result = FileSecretDownload.update_download( - id = dl.id - ,file_name = "a" - ,record_id = "c" - ,created = dl.created - ,download_count = 100 - ) - if result: - assert result[0].download_count == 100 - else: - assert False - - # 40 - assert FileSecretDownload.update_download( - id = dl.id + 1 - ,file_name = "a" - ,record_id = "c" - ,created = dl.created - ) == None - assert FileSecretDownload.update_download( - id = dl.id - ,file_name = "a" - ,record_id = "c" - ,created = dl.created - ) -def test_update_download2(app,db): - with app.test_request_context(): - # 41 - dl = FileSecretDownload.create( - file_name = "a" - ,user_mail ="b" - ,record_id = "c" - ,download_count = 1 - ,expiration_date= 2 - ) - with patch("weko_records_ui.models.db.session.merge", side_effect=Exception("test_error")): - before = FileSecretDownload.query.filter_by().one_or_none().download_count - try: - dl = FileSecretDownload.update_download( - id = dl.id - ,file_name = "a" - ,record_id = "c" - ,created = dl.created - ,download_count = 200 - ) - assert False - except: - assert before == FileSecretDownload.query.filter_by().one_or_none().download_count From 141ed73ab64222ee72b1b667e1cc06fa6c64441c Mon Sep 17 00:00:00 2001 From: "sei.nakamura" Date: Wed, 25 Dec 2024 17:54:11 +0900 Subject: [PATCH 10/61] =?UTF-8?q?=E3=82=B7=E3=83=BC=E3=82=AF=E3=83=AC?= =?UTF-8?q?=E3=83=83=E3=83=88URL=E3=83=9C=E3=82=BF=E3=83=B3=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E6=A8=A9=E9=99=90=E6=A9=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/weko-records-ui/tests/test_utils.py | 134 +++++++++++ modules/weko-records-ui/tests/test_views.py | 225 ++++++------------ .../weko-records-ui/weko_records_ui/utils.py | 89 +++++++ .../weko-records-ui/weko_records_ui/views.py | 51 +--- 4 files changed, 299 insertions(+), 200 deletions(-) diff --git a/modules/weko-records-ui/tests/test_utils.py b/modules/weko-records-ui/tests/test_utils.py index 579ecfb0be..090b3142c8 100644 --- a/modules/weko-records-ui/tests/test_utils.py +++ b/modules/weko-records-ui/tests/test_utils.py @@ -45,6 +45,10 @@ check_items_settings, #RoCrateConverter, #create_tsv + is_secret_url_feature_enabled, + has_permission_to_manage_secret_url, + is_secret_file, + can_manage_secret_url, ) import base64 from unittest.mock import MagicMock @@ -644,6 +648,135 @@ def test_validate_download_record(app, records): except: pass +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_is_secret_url_feature_enabled -vv -s --cov-branch --cov-report=html --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings +def test_is_secret_url_feature_enabled(app): + with app.app_context(): + # Case 1: Trueを返す + with patch('weko_records_ui.utils.AdminSettings.get') as mock_get: + mock_get.return_value = { + 'secret_URL_file_download': { + 'secret_enable': True, + } + } + assert is_secret_url_feature_enabled() is True + + # Case 2: Falseを返す + with patch('weko_records_ui.utils.AdminSettings.get') as mock_get: + mock_get.return_value = { + 'secret_URL_file_download': { + 'secret_enable': False, + } + } + assert is_secret_url_feature_enabled() is False + + # Case 3: AdminSettingsがNoneでcurrent_app.configが存在し、期待するデフォルト設定がある場合 + with patch('weko_records_ui.utils.AdminSettings.get', return_value=None): + with patch('weko_records_ui.utils.current_app.config', { + 'WEKO_ADMIN_RESTRICTED_ACCESS_SETTINGS': { + 'secret_URL_file_download': {} + } + }): + # secret_enable は存在しないのでデフォルト値の False を返すことを検証 + assert is_secret_url_feature_enabled() is False + + # Case 4: AdminSettingsがNoneでcurrent_app.configが存在しない場合 + with patch('weko_records_ui.utils.AdminSettings.get', return_value=None): + with patch('weko_records_ui.utils.current_app.config', {'WEKO_ADMIN_RESTRICTED_ACCESS_SETTINGS': {}}): + # 設定がない場合も False を返すことを検証 + assert is_secret_url_feature_enabled() is False + +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_has_permission_to_manage_secret_url -vv -s --cov-branch --cov-report=html --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings +@pytest.mark.parametrize( + "user_id, expected", + [ + (0, True), # Owner + (4, True), # Shared user + (1, True), # Superuser + (2, True), # Superuser + (3, False), # No permission + (5, False), # No superuser role + ], +) +def test_has_permission_to_manage_secret_url(user_id, expected, app, users): + # レコードに必要なデータを設定 + # 'owner'と'weko_shared_id'は、usersリストから取り出した値を使用 + record = {'owner': str(users[0]["id"]), 'weko_shared_id': users[4]["id"]} + + # アプリケーションコンテキスト内でテスト実行 + with app.app_context(): + # has_permission_to_manage_secret_url関数を実行し、 + # 結果が期待される値 (expected) と一致するかを検証 + assert has_permission_to_manage_secret_url(record, users[user_id]["id"]) == expected + +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_is_secret_file -vv -s --cov-branch --cov-report=html --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings +@pytest.mark.parametrize( + "file_data, filename, expected", + [ + # ケース1: accessroleが 'open_no' の場合 + ([{'filename': 'testfile.txt', 'accessrole': 'open_no', 'date': [{'dateValue': '2999-12-31'}]}], 'testfile.txt', True), + # ケース2: accessroleが 'open_date' で公開日が未来の場合 + ([{'filename': 'testfile.txt', 'accessrole': 'open_date', 'date': [{'dateValue': '2999-12-31'}]}], 'testfile.txt', True), + # ケース3: accessroleが 'open_date' で公開日が過去の場合 + ([{'filename': 'testfile.txt', 'accessrole': 'open_date', 'date': [{'dateValue': '2000-01-01'}]}], 'testfile.txt', False), + # ケース4: accessroleが 'open_no' や 'open_date' でない場合 + ([{'filename': 'testfile.txt', 'accessrole': 'open_test', 'date': [{'dateValue': '2999-12-31'}]}], 'testfile.txt', False), + # ケース5: ファイル名が一致しない場合 + ([{'filename': 'otherfile.txt', 'accessrole': 'open_no', 'date': [{'dateValue': '2999-12-31'}]}], 'testfile.txt', False), + # ケース6: file_dataが空の場合 + ([], 'testfile.txt', False), + ], +) +def test_is_secret_file(file_data, filename, expected): + # WekoRecordのモックを作成し、get_file_dataメソッドをファイルデータでモックする + mock_record = MagicMock(spec=WekoRecord) + mock_record.get_file_data.return_value = file_data # モックのget_file_dataメソッドが返す値を設定 + + # dt(日時関連)をモックして、現在の日付や日付文字列の変換を制御する + with patch('weko_records_ui.utils.dt') as mock_dt: + mock_dt.now.return_value = dt(2024, 1, 1) # 現在の日付を2023年1月1日に設定 + mock_dt.strptime.side_effect = lambda *args, **kwargs: dt.strptime(*args, **kwargs) # strptimeの動作をモック + + # is_secret_file関数を実行して、結果が期待される値と一致するかを確認 + result = is_secret_file(mock_record, filename) + + # 実際の結果が期待値と一致することを確認 + assert result == expected + +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_can_manage_secret_url -vv -s --cov-branch --cov-report=html --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings +@pytest.mark.parametrize( + "is_authenticated, feature_enabled, has_permission, is_secret, expected", + [ + # Case 1: ユーザーが認証されていない場合 + (False, True, True, True, False), + # Case 2: 機能が有効でない場合 + (True, False, True, True, False), + # Case 3: ユーザーに権限がない場合 + (True, True, False, True, False), + # Case 4: ファイルが秘密でない場合 + (True, True, True, False, False), + # Case 5: すべての条件を満たす場合 + (True, True, True, True, True), + ], +) +def test_can_manage_secret_url(is_authenticated, feature_enabled, has_permission, is_secret, expected): + # WekoRecordのモックを作成 + mock_record = MagicMock(spec=WekoRecord) + + # ユーザーのモックを作成 + mock_user = MagicMock() + mock_user.is_authenticated = is_authenticated # ユーザーが認証されているかどうかを設定 + + # current_userのモックを作成して、`mock_user`を返すように設定 + with patch('weko_records_ui.utils.current_user', mock_user): + # is_secret_url_feature_enabledのモックを作成して、`feature_enabled`を返すように設定 + with patch('weko_records_ui.utils.is_secret_url_feature_enabled', return_value=feature_enabled): + # has_permission_to_manage_secret_urlのモックを作成して、`has_permission`を返すように設定 + with patch('weko_records_ui.utils.has_permission_to_manage_secret_url', return_value=has_permission): + # is_secret_fileのモックを作成して、`is_secret`を返すように設定 + with patch('weko_records_ui.utils.is_secret_file', return_value=is_secret): + # can_manage_secret_url関数を実行し、結果が期待される値と一致するかを確認 + assert can_manage_secret_url(mock_record, 'testfile.txt') == expected + # def get_onetime_download(file_name: str, record_id: str, # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_get_onetime_download -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp @@ -1034,3 +1167,4 @@ def test_create_tsv(app, records): res_tsv = create_tsv(record.files) for field in WEKO_RECORDS_UI_TSV_FIELD_NAMES_DEFAULT: assert field in res_tsv.getvalue() + diff --git a/modules/weko-records-ui/tests/test_views.py b/modules/weko-records-ui/tests/test_views.py index e51f6488a6..a4c9095f3e 100644 --- a/modules/weko-records-ui/tests/test_views.py +++ b/modules/weko-records-ui/tests/test_views.py @@ -2,7 +2,7 @@ import uuid import pytest import io -from flask import Flask, json, jsonify, session, url_for ,make_response +from flask import Flask, json, jsonify, session, url_for ,make_response, current_app from flask_security.utils import login_user from invenio_accounts.testutils import login_user_via_session from invenio_files_rest.models import ObjectVersion @@ -24,7 +24,6 @@ ) from weko_records_ui.models import PDFCoverPageSettings, FilePermission from weko_records_ui.views import ( - _get_show_secret_url_button, check_permission, citation, escape_newline, @@ -53,7 +52,7 @@ preview_able, get_uri, ) - +from weko_records_ui.utils import can_manage_secret_url # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_views.py -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp # def record_from_pid(pid_value): @@ -446,7 +445,7 @@ def test_get_workflow_detail(app,workflows): # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_views.py::test_default_view_method -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp # """Display default view. # def _get_rights_title(result, rights_key, rights_values, current_lang, meta_options): -def test_default_view_method(app, records, itemtypes, indexstyle ,users): +def test_default_view_method(app, records, itemtypes, indexstyle, users): indexer, results = records record = results[0]["record"] recid = results[0]["recid"] @@ -456,16 +455,41 @@ def test_default_view_method(app, records, itemtypes, indexstyle ,users): with patch("weko_records_ui.views.get_index_link_list", return_value=[]): with patch("weko_records_ui.views.render_template", return_value=make_response()): assert default_view_method(recid, record, 'helloworld.pdf').status_code == 200 - # # need to fix - # with pytest.raises(Exception) as e: - # res = default_view_method(recid, record, 'helloworld.pdf') - # assert e.type==TemplatesNotFound - default_view_method(recid, record ) - with pytest.raises(NotFound) : #404 - default_view_method(recid, record ,'notfound.pdf') - with pytest.raises(NotFound) : #404 - default_view_method(recid, record ,'[No FileName]') + #メソッド_get_show_secret_url_buttonからcan_manage_secret_urlに変更に伴うケース追加: show_secret_URLのテスト + with patch('weko_records_ui.views.can_manage_secret_url', return_value=True): + response = default_view_method(recid, record, 'helloworld.pdf') + assert response.status_code == 200 + # show_secret_URLがTrueの場合の動作確認 + with patch('weko_records_ui.views.render_template') as mock_render_template: + default_view_method(recid, record, 'helloworld.pdf') + args, kwargs = mock_render_template.call_args + assert kwargs['show_secret_URL'] is True + + with patch('weko_records_ui.views.can_manage_secret_url', return_value=False): + response = default_view_method(recid, record, 'helloworld.pdf') + assert response.status_code == 200 + # show_secret_URLがFalseの場合の動作確認 + with patch('weko_records_ui.views.render_template') as mock_render_template: + default_view_method(recid, record, 'helloworld.pdf') + args, kwargs = mock_render_template.call_args + assert kwargs['show_secret_URL'] is False + + with patch('weko_records_ui.views.can_manage_secret_url', return_value=None): + response = default_view_method(recid, record, 'helloworld.pdf') + assert response.status_code == 200 + # show_secret_URLがNoneの場合の動作確認 + with patch('weko_records_ui.views.render_template') as mock_render_template: + default_view_method(recid, record, 'helloworld.pdf') + args, kwargs = mock_render_template.call_args + assert kwargs['show_secret_URL'] is None + + # 既存のテストケース + default_view_method(recid, record) + with pytest.raises(NotFound): # 404 + default_view_method(recid, record, 'notfound.pdf') + with pytest.raises(NotFound): # 404 + default_view_method(recid, record, '[No FileName]') def cannnot(): return False @@ -473,38 +497,35 @@ def cannnot(): file_permission_factory.can = cannnot with patch('weko_records_ui.views.file_permission_factory', return_value=file_permission_factory): with patch('weko_records_ui.views._redirect_method', return_value="redirect"): - assert default_view_method(recid, record ,'helloworld.pdf') == "redirect" + assert default_view_method(recid, record, 'helloworld.pdf') == "redirect" with patch("flask_login.utils._get_user", return_value=users[3]["obj"]): - with pytest.raises(Forbidden) : #404 - assert default_view_method(recid, record ,'helloworld.pdf').status_code == 200 - with patch('weko_records_ui.views.AdminSettings.get' - , side_effect=lambda name , dict_to_object : {'display_stats' : False} if name == 'display_stats_settings' else None): - assert default_view_method(recid, record ,'helloworld.pdf').status_code == 200 - with patch('weko_records_ui.views.AdminSettings.get' - , side_effect=lambda name , dict_to_object : {'items_search_author' : "author"} if name == 'items_display_settings' else None): - assert default_view_method(recid, record ,'helloworld.pdf').status_code == 200 - with patch('weko_search_ui.utils.get_data_by_property', return_value=(False,False)): - with patch('weko_records_ui.views.selected_value_by_language' ,return_value="helloworld.pdf"): - assert default_view_method(recid, record ,'helloworld.pdf').status_code == 200 + with pytest.raises(Forbidden): # 404 + assert default_view_method(recid, record, 'helloworld.pdf').status_code == 200 + with patch('weko_records_ui.views.AdminSettings.get', + side_effect=lambda name, dict_to_object: {'display_stats': False} if name == 'display_stats_settings' else None): + assert default_view_method(recid, record, 'helloworld.pdf').status_code == 200 + with patch('weko_records_ui.views.AdminSettings.get', + side_effect=lambda name, dict_to_object: {'items_search_author': "author"} if name == 'items_display_settings' else None): + assert default_view_method(recid, record, 'helloworld.pdf').status_code == 200 + with patch('weko_search_ui.utils.get_data_by_property', return_value=(False, False)): + with patch('weko_records_ui.views.selected_value_by_language', return_value="helloworld.pdf"): + assert default_view_method(recid, record, 'helloworld.pdf').status_code == 200 with patch('weko_records_ui.views.get_record_permalink', return_value=False): - assert default_view_method(recid, record ,'helloworld.pdf').status_code == 200 - - record.update( - {'system_identifier_doi' : - {"attribute_value_mlt" :[{'subitem_systemidt_identifier':"permalink_uri"}]}}) - assert default_view_method(recid, record ,'helloworld.pdf').status_code == 200 + assert default_view_method(recid, record, 'helloworld.pdf').status_code == 200 + record.update( + {'system_identifier_doi': + {"attribute_value_mlt": [{'subitem_systemidt_identifier': "permalink_uri"}]}}) + assert default_view_method(recid, record, 'helloworld.pdf').status_code == 200 def side_effect(arg): values = ['a', 'b'] return values[arg] - # with patch('weko_search_ui.utils.get_sub_item_value', side_effect=side_effect): - # default_view_method(recid, record ,'helloworld.pdf') pid_ver = MagicMock pid_ver.exists = False - with patch('weko_records_ui.views.PIDVersioning',return_value=pid_ver): - with pytest.raises(NotFound) : #404 - assert default_view_method(recid, record ,'helloworld.pdf') + with patch('weko_records_ui.views.PIDVersioning', return_value=pid_ver): + with pytest.raises(NotFound): # 404 + assert default_view_method(recid, record, 'helloworld.pdf') pid_ver = MagicMock pid_ver.exists = True @@ -512,21 +533,21 @@ def side_effect(arg): mock = MagicMock mock.object_uuid = uuid.uuid4() pid_ver.children = [mock] - pid_ver.get_children = lambda ordered,pid_status : [mock] - with patch('weko_records_ui.views.PIDVersioning',return_value=pid_ver): - with patch('weko_records_ui.views.WekoRecord.get_record',return_value={'_deposit':{'status':'draft'}}): - assert default_view_method(recid, record ,'helloworld.pdf').status_code == 200 - - with patch('weko_records_ui.views.WekoRecord.get_record',side_effect=Exception): - assert default_view_method(recid, record ,'helloworld.pdf').status_code == 200 - with patch('weko_records_ui.views.ItemLink.get_item_link_info',return_value={"relation":"res"}): - assert default_view_method(recid, record ,'helloworld.pdf').status_code == 200 - + pid_ver.get_children = lambda ordered, pid_status: [mock] + with patch('weko_records_ui.views.PIDVersioning', return_value=pid_ver): + with patch('weko_records_ui.views.WekoRecord.get_record', return_value={'_deposit': {'status': 'draft'}}): + assert default_view_method(recid, record, 'helloworld.pdf').status_code == 200 + + with patch('weko_records_ui.views.WekoRecord.get_record', side_effect=Exception): + assert default_view_method(recid, record, 'helloworld.pdf').status_code == 200 + with patch('weko_records_ui.views.ItemLink.get_item_link_info', return_value={"relation": "res"}): + assert default_view_method(recid, record, 'helloworld.pdf').status_code == 200 + index = MagicMock() index.index_name = "" - index.index_name_english ="index" - with patch('weko_records_ui.views.Indexes.get_index',return_value=index): - assert default_view_method(recid, record ,'helloworld.pdf').status_code == 200 + index.index_name_english = "index" + with patch('weko_records_ui.views.Indexes.get_index', return_value=index): + assert default_view_method(recid, record, 'helloworld.pdf').status_code == 200 @@ -981,7 +1002,7 @@ def test_create_secret_url_and_send_mail(app,client,db,users,records): ,pid_value=results[1]["recid"].pid_value ,filename=results[1]["filename"]) login_user_via_session(client=client, user=users[id]["obj"] ,email=users[id]["email"]) - with patch('weko_records_ui.views._get_show_secret_url_button',return_value = True): + with patch('weko_records_ui.views.can_manage_secret_url',return_value = True): with patch('weko_records_ui.views.process_send_mail',return_value = True): # with app.test_request_context(): res = client.get(secret_file_url) @@ -993,110 +1014,10 @@ def test_create_secret_url_and_send_mail(app,client,db,users,records): with patch("flask.templating._render", return_value=""): res = client.post(secret_file_url ,data=json.dumps({}), content_type='application/json') assert res.status_code == 500 - with patch('weko_records_ui.views._get_show_secret_url_button',return_value = False): + with patch('weko_records_ui.views.can_manage_secret_url',return_value = False): with patch('weko_records_ui.views.process_send_mail',return_value = True): with patch("flask.templating._render", return_value=""): res = client.post(secret_file_url ,data=json.dumps({}), content_type='application/json') assert res.status_code == 403 -# def create_secret_url_and_send_mail(pid:PersistentIdentifier, record:WekoRecord, filename:str, **kwargs) -> str: -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_views.py::test__get_show_secret_url_button -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -@pytest.mark.parametrize( - "id, is_show", - [ - (0, False), #contributor - (1, False), #repoadmin - (2, False), #sysadmin - (3, False), #comadmin - (4, False), #generaluser - (5, False), #originalroleuser (owner) - (6, False), #originalroleuser2 (repoadmin) - (7, False), #user (weko_shared owner) - ], -) -def test__get_show_secret_url_button(users,records,id ,is_show): - indexer, results = records - # 80 - i = 0 - role = ["open_access" , "open_no" ,"open_date"] - for record in results: - record["record"]['owner'] = users[5]["id"] - record["record"]['weko_shared_id'] = users[7]["id"] - file_data = record["record"].get_file_data() - if len(file_data) > 0: - file_data[0].update({'accessrole':role[i%3]}) - file_data[0].update({'date':[{"dateValue" :'2999-12-31'}]}) - i = i + 1 - - with patch("flask_login.utils._get_user", return_value=users[id]["obj"]): - res = [] - for record in results: - if 'filename' in record: - res.append( _get_show_secret_url_button(record["record"] , record["filename"]) ) - - assert not res[0] - assert res[1] == is_show - assert res[2] == is_show - -# def create_secret_url_and_send_mail(pid:PersistentIdentifier, record:WekoRecord, filename:str, **kwargs) -> str: -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_views.py::test__get_show_secret_url_button2 -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -@pytest.mark.parametrize( - "id, is_show", - [ - (1, True), #repoadmin - ], -) -def test__get_show_secret_url_button2(users,records ,id,is_show): - indexer, results = records - # 80 - # pattern of not db_restricted_access_secret - i = 0 - role = ["open_access" , "open_no" ,"open_date"] - for record in results: - record["record"]['owner'] = users[5]["id"] - record["record"]['weko_shared_id'] = users[7]["id"] - file_data = record["record"].get_file_data() - if len(file_data) > 0: - file_data[0].update({'accessrole':role[i%3]}) - file_data[0].update({'date':[{"dateValue" :'2999-12-31'}]}) - i = i + 1 - with patch("flask_login.utils._get_user", return_value=users[id]["obj"]): - res = [] - for record in results: - if 'filename' in record: - res.append( _get_show_secret_url_button(record["record"] , record["filename"]) ) - - assert res[0] == False - assert res[1] == False - assert res[2] == False - -# def create_secret_url_and_send_mail(pid:PersistentIdentifier, record:WekoRecord, filename:str, **kwargs) -> str: -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_views.py::test__get_show_secret_url_button3 -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -@pytest.mark.parametrize( - "id, is_show", - [ - (1, False), #repoadmin - ], -) -def test__get_show_secret_url_button3(users,records,id,is_show): - indexer, results = records - # 80 - i = 0 - role = ["open_access" , "open_no" ,"open_date"] - for record in results: - record["record"]['owner'] = users[5]["id"] - record["record"]['weko_shared_id'] = users[7]["id"] - file_data = record["record"].get_file_data() - if len(file_data) > 0: - file_data[0].update({'accessrole':role[i%3]}) - file_data[0].update({'date':[{"dateValue" :'2999-12-31'}]}) - i = i + 1 - with patch("flask_login.utils._get_user", return_value=users[id]["obj"]): - res = [] - for record in results: - if 'filename' in record: - res.append( _get_show_secret_url_button(record["record"] , record["filename"]) ) - - assert res[0] == False - assert res[1] == is_show - assert res[2] == False +# def create_secret_url_and_send_mail(pid:PersistentIdentifier, record:WekoRecord, filename:str, **kwargs) -> str: \ No newline at end of file diff --git a/modules/weko-records-ui/weko_records_ui/utils.py b/modules/weko-records-ui/weko_records_ui/utils.py index e69bfd20c7..ac5883aae9 100644 --- a/modules/weko-records-ui/weko_records_ui/utils.py +++ b/modules/weko-records-ui/weko_records_ui/utils.py @@ -1249,6 +1249,95 @@ def validate_download_record(record: dict): if is_private_index(record): abort(403) +def is_secret_url_feature_enabled(): + """Check if the feature is enabled. + + Returns: + bool: True if the feature is enabled, False otherwise. + """ + settings = AdminSettings.get(name='restricted_access',dict_to_object=False) + if not settings: + settings = current_app.config['WEKO_ADMIN_RESTRICTED_ACCESS_SETTINGS'] + secret_url_settings = settings.get('secret_URL_file_download', {}) + is_enabled = secret_url_settings.get('secret_enable', False) + return is_enabled + + +def has_permission_to_manage_secret_url(record, user_id): + """Check if the user has permission to manage the secret URL feature. + + Following users have the permission. + - The administrators. + - The user who registered the item(record). + - The user who registered the item on behalf of other users. + + Returns: + bool: True if the user has permission, False otherwise. + """ + super_roles = current_app.config['WEKO_PERMISSION_SUPER_ROLE_USER'] + user = User.query.filter_by(id=user_id).first() + # Need to change the 'weko_shared_id' to 'weko_shared_ids' in the future. + has_permission = ( + user_id == int(record['owner']) or + user_id in [record['weko_shared_id']] or + any(role.name in super_roles for role in user.roles or []) + ) + return has_permission + + +def is_secret_file(record: WekoRecord, filename): + """Check if the target file meets the requirements for secret URL use. + + Args: + record (WekoRecord): The record object. + filename (str): The target file name. + + Returns: + bool: True if the file is for secret URL use, False otherwise. + """ + target_data = {} + for file_data in record.get_file_data(): + if file_data.get('filename') == filename: + target_data = file_data + break + if not target_data: + return False + + publish_date = dt.strptime( + target_data.get('date')[0].get('dateValue'), '%Y-%m-%d') + is_secret_file = ( + target_data.get('accessrole') == 'open_no' or ( + target_data.get('accessrole') == 'open_date' and + dt.now() < publish_date + )) + return is_secret_file + + +def can_manage_secret_url(record, filename): + """Determine if the user can manage the secret URL feature for a file. + + This function checks whether the secret URL feature can be used for a given + file in a record by evaluating the following conditions: + 1. The secret URL feature is enabled system-wide. + 2. The logged-in user has the necessary permissions. + 3. The specified file qualifies for secret URL use. + + Args: + record (WekoRecord): The record object containing the file. + filename (str): The name of the target file. + + Returns: + bool: True if all conditions are met, False otherwise. + """ + if not current_user or not current_user.is_authenticated: + return False + else: + result = ( + is_secret_url_feature_enabled() and + has_permission_to_manage_secret_url(record, current_user.id) and + is_secret_file(record, filename) + ) + return result def get_onetime_download(file_name: str, record_id: str, user_mail: str) -> Optional[FileOnetimeDownload]: diff --git a/modules/weko-records-ui/weko_records_ui/views.py b/modules/weko-records-ui/weko_records_ui/views.py index d2eb347950..1280ff9562 100644 --- a/modules/weko-records-ui/weko_records_ui/views.py +++ b/modules/weko-records-ui/weko_records_ui/views.py @@ -75,7 +75,7 @@ from .utils import create_secret_url, get_billing_file_download_permission, \ get_google_detaset_meta, get_google_scholar_meta, get_groups_price, \ get_min_price_billing_file_download, get_record_permalink, hide_by_email, \ - delete_version, is_show_email_of_creator,hide_by_itemtype + delete_version, is_show_email_of_creator,hide_by_itemtype, can_manage_secret_url from .utils import restore as restore_imp from .utils import soft_delete as soft_delete_imp @@ -722,7 +722,7 @@ def _get_rights_title(result, rights_key_str, rights_values, current_lang, meta_ flg_display_itemtype = current_app.config.get('WEKO_RECORDS_UI_DISPLAY_ITEM_TYPE') , flg_display_resourcetype = current_app.config.get('WEKO_RECORDS_UI_DISPLAY_RESOURCE_TYPE') , search_author_flg=search_author_flg, - show_secret_URL=_get_show_secret_url_button(record,filename), + show_secret_URL=can_manage_secret_url(record,filename), **ctx, **kwargs ) @@ -747,7 +747,7 @@ def create_secret_url_and_send_mail(pid:PersistentIdentifier, record:WekoRecord, #permission check # "Someone who can show Secret URL button" can also use generate Secret URL function. - if not _get_show_secret_url_button(record ,filename): + if not can_manage_secret_url(record ,filename): abort(403) userprof:UserProfile = UserProfile.get_by_userid(current_user.id) @@ -767,51 +767,6 @@ def create_secret_url_and_send_mail(pid:PersistentIdentifier, record:WekoRecord, else: abort(500) -def _get_show_secret_url_button(record : WekoRecord, filename :str) -> bool: - """ - Args: - WekoRecord : records_metadata for target item - str : target content name - Returns: - bool : return true if be able to show Secret URL button. or false. - """ - - #1.check secret url function is enabled - restricted_access = AdminSettings.get('restricted_access', False) - if not restricted_access: - restricted_access = current_app.config[ - 'WEKO_ADMIN_RESTRICTED_ACCESS_SETTINGS'] - - enable:bool = restricted_access.get('secret_URL_file_download',{}).get('secret_enable',False) - - #2.check the user has permittion - has_parmission = False - # Registered user - owner_user_id = [int(record['owner'])] if record.get('owner') else [] - shared_user_id = [int(record['weko_shared_id'])] if int(record.get('weko_shared_id', -1)) != -1 else [] - if current_user and current_user.is_authenticated and \ - current_user.id in owner_user_id + shared_user_id: - has_parmission = True - # Super users - supers = current_app.config['WEKO_PERMISSION_SUPER_ROLE_USER'] - for role in list(current_user.roles or []): - if role.name in supers: - has_parmission = True - - #3.check the file's accessrole is "open_no" ,or "open_date" and not open yet. - is_secret_file = False - current_app.logger.info(record.get_file_data()) - for content in record.get_file_data(): - if content.get('filename') == filename: - if content.get('accessrole') == "open_no": - is_secret_file = True - elif content.get('accessrole') == "open_date" and \ - datetime.now() < datetime.strptime(content.get('date',[{"dateValue" :'1970-01-01'}])[0].get("dateValue" ,'1970-01-01'), '%Y-%m-%d') : - is_secret_file = True - - # all true is show - return enable and has_parmission and is_secret_file - @blueprint.route('/r/', methods=['GET']) @blueprint.route('/r/.', methods=['GET']) @login_required From 2881033112f09e5ac5d68662fc2a0b1a877dd8fe Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Mon, 6 Jan 2025 16:23:28 +0900 Subject: [PATCH 11/61] Change URL generation and download logic --- .../weko-records-ui/weko_records_ui/config.py | 4 - modules/weko-records-ui/weko_records_ui/fd.py | 191 ++--- .../weko-records-ui/weko_records_ui/models.py | 29 +- .../weko-records-ui/weko_records_ui/utils.py | 686 +++++++++--------- .../weko-records-ui/weko_records_ui/views.py | 116 +-- modules/weko-workflow/weko_workflow/utils.py | 62 +- 6 files changed, 529 insertions(+), 559 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/config.py b/modules/weko-records-ui/weko_records_ui/config.py index 3f0e997164..900ffdf5d1 100644 --- a/modules/weko-records-ui/weko_records_ui/config.py +++ b/modules/weko-records-ui/weko_records_ui/config.py @@ -193,10 +193,6 @@ "filename={} record_id={} user_mail={} date={}" """Onetime download pattern.""" -WEKO_RECORDS_UI_SECRET_DOWNLOAD_PATTERN = \ - "filename={} record_id={} id={} date={}" -"""Secret URL download pattern.""" - WEKO_RECORDS_UI_MAIL_TEMPLATE_SECRET_URL = "email_pattern_send_secret_url.tpl" RECORDS_UI_EXPORT_FORMATS = { diff --git a/modules/weko-records-ui/weko_records_ui/fd.py b/modules/weko-records-ui/weko_records_ui/fd.py index 361601a195..8bf8c650a3 100644 --- a/modules/weko-records-ui/weko_records_ui/fd.py +++ b/modules/weko-records-ui/weko_records_ui/fd.py @@ -20,11 +20,12 @@ """Utilities for download file.""" +import base64 import json import mimetypes import traceback import unicodedata -from datetime import datetime +from datetime import datetime, timezone from flask import abort, current_app, render_template, request ,redirect ,url_for from flask_babelex import gettext as _ @@ -46,16 +47,14 @@ from werkzeug.datastructures import Headers from werkzeug.urls import url_quote -from .models import FileOnetimeDownload, FileSecretDownload, PDFCoverPageSettings +from .models import FileOnetimeDownload, PDFCoverPageSettings from .pdf import make_combined_pdf from .permissions import check_original_pdf_download_permission, \ file_permission_factory, is_owners_or_superusers -from .utils import ( - check_and_send_usage_report, get_billing_file_download_permission, - get_groups_price, get_min_price_billing_file_download, get_onetime_download, - get_secret_download, is_billing_item, parse_one_time_download_token, - parse_secret_download_token, validate_download_record, - validate_onetime_download_token, validate_secret_download_token) +from .utils import check_and_send_usage_report, convert_token_into_obj, \ + create_download_url, get_billing_file_download_permission, \ + get_groups_price, get_min_price_billing_file_download, \ + get_onetime_download, is_billing_item, validate_url_download def weko_view_method(pid, record, template=None, **kwargs): @@ -249,8 +248,7 @@ def file_ui( current_app.logger.info('onetime_download is None') abort(403) - onetime_file_url = generate_one_time_download_url( - file_name, record_id, user_mail ) + onetime_file_url = create_download_url(onetime_download) return redirect(onetime_file_url) #redirect to file_download_onetime() # #Check permissions @@ -409,75 +407,64 @@ def add_signals_info(record, obj): obj.item_id = record['_deposit']['id'] -def file_download_onetime(pid, record, _record_file_factory=None, **kwargs): - """File download onetime. - :param pid: - :param record: Record json - :param _record_file_factory: - :param kwargs: - :return: +def file_download_onetime(pid, record, filename, _record_file_factory=None, + **kwargs): + """Download a file using a one-time download URL. + + Args: + pid (PersistentIdentifier): The identifier for the item. + record (WekoRecord): The record metadata of the item. + filename (str): The name of the file to download. + + Returns: + Response: The Flask wrapper object for the file download """ - token = request.args.get('token', type=str) - filename = kwargs.get("filename") - error_template = "weko_theme/error.html" - # Parse token - error, token_data = \ - parse_one_time_download_token(token) - if error: - return render_template(error_template, error=error) - record_id, user_mail, date, secret_token = token_data - - # Validate record status - validate_download_record(record) - - # Get one time download record. - onetime_download = get_onetime_download( - file_name=filename, record_id=record_id, user_mail=user_mail - ) + def error_response(error_message, status_code=400): + error_template = "weko_theme/error.html" + return render_template(error_template, error_message), status_code - # Validate token - is_valid, error = validate_onetime_download_token( - onetime_download, filename, record_id, user_mail, date, secret_token) - if not is_valid: - return render_template(error_template, error=error) + token = request.args.get('token', type=str) + is_validated, error_msg = ( + validate_url_download(record, filename, token, is_secret_url=False)) + if not is_validated: + return error_response(error_msg, 403) _record_file_factory = _record_file_factory or record_file_factory - - # Get file object file_object = _record_file_factory(pid, record, filename) if not file_object or not file_object.obj: - return render_template(error_template, - error="{} does not exist.".format(filename)) + return error_response(f'The file "{filename}" does not exist.', 404) - # Check and send usage report for Guest User. - if onetime_download.extra_info and 'open_restricted' == file_object.get( - 'accessrole'): - extra_info = onetime_download.extra_info + url_obj = convert_token_into_obj(token) + if (url_obj.extra_info and + (file_object.get('accessrole') == 'open_restricted')): + extra_info = url_obj.extra_info try: - error_msg = check_and_send_usage_report(extra_info, user_mail ,record, file_object) - if error_msg: - return render_template(error_template, error=error_msg) + # extra_info can be changed by this method + error = check_and_send_usage_report( + extra_info, url_obj.user_mail ,record, file_object) + if error: + return error_response(error, 403) db.session.commit() except SQLAlchemyError as ex: - current_app.logger.error("sqlalchemy error: {}".format(ex)) + current_app.logger.error(f'SQLAlchemy error: {ex}') db.session.rollback() - return render_template(error_template, error=_("Unexpected error occurred.")) + return error_response('Unexpected error occurred.', 500) except BaseException as ex: - current_app.logger.error("Unexpected error: {}".format(ex)) + current_app.logger.error(f'Unexpected error: {ex}') db.session.rollback() - return render_template(error_template, error=_("Unexpected error occurred.")) + return error_response('Unexpected error occurred.', 500) - # Update download data + # Update download count and extra info try: - onetime_download.increment_download_count() - onetime_download.update_extra_info(extra_info) + url_obj.increment_download_count() + url_obj.update_extra_info(extra_info) except: - return render_template(error_template, - error=_("Unexpected error occurred.")) + return error_response('Unexpected error occurred.', 500) + + return _download_file( + file_object, False, 'en', file_object.obj, pid, record) - return _download_file(file_object, False, 'en', file_object.obj, pid, - record) def _is_terms_of_use_only(file_obj:dict , req :dict) -> bool: """ @@ -512,66 +499,44 @@ def _is_terms_of_use_only(file_obj:dict , req :dict) -> bool: return is_terms_of_use_only(workflow_id) if workflow_id != "" else False -def file_download_secret(pid, record, _record_file_factory=None, **kwargs): - """File download secret. - :param pid: - :param record: Record json - :param _record_file_factory: - :param kwargs: - :return: - """ - token = request.args.get('token', type=str) - filename:str = str(kwargs.get("filename")) - error_template = "weko_theme/error.html" - # Parse token - error, token_data = \ - parse_secret_download_token(token) - if error: - return render_template(error_template, error=error) - record_id, id, date, secret_token = token_data - - # Validate record status - validate_download_record(record) - - if isinstance(date,str): - date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%S.%f") - - # Get secret download record. - secret_download :FileSecretDownload = get_secret_download( - file_name=filename, record_id=pid.pid_value, id=id , created=date - ) +def file_download_secret(pid, record, filename, _record_file_factory=None, + **kwargs): + """Download a file using a secret URL. - if not secret_download: - abort(403) + Args: + pid (PersistentIdentifier): The identifier for the item. + record (WekoRecord): The record metadata of the item. + filename (str): The name of the file to download. - # Validate token - is_valid, error = validate_secret_download_token( - secret_download, filename, pid.pid_value, id, date.isoformat(), secret_token) - current_app.logger.debug("is_valid: {}, error: {}".format(is_valid,error)) - - if not is_valid: - return render_template(error_template, error=error) + Returns: + Response: The Flask wrapper object for the file download. + """ + def error_response(error_message, status_code=400): + error_template = "weko_theme/error.html" + return render_template(error_template, error_message), status_code - _record_file_factory = _record_file_factory or record_file_factory + is_validated, error_msg = ( + validate_url_download(record, filename, is_secret_url=True)) + if not is_validated: + return error_response(error_msg, 403) - # Get file object + _record_file_factory = _record_file_factory or record_file_factory file_object = _record_file_factory(pid, record, filename) if not file_object or not file_object.obj: - return render_template(error_template, - error="{} does not exist.".format(filename)) - - # Update download data - try: - secret_download.increment_download_count() - except: - return render_template(error_template, - error=_("Unexpected error occurred.")) + return error_response(f'The file "{filename}" does not exist.', 404) - # Get user's language and defautl language for PDF coverpage. + # Set language for PDF cover page lang = 'en' if current_user.is_authenticated : user_profile = UserProfile.get_by_userid(current_user.get_id()) - lang = user_profile.language if user_profile and user_profile.language \ - else 'en' - return _download_file(file_object, False, lang, file_object.obj, pid, record) + lang = user_profile.language if user_profile else 'en' + + url_obj = convert_token_into_obj(request.args.get('token')) + try: + url_obj.increment_download_count() + except: + return error_response('Unexpected error occurred.', 500) + + return _download_file( + file_object, False, lang, file_object.obj, pid, record) diff --git a/modules/weko-records-ui/weko_records_ui/models.py b/modules/weko-records-ui/weko_records_ui/models.py index 843dc120b6..d78671f83b 100644 --- a/modules/weko-records-ui/weko_records_ui/models.py +++ b/modules/weko-records-ui/weko_records_ui/models.py @@ -447,6 +447,18 @@ def create(cls, **data): current_app.logger.error(ex) raise ex + @classmethod + def get_by_id(cls, id): + """Get a record by its ID. + + Args: + id (int): The ID of the record to retrieve. + + Returns: + FileOnetimeDownload: The record instance, or None if not found. + """ + return cls.query.get(id) + @classmethod def find(cls, **obj) -> list: """Find file onetime download. @@ -472,8 +484,9 @@ def find_downloadable_only(cls, **obj) -> list: cls.file_name == obj.get("file_name"), cls.record_id == obj.get("record_id"), cls.user_mail == obj.get("user_mail"), - cls.download_count > 0 , - now() < cls.created + func.cast( concat( cls.expiration_date , ' days' ) , INTERVAL) + cls.download_count < cls.download_limit, + cls.expiration_date > now(), + cls.is_deleted == False ) return query.order_by(desc(cls.id)).all() @@ -589,6 +602,18 @@ def create(cls, **data): current_app.logger.error(ex) raise ex + @classmethod + def get_by_id(cls, id): + """Get a record by its ID. + + Args: + id (int): The ID of the record to retrieve. + + Returns: + FileSecretDownload: The record instance, or None if not found. + """ + return cls.query.get(id) + @classmethod def find(cls, **obj) -> list: """Find file onetime download. diff --git a/modules/weko-records-ui/weko_records_ui/utils.py b/modules/weko-records-ui/weko_records_ui/utils.py index 66f69733ac..0937d44905 100644 --- a/modules/weko-records-ui/weko_records_ui/utils.py +++ b/modules/weko-records-ui/weko_records_ui/utils.py @@ -21,8 +21,9 @@ """Module of weko-records-ui utils.""" import base64 +import hashlib import os -from datetime import datetime as dt +from datetime import datetime as dt, timezone from datetime import timedelta from decimal import Decimal from typing import List, NoReturn, Optional, Tuple @@ -52,11 +53,13 @@ from weko_records.utils import replace_fqdn from weko_records.models import ItemReference from weko_schema_ui.models import PublishStatus +from weko_user_profiles.models import UserProfile from weko_workflow.api import WorkActivity, WorkFlow, UpdateItem from weko_workflow.models import ActivityStatusPolicy from weko_records_ui.models import InstitutionName from weko_workflow.models import Activity +from weko_workflow.utils import get_item_info, process_send_mail, set_mail_info from .models import FileOnetimeDownload, FilePermission, FileSecretDownload from .permissions import check_create_usage_report, \ @@ -1115,110 +1118,6 @@ def check_and_send_usage_report(extra_info:dict, user_mail:str ,record:dict, fil FilePermission.update_usage_report_activity_id(permission,activity_id) -def generate_one_time_download_url( - file_name: str, record_id: str, guest_mail: str -) -> str: - """Generate one time download URL. - - :param file_name: File name - :param record_id: File Version ID - :param guest_mail: guest email - :return: - """ - secret_key = current_app.config['WEKO_RECORDS_UI_SECRET_KEY'] - download_pattern = current_app.config[ - 'WEKO_RECORDS_UI_ONETIME_DOWNLOAD_PATTERN'] - current_date = dt.utcnow().strftime("%Y-%m-%d") - hash_value = download_pattern.format(file_name, record_id, guest_mail, - current_date) - secret_token = oracle10.hash(secret_key, hash_value) - - token_pattern = "{} {} {} {}" - token = token_pattern.format(record_id, guest_mail, current_date, - secret_token) - token_value = base64.b64encode(token.encode()).decode() - host_name = request.host_url - url = "{}record/{}/file/onetime/{}?token={}" \ - .format(host_name, record_id, file_name, token_value) - return url - - -def parse_one_time_download_token(token: str) -> Tuple[str, Tuple]: - """Parse onetime download token. - - @param token: - @return: - """ - # current_app.logger.debug("token:{}".format(token)) - error = _("Token is invalid.") - if token is None: - return error, () - try: - decode_token = base64.b64decode(token.encode()).decode() - param = decode_token.split(" ") - if not param or len(param) != 4: - return error, () - - return "", (param[0], param[1], param[2], param[3]) - except Exception as err: - current_app.logger.error(err) - return error, () - - -def validate_onetime_download_token( - onetime_download: FileOnetimeDownload, file_name: str, record_id: str, - guest_mail: str, date: str, token: str -) -> Tuple[bool, str]: - """Validate onetime download token. - - @param onetime_download: - @param file_name: - @param record_id: - @param guest_mail: - @param date: - @param token: - @return: - """ - # current_app.logger.debug("onetime_download:{}".format(onetime_download)) - # current_app.logger.debug("file_name:{}".format(file_name)) - # current_app.logger.debug("record_id:{}".format(record_id)) - # current_app.logger.debug("guest_mail:{}".format(guest_mail)) - # current_app.logger.debug("date:{}".format(date)) - # current_app.logger.debug("token:{}".format(token)) - - token_invalid = _("Token is invalid.") - secret_key = current_app.config['WEKO_RECORDS_UI_SECRET_KEY'] - download_pattern = current_app.config[ - 'WEKO_RECORDS_UI_ONETIME_DOWNLOAD_PATTERN'] - hash_value = download_pattern.format( - file_name, record_id, guest_mail, date) - - if not oracle10.verify(secret_key, token, hash_value): - current_app.logger.debug('Validate token error: {}'.format(hash_value)) - return False, token_invalid - try: - if not onetime_download: - return False, token_invalid - try: - expiration_date = timedelta(onetime_download.expiration_date) - download_date = onetime_download.created.date() + expiration_date - current_date = dt.utcnow().date() - if current_date > download_date: - return False, _( - "The expiration date for download has been exceeded.") - except OverflowError: - current_app.logger.error('date value out of range:', - onetime_download.expiration_date) - - if onetime_download.download_count <= 0: - return False, _("The download limit has been exceeded.") - return True, "" - except Exception as err: - current_app.logger.error('Validate onetime download token error:') - current_app.logger.error(err) - return False, token_invalid - - def is_private_index(record): """Check index of workflow is private. @@ -1245,9 +1144,100 @@ def validate_download_record(record: dict): :param record: """ if record['publish_status'] != PublishStatus.PUBLIC.value: - abort(403) + return False if is_private_index(record): - abort(403) + return False + + +def is_secret_url_feature_enabled(): + """Check if the feature is enabled. + + Returns: + bool: True if the feature is enabled, False otherwise. + """ + settings = AdminSettings.get(name='restricted_access',dict_to_object=False) + if not settings: + settings = current_app.config['WEKO_ADMIN_RESTRICTED_ACCESS_SETTINGS'] + secret_url_settings = settings.get('secret_URL_file_download', {}) + is_enabled = secret_url_settings.get('secret_enable', False) + return is_enabled + + +def has_permission_to_manage_secret_url(record, user_id): + """Check if the user has permission to manage the secret URL feature. + + Following users have the permission. + - The administrators. + - The user who registered the item(record). + - The user who registered the item on behalf of other users. + + Returns: + bool: True if the user has permission, False otherwise. + """ + super_roles = current_app.config['WEKO_PERMISSION_SUPER_ROLE_USER'] + user = User.query.filter_by(id=user_id).first() + # Need to change the 'weko_shared_id' to 'weko_shared_ids' in the future. + has_permission = ( + user_id == int(record['owner']) or + user_id in [record['weko_shared_id']] or + any(role.name in super_roles for role in user.roles or []) + ) + return has_permission + + +def is_secret_file(record, file_name): + """Check if the target file meets the requirements for secret URL use. + + Args: + record (WekoRecord): The record object. + filename (str): The target file name. + + Returns: + bool: True if the file is for secret URL use, False otherwise. + """ + target_data = {} + for file_data in record.get_file_data(): + if file_data.get('filename') == file_name: + target_data = file_data + break + if not target_data: + return False + + publish_date = dt.strptime( + target_data.get('date')[0].get('dateValue'), '%Y-%m-%d') + is_secret_file = ( + target_data.get('accessrole') == 'open_no' or ( + target_data.get('accessrole') == 'open_date' and + dt.now < publish_date + )) + return is_secret_file + + +def can_manage_secret_url(record, filename): + """Determine if the user can manage the secret URL feature for a file. + + This function checks whether the secret URL feature can be used for a given + file in a record by evaluating the following conditions: + 1. The secret URL feature is enabled system-wide. + 2. The logged-in user has the necessary permissions. + 3. The specified file qualifies for secret URL use. + + Args: + record (WekoRecord): The record object containing the file. + filename (str): The name of the target file. + + Returns: + bool: True if all conditions are met, False otherwise. + """ + if not current_user or not current_user.is_authenticated: + return False + else: + result = ( + is_secret_url_feature_enabled() and + has_permission_to_manage_secret_url(record, current_user.id) and + is_secret_file(record, filename) + ) + return result def get_onetime_download(file_name: str, record_id: str, @@ -1291,40 +1281,6 @@ def get_valid_onetime_download(file_name: str, record_id: str,user_mail: str) -> return None -def create_onetime_download_url( - activity_id: str, file_name: str, record_id: str, user_mail: str, - is_guest: bool = False -): - """Create onetime download. - - :param activity_id: - :param file_name: - :param record_id: - :param user_mail: - :param is_guest: - :return: - """ - content_file_download = get_restricted_access('content_file_download') - if content_file_download and isinstance(content_file_download, dict): - expiration_date = content_file_download.get("expiration_date", 30) - download_limit = content_file_download.get("download_limit", 10) - extra_info = dict( - usage_application_activity_id=activity_id, - send_usage_report=True, - is_guest=is_guest - ) - file_onetime = FileOnetimeDownload.create(**{ - "file_name": file_name, - "record_id": record_id, - "user_mail": user_mail, - "expiration_date": expiration_date, - "download_count": download_limit, - "extra_info": extra_info, - }) - return file_onetime - return False - - def get_workflows(): """Get workflow. @@ -1710,212 +1666,290 @@ def get_google_detaset_meta(record,record_tree=None): return json.dumps(res_data, ensure_ascii=False) -def create_secret_url(record_id:str ,file_name:str ,user_mail:str ,restricted_fullname='',restricted_data_name='') -> dict: + +def validate_secret_url_generation_request(request_data): + """Validate request for secret URL generation. + + Args: + request_data (dict): The request object. + + Returns: + bool: True if the request is valid, False otherwise. """ - Save in FileSecretDownload - and Generate Secret Download URL. - + if not request_data: + return False + + label_name = request_data.get('link_name') + expiration_date = request_data.get('expiration_date') + download_limit = request_data.get('download_limit') + if not isinstance(label_name, str) or len(label_name) > 255: + return False + if not isinstance(expiration_date, dt) or expiration_date < dt.now(): + return False + if not isinstance(download_limit, int) or download_limit <= 0: + return False + + return True + + +def create_secret_url_record(record_id, file_name, request_data): + """Create a secret URL record. + Args: - str :record_id: - str :file_name: - str :user_mail - str :restricted_fullname :embed mail string - str :restricted_data_name :embed mail string - Return: - dict: created info + record_id (int): The record(item) ID to which the file belongs. + file_name (str): The file name for which the secret URL is created. + request_data (dict): The request data from the user. + + Returns: + FileSecretDownload: The created secret URL object. + + Raises: + Exception: If an unexpected error occurs during the creation. """ - # Save to Database. - secret_obj:FileSecretDownload = _create_secret_download_url( - file_name, record_id, user_mail) - - # generate url - secret_file_url = _generate_secret_download_url( - file_name, record_id, secret_obj.id , secret_obj.created) - - return_dict:dict = { - "restricted_download_link":"", - "mail_recipient":"", - "file_name":file_name, - "restricted_expiration_date": "", - "restricted_expiration_date_ja": "", - "restricted_expiration_date_en": "", - "restricted_download_count":"", - "restricted_download_count_ja":"", - "restricted_download_count_en":"", - "restricted_fullname" :restricted_fullname, - "restricted_data_name" :restricted_data_name, - } - return_dict["mail_recipient"] = secret_obj.user_mail - return_dict["restricted_download_link"] = secret_file_url - - max_int :int = current_app.config["WEKO_ADMIN_RESTRICTED_ACCESS_MAX_INTEGER"] - if secret_obj.expiration_date < max_int: - expiration_date = timedelta(days=secret_obj.expiration_date) - expiration_date = dt.today() + expiration_date - expiration_date = expiration_date.strftime("%Y-%m-%d") - return_dict['restricted_expiration_date'] = expiration_date - else: - return_dict["restricted_expiration_date_ja"] = "無制限" - return_dict["restricted_expiration_date_en"] = "Unlimited" - + content_file_download = get_restricted_access('content_file_download') + if (not content_file_download or + not isinstance(content_file_download, dict)): + return None - if secret_obj.download_count < max_int : - return_dict["restricted_download_count"] = str(secret_obj.download_count) - else: - return_dict["restricted_download_count_ja"] = "無制限" - return_dict["restricted_download_count_en"] = "Unlimited" - - return return_dict + secret_url_obj = FileSecretDownload.create( + creator_id = current_user.id, + record_id = record_id, + file_name = file_name, + label_name = request_data['link_name'], + expiration_date = request_data['expiration_date'], + download_limit = request_data['download_limit']) + return secret_url_obj -def _generate_secret_download_url(file_name: str, record_id: str, id: str ,created :dt) -> str: - """Generate Secret download URL. - - Args - str: file_name: File name - str: record_id: File Version ID - str: id: FileSecretDownload id - datetime :created :FileSecretDownload created - - Returns - str: generated url - """ - secret_key = current_app.config['WEKO_RECORDS_UI_SECRET_KEY'] - download_pattern = current_app.config[ - 'WEKO_RECORDS_UI_SECRET_DOWNLOAD_PATTERN'] - current_date = created - hash_value = download_pattern.format(file_name, record_id, id, - current_date.isoformat()) - secret_token = oracle10.hash(secret_key, hash_value) - - token_pattern = "{} {} {} {}" - token = token_pattern.format(record_id, id, current_date.isoformat(), - secret_token) - token_value = base64.b64encode(token.encode()).decode() - host_name = request.host_url - url = "{}record/{}/file/secret/{}?token={}" \ - .format(host_name, record_id, file_name, token_value) - current_app.logger.debug("secret_file_url:{}".format(url)) - return url +def create_onetime_download_record( + activity_id, approver_id, record_id, file_name, user_mail, is_guest=False, + extra_info=None): + """Create onetime download record. + Args: + activity_id: + approver_id: The ID of the user who approved the usage application. + record_id: The ID of the record which the file belongs to. + file_name: The name of the file to be downloaded. + user_mail: The email address of the user who requested the download. + is_guest: True if the user is a guest user, False otherwise. + extra_info: Additional information. -def parse_secret_download_token(token: str) -> Tuple[str, Tuple]: - """Parse secret download token. + Returns: + FileOnetimeDownload: The created onetime download record, or None if + the restricted access settings are not configured properly. + """ + content_file_download = get_restricted_access('content_file_download') + if (not content_file_download or + not isinstance(content_file_download, dict)): + return None - Args - token: - Returns: - str : error message - Tuple : (record_id, id, date, secret_token) + default_days = content_file_download.get("expiration_date", 30) + expiration_date = dt.now() + timedelta(days=default_days) + download_limit = content_file_download.get("download_limit", 10) + extra_info = {'usage_application_activity_id': activity_id, + 'send_usage_report': True} + onetime_url_obj = FileOnetimeDownload.create( + approver_id = approver_id, + record_id = record_id, + file_name = file_name, + expiration_date = expiration_date, + download_limit = download_limit, + user_mail = user_mail, + is_guest = is_guest, + extra_info = extra_info + ) + + return onetime_url_obj + + +def create_download_url(url_obj, is_secret_url): + """Create a download URL from a object. + + Note: + - This function can be used for both secret URL and onetime URL. + - Same URL is generated from a same object. + + Args: + url_obj (FileSecretDownload or FileOnetimeDownload): + The secret URL or onetime URL object. + is_secret_url (bool): + True if the URL is for secret URL, False if for onetime URL. + + Returns: + str: The generated URL. """ - # current_app.logger.debug("token:{}".format(token)) - error = _("Token is invalid.") - if token is None: - return error, () - try: - decode_token = base64.b64decode(token.encode()).decode() - current_app.logger.debug("decode_token:{}".format(decode_token)) - param = decode_token.split(" ") - if not param or len(param) != 4: - return error, () + host_url = request.host_url + url_type = 'secret' if is_secret_url else 'onetime' + hash = generate_sha256_hash(url_obj) + bytes = hash + b'_' + str(url_obj.id).encode() + token = base64.urlsafe_b64encode(bytes).decode() + url = (f'{host_url}record/{url_obj.record_id}/file/{url_type}/' + f'{url_obj.file_name}?token={token}') + return url - return "", (param[0], param[1], param[2], param[3]) #record_id, id, current_date, secret_token - except Exception as err: - current_app.logger.error(err) - return error, () +def generate_sha256_hash(url_obj): + """Generate a SHA-256 hash value from a download URL object. -def validate_secret_download_token( - secret_download: FileSecretDownload , file_name: str, record_id: str, - id: str, date: str, token: str -) -> Tuple[bool, str]: - """Validate secret download token. + Note: + Same object always generates the same value, so it can be used both for + creating a new hash and verifying it. - Args - FileSecretDownload:secret_download: - str:file_name: - str:record_id: - str:id: - str:date: - str:token: - Returns - Tuple: - bool : is valid - str : error message + Args: + url_obj (FileSecretDownload or FileOnetimeDownload): + The secret URL or onetime URL object. + + Returns: + bytes: The SHA-256 hash value. """ - token_invalid = _("Token is invalid.") secret_key = current_app.config['WEKO_RECORDS_UI_SECRET_KEY'] - download_pattern = current_app.config[ - 'WEKO_RECORDS_UI_SECRET_DOWNLOAD_PATTERN'] - hash_value = download_pattern.format( - file_name, record_id, id, date) - - if not oracle10.verify(secret_key, token, hash_value): - current_app.logger.error('Validate token error: {}'.format(hash_value)) - return False, token_invalid + token_parts = [ + secret_key, + str(url_obj.created), + str(url_obj.id), + str(url_obj.record_id), + str(url_obj.file_name), + str(url_obj.expiration_date), + str(url_obj.download_limit), + ] + hash_obj = hashlib.sha256() + for part in token_parts: + hash_obj.update(part.encode()) + return hash_obj.digest() + + +def send_secret_url_mail(uuid, secret_url_obj, item_title): + """Send an email with a secret URL. + + Args: + uuid (UUID): The UUID of the item. + secret_url_obj (FileSecretDownload): The secret URL object. + item_title (str): The item title. + + Returns: + bool: True if the email sent successfully, False otherwise. + """ + # Setup mail info + user_profile = UserProfile.get_by_userid(current_user.id) + fullname = user_profile._displayname if user_profile else '' + secret_url_info = { + 'restricted_download_link' : create_download_url(secret_url_obj), + 'mail_recipient' : current_user.email, + 'file_name' : secret_url_obj.file_name, + 'restricted_expiration_date': str(secret_url_obj.expiration_date), + 'restricted_download_count' : str(secret_url_obj.download_limit), + 'restricted_fullname' : fullname, + 'restricted_data_name' : item_title, + } + mail_info = set_mail_info(get_item_info(uuid), + type('' ,(object,), {'activity_id': ''})()) + mail_info.update(secret_url_info) + + # Send mail + mail_pattern = current_app.config.get( + 'WEKO_RECORDS_UI_MAIL_TEMPLATE_SECRET_URL') + is_succeeded = process_send_mail(mail_info, mail_pattern) + return is_succeeded + + +def validate_token(token): + """Validate the provided token. + + This function can be used for both secret URL and onetime URL. + + Args: + token (str): The token to validate. + + Returns: + bool: True if the token is valid, False otherwise. + """ try: - if not secret_download: - return False, token_invalid - try: - expiration_date = timedelta(secret_download.expiration_date) - download_date = secret_download.created.date() + expiration_date - current_date = dt.utcnow().date() - if current_date > download_date: - return False, _( - "The expiration date for download has been exceeded.") - except OverflowError: - # in case of "Unlimited" - current_app.logger.debug('date value out of range:'+ - str(secret_download.expiration_date)) - - if secret_download.download_count <= 0: - return False, _("The download limit has been exceeded.") - return True, "" - except Exception as err: - current_app.logger.error('Validate secret download token error:') - current_app.logger.error(err) - return False, token_invalid - -def get_secret_download(file_name: str, record_id: str, - id: str , created :dt ) -> Optional[FileSecretDownload]: - """Get secret download count. - - Args : - str:file_name - str:record_id - str:id - dt :created - @return: - FileSecretDownload or None + bytes = base64.urlsafe_b64decode(token.encode()) + token_hash, token_id = bytes.split(b'_') + url_obj = FileSecretDownload.get_by_id(token_id.decode()) + if not url_obj: + url_obj = FileOnetimeDownload.get_by_id(token_id.decode()) + if url_obj and (token_hash == generate_sha256_hash(url_obj)): + return True + else: + return False + except Exception as e: + current_app.logger.error(e) + return False + + +def convert_token_into_obj(token, is_secret_file): + """Convert the token into a download URL object. + + Args: + token (str): The token to convert. + is_secret_file (bool): + True if the URL is for secret URL, False if for onetime URL. + + Returns: + FileSecretDownload or FileOnetimeDownload: The download URL object. """ - file_downloads = FileSecretDownload.find( - file_name=file_name, record_id=record_id, id=id ,created=created - ) - if file_downloads and len(file_downloads) == 1: - return file_downloads[0] - else: + if not validate_token(token): return None + bytes = base64.urlsafe_b64decode(token.encode()) + url_obj_id = bytes.split(b'_')[1].decode() + if is_secret_file: + url_obj = FileSecretDownload.get_by_id(url_obj_id) + else: + url_obj = FileOnetimeDownload.get_by_id(url_obj_id) + return url_obj + -def _create_secret_download_url(file_name: str, record_id: str, user_mail: str) -> FileSecretDownload: - """Create secret download. +def validate_url_download(record, filename, token, is_secret_url): + """Validate the request for URL download. Args: - str : file_name: - str : record_id: - str : user_mail: + record (WekoRecord): The record object. + filename (str): The name of the target file. + token (str): The token for the download URL. + is_secret_url (bool): + True if the URL is for secret URL, False if for onetime URL. + Returns: - FileSecretDownload : inserted record + Tuple[bool, str]: A tuple of the validation result and error message. """ - secret_url_file_download:dict = get_restricted_access('secret_URL_file_download') - - expiration_date = secret_url_file_download.get("secret_expiration_date", 30) - download_limit = secret_url_file_download.get("secret_download_limit", 10) - - file_secret = FileSecretDownload.create(**{ - "file_name": file_name, - "record_id": record_id, - "user_mail": user_mail, - "expiration_date": expiration_date, - "download_count": download_limit, - }) - return file_secret + # Check if the token is valid + token = request.args.get('token', type=str) + if not validate_token(token): + return False, 'The provided token is invalid.' + + if is_secret_url: + if not is_secret_url_feature_enabled(): + return False, 'This feature is currently disabled.' + + # Check if the file is available for download + if (not validate_file_access(record, filename, is_secret_url) or + not validate_download_record(record)): + return False, 'This file is currently not available for this feature.' + + # Check if the URL is still valid + url_obj = convert_token_into_obj(token, is_secret_url) + if url_obj.is_deleted is True: + return False, 'This URL has been deactivated.' + if url_obj.download_count >= url_obj.download_limit: + return False, 'The download limit has been exceeded.' + limit_date = url_obj.expiration_date.replace(tzinfo=timezone.utc) + if limit_date < dt.now(timezone.utc): + return False, 'The expiration date for download has been exceeded.' + + return True, '' + + +def validate_file_access(record, filename, is_secret_url): + if is_secret_url: + return is_secret_file(record, filename) + else: + return is_onetime_file(record, filename) + + +def is_onetime_file(record, file_name): + for file_data in record.get_file_data(): + if file_data.get('filename') == file_name: + return file_data.get('accessrole') == 'open_restricted' + return False diff --git a/modules/weko-records-ui/weko_records_ui/views.py b/modules/weko-records-ui/weko_records_ui/views.py index d2eb347950..c478d87292 100644 --- a/modules/weko-records-ui/weko_records_ui/views.py +++ b/modules/weko-records-ui/weko_records_ui/views.py @@ -72,10 +72,12 @@ from .permissions import check_content_clickable, check_created_id, \ check_file_download_permission, check_original_pdf_download_permission, \ check_permission_period, file_permission_factory, get_permission -from .utils import create_secret_url, get_billing_file_download_permission, \ - get_google_detaset_meta, get_google_scholar_meta, get_groups_price, \ +from .utils import can_manage_secret_url, create_secret_url_record, \ + get_billing_file_download_permission, get_google_detaset_meta, \ + get_google_scholar_meta, get_groups_price, \ get_min_price_billing_file_download, get_record_permalink, hide_by_email, \ - delete_version, is_show_email_of_creator,hide_by_itemtype + delete_version, is_show_email_of_creator,hide_by_itemtype, \ + send_secret_url_mail, validate_secret_url_generation_request from .utils import restore as restore_imp from .utils import soft_delete as soft_delete_imp @@ -722,95 +724,53 @@ def _get_rights_title(result, rights_key_str, rights_values, current_lang, meta_ flg_display_itemtype = current_app.config.get('WEKO_RECORDS_UI_DISPLAY_ITEM_TYPE') , flg_display_resourcetype = current_app.config.get('WEKO_RECORDS_UI_DISPLAY_RESOURCE_TYPE') , search_author_flg=search_author_flg, - show_secret_URL=_get_show_secret_url_button(record,filename), + show_secret_URL=can_manage_secret_url(record, filename), **ctx, **kwargs ) -def create_secret_url_and_send_mail(pid:PersistentIdentifier, record:WekoRecord, filename:str, **kwargs) -> str: - """on click button 'Secret URL' - generate secret URL and send mail. - about entrypoint settings, see at .config RECORDS_UI_ENDPOINTS.recid_secret_url - +def create_secret_url_and_send_mail(pid, record, filename, **kwargs): + """Issue a new secret URL for a file in a record. + + This method issues a new secret URL by creating a new record in the + FileSecretDownload table. The method also sends an email including the + URL to the user if the 'send_email' parameter of the request is set to + True. + Args: - pid: PID object. - record: Record object. - filename: File name. + pid (PersistentIdentifier): The identifier for the item. + record (WekoRecord): The record metadata of the item. + filename (str): The file name to download. Returns: - result status and message text. + dict: A dictionary containing the message to be displayed to the user. """ - current_app.logger.info("pid:" + pid.pid_value) - current_app.logger.info("record:" + str(record.id)) - current_app.logger.info("filename:" + filename) - - #permission check - # "Someone who can show Secret URL button" can also use generate Secret URL function. - if not _get_show_secret_url_button(record ,filename): + if not validate_secret_url_generation_request(request.json): + abort(400) + if not can_manage_secret_url(record, filename): abort(403) - userprof:UserProfile = UserProfile.get_by_userid(current_user.id) - restricted_fullname = userprof._displayname or '' if userprof else '' - restricted_data_name = record.get('item_title','') - - #generate url and regist db(FileSecretDownload) - result = create_secret_url(pid.pid_value,filename,current_user.email , restricted_fullname , restricted_data_name) - - #send mail - mail_pattern_name:str = current_app.config.get('WEKO_RECORDS_UI_MAIL_TEMPLATE_SECRET_URL') - - mail_info = set_mail_info(get_item_info(pid.object_uuid), type("" ,(object,),dict(activity_id = ''))) - mail_info.update(result) - if process_send_mail( mail_info = mail_info, mail_pattern_name=mail_pattern_name) : - return _('Success Secret URL Generate') - else: + try: + url_obj = create_secret_url_record(pid.pid_value, + filename, + request.json) + except: abort(500) -def _get_show_secret_url_button(record : WekoRecord, filename :str) -> bool: - """ - Args: - WekoRecord : records_metadata for target item - str : target content name - Returns: - bool : return true if be able to show Secret URL button. or false. - """ + message = 'Secret URL generated successfully' + if request.json['send_email'] is True: + sending_result = send_secret_url_mail( + pid.object_uuid, url_obj, record.get('item_title', '')) + if sending_result: + message += ', please check your email inbox.' + else: + message += (', but there was an error while sending the email. ' + 'To use the URL, please refresh the page and copy it ' + 'from the issued URL list.') + else: + return jsonify({'message': message + '.'}) - #1.check secret url function is enabled - restricted_access = AdminSettings.get('restricted_access', False) - if not restricted_access: - restricted_access = current_app.config[ - 'WEKO_ADMIN_RESTRICTED_ACCESS_SETTINGS'] - - enable:bool = restricted_access.get('secret_URL_file_download',{}).get('secret_enable',False) - - #2.check the user has permittion - has_parmission = False - # Registered user - owner_user_id = [int(record['owner'])] if record.get('owner') else [] - shared_user_id = [int(record['weko_shared_id'])] if int(record.get('weko_shared_id', -1)) != -1 else [] - if current_user and current_user.is_authenticated and \ - current_user.id in owner_user_id + shared_user_id: - has_parmission = True - # Super users - supers = current_app.config['WEKO_PERMISSION_SUPER_ROLE_USER'] - for role in list(current_user.roles or []): - if role.name in supers: - has_parmission = True - - #3.check the file's accessrole is "open_no" ,or "open_date" and not open yet. - is_secret_file = False - current_app.logger.info(record.get_file_data()) - for content in record.get_file_data(): - if content.get('filename') == filename: - if content.get('accessrole') == "open_no": - is_secret_file = True - elif content.get('accessrole') == "open_date" and \ - datetime.now() < datetime.strptime(content.get('date',[{"dateValue" :'1970-01-01'}])[0].get("dateValue" ,'1970-01-01'), '%Y-%m-%d') : - is_secret_file = True - - # all true is show - return enable and has_parmission and is_secret_file @blueprint.route('/r/', methods=['GET']) @blueprint.route('/r/.', methods=['GET']) diff --git a/modules/weko-workflow/weko_workflow/utils.py b/modules/weko-workflow/weko_workflow/utils.py index 5569bc6bed..c11bf10c9f 100644 --- a/modules/weko-workflow/weko_workflow/utils.py +++ b/modules/weko-workflow/weko_workflow/utils.py @@ -3324,10 +3324,15 @@ def create_onetime_download_url_to_guest(activity_id: str, extra_info: dict): """Create onetime download URL to guest. - @param activity_id: - @param extra_info: - @return: + Args: + activity_id (str): The ID of the usage application activity. + extra_info (dict): Additional information. + + Returns: + dict: onetime URL and expiration date. """ + from weko_records_ui.utils import (create_download_url, + create_onetime_download_record) file_name = extra_info.get('file_name') record_id = extra_info.get('record_id') user_mail = extra_info.get('user_mail') @@ -3335,39 +3340,24 @@ def create_onetime_download_url_to_guest(activity_id: str, if not user_mail: user_mail = extra_info.get('guest_mail') is_guest_user = True - if file_name and record_id and user_mail: - from weko_records_ui.utils import generate_one_time_download_url - onetime_file_url = generate_one_time_download_url( - file_name, record_id, user_mail) - - # Delete guest activity. - delete_guest_activity(activity_id) - - # Save onetime to Database. - from weko_records_ui.utils import create_onetime_download_url - one_time_obj = create_onetime_download_url( - activity_id, file_name, record_id, user_mail, is_guest_user) - expiration_tmp = { - "expiration_date": "", - "expiration_date_ja": "", - "expiration_date_en": "", - } - if one_time_obj: - try: - expiration_date = timedelta(days=one_time_obj.expiration_date) - expiration_date = datetime.today() + expiration_date - expiration_date = expiration_date.strftime("%Y-%m-%d") - expiration_tmp['expiration_date'] = expiration_date - except OverflowError: - expiration_tmp["expiration_date_ja"] = "無制限" - expiration_tmp["expiration_date_en"] = "Unlimited" - return { - "file_url": onetime_file_url, - **expiration_tmp, - } - else: - current_app.logger.error("Can not create onetime download.") - return False + if not file_name or not record_id or not user_mail: + return {} + + try: + url_obj = create_onetime_download_record( + activity_id, current_user.id, record_id, file_name, user_mail, + is_guest_user) + except: + return {} + if not url_obj: + return {} + + delete_guest_activity(activity_id) + + return { + 'file_url': create_download_url(url_obj, is_secret_file=False), + 'expiration_date': url_obj.expiration_date + } def delete_guest_activity(activity_id: str) -> bool: From 2c4be15d88463fcbbc7e023ac709b0172dbe6100 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Mon, 6 Jan 2025 16:43:11 +0900 Subject: [PATCH 12/61] Fix extra_info update --- modules/weko-records-ui/weko_records_ui/fd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/fd.py b/modules/weko-records-ui/weko_records_ui/fd.py index 8bf8c650a3..366c48a57d 100644 --- a/modules/weko-records-ui/weko_records_ui/fd.py +++ b/modules/weko-records-ui/weko_records_ui/fd.py @@ -445,6 +445,7 @@ def error_response(error_message, status_code=400): extra_info, url_obj.user_mail ,record, file_object) if error: return error_response(error, 403) + url_obj.update_extra_info(extra_info) db.session.commit() except SQLAlchemyError as ex: current_app.logger.error(f'SQLAlchemy error: {ex}') @@ -455,10 +456,9 @@ def error_response(error_message, status_code=400): db.session.rollback() return error_response('Unexpected error occurred.', 500) - # Update download count and extra info + # Update download count try: url_obj.increment_download_count() - url_obj.update_extra_info(extra_info) except: return error_response('Unexpected error occurred.', 500) From c6b7e617be31cd18373b1adcc146cc18540d6131 Mon Sep 17 00:00:00 2001 From: "sei.nakamura" Date: Tue, 7 Jan 2025 11:15:28 +0900 Subject: [PATCH 13/61] =?UTF-8?q?=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/weko-records-ui/tests/test_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/weko-records-ui/tests/test_utils.py b/modules/weko-records-ui/tests/test_utils.py index 090b3142c8..f8ee11e252 100644 --- a/modules/weko-records-ui/tests/test_utils.py +++ b/modules/weko-records-ui/tests/test_utils.py @@ -651,7 +651,7 @@ def test_validate_download_record(app, records): # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_is_secret_url_feature_enabled -vv -s --cov-branch --cov-report=html --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings def test_is_secret_url_feature_enabled(app): with app.app_context(): - # Case 1: Trueを返す + # Case 1: secret_enableがTrueを返す with patch('weko_records_ui.utils.AdminSettings.get') as mock_get: mock_get.return_value = { 'secret_URL_file_download': { @@ -660,7 +660,7 @@ def test_is_secret_url_feature_enabled(app): } assert is_secret_url_feature_enabled() is True - # Case 2: Falseを返す + # Case 2: secret_enableがFalseを返す with patch('weko_records_ui.utils.AdminSettings.get') as mock_get: mock_get.return_value = { 'secret_URL_file_download': { @@ -733,7 +733,7 @@ def test_is_secret_file(file_data, filename, expected): # dt(日時関連)をモックして、現在の日付や日付文字列の変換を制御する with patch('weko_records_ui.utils.dt') as mock_dt: - mock_dt.now.return_value = dt(2024, 1, 1) # 現在の日付を2023年1月1日に設定 + mock_dt.now.return_value = dt(2024, 1, 1) # 現在の日付を2024年1月1日に設定 mock_dt.strptime.side_effect = lambda *args, **kwargs: dt.strptime(*args, **kwargs) # strptimeの動作をモック # is_secret_file関数を実行して、結果が期待される値と一致するかを確認 From 47a4a0b08b090263024997919ed6d09ab11fc6f2 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Wed, 8 Jan 2025 10:54:31 +0900 Subject: [PATCH 14/61] Fix request validation --- .../weko-records-ui/weko_records_ui/utils.py | 74 +++++++++++++------ modules/weko-workflow/weko_workflow/utils.py | 3 +- 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/utils.py b/modules/weko-records-ui/weko_records_ui/utils.py index 85bbc571ff..15a5f8449a 100644 --- a/modules/weko-records-ui/weko_records_ui/utils.py +++ b/modules/weko-records-ui/weko_records_ui/utils.py @@ -1138,15 +1138,20 @@ def is_private_index(record): return False -def validate_download_record(record: dict): - """Validate record. +def validate_download_record(record): + """Validate the record(item) if it is downloadable. - :param record: + Args: + record (dict): Record meta data. + + Returns: + bool: True if record is downloadable, False otherwise. """ if record['publish_status'] != PublishStatus.PUBLIC.value: return False if is_private_index(record): return False + return True def is_secret_url_feature_enabled(): @@ -1760,7 +1765,10 @@ def validate_secret_url_generation_request(request_data): """Validate request for secret URL generation. Args: - request_data (dict): The request object. + request_data (dict): The request object containing the following keys: + - link_name (str): The name of the secret link. + - expiration_date (str): The expiration date of the link. + - download_limit (int): The maximum number of downloads allowed. Returns: bool: True if the request is valid, False otherwise. @@ -1768,16 +1776,30 @@ def validate_secret_url_generation_request(request_data): if not request_data: return False - label_name = request_data.get('link_name') - expiration_date = request_data.get('expiration_date') - download_limit = request_data.get('download_limit') - if not isinstance(label_name, str) or len(label_name) > 255: - return False - if not isinstance(expiration_date, dt) or expiration_date < dt.now(): - return False - if not isinstance(download_limit, int) or download_limit <= 0: + expected_keys = ['link_name', 'expiration_date', 'download_limit'] + if not all(key in request_data for key in expected_keys): return False + link_name = request_data['link_name'] + expiration_date = request_data['expiration_date'] + download_limit = request_data['download_limit'] + if link_name: + if not isinstance(link_name, str) or len(link_name) > 255: + return False + if expiration_date: + if not isinstance(expiration_date, str): + return False + try: + dt_expiration_date = dt.strptime(expiration_date, '%Y-%m-%d') + except ValueError: + current_app.logger.error(f'Invalid date format: {expiration_date}') + return False + if dt_expiration_date < dt.now(): + return False + if download_limit is not None: + if not isinstance(download_limit, int) or download_limit <= 0: + return False + return True @@ -1800,24 +1822,34 @@ def create_secret_url_record(record_id, file_name, request_data): not isinstance(content_file_download, dict)): return None + label_name = request_data['link_name'] + if not label_name: + label_name = f'{file_name}_{dt.now().strftime('%Y/%m/%d')}' + expiration_date = request_data['expiration_date'] + if not expiration_date: + default_days = content_file_download.get('expiration_date', 30) + expiration_date = dt.now() + timedelta(days=default_days) + download_limit = request_data['download_limit'] + if not download_limit: + download_limit = content_file_download.get('download_limit', 10) + secret_url_obj = FileSecretDownload.create( creator_id = current_user.id, record_id = record_id, file_name = file_name, - label_name = request_data['link_name'], - expiration_date = request_data['expiration_date'], - download_limit = request_data['download_limit']) + label_name = label_name, + expiration_date = expiration_date, + download_limit = download_limit) return secret_url_obj def create_onetime_download_record( - activity_id, approver_id, record_id, file_name, user_mail, is_guest=False, + activity_id, record_id, file_name, user_mail, is_guest=False, extra_info=None): """Create onetime download record. Args: - activity_id: - approver_id: The ID of the user who approved the usage application. + activity_id: The ID of the usage application activity. record_id: The ID of the record which the file belongs to. file_name: The name of the file to be downloaded. user_mail: The email address of the user who requested the download. @@ -1833,13 +1865,13 @@ def create_onetime_download_record( not isinstance(content_file_download, dict)): return None - default_days = content_file_download.get("expiration_date", 30) + default_days = content_file_download.get('expiration_date', 30) expiration_date = dt.now() + timedelta(days=default_days) - download_limit = content_file_download.get("download_limit", 10) + download_limit = content_file_download.get('download_limit', 10) extra_info = {'usage_application_activity_id': activity_id, 'send_usage_report': True} onetime_url_obj = FileOnetimeDownload.create( - approver_id = approver_id, + approver_id = current_user.id, record_id = record_id, file_name = file_name, expiration_date = expiration_date, diff --git a/modules/weko-workflow/weko_workflow/utils.py b/modules/weko-workflow/weko_workflow/utils.py index c11bf10c9f..89ef881c9d 100644 --- a/modules/weko-workflow/weko_workflow/utils.py +++ b/modules/weko-workflow/weko_workflow/utils.py @@ -3345,8 +3345,7 @@ def create_onetime_download_url_to_guest(activity_id: str, try: url_obj = create_onetime_download_record( - activity_id, current_user.id, record_id, file_name, user_mail, - is_guest_user) + activity_id, record_id, file_name, user_mail, is_guest_user) except: return {} if not url_obj: From d7566674dfcd081b426f396974c2f0888adc6d08 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Wed, 8 Jan 2025 11:10:32 +0900 Subject: [PATCH 15/61] Fix merge error --- .../weko-records-ui/weko_records_ui/utils.py | 91 +------------------ 1 file changed, 1 insertion(+), 90 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/utils.py b/modules/weko-records-ui/weko_records_ui/utils.py index 15a5f8449a..95342b449b 100644 --- a/modules/weko-records-ui/weko_records_ui/utils.py +++ b/modules/weko-records-ui/weko_records_ui/utils.py @@ -1208,96 +1208,6 @@ def is_secret_file(record, file_name): if not target_data: return False - publish_date = dt.strptime( - target_data.get('date')[0].get('dateValue'), '%Y-%m-%d') - is_secret_file = ( - target_data.get('accessrole') == 'open_no' or ( - target_data.get('accessrole') == 'open_date' and - dt.now < publish_date - )) - return is_secret_file - - -def can_manage_secret_url(record, filename): - """Determine if the user can manage the secret URL feature for a file. - - This function checks whether the secret URL feature can be used for a given - file in a record by evaluating the following conditions: - 1. The secret URL feature is enabled system-wide. - 2. The logged-in user has the necessary permissions. - 3. The specified file qualifies for secret URL use. - - Args: - record (WekoRecord): The record object containing the file. - filename (str): The name of the target file. - - Returns: - bool: True if all conditions are met, False otherwise. - """ - if not current_user or not current_user.is_authenticated: - return False - else: - result = ( - is_secret_url_feature_enabled() and - has_permission_to_manage_secret_url(record, current_user.id) and - is_secret_file(record, filename) - ) - return result - -def is_secret_url_feature_enabled(): - """Check if the feature is enabled. - - Returns: - bool: True if the feature is enabled, False otherwise. - """ - settings = AdminSettings.get(name='restricted_access',dict_to_object=False) - if not settings: - settings = current_app.config['WEKO_ADMIN_RESTRICTED_ACCESS_SETTINGS'] - secret_url_settings = settings.get('secret_URL_file_download', {}) - is_enabled = secret_url_settings.get('secret_enable', False) - return is_enabled - - -def has_permission_to_manage_secret_url(record, user_id): - """Check if the user has permission to manage the secret URL feature. - - Following users have the permission. - - The administrators. - - The user who registered the item(record). - - The user who registered the item on behalf of other users. - - Returns: - bool: True if the user has permission, False otherwise. - """ - super_roles = current_app.config['WEKO_PERMISSION_SUPER_ROLE_USER'] - user = User.query.filter_by(id=user_id).first() - # Need to change the 'weko_shared_id' to 'weko_shared_ids' in the future. - has_permission = ( - user_id == int(record['owner']) or - user_id in [record['weko_shared_id']] or - any(role.name in super_roles for role in user.roles or []) - ) - return has_permission - - -def is_secret_file(record: WekoRecord, filename): - """Check if the target file meets the requirements for secret URL use. - - Args: - record (WekoRecord): The record object. - filename (str): The target file name. - - Returns: - bool: True if the file is for secret URL use, False otherwise. - """ - target_data = {} - for file_data in record.get_file_data(): - if file_data.get('filename') == filename: - target_data = file_data - break - if not target_data: - return False - publish_date = dt.strptime( target_data.get('date')[0].get('dateValue'), '%Y-%m-%d') is_secret_file = ( @@ -1334,6 +1244,7 @@ def can_manage_secret_url(record, filename): ) return result + def get_onetime_download(file_name: str, record_id: str, user_mail: str) -> Optional[FileOnetimeDownload]: """Get onetime download count. From cc37f4827ae5154804539db48baeaa043bbe864a Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Wed, 8 Jan 2025 19:53:11 +0900 Subject: [PATCH 16/61] Fix token validation --- modules/weko-records-ui/weko_records_ui/utils.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/utils.py b/modules/weko-records-ui/weko_records_ui/utils.py index 95342b449b..818037d806 100644 --- a/modules/weko-records-ui/weko_records_ui/utils.py +++ b/modules/weko-records-ui/weko_records_ui/utils.py @@ -1899,13 +1899,13 @@ def validate_token(token): try: bytes = base64.urlsafe_b64decode(token.encode()) token_hash, token_id = bytes.split(b'_') - url_obj = FileSecretDownload.get_by_id(token_id.decode()) - if not url_obj: - url_obj = FileOnetimeDownload.get_by_id(token_id.decode()) - if url_obj and (token_hash == generate_sha256_hash(url_obj)): + secret_obj = FileSecretDownload.get_by_id(token_id.decode()) + if secret_obj and (token_hash == generate_sha256_hash(secret_obj)): return True - else: - return False + onetime_obj = FileOnetimeDownload.get_by_id(token_id.decode()) + if onetime_obj and (token_hash == generate_sha256_hash(onetime_obj)): + return True + return False except Exception as e: current_app.logger.error(e) return False From c3f2a8665ba8a62383b0592a7f095a2f6d8ee9d5 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Thu, 9 Jan 2025 10:07:25 +0900 Subject: [PATCH 17/61] Change token validation logic --- .../weko-records-ui/weko_records_ui/utils.py | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/utils.py b/modules/weko-records-ui/weko_records_ui/utils.py index 818037d806..8990558b40 100644 --- a/modules/weko-records-ui/weko_records_ui/utils.py +++ b/modules/weko-records-ui/weko_records_ui/utils.py @@ -1885,13 +1885,14 @@ def send_secret_url_mail(uuid, secret_url_obj, item_title): return is_succeeded -def validate_token(token): +def validate_token(token, is_secret_url): """Validate the provided token. This function can be used for both secret URL and onetime URL. Args: token (str): The token to validate. + is_secret_url (bool): True if for secret URL, False if for onetime URL. Returns: bool: True if the token is valid, False otherwise. @@ -1899,34 +1900,36 @@ def validate_token(token): try: bytes = base64.urlsafe_b64decode(token.encode()) token_hash, token_id = bytes.split(b'_') - secret_obj = FileSecretDownload.get_by_id(token_id.decode()) - if secret_obj and (token_hash == generate_sha256_hash(secret_obj)): - return True - onetime_obj = FileOnetimeDownload.get_by_id(token_id.decode()) - if onetime_obj and (token_hash == generate_sha256_hash(onetime_obj)): + if is_secret_url: + url_obj = FileSecretDownload.get_by_id(token_id.decode()) + else: + url_obj = FileOnetimeDownload.get_by_id(token_id.decode()) + if url_obj and (token_hash == generate_sha256_hash(url_obj)): return True - return False + else: + return False except Exception as e: current_app.logger.error(e) return False -def convert_token_into_obj(token, is_secret_file): +def convert_token_into_obj(token, is_secret_url): """Convert the token into a download URL object. Args: token (str): The token to convert. - is_secret_file (bool): + is_secret_url (bool): True if the URL is for secret URL, False if for onetime URL. Returns: - FileSecretDownload or FileOnetimeDownload: The download URL object. + FileSecretDownload or FileOnetimeDownload or None: + The download URL object, or None if the token is invalid. """ - if not validate_token(token): + if not validate_token(token, is_secret_url): return None bytes = base64.urlsafe_b64decode(token.encode()) url_obj_id = bytes.split(b'_')[1].decode() - if is_secret_file: + if is_secret_url: url_obj = FileSecretDownload.get_by_id(url_obj_id) else: url_obj = FileOnetimeDownload.get_by_id(url_obj_id) @@ -1948,7 +1951,7 @@ def validate_url_download(record, filename, token, is_secret_url): """ # Check if the token is valid token = request.args.get('token', type=str) - if not validate_token(token): + if not validate_token(token, is_secret_url): return False, 'The provided token is invalid.' if is_secret_url: From a11d6a6b9bd43a4dd651bb580ba3b2855bb35317 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Tue, 14 Jan 2025 22:09:50 +0900 Subject: [PATCH 18/61] Fix timezone problem --- modules/weko-records-ui/weko_records_ui/fd.py | 12 +- .../weko-records-ui/weko_records_ui/models.py | 22 +-- .../weko-records-ui/weko_records_ui/utils.py | 160 ++++++++++++------ .../weko-records-ui/weko_records_ui/views.py | 16 +- modules/weko-workflow/weko_workflow/utils.py | 6 +- 5 files changed, 137 insertions(+), 79 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/fd.py b/modules/weko-records-ui/weko_records_ui/fd.py index 366c48a57d..5030c7263f 100644 --- a/modules/weko-records-ui/weko_records_ui/fd.py +++ b/modules/weko-records-ui/weko_records_ui/fd.py @@ -41,7 +41,6 @@ from weko_deposit.api import WekoRecord from weko_groups.api import Group from weko_records.api import FilesMetadata, ItemTypes -from weko_records_ui.utils import generate_one_time_download_url from weko_user_profiles.models import UserProfile from weko_workflow.utils import is_terms_of_use_only from werkzeug.datastructures import Headers @@ -425,8 +424,8 @@ def error_response(error_message, status_code=400): return render_template(error_template, error_message), status_code token = request.args.get('token', type=str) - is_validated, error_msg = ( - validate_url_download(record, filename, token, is_secret_url=False)) + is_validated, error_msg = validate_url_download( + record, filename, token, is_secret_url=False) if not is_validated: return error_response(error_msg, 403) @@ -514,10 +513,11 @@ def file_download_secret(pid, record, filename, _record_file_factory=None, """ def error_response(error_message, status_code=400): error_template = "weko_theme/error.html" - return render_template(error_template, error_message), status_code + return render_template(error_template, error=error_message), status_code + token = request.args.get('token', type=str) is_validated, error_msg = ( - validate_url_download(record, filename, is_secret_url=True)) + validate_url_download(record, filename, token, is_secret_url=True)) if not is_validated: return error_response(error_msg, 403) @@ -532,7 +532,7 @@ def error_response(error_message, status_code=400): user_profile = UserProfile.get_by_userid(current_user.get_id()) lang = user_profile.language if user_profile else 'en' - url_obj = convert_token_into_obj(request.args.get('token')) + url_obj = convert_token_into_obj(token, is_secret_url=True) try: url_obj.increment_download_count() except: diff --git a/modules/weko-records-ui/weko_records_ui/models.py b/modules/weko-records-ui/weko_records_ui/models.py index d78671f83b..6334bb467e 100644 --- a/modules/weko-records-ui/weko_records_ui/models.py +++ b/modules/weko-records-ui/weko_records_ui/models.py @@ -21,7 +21,7 @@ """Database models for weko-admin.""" -from datetime import datetime +from datetime import datetime, timezone from typing import List from flask import current_app @@ -433,9 +433,9 @@ def create(cls, **data): Raises: Exception: If an unexpected error occurs during the creation. """ - if data.get('expiration_date') < datetime.now(): + if data['expiration_date'] < datetime.now(tz=timezone.utc): raise ValueError('The expiration date must be in the future.') - if data.get('download_limit') <= 0: + if data['download_limit'] <= 0: raise ValueError('The download limit must be greater than 0.') try: file_download = cls(**data) @@ -564,15 +564,15 @@ def __init__(self, creator_id, record_id, file_name, label_name, record_id (str): The ID of the record that has the file. file_name (str): The name of the file. label_name (str): The label of the secret URL. - expiration_date (datetime): The date and time when the URL expires. + expiration_date (date): The date when the URL expires. download_limit (int): The download limit of the URL. """ - self.creator_id = creator_id - self.record_id = record_id - self.file_name = file_name - self.label_name = label_name + self.creator_id = creator_id + self.record_id = record_id + self.file_name = file_name + self.label_name = label_name self.expiration_date = expiration_date - self.download_limit = download_limit + self.download_limit = download_limit @classmethod def create(cls, **data): @@ -588,9 +588,9 @@ def create(cls, **data): ValueError: If the arguments are invalid. Exception: If an unexpected error occurs during the creation. """ - if data.get('expiration_date') < datetime.now(): + if data['expiration_date'] < datetime.now(tz=timezone.utc): raise ValueError('The expiration date must be in the future.') - if data.get('download_limit') <= 0: + if data['download_limit'] <= 0: raise ValueError('The download limit must be greater than 0.') try: file_download = cls(**data) diff --git a/modules/weko-records-ui/weko_records_ui/utils.py b/modules/weko-records-ui/weko_records_ui/utils.py index 8990558b40..b5de1071cb 100644 --- a/modules/weko-records-ui/weko_records_ui/utils.py +++ b/modules/weko-records-ui/weko_records_ui/utils.py @@ -1672,44 +1672,80 @@ def get_google_detaset_meta(record,record_tree=None): return json.dumps(res_data, ensure_ascii=False) -def validate_secret_url_generation_request(request_data): +def to_utc_datetime(str_date, offset_minutes=0): + """Parse string date info into datetime object in UTC timezone. + + Args: + str_date (str): The date string as 'YYYY-MM-DD'. + offset_minutes (int): The timezone offset in minutes. + + Returns: + datetime: The datetime object in UTC timezone. + """ + local_naive_dt = dt.strptime(str_date, '%Y-%m-%d') + local_tz = timezone(timedelta(minutes=-offset_minutes)) + local_aware_dt = local_naive_dt.replace(tzinfo=local_tz) + utc_dt = local_aware_dt.astimezone(timezone.utc) + return utc_dt + + +def validate_secret_url_generation_request(request_json): """Validate request for secret URL generation. + The reqeust data must contain the following keys: + - link_name (optional): The name of the secret link. + - expiration_date (optional): The expiration date of the link. + - download_limit (optional): The maximum number of downloads allowed. + - send_email: True if the user wants to send an email, False otherwise. + - timezone_offset_minutes: The timezone offset in minutes. + + Keys marked as optional must exist in the request, but their values can be + empty. + Args: - request_data (dict): The request object containing the following keys: - - link_name (str): The name of the secret link. - - expiration_date (str): The expiration date of the link. - - download_limit (int): The maximum number of downloads allowed. + request_json (dict): The request.json data from the user. Returns: bool: True if the request is valid, False otherwise. """ - if not request_data: + current_app.logger.error(f'request_json: {request_json}') + if not isinstance(request_json, dict): return False - - expected_keys = ['link_name', 'expiration_date', 'download_limit'] - if not all(key in request_data for key in expected_keys): + expected_keys = ['link_name', + 'expiration_date', + 'download_limit', + 'send_email', + 'timezone_offset_minutes'] + if not all(key in request_json for key in expected_keys): return False - link_name = request_data['link_name'] - expiration_date = request_data['expiration_date'] - download_limit = request_data['download_limit'] + link_name = request_json['link_name'] + expiration_str = request_json['expiration_date'] + download_limit = request_json['download_limit'] + send_email = request_json['send_email'] + offset_minutes = request_json['timezone_offset_minutes'] if link_name: if not isinstance(link_name, str) or len(link_name) > 255: return False - if expiration_date: - if not isinstance(expiration_date, str): + if (not isinstance(offset_minutes, int) or + abs(offset_minutes) > 720): # Max timezone offset is ±720 minutes + return False + if expiration_str: + if not isinstance(expiration_str, str): return False - try: - dt_expiration_date = dt.strptime(expiration_date, '%Y-%m-%d') - except ValueError: - current_app.logger.error(f'Invalid date format: {expiration_date}') + expiration_dt = to_utc_datetime(expiration_str, offset_minutes) + if not expiration_dt: + return False + expiration_dt += timedelta(days=1) + if expiration_dt < dt.now(timezone.utc): return False - if dt_expiration_date < dt.now(): + if download_limit is not None: # To detect 0 + if not isinstance(download_limit, int): return False - if download_limit is not None: - if not isinstance(download_limit, int) or download_limit <= 0: + if int(download_limit) <= 0: return False + if not isinstance(send_email, bool): + return False return True @@ -1723,40 +1759,51 @@ def create_secret_url_record(record_id, file_name, request_data): request_data (dict): The request data from the user. Returns: - FileSecretDownload: The created secret URL object. + FileSecretDownload, or None: + The created secret URL object, or None if the restricted access + settings are not configured properly. Raises: Exception: If an unexpected error occurs during the creation. """ - content_file_download = get_restricted_access('content_file_download') - if (not content_file_download or - not isinstance(content_file_download, dict)): + secret_url_settings = get_restricted_access('secret_URL_file_download') + if (not secret_url_settings or + not isinstance(secret_url_settings, dict)): return None - label_name = request_data['link_name'] - if not label_name: - label_name = f'{file_name}_{dt.now().strftime('%Y/%m/%d')}' - expiration_date = request_data['expiration_date'] - if not expiration_date: - default_days = content_file_download.get('expiration_date', 30) - expiration_date = dt.now() + timedelta(days=default_days) + label_name = request_data['link_name'] + local_expiration_str = request_data['expiration_date'] download_limit = request_data['download_limit'] - if not download_limit: - download_limit = content_file_download.get('download_limit', 10) + offset_minutes = request_data['timezone_offset_minutes'] + # Set default values if these values are empty. + if label_name == '': + utc_today = dt.now(timezone.utc).strftime('%Y-%m-%d') + url_created_at = to_utc_datetime( + utc_today, offset_minutes).strftime('%Y-%m-%d') + label_name = f'{file_name}_{url_created_at}' + if local_expiration_str == '': + expiration_days = secret_url_settings.get('secret_expiration_date', 30) + local_tz = timezone(timedelta(minutes=offset_minutes)) + local_date = dt.now(timezone.utc).replace(tzinfo=local_tz).date() + local_expiration_date = local_date + timedelta(expiration_days) + local_expiration_str = dt.strftime(local_expiration_date, '%Y-%m-%d') + utc_expiration_dt = to_utc_datetime(local_expiration_str, offset_minutes) + utc_expiration_dt += timedelta(days=1) # To include the last day + if download_limit is None: + download_limit = secret_url_settings.get('secret_download_limit', 10) secret_url_obj = FileSecretDownload.create( creator_id = current_user.id, record_id = record_id, file_name = file_name, label_name = label_name, - expiration_date = expiration_date, + expiration_date = utc_expiration_dt, download_limit = download_limit) return secret_url_obj -def create_onetime_download_record( - activity_id, record_id, file_name, user_mail, is_guest=False, - extra_info=None): +def create_onetime_url_record(activity_id, record_id, file_name, + user_mail, is_guest=False): """Create onetime download record. Args: @@ -1765,22 +1812,24 @@ def create_onetime_download_record( file_name: The name of the file to be downloaded. user_mail: The email address of the user who requested the download. is_guest: True if the user is a guest user, False otherwise. - extra_info: Additional information. Returns: - FileOnetimeDownload: The created onetime download record, or None if - the restricted access settings are not configured properly. + FileOnetimeDownload or None: + The created onetime download record, or None if the restricted + access settings are not configured properly. """ - content_file_download = get_restricted_access('content_file_download') - if (not content_file_download or - not isinstance(content_file_download, dict)): + onetime_url_settings = get_restricted_access('content_file_download') + if (not onetime_url_settings or + not isinstance(onetime_url_settings, dict)): return None - default_days = content_file_download.get('expiration_date', 30) - expiration_date = dt.now() + timedelta(days=default_days) - download_limit = content_file_download.get('download_limit', 10) + expiration_days = onetime_url_settings.get('expiration_date', 30) + expiration_date = dt.now(timezone.utc) + timedelta(days=expiration_days) + expiration_date += timedelta(days=1) # To include the last day + download_limit = onetime_url_settings.get('download_limit', 10) extra_info = {'usage_application_activity_id': activity_id, 'send_usage_report': True} + onetime_url_obj = FileOnetimeDownload.create( approver_id = current_user.id, record_id = record_id, @@ -1791,11 +1840,10 @@ def create_onetime_download_record( is_guest = is_guest, extra_info = extra_info ) - return onetime_url_obj -def create_download_url(url_obj, is_secret_url): +def create_download_url(url_obj): """Create a download URL from a object. Note: @@ -1811,9 +1859,15 @@ def create_download_url(url_obj, is_secret_url): Returns: str: The generated URL. """ + if isinstance(url_obj, FileSecretDownload): + url_type = 'secret' + elif isinstance(url_obj, FileOnetimeDownload): + url_type = 'onetime' + else: + return None host_url = request.host_url - url_type = 'secret' if is_secret_url else 'onetime' hash = generate_sha256_hash(url_obj) + current_app.logger.error(f'generated hash: {hash}') bytes = hash + b'_' + str(url_obj.id).encode() token = base64.urlsafe_b64encode(bytes).decode() url = (f'{host_url}record/{url_obj.record_id}/file/{url_type}/' @@ -1865,11 +1919,14 @@ def send_secret_url_mail(uuid, secret_url_obj, item_title): # Setup mail info user_profile = UserProfile.get_by_userid(current_user.id) fullname = user_profile._displayname if user_profile else '' + expiration_dt = secret_url_obj.expiration_date + jst_date = expiration_dt.astimezone(timezone(timedelta(hours=9))).date() + jst_str = jst_date.strftime('%Y-%m-%d') + ' 23:59:59(JST)' secret_url_info = { 'restricted_download_link' : create_download_url(secret_url_obj), 'mail_recipient' : current_user.email, 'file_name' : secret_url_obj.file_name, - 'restricted_expiration_date': str(secret_url_obj.expiration_date), + 'restricted_expiration_date': jst_str, 'restricted_download_count' : str(secret_url_obj.download_limit), 'restricted_fullname' : fullname, 'restricted_data_name' : item_title, @@ -1950,7 +2007,6 @@ def validate_url_download(record, filename, token, is_secret_url): Tuple[bool, str]: A tuple of the validation result and error message. """ # Check if the token is valid - token = request.args.get('token', type=str) if not validate_token(token, is_secret_url): return False, 'The provided token is invalid.' diff --git a/modules/weko-records-ui/weko_records_ui/views.py b/modules/weko-records-ui/weko_records_ui/views.py index c478d87292..e77e84694b 100644 --- a/modules/weko-records-ui/weko_records_ui/views.py +++ b/modules/weko-records-ui/weko_records_ui/views.py @@ -72,7 +72,7 @@ from .permissions import check_content_clickable, check_created_id, \ check_file_download_permission, check_original_pdf_download_permission, \ check_permission_period, file_permission_factory, get_permission -from .utils import can_manage_secret_url, create_secret_url_record, \ +from .utils import can_manage_secret_url, create_download_url, create_secret_url_record, \ get_billing_file_download_permission, get_google_detaset_meta, \ get_google_scholar_meta, get_groups_price, \ get_min_price_billing_file_download, get_record_permalink, hide_by_email, \ @@ -755,21 +755,23 @@ def create_secret_url_and_send_mail(pid, record, filename, **kwargs): url_obj = create_secret_url_record(pid.pid_value, filename, request.json) - except: + except Exception as e: + current_app.logger.error(e) abort(500) - message = 'Secret URL generated successfully' + url = create_download_url(url_obj) + + message = f'Secret URL generated successfully: "{url}"' if request.json['send_email'] is True: sending_result = send_secret_url_mail( pid.object_uuid, url_obj, record.get('item_title', '')) if sending_result: - message += ', please check your email inbox.' + message += ', please check your email inbox' else: message += (', but there was an error while sending the email. ' 'To use the URL, please refresh the page and copy it ' - 'from the issued URL list.') - else: - return jsonify({'message': message + '.'}) + 'from the issued URL list') + return jsonify({'message': message + '.'}) @blueprint.route('/r/', methods=['GET']) diff --git a/modules/weko-workflow/weko_workflow/utils.py b/modules/weko-workflow/weko_workflow/utils.py index 89ef881c9d..ec5f6e89f6 100644 --- a/modules/weko-workflow/weko_workflow/utils.py +++ b/modules/weko-workflow/weko_workflow/utils.py @@ -3332,7 +3332,7 @@ def create_onetime_download_url_to_guest(activity_id: str, dict: onetime URL and expiration date. """ from weko_records_ui.utils import (create_download_url, - create_onetime_download_record) + create_onetime_url_record) file_name = extra_info.get('file_name') record_id = extra_info.get('record_id') user_mail = extra_info.get('user_mail') @@ -3344,7 +3344,7 @@ def create_onetime_download_url_to_guest(activity_id: str, return {} try: - url_obj = create_onetime_download_record( + url_obj = create_onetime_url_record( activity_id, record_id, file_name, user_mail, is_guest_user) except: return {} @@ -3354,7 +3354,7 @@ def create_onetime_download_url_to_guest(activity_id: str, delete_guest_activity(activity_id) return { - 'file_url': create_download_url(url_obj, is_secret_file=False), + 'file_url': create_download_url(url_obj), 'expiration_date': url_obj.expiration_date } From ed6a87d175b5dfe311dd8aaacc69e0ed61c4423d Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Tue, 14 Jan 2025 22:10:24 +0900 Subject: [PATCH 19/61] Add tests for secret and onetime URL generation --- modules/weko-records-ui/tests/test_models.py | 35 +- modules/weko-records-ui/tests/test_utils.py | 762 +++++++++++++------ modules/weko-records-ui/tests/test_views.py | 113 ++- modules/weko-workflow/tests/test_utils.py | 94 +-- 4 files changed, 672 insertions(+), 332 deletions(-) diff --git a/modules/weko-records-ui/tests/test_models.py b/modules/weko-records-ui/tests/test_models.py index c554db172e..036dd147fd 100644 --- a/modules/weko-records-ui/tests/test_models.py +++ b/modules/weko-records-ui/tests/test_models.py @@ -138,7 +138,8 @@ def test_find_by_activity(db_file_permission): class TestFileOnetimeDownload: - expiration_date = datetime.now() + timedelta(hours=24) + expiration_date = datetime.now(timezone.utc) + timedelta(hours=24) + no_tz = expiration_date.replace(tzinfo=None) base_data = { 'approver_id': 1, 'record_id': '1', @@ -149,6 +150,10 @@ class TestFileOnetimeDownload: 'is_guest': False, 'extra_info': {'info': 'value'} } + expected_data = { + **base_data, + 'expiration_date': no_tz + } # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileOnetimeDownload::test_init -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings def test_init(self, db): @@ -168,7 +173,7 @@ def test_create(self, users): assert isinstance(obj, FileOnetimeDownload) assert FileOnetimeDownload.query.count() == 1 rec = FileOnetimeDownload.query.first() - for key, value in self.base_data.items(): + for key, value in self.expected_data.items(): assert getattr(rec, key) == value bad_data1 = self.base_data.copy() @@ -190,6 +195,14 @@ def test_create(self, users): mock_commit.assert_called_once() assert FileOnetimeDownload.query.count() == 1 + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileOnetimeDownload::test_get_by_id -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings + def test_get_by_id(self, users): + FileOnetimeDownload.create(**self.base_data) + record = FileOnetimeDownload.get_by_id(1) + assert isinstance(record, FileOnetimeDownload) + not_found = FileOnetimeDownload.get_by_id(100) + assert not_found is None + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileOnetimeDownload::test_update_extra_info -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings def test_update_extra_info(self, users): obj = FileOnetimeDownload.create(**self.base_data) @@ -226,7 +239,6 @@ def test_increment_download_count(self, users): mock_commit.assert_called_once() assert rec2.download_count == 0 - # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileOnetimeDownload::test_delete_logically -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings def test_delete_logically(self, users): rec = FileOnetimeDownload.create(**self.base_data) @@ -246,7 +258,8 @@ def test_delete_logically(self, users): assert rec2.is_deleted is False class TestFileSecretDownload: - expiration_date = datetime.now() + timedelta(hours=24) + expiration_date = datetime.now(timezone.utc) + timedelta(hours=24) + no_tz = expiration_date.replace(tzinfo=None) base_data = { 'creator_id': 1, 'record_id': '1', @@ -255,6 +268,10 @@ class TestFileSecretDownload: 'expiration_date': expiration_date, 'download_limit': 1 } + expected_data = { + **base_data, + 'expiration_date': no_tz + } # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileSecretDownload::test_init -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings def test_init(self, db): @@ -274,7 +291,7 @@ def test_create(self, users): assert isinstance(obj, FileSecretDownload) assert FileSecretDownload.query.count() == 1 rec = FileSecretDownload.query.first() - for key, value in self.base_data.items(): + for key, value in self.expected_data.items(): assert getattr(rec, key) == value bad_data1 = self.base_data.copy() @@ -296,6 +313,14 @@ def test_create(self, users): mock_commit.assert_called_once() assert FileSecretDownload.query.count() == 1 + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileSecretDownload::test_get_by_id -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings + def test_get_by_id(self, users): + FileSecretDownload.create(**self.base_data) + record = FileSecretDownload.get_by_id(1) + assert isinstance(record, FileSecretDownload) + not_found = FileSecretDownload.get_by_id(100) + assert not_found is None + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileSecretDownload::test_increment_download_count -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings def test_increment_download_count(self, users): rec = FileSecretDownload.create(**self.base_data) diff --git a/modules/weko-records-ui/tests/test_utils.py b/modules/weko-records-ui/tests/test_utils.py index f8ee11e252..9f7c12aed4 100644 --- a/modules/weko-records-ui/tests/test_utils.py +++ b/modules/weko-records-ui/tests/test_utils.py @@ -1,20 +1,24 @@ +import re import pytest from weko_records_ui.utils import ( + convert_token_into_obj, + is_onetime_file, + to_utc_datetime, + create_download_url, + create_onetime_url_record, + create_secret_url_record, + generate_sha256_hash, is_future, create_usage_report_for_user, get_data_usage_application_data, + send_secret_url_mail, send_usage_report_mail_for_user, check_and_send_usage_report, - update_onetime_download, - create_onetime_download_url, get_onetime_download, - validate_onetime_download_token, get_license_pdf, hide_item_metadata, get_pair_value, get_min_price_billing_file_download, - parse_one_time_download_token, - generate_one_time_download_url, validate_download_record, is_private_index, get_file_info_list, @@ -33,29 +37,28 @@ get_record_permalink, get_google_detaset_meta, get_google_scholar_meta, - create_secret_url, - parse_secret_download_token, - validate_secret_download_token, - get_secret_download, - update_secret_download, get_valid_onetime_download, display_oaiset_path, get_terms, get_roles, check_items_settings, + validate_file_access, + validate_secret_url_generation_request, #RoCrateConverter, #create_tsv is_secret_url_feature_enabled, has_permission_to_manage_secret_url, is_secret_file, can_manage_secret_url, + validate_token, + validate_url_download, ) import base64 from unittest.mock import MagicMock import copy import pytest import io -from datetime import datetime as dt +from datetime import date, datetime as dt, time, timezone from datetime import timedelta from lxml import etree from fpdf import FPDF @@ -75,6 +78,8 @@ from flask_babelex import gettext as _ from datetime import datetime ,timedelta +from weko_schema_ui.models import PublishStatus + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp # def is_future(settings=None): @@ -548,55 +553,6 @@ def send_reminder_mail_2(x, y, z): with patch("weko_records_ui.utils.check_create_usage_report",return_value=None): check_and_send_usage_report({"is_guest": False, "send_usage_report": True, "usage_application_activity_id": "A-20230101-00001"},users[7]['email'],data1, data2) -# def generate_one_time_download_url( -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_generate_one_time_download_url -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -def test_generate_one_time_download_url(app): - file_name = "003.jpg" - record_id = "1" - guest_mail= "user@example.org" - with app.test_request_context(): - ret = generate_one_time_download_url(file_name,record_id,guest_mail) - rets = ret.split('token=') - token_str =base64.b64decode(rets[1]) - token = (token_str.decode('utf-8')).split(' ') - assert token[0] == record_id - assert token[1] == guest_mail - - -# def parse_one_time_download_token(token: str) -> Tuple[str, Tuple]: -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_parse_one_time_download_token -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -def test_parse_one_time_download_token(app): - token = "MSB1c2VyQGV4YW1wbGUub3JnIDIwMjItMDktMjcgNDBDRkNGODFGM0FFRUI0Ng==" - with app.test_request_context(): - assert parse_one_time_download_token(token)==('', ('1', 'user@example.org', '2022-09-27', '40CFCF81F3AEEB46')) - assert parse_one_time_download_token("test") != None - assert parse_one_time_download_token(None) != None - - -# def validate_onetime_download_token( -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_validate_onetime_download_token -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -def test_validate_onetime_download_token(app,db_fileonetimedownload): - file_name='helloworld.pdf' - record_id='1' - user_email='wekosoftware@nii.ac.jp' - token = "9948A41F46456DF5" - date = "2022-09-28" - with app.test_request_context(): - file_downloads = FileOnetimeDownload.find( - file_name=file_name, record_id=record_id, user_mail=user_email - ) - assert validate_onetime_download_token(file_downloads[0],file_name,record_id,user_email,date,token)== (True, '') - - data1 = MagicMock() - data1.download_count = 0 - - with patch('passlib.handlers.oracle.oracle10.verify', return_value=False): - assert validate_onetime_download_token(file_downloads[0],file_name,record_id,user_email,date,token)== (False, 'Token is invalid.') - - assert validate_onetime_download_token(False,file_name,record_id,user_email,date,token)== (False, 'Token is invalid.') - - assert validate_onetime_download_token(data1,file_name,record_id,user_email,date,token)== (False, 'Token is invalid.') - # def is_private_index(record): # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_is_private_index -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp @@ -627,26 +583,19 @@ def test_is_private_index(app,records): # def validate_download_record(record: dict): -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_validate_download_record -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -def test_validate_download_record(app, records): - indexer, results = records - record = results[0]["record"] - assert validate_download_record(record)==None - - data1 = { - "publish_status": 1 - } +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_validate_download_record -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings +def test_validate_download_record(): + record = {'publish_status': PublishStatus.PUBLIC.value} + with patch('weko_records_ui.utils.is_private_index', return_value=False): + assert validate_download_record(record) is True + record['publish_status'] = None + assert validate_download_record(record) is False + record['publish_status'] = PublishStatus.PUBLIC.value + with patch('weko_records_ui.utils.is_private_index', return_value=True): + assert validate_download_record(record) is False + record['publish_status'] = None + assert validate_download_record(record) is False - try: - validate_download_record(data1) - except: - pass - - with patch("weko_records_ui.utils.is_private_index", return_value=True): - try: - validate_download_record(record) - except: - pass # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_is_secret_url_feature_enabled -vv -s --cov-branch --cov-report=html --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings def test_is_secret_url_feature_enabled(app): @@ -798,24 +747,6 @@ def test_get_valid_onetime_download(): with patch("weko_records_ui.models.FileOnetimeDownload.find_downloadable_only",return_value=["a","b"]): assert "a" == get_valid_onetime_download(file_name= "str", record_id= "str",user_mail= "str") -# def create_onetime_download_url( -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_create_onetime_download_url -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -def test_create_onetime_download_url(app): - with app.test_request_context(): - assert create_onetime_download_url('ACT','helloworld.pdf','1','wekosoftware@nii.ac.jp') == None - - data1 = [] - - with patch('weko_records_ui.utils.get_restricted_access', return_value=data1): - assert create_onetime_download_url('ACT','helloworld.pdf','1','wekosoftware@nii.ac.jp') == False - - -# def update_onetime_download(**kwargs) -> NoReturn: -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_update_onetime_download -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -def test_update_onetime_download(app): - with app.test_request_context(): - assert update_onetime_download(file_name="helloworld.pdf", user_mail="wekosoftware@nii.ac.jp", record_id="1", download_count=0)==None - # def get_workflows(): # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_get_workflows -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp @@ -898,158 +829,511 @@ def test_get_google_detaset_meta(app, records, itemtypes, oaischema, oaiidentify with patch("lxml.etree", return_value=data1): assert get_google_detaset_meta(record) == None -#def create_secret_url(record_id:str ,file_name:str ,user_mail:str ,restricted_fullname='',restricted_data_name='') -> dict: -# def _generate_secret_download_url(file_name: str, record_id: str, id: str ,created :dt) -> str: -# _create_secret_download_url(file_name: str, record_id: str, user_mail: str) -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_create_secret_url -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -def test_create_secret_url(app,db,users,records): - url , results = records - file_name= results[1]["filename"] - record_id=results[1]["recid"].pid_value - user_mail = users[0]["email"] - - db.session.add(AdminSettings(id=6,name='restricted_access',settings={"secret_URL_file_download": - {"secret_enable": True, - "secret_download_limit": 1, - "secret_expiration_date": 9999999, - "secret_download_limit_unlimited_chk": False, - "secret_expiration_date_unlimited_chk": False}})) - - with app.test_request_context(): - #60 - #76 - # with db.session.begin_nested(): - return_dict = create_secret_url(file_name= file_name, record_id=record_id, user_mail=user_mail) - - assert return_dict["restricted_download_count"] == '1' - assert return_dict["restricted_download_count_ja"] == "" - assert return_dict["restricted_download_count_en"] == "" - assert return_dict['restricted_expiration_date'] == "" - assert return_dict['restricted_expiration_date_ja'] == "無制限" - assert return_dict['restricted_expiration_date_en'] == "Unlimited" - - #61 - # with db.session.begin_nested(): - db.session.merge(AdminSettings(id=6,name='restricted_access',settings={"secret_URL_file_download": - {"secret_enable": True, - "secret_download_limit": 9999999, - "secret_expiration_date": 1, - "secret_download_limit_unlimited_chk": False, - "secret_expiration_date_unlimited_chk": False}})) - return_dict = create_secret_url(file_name= file_name - , record_id=record_id - , user_mail=user_mail) - assert return_dict["restricted_download_count"] == "" - assert return_dict["restricted_download_count_ja"] == "無制限" - assert return_dict["restricted_download_count_en"] == "Unlimited" - assert return_dict['restricted_expiration_date'] == (datetime.today() + timedelta(1)).strftime("%Y-%m-%d") - assert return_dict['restricted_expiration_date_ja'] == "" - assert return_dict['restricted_expiration_date_en'] == "" - - #62 - #63 - from re import match - assert match("^.+record\/" + record_id + "\/file\/secret\/"+file_name+"\?token=.+=$",return_dict["restricted_download_link"]) - assert return_dict["restricted_download_link"] != "" - assert return_dict["mail_recipient"] == user_mail - -# def parse_secret_download_token(token: str) -> Tuple[str, Tuple]: -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_parse_secret_download_token -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -def test_parse_secret_download_token(app ,db): - #64 - assert parse_secret_download_token(None) == (_("Token is invalid."),()) - assert parse_secret_download_token("") == (_("Token is invalid."),()) - #65 - assert parse_secret_download_token("random_string sajfosijdfasodfjv") == (_("Token is invalid."),()) +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_to_utc_datetime -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings +def test_to_utc_datetime(): + assert to_utc_datetime('2025-1-1') == datetime( + 2025, 1, 1, 0, 0, tzinfo=timezone.utc) + assert to_utc_datetime('2025-01-01') == datetime( + 2025, 1, 1, 0, 0, tzinfo=timezone.utc) + assert to_utc_datetime('2025-1-1', 720) == datetime( + 2025, 1, 1, 12, 0, tzinfo=timezone.utc) + assert to_utc_datetime('2025-1-1', -720) == datetime( + 2024, 12, 31, 12, 0, tzinfo=timezone.utc) + assert to_utc_datetime('2025-1-1', -540) == datetime( + 2024, 12, 31, 15, 0, tzinfo=timezone.utc) + with pytest.raises(ValueError): + to_utc_datetime('2025/01/01') + +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_validate_secret_url_generation_request -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings +def test_validate_secret_url_generation_request(app): + today = datetime.now().strftime("%Y-%m-%d") + tomorrow = (datetime.now() + timedelta(1)).strftime("%Y-%m-%d") + yesterday = (datetime.now() - timedelta(1)).strftime("%Y-%m-%d") + base_case = {'link_name' : '', + 'expiration_date' : '', + 'download_limit' : None, + 'send_email' : False, + 'timezone_offset_minutes': 0} + test_cases = [ + (None, + False), + # Base case is valid + (base_case, + True), + # When all fields are valid + ({'link_name' : '123', + 'expiration_date' : tomorrow, + 'download_limit' : 1, + 'send_email' : False, + 'timezone_offset_minutes': 0}, + True), + # When all fields are invalid + ({'link_name' : 123, + 'expiration_date' : yesterday, + 'download_limit' : 0, + 'send_email' : None, + 'timezone_offset_minutes': '0'}, + False), + # For link_name + ({**base_case, 'link_name': '123' }, True), + ({**base_case, 'link_name': 123 }, False), + ({**base_case, 'link_name': 'a' * 256}, False), + # For expiration_date + ({**base_case, 'expiration_date': today }, True), + ({**base_case, 'expiration_date': tomorrow }, True), + ({**base_case, 'expiration_date': yesterday}, False), + ({**base_case, 'expiration_date': 'abc' }, False), + # For download_limit + ({**base_case, 'download_limit': 1 }, True), + ({**base_case, 'download_limit': 0 }, False), + ({**base_case, 'download_limit': -1 }, False), + ({**base_case, 'download_limit': 1.1 }, False), + ({**base_case, 'download_limit': 'abc'}, False), + # For send_email + ({**base_case, 'send_email': False}, True), + ({**base_case, 'send_email': True }, True), + ({**base_case, 'send_email': None }, False), + # For timezone_offset_minutes + ({**base_case, 'timezone_offset_minutes': 0 }, True), + ({**base_case, 'timezone_offset_minutes': 720 }, True), + ({**base_case, 'timezone_offset_minutes': -720}, True), + ({**base_case, 'timezone_offset_minutes': 800 }, False), + ] + for request_data, expected in test_cases: + assert validate_secret_url_generation_request(request_data) is expected + + +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_create_secret_url_record -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings -p no:warnings +@patch('weko_records_ui.utils.get_restricted_access') +@patch('weko_records_ui.utils.current_user') +def test_create_secret_url_record(mock_user, mock_settings, users): + mock_settings.return_value = {'expiration_date': 30, 'download_limit': 10} + mock_user.id = 1 + record_id = 1 + file_name = 'test.txt' + + # Test request data + request = {'link_name': '', 'expiration_date': '', 'download_limit': None, + 'timezone_offset_minutes': 0} + url_obj = create_secret_url_record(record_id, file_name, request) + assert isinstance(url_obj, FileSecretDownload) + assert url_obj.creator_id == 1 + assert url_obj.record_id == str(record_id) + assert url_obj.file_name == file_name + expected_name = 'test.txt_' + datetime.now(timezone.utc).strftime('%Y-%m-%d') + assert url_obj.label_name == expected_name + expected_date = datetime.combine((datetime.now(timezone.utc).date()+timedelta(days=31)), time(0, 0, 0)) + assert url_obj.expiration_date == expected_date + assert url_obj.download_limit == 10 + + # If request is valid + request = { + 'link_name': 'test', + 'expiration_date': (datetime.now(timezone.utc).date()).strftime('%Y-%m-%d'), + 'download_limit': 5, + 'timezone_offset_minutes': 720 + } + url_obj2 = create_secret_url_record(record_id, file_name, request) + assert url_obj2.creator_id == 1 + assert url_obj2.record_id == str(record_id) + assert url_obj2.file_name == file_name + assert url_obj2.label_name == 'test' + expected_date2 = datetime.combine((datetime.now(timezone.utc).date()+timedelta(days=1)), time(12,0,0)) + assert url_obj2.expiration_date == expected_date2 + assert url_obj2.download_limit == 5 + request = {'link_name': '', + 'expiration_date': '2022-10-10', + 'download_limit': '', + 'timezone_offset_minutes': 0} + with pytest.raises(ValueError): + create_secret_url_record(record_id, file_name, request) + request = {'link_name': '', + 'expiration_date': '', + 'download_limit': 0, + 'timezone_offset_minutes': 0} + with pytest.raises(ValueError): + create_secret_url_record(record_id, file_name, request) + + # If settings is invalid + mock_settings.return_value = {} + assert create_secret_url_record(record_id, file_name, request) is None + mock_settings.return_value = 'invalid data' + assert create_secret_url_record(record_id, file_name, request) is None + + +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_create_onetime_download_record -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings +@patch('weko_records_ui.utils.get_restricted_access') +@patch('weko_records_ui.utils.current_user') +def test_create_onetime_download_record(mock_user, mock_get, users): + mock_get.return_value = {'expiration_date': 30} + mock_user.id = 1 + activity_id = 1 + record_id = 1 + file_name = 'test.txt' + user_mail = 'test@example.org' + + assert FileOnetimeDownload.query.count() == 0 + url_obj = create_onetime_url_record( + activity_id, record_id, file_name, user_mail) + assert FileOnetimeDownload.query.count() == 1 + assert isinstance(url_obj, FileOnetimeDownload) + assert url_obj.approver_id == 1 + assert url_obj.record_id == str(record_id) + assert url_obj.file_name == file_name + now = (dt.now(timezone.utc) + timedelta(days=31)).replace(tzinfo=None) + tolerance = timedelta(seconds=1) + assert now - url_obj.expiration_date <= tolerance + assert url_obj.download_limit == 10 + assert url_obj.user_mail == user_mail + assert url_obj.is_guest is False + + mock_get.return_value = {} + assert create_onetime_url_record( + activity_id, record_id, file_name, user_mail) is None + mock_get.return_value = 'invalid data' + assert create_onetime_url_record( + activity_id, record_id, file_name, user_mail) is None + + +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_create_download_url -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings +@patch('weko_records_ui.utils.base64.urlsafe_b64encode', return_value=b'test') +def test_create_download_url(mock_encode, app): + with patch('weko_records_ui.utils.base64.urlsafe_b64encode') as m_encode: + m_encode.return_value = b'test' + with app.test_request_context(): + secret_obj = FileSecretDownload( + creator_id=1, + record_id=1, + file_name='test.txt', + label_name='test_url', + expiration_date=dt.now() + timedelta(days=30), + download_limit=10) + url = create_download_url(secret_obj) + assert url == (f'http://TEST_SERVER/record/1/file/secret/test.txt' + f'?token={b"test".decode()}') + with app.test_request_context(): + onetime_obj = FileOnetimeDownload( + approver_id=1, + record_id=1, + file_name='test.txt', + expiration_date=dt.now() + timedelta(days=30), + download_limit=10, + user_mail='test@example.org', + is_guest=False, + extra_info={'activity_id': 1}) + url = create_download_url(onetime_obj) + assert url == (f'http://TEST_SERVER/record/1/file/onetime/test.txt' + f'?token={b"test".decode()}') + + +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_generate_sha256_hash -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings +def test_generate_sha256_hash(app): + app.config['WEKO_RECORDS_UI_SECRET_KEY'] = 'secret' + secret_ubj = FileSecretDownload( + creator_id=1, + record_id=1, + file_name='test.txt', + label_name='test_url', + expiration_date=dt.now().date() + timedelta(days=30), + download_limit=10) + secret_url = generate_sha256_hash(secret_ubj) + assert len(secret_url) == 32 + secret_obj2 = FileSecretDownload( + creator_id=2, + record_id=2, + file_name='test2.txt', + label_name='test_url2', + expiration_date=dt.now().date() + timedelta(days=10), + download_limit=5) + secret_url2 = generate_sha256_hash(secret_obj2) + assert len(secret_url2) == 32 + assert secret_url != secret_url2 + same_url = generate_sha256_hash(secret_ubj) + assert secret_url == same_url + + onetime_obj = FileOnetimeDownload( + approver_id=1, + record_id=1, + file_name='test.txt', + expiration_date=dt.now().date() + timedelta(days=30), + download_limit=10, + user_mail='test@example.org', + is_guest=False, + extra_info={'activity_id': 1}) + onetime_url = generate_sha256_hash(onetime_obj) + assert len(onetime_url) == 32 + onetime_obj2 = FileOnetimeDownload( + approver_id=2, + record_id=2, + file_name='test2.txt', + expiration_date=dt.now().date() + timedelta(days=10), + download_limit=5, + user_mail='test2@example.org', + is_guest=True, + extra_info={'activity_id': 2}) + onetime_url2 = generate_sha256_hash(onetime_obj2) + assert len(onetime_url2) == 32 + assert onetime_url != onetime_url2 + same_url = generate_sha256_hash(onetime_obj) + assert onetime_url == same_url + + +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_send_secret_url_mail -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings +@patch('weko_records_ui.utils.UserProfile.get_by_userid') +@patch('weko_records_ui.utils.current_user') +@patch('weko_records_ui.utils.set_mail_info', return_value={}) +@patch('weko_records_ui.utils.process_send_mail', return_value=True) +def test_send_secret_url_mail(mock_send, mock_set_info, mock_user, + mock_profile, app): + app.config['WEKO_RECORDS_UI_MAIL_TEMPLATE_SECRET_URL'] = 'test_template' + mock_profile_obj = MagicMock() + mock_profile_obj._displayname = 'test_user' + mock_profile.return_value = mock_profile_obj + mock_user.id = 1 + mock_user.email = 'test@example.org' + + uuid = 'test_uuid' + url_obj = FileSecretDownload( + creator_id=1, + record_id=1, + file_name='test.txt', + label_name='test_url', + expiration_date=datetime(2125, 1, 1, 0, 0), + download_limit=10) + item_title = 'test_title' + mock_user.id = 1 + expected_info = { + 'restricted_download_link' : create_download_url(url_obj), + 'mail_recipient' : 'test@example.org', + 'file_name' : url_obj.file_name, + 'restricted_expiration_date': '2125-01-01 23:59:59(JST)', + 'restricted_download_count' : str(url_obj.download_limit), + 'restricted_fullname' : 'test_user', + 'restricted_data_name' : item_title, + } + expected_pattern = 'test_template' + with app.test_request_context(): + assert send_secret_url_mail(uuid, url_obj, item_title) is True + mock_send.assert_called_once_with(expected_info, expected_pattern) + mock_send.reset_mock() - # 66 - # onetime_download pattern - assert parse_secret_download_token("MSB1c2VyQGV4YW1wbGUub3JnIDIwMjItMDktMjcgNDBDRkNGODFGM0FFRUI0Ng==") == ('', ('1', 'user@example.org', '2022-09-27', '40CFCF81F3AEEB46')) + mock_profile.return_value = None + with app.test_request_context(): + assert send_secret_url_mail(uuid, url_obj, item_title) is True + expected_info['restricted_fullname'] = '' + mock_send.assert_called_once_with(expected_info, expected_pattern) - # 67 - # secret_download pattern - error , res = parse_secret_download_token("MSA1IDIwMjMtMDMtMDggMDA6NTI6MTkuNjI0NTUyIDZGQTdEMzIxQTk0OTU1MEQ=") - assert error == 'Token is invalid.' - assert res == () + mock_send.return_value = False + with app.test_request_context(): + assert send_secret_url_mail(uuid, url_obj, item_title) is False -# def validate_secret_download_token( -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_validate_secret_download_token -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -def test_validate_secret_download_token(app): +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_validate_token -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings +def test_validate_token(app, users): with app.test_request_context(): - secret_download=FileSecretDownload( - file_name= "eee.txt", record_id= '1',user_mail="repoadmin@example.org",expiration_date=999999,download_count=10 - ) - secret_download.created = datetime(2023,3,8,0,52,19,624552) - secret_download.id = 5 - # 68 - res = validate_secret_download_token(secret_download=None , file_name= "eee.txt", record_id= '1', id= '5', date= '2023-03-08 00:52:19.624552', token= '6FA7D321A949550D') - assert res == (False , _("Token is invalid.")) - - #69 - res = validate_secret_download_token(secret_download=secret_download , file_name= "aaa.txt", record_id= '1', id= '5', date= '2023-03-08 00:52:19.624552', token= '6FA7D321A949550D') - assert res == (False , _("Token is invalid.")) - res = validate_secret_download_token(secret_download=secret_download , file_name= "eee.txt", record_id= '5', id= '5', date= '2023-03-08 00:52:19.624552', token= '6FA7D321A949550D') - assert res == (False , _("Token is invalid.")) - res = validate_secret_download_token(secret_download=secret_download , file_name= "eee.txt", record_id= '1', id= '1', date= '2023-03-08 00:52:19.624552', token= '6FA7D321A949550D') - assert res == (False , _("Token is invalid.")) - res = validate_secret_download_token(secret_download=secret_download , file_name= "eee.txt", record_id= '1', id= '5', date= '2099-03-08 00:52:19.624552', token= '6FA7D321A949550D') - assert res == (False , _("Token is invalid.")) - res = validate_secret_download_token(secret_download=secret_download , file_name= "eee.txt", record_id= '1', id= '5', date= '2023-03-08 00:52:19.624552', token= '7FA7D321A949550D') - assert res == (False , _("Token is invalid.")) - - # 70 - secret_download2=FileSecretDownload( - file_name= "eee.txt", record_id= '5',user_mail="repoadmin@example.org",expiration_date=-1,download_count=10 - ) - secret_download2.created = datetime(2023,3,8,0,52,19,624552) - secret_download2.id = 5 - res = validate_secret_download_token(secret_download=secret_download2 , file_name= "eee.txt", record_id= '1', id= '5', date= '2023-03-08 00:52:19.624552', token= '6FA7D321A949550D') - assert res == (False , _("The expiration date for download has been exceeded.")) - - #71 - secret_download2.expiration_date = 99999999 - res = validate_secret_download_token(secret_download=secret_download2 , file_name= "eee.txt", record_id= '1', id= '5', date= '2023-03-08 00:52:19.624552', token= '6FA7D321A949550D') - assert res == (True ,"") - - # 72 - secret_download2.expiration_date = 9999999 - secret_download2.download_count = 0 - res = validate_secret_download_token(secret_download=secret_download2 , file_name= "eee.txt", record_id= '1', id= '5', date= '2023-03-08 00:52:19.624552', token= '6FA7D321A949550D') - assert res == (False , _("The download limit has been exceeded.")) - - # 73 - res = validate_secret_download_token(secret_download=secret_download , file_name= "eee.txt", record_id= '1', id= '5', date= '2023-03-08 00:52:19.624552', token= '6FA7D321A949550D') - assert res == (True ,"") - - secret_download2.expiration_date = "hoge" - res = validate_secret_download_token(secret_download=secret_download2 , file_name= "eee.txt", record_id= '1', id= '5', date= '2023-03-08 00:52:19.624552', token= '6FA7D321A949550D') - assert res == (False , _("Token is invalid.")) - -# def get_secret_download(file_name: str, record_id: str, -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_get_secret_download -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -def test_get_secret_download(app ,db ): + secret_obj = FileSecretDownload.create( + creator_id=1, + record_id=1, + file_name='test.txt', + label_name='test_url', + expiration_date=dt.now(timezone.utc) + timedelta(days=30), + download_limit=10) + url = create_download_url(secret_obj) + match = re.search(r'[?&]token=([^&]+)', url) + secret_token = match.group(1) + assert validate_token(secret_token, is_secret_url=True) is True with app.test_request_context(): - with db.session.begin_nested(): - secret_download=FileSecretDownload( - file_name= "eee.txt", record_id= '1',user_mail="repoadmin@example.org",expiration_date=999999,download_count=10 - ) - db.session.add(secret_download) - + onetime_obj = FileOnetimeDownload.create( + approver_id=1, + record_id=1, + file_name='test.txt', + expiration_date=dt.now(timezone.utc) + timedelta(days=30), + download_limit=10, + user_mail='test@example.org', + is_guest=False, + extra_info={'activity_id': 1}) + url = create_download_url(onetime_obj) + match = re.search(r'[?&]token=([^&]+)', url) + onetime_token = match.group(1) + assert validate_token(onetime_token, is_secret_url=False) is True + wrong_token = b'\xb2q\xff\x19\xaf\xfc\xc6T\x8bt\xd6\xf6\xc6 \x08D\xe7\xf3G;cN\x1bn|\xa2\x88\x01v\xed\x1cA_1' + with app.test_request_context(): + secret_token = base64.urlsafe_b64decode(secret_token.encode()) + assert secret_token.split(b'_')[1] == wrong_token.split(b'_')[1] + assert validate_token(wrong_token, is_secret_url=True) is False + with app.test_request_context(): + onetime_token = base64.urlsafe_b64decode(onetime_token.encode()) + assert onetime_token.split(b'_')[1] == wrong_token.split(b'_')[1] + assert validate_token(wrong_token, is_secret_url=False) is False + + +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_convert_token_into_obj -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings +def test_convert_token_into_obj(app, users): + created_at = dt.now(timezone.utc) + with app.test_request_context(): + secret_obj = FileSecretDownload.create( + creator_id=1, + record_id=1, + file_name='test.txt', + label_name='test_url', + expiration_date=created_at + timedelta(days=30), + download_limit=10) + assert FileSecretDownload.query.count() == 1 + url = create_download_url(secret_obj) + match = re.search(r'[?&]token=([^&]+)', url) + secret_token = match.group(1) + secret_obj = convert_token_into_obj(secret_token, is_secret_url=True) + assert isinstance(secret_obj, FileSecretDownload) + assert secret_obj.id == 1 + assert secret_obj.creator_id == 1 + assert secret_obj.record_id == '1' + assert secret_obj.file_name == 'test.txt' + assert secret_obj.label_name == 'test_url' + expected_date = (created_at + timedelta(days=30)).replace(tzinfo=None) + assert secret_obj.expiration_date == expected_date + assert secret_obj.download_limit == 10 + with app.test_request_context(): + onetime_obj = FileOnetimeDownload.create( + approver_id=1, + record_id=1, + file_name='test.txt', + expiration_date=created_at + timedelta(days=30), + download_limit=10, + user_mail='test@example.org', + is_guest=False, + extra_info={'activity_id': 1}) + assert FileOnetimeDownload.query.count() == 1 + url = create_download_url(onetime_obj) + match = re.search(r'[?&]token=([^&]+)', url) + onetime_token = match.group(1) + onetime_obj = convert_token_into_obj(onetime_token, is_secret_url=False) + assert isinstance(onetime_obj, FileOnetimeDownload) + assert onetime_obj.id == 1 + assert onetime_obj.approver_id == 1 + assert onetime_obj.record_id == '1' + assert onetime_obj.file_name == 'test.txt' + expected_date = (created_at + timedelta(days=30)).replace(tzinfo=None) + assert onetime_obj.expiration_date == expected_date + assert onetime_obj.download_limit == 10 + assert onetime_obj.is_guest == False + assert onetime_obj.extra_info == {'activity_id': 1} + + +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_validate_url_download -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings +@patch('weko_records_ui.utils.validate_token') +@patch('weko_records_ui.utils.is_secret_url_feature_enabled') +@patch('weko_records_ui.utils.validate_file_access') +@patch('weko_records_ui.utils.validate_download_record') +def test_validate_url_download(vldt_record, vldt_file, is_enabled, vldt_token, + app, db, users): + with app.test_request_context(): + secret_obj = FileSecretDownload.create( + creator_id=1, + record_id=1, + file_name='test.txt', + label_name='test_url', + expiration_date=dt.now(timezone.utc) + timedelta(days=30), + download_limit=10) + db.session.flush() + match = re.search(r'[?&]token=([^&]+)', create_download_url(secret_obj)) + secret_token = match.group(1) + vldt_token.return_value = True + is_enabled.return_value = True + vldt_file.return_value = True + vldt_record.return_value = True + assert validate_url_download('', '', secret_token, True) == (True, '') + + with app.test_request_context(): + onetime_obj = FileOnetimeDownload.create( + approver_id=1, + record_id=1, + file_name='test.txt', + expiration_date=dt.now(timezone.utc) + timedelta(days=30), + download_limit=10, + user_mail='test@example.org', + is_guest=False, + extra_info={'activity_id': 1}) + db.session.flush() + match = re.search(r'[?&]token=([^&]+)', create_download_url(onetime_obj)) + onetime_token = match.group(1) + assert validate_url_download('', '', onetime_token, False) == (True, '') + + with patch('weko_records_ui.utils.validate_token', + return_value=False): + assert validate_url_download('', '', secret_token, True) == ( + False, 'The provided token is invalid.') + with patch('weko_records_ui.utils.is_secret_url_feature_enabled', + return_value=False): + assert validate_url_download('', '', secret_token, True) == ( + False, 'This feature is currently disabled.') + with patch('weko_records_ui.utils.validate_file_access', + return_value=False): + assert validate_url_download('', '', secret_token, True) == ( + False, 'This file is currently not available for this feature.') + with patch('weko_records_ui.utils.validate_download_record', + return_value=False): + assert validate_url_download('', '', secret_token, True) == ( + False, 'This file is currently not available for this feature.') + + secret_obj.is_deleted = True + db.session.commit() + assert validate_url_download('', '', secret_token, True) == ( + False, 'This URL has been deactivated.') + secret_obj.is_deleted = False + secret_obj.download_count = 10 + db.session.commit() + assert validate_url_download('', '', secret_token, True) == ( + False, 'The download limit has been exceeded.') + secret_obj.download_count = 0 + db.session.commit() + with patch('weko_records_ui.utils.dt') as mock_dt: + mock_dt.now.return_value = dt.now(timezone.utc) + timedelta(days=31) + assert validate_url_download('', '', secret_token, True) == ( + False, 'The expiration date for download has been exceeded.') + + onetime_obj.is_deleted = True + db.session.commit() + assert validate_url_download('', '', onetime_token, False) == ( + False, 'This URL has been deactivated.') + onetime_obj.is_deleted = False + onetime_obj.download_count = 10 + db.session.commit() + assert validate_url_download('', '', onetime_token, False) == ( + False, 'The download limit has been exceeded.') + onetime_obj.download_count = 0 + db.session.commit() + with patch('weko_records_ui.utils.dt') as mock_dt: + mock_dt.now.return_value = dt.now(timezone.utc) + timedelta(days=31) + assert validate_url_download('', '', onetime_token, False) == ( + False, 'The expiration date for download has been exceeded.') + + +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_validate_file_access -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings +def test_validate_file_access(): + with patch('weko_records_ui.utils.is_secret_file', return_value=True): + assert validate_file_access('', '', is_secret_url=True) is True + with patch('weko_records_ui.utils.is_secret_file', return_value=False): + assert validate_file_access('', '', is_secret_url=True) is False + with patch('weko_records_ui.utils.is_onetime_file', return_value=True): + assert validate_file_access('', '', is_secret_url=False) is True + with patch('weko_records_ui.utils.is_onetime_file', return_value=False): + assert validate_file_access('', '', is_secret_url=False) is False + + +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_is_onetime_file -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings +def test_is_onetime_file(): + mock_record = MagicMock() + mock_record.get_file_data.return_value = [ + {"filename": "file1.txt", "accessrole": "open_restricted"}, + {"filename": "file2.txt", "accessrole": "public"} + ] + assert is_onetime_file(mock_record, "file1.txt") is True + assert is_onetime_file(mock_record, "file2.txt") is False + assert is_onetime_file(mock_record, "file3.txt") is False - - assert get_secret_download(file_name= secret_download.file_name - , record_id= secret_download.record_id - , id= secret_download.id - , created =secret_download.created) - - assert not get_secret_download(file_name= secret_download.file_name - , record_id= secret_download.record_id - , id= secret_download.id + 1 - , created =secret_download.created) # def update_secret_download(**kwargs) -> Optional[List[FileSecretDownload]]: # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_get_data_usage_application_data -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp diff --git a/modules/weko-records-ui/tests/test_views.py b/modules/weko-records-ui/tests/test_views.py index a4c9095f3e..7f1f4f786b 100644 --- a/modules/weko-records-ui/tests/test_views.py +++ b/modules/weko-records-ui/tests/test_views.py @@ -990,34 +990,95 @@ def test_default_view_method_fix35133(app, records, itemtypes, indexstyle,mocker ] assert kwargs["google_dataset_meta"] == '{"@context": "https://schema.org/", "@type": "Dataset", "citation": ["http://hdl.handle.net/2261/0002005680", "https://repository.dl.itc.u-tokyo.ac.jp/records/2005680"], "creator": [{"@type": "Person", "alternateName": "creator alternative name", "familyName": "creator family name", "givenName": "creator given name", "identifier": "123", "name": "creator name"}], "description": "『史料編纂掛備用寫眞畫像圖畫類目録』(1905年)の「画像」(肖像画模本)の部に著録する資料の架番号の新旧対照表。史料編纂所所蔵肖像画模本データベースおよび『目録』版面画像へのリンク付き。『画像史料解析センター通信』98(2022年10月)に解説記事あり。", "distribution": [{"@type": "DataDownload", "contentUrl": "https://repository.dl.itc.u-tokyo.ac.jp/record/2005680/files/comparison_table_of_preparation_image_catalog.xlsx", "encodingFormat": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, {"@type": "DataDownload", "contentUrl": "https://raw.githubusercontent.com/RCOSDP/JDCat-base/main/apt.txt", "encodingFormat": "text/plain"}, {"@type": "DataDownload", "contentUrl": "https://raw.githubusercontent.com/RCOSDP/JDCat-base/main/environment.yml", "encodingFormat": "application/x-yaml"}, {"@type": "DataDownload", "contentUrl": "https://raw.githubusercontent.com/RCOSDP/JDCat-base/main/postBuild", "encodingFormat": "text/x-shellscript"}], "includedInDataCatalog": {"@type": "DataCatalog", "name": "https://localhost"}, "license": ["CC BY"], "name": "『史料編纂掛備用写真画像図画類目録』画像の部:新旧架番号対照表", "spatialCoverage": [{"@type": "Place", "geo": {"@type": "GeoCoordinates", "latitude": "point latitude test", "longitude": "point longitude test"}}, {"@type": "Place", "geo": {"@type": "GeoShape", "box": "1 3 2 4"}}, "geo location place test"]}' # def create_secret_url_and_send_mail(pid:PersistentIdentifier, record:WekoRecord, filename:str, **kwargs) -> str: -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_views.py::test_create_secret_url_and_send_mail -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -def test_create_secret_url_and_send_mail(app,client,db,users,records): - app.config['WEKO_WORKFLOW_DATE_FORMAT'] = "%Y-%m-%d" - indexer, results = records +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_views.py::test_create_secret_url_and_send_mail -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings +@patch('weko_records_ui.views.validate_secret_url_generation_request') +@patch('weko_records_ui.views.can_manage_secret_url') +@patch('weko_records_ui.utils.current_user') +@patch('weko_records_ui.views.send_secret_url_mail') +def test_create_secret_url_and_send_mail(send_mail, login_user, can_manage, + vldt_req, client, users, records): + vldt_req.return_value = True + can_manage.return_value = True + login_user.id = 1 + tmp, results = records record = results[1] + url = url_for('invenio_records_ui.recid_secret_url', + pid_value=results[1]["recid"].pid_value, + filename=results[1]["filename"]) + base_data = { + 'link_name': '', + 'expiration_date': '', + 'download_limit': None, + 'send_email': False, + 'timezone_offset_minutes': 0 + } - # 79 - id = 1 #repoadmin - secret_file_url = url_for("invenio_records_ui.recid_secret_url" - ,pid_value=results[1]["recid"].pid_value - ,filename=results[1]["filename"]) - login_user_via_session(client=client, user=users[id]["obj"] ,email=users[id]["email"]) - with patch('weko_records_ui.views.can_manage_secret_url',return_value = True): - with patch('weko_records_ui.views.process_send_mail',return_value = True): - # with app.test_request_context(): - res = client.get(secret_file_url) - assert res.status_code == 405 + # Success + res = client.post(url, data=json.dumps(base_data), + content_type='application/json') + assert res.status_code == 200 + assert 'Secret URL generated successfully' in res.get_data(as_text=True) + send_mail.return_value = True + data = {**base_data, 'send_email': True} + res = client.post(url, data=json.dumps(data), + content_type='application/json') + assert res.status_code == 200 + assert 'Secret URL generated successfully' in res.get_data(as_text=True) + assert 'please check your email inbox' in res.get_data(as_text=True) + send_mail.return_value = False + res = client.post(url, data=json.dumps(data), + content_type='application/json') + assert res.status_code == 200 + assert 'Secret URL generated successfully' in res.get_data(as_text=True) + assert 'there was an error' in res.get_data(as_text=True) + + # Fail + with patch('weko_records_ui.views.create_secret_url_record', + side_effect=Exception('Test DB Error')): + with pytest.raises(Exception): + res = client.post(url, data=json.dumps(data), + content_type='application/json') + assert res.status_code == 500 + can_manage.return_value = False + with pytest.raises(Exception): + res = client.post(url, data=json.dumps(data), + content_type='application/json') + assert res.status_code == 403 + vldt_req.return_value = False + res = client.post(url, data=json.dumps(data), + content_type='application/json') + assert res.status_code == 400 + + + + +# def test_create_secret_url_and_send_mail(app,client,db,users,records): +# app.config['WEKO_WORKFLOW_DATE_FORMAT'] = "%Y-%m-%d" +# indexer, results = records +# record = results[1] + +# # 79 +# id = 1 #repoadmin +# secret_file_url = url_for("invenio_records_ui.recid_secret_url" +# ,pid_value=results[1]["recid"].pid_value +# ,filename=results[1]["filename"]) +# login_user_via_session(client=client, user=users[id]["obj"] ,email=users[id]["email"]) +# with patch('weko_records_ui.views.can_manage_secret_url',return_value = True): +# with patch('weko_records_ui.views.process_send_mail',return_value = True): +# # with app.test_request_context(): +# res = client.get(secret_file_url) +# assert res.status_code == 405 - res = client.post(secret_file_url ,data=json.dumps({}), content_type='application/json') - assert res.status_code == 200 - with patch('weko_records_ui.views.process_send_mail',return_value = False): - with patch("flask.templating._render", return_value=""): - res = client.post(secret_file_url ,data=json.dumps({}), content_type='application/json') - assert res.status_code == 500 - with patch('weko_records_ui.views.can_manage_secret_url',return_value = False): - with patch('weko_records_ui.views.process_send_mail',return_value = True): - with patch("flask.templating._render", return_value=""): - res = client.post(secret_file_url ,data=json.dumps({}), content_type='application/json') - assert res.status_code == 403 +# res = client.post(secret_file_url ,data=json.dumps({}), content_type='application/json') +# assert res.status_code == 200 +# with patch('weko_records_ui.views.process_send_mail',return_value = False): +# with patch("flask.templating._render", return_value=""): +# res = client.post(secret_file_url ,data=json.dumps({}), content_type='application/json') +# assert res.status_code == 500 +# with patch('weko_records_ui.views.can_manage_secret_url',return_value = False): +# with patch('weko_records_ui.views.process_send_mail',return_value = True): +# with patch("flask.templating._render", return_value=""): +# res = client.post(secret_file_url ,data=json.dumps({}), content_type='application/json') +# assert res.status_code == 403 # def create_secret_url_and_send_mail(pid:PersistentIdentifier, record:WekoRecord, filename:str, **kwargs) -> str: \ No newline at end of file diff --git a/modules/weko-workflow/tests/test_utils.py b/modules/weko-workflow/tests/test_utils.py index 515ded0e5a..bb00ed4854 100644 --- a/modules/weko-workflow/tests/test_utils.py +++ b/modules/weko-workflow/tests/test_utils.py @@ -2245,68 +2245,38 @@ def test_validate_guest_activity_expired(app,workflow,mocker): with patch("weko_workflow.utils.timedelta",side_effect=OverflowError): result = validate_guest_activity_expired(activity_id) assert result == "" -# def create_onetime_download_url_to_guest(activity_id: str, -# .tox/c1/bin/pytest --cov=weko_workflow tests/test_utils.py::test_create_onetime_download_url_to_guest -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp -def test_create_onetime_download_url_to_guest(app, workflow,mocker): - with app.test_request_context(): - today = datetime.datetime(2022,10,6,1,2,3,4) - datetime_mock = mocker.patch("weko_workflow.utils.datetime") - datetime_mock.today.return_value=today - datetime_mock.utcnow.return_value=today - file_name="test_file.txt" - record_id = str(uuid.uuid4()) - user_mail = "user@test.org" - extra_info = { - "file_name":file_name, - "record_id":record_id, - "user_mail":user_mail - } - token_value="A-20221003-00001 2022-10-01 guest@test.org CE06FDFB15823A5C" - token_value = base64.b64encode(token_value.encode()).decode() - activity_id = "A-20221003-00001" - guest_activity = GuestActivity.create( - user_mail="guest@test.org", - record_id=record_id, - file_name=file_name, - activity_id=activity_id, - token=token_value, - expiration_date=30 - ) - datetime_mock_ui = mocker.patch("weko_records_ui.utils.dt") - datetime_mock_ui.utcnow.return_value=today - expiration_date = today + datetime.timedelta(days=30) - mocker.patch("weko_records_ui.utils.oracle10.hash",return_value="CE06FDFB15823A5C") - url_token = "{} {} {} {}".format(record_id,user_mail,"2022-10-06","CE06FDFB15823A5C") - url_token_value = base64.b64encode(url_token.encode()).decode() - url = 'http://TEST_SERVER.localdomain/record/{}/file/onetime/test_file.txt?token={}'.format(record_id,url_token_value) - test = { - "file_url":url, - "expiration_date":expiration_date.strftime("%Y-%m-%d"), - "expiration_date_ja":"", - "expiration_date_en":"" - } - result = create_onetime_download_url_to_guest(activity_id, extra_info) - assert result == test - - # not exist user_mail - extra_info = { - "file_name":file_name, - "record_id":record_id, - "guest_mail":user_mail - } - result = create_onetime_download_url_to_guest(activity_id, extra_info) - assert result == test - - # raise OverflowError - with patch("weko_workflow.utils.timedelta",side_effect=OverflowError): - test = { - "file_url":url, - "expiration_date":"", - "expiration_date_ja":"無制限", - "expiration_date_en":"Unlimited" - } - result = create_onetime_download_url_to_guest(activity_id, extra_info) - assert result == test + + +# .tox/c1/bin/pytest --cov=weko_workflow tests/test_utils.py::test_create_onetime_download_url_to_guest -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp -p no:warnings +@patch('weko_workflow.utils.delete_guest_activity') +@patch('weko_records_ui.utils.current_user') +def test_create_onetime_download_url_to_guest(login_user, delete_activity, db, + users): + login_user.id = 1 + valid_dicts = [ + {'file_name': 'test_file.txt', + 'record_id': '1', + 'user_mail': 'test@example.org'}, + {'file_name': + 'test_file.txt', + 'record_id': '1', + 'guest_mail': 'test@example.org'}, + ] + invalid_dicts = [ + {'record_id': '1', 'user_mail': 'test@example.org'}, + {'file_name': 'test_file.txt', 'user_mail': 'test@example.org'}, + {'file_name': 'test_file.txt', 'record_id': '1'}, + ] + for valid_dict in valid_dicts: + result = create_onetime_download_url_to_guest(1, valid_dict) + assert 'file_url' in result + assert 'expiration_date' in result + delete_activity.reset_mock() + for invalid_dict in invalid_dicts: + result = create_onetime_download_url_to_guest(1, invalid_dict) + assert result == {} + + # def delete_guest_activity(activity_id: str) -> bool: # .tox/c1/bin/pytest --cov=weko_workflow tests/test_utils.py::test_delete_guest_activity -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-workflow/.tox/c1/tmp def test_delete_guest_activity(client,workflow): From cfb9621cdd8bcdb7ce4db89814a4f1484b753332 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Wed, 15 Jan 2025 09:20:04 +0900 Subject: [PATCH 20/61] Delete debug codes --- modules/weko-records-ui/weko_records_ui/utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/utils.py b/modules/weko-records-ui/weko_records_ui/utils.py index b5de1071cb..e93f0b2f71 100644 --- a/modules/weko-records-ui/weko_records_ui/utils.py +++ b/modules/weko-records-ui/weko_records_ui/utils.py @@ -1708,7 +1708,6 @@ def validate_secret_url_generation_request(request_json): Returns: bool: True if the request is valid, False otherwise. """ - current_app.logger.error(f'request_json: {request_json}') if not isinstance(request_json, dict): return False expected_keys = ['link_name', @@ -1867,7 +1866,6 @@ def create_download_url(url_obj): return None host_url = request.host_url hash = generate_sha256_hash(url_obj) - current_app.logger.error(f'generated hash: {hash}') bytes = hash + b'_' + str(url_obj.id).encode() token = base64.urlsafe_b64encode(bytes).decode() url = (f'{host_url}record/{url_obj.record_id}/file/{url_type}/' From ab6f6c09eb1c7bd2e6b3ab834c659fe046681f79 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Wed, 15 Jan 2025 10:18:25 +0900 Subject: [PATCH 21/61] Fix minor problems in tests --- modules/weko-records-ui/tests/conftest.py | 17 +++++++++-------- modules/weko-records-ui/tests/test_models.py | 17 ++++------------- modules/weko-records-ui/tests/test_utils.py | 18 ++++++++++-------- .../weko-records-ui/weko_records_ui/utils.py | 6 +++++- 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/modules/weko-records-ui/tests/conftest.py b/modules/weko-records-ui/tests/conftest.py index d90d5fd8f1..a3e579100e 100644 --- a/modules/weko-records-ui/tests/conftest.py +++ b/modules/weko-records-ui/tests/conftest.py @@ -29,7 +29,7 @@ import tempfile import time import uuid -from datetime import datetime +from datetime import datetime, timezone from collections import OrderedDict from unittest.mock import patch from datetime import timedelta @@ -4330,15 +4330,16 @@ def site_license_ipaddr(app, db,site_license_info): return record1 @pytest.fixture() -def db_fileonetimedownload(app, db): - record = FileOnetimeDownload( +def db_fileonetimedownload(app, users): + record = FileOnetimeDownload.create( + approver_id=1, + record_id='1', file_name="helloworld.pdf", + expiration_date=datetime.now(timezone.utc) + timedelta(days=30), + download_limit=10, user_mail="wekosoftware@nii.ac.jp", - record_id='1', - download_count=10, - expiration_date=0) - with db.session.begin_nested(): - db.session.add(record) + is_guest=False, + extra_info={}) return record diff --git a/modules/weko-records-ui/tests/test_models.py b/modules/weko-records-ui/tests/test_models.py index 036dd147fd..9e436012c5 100644 --- a/modules/weko-records-ui/tests/test_models.py +++ b/modules/weko-records-ui/tests/test_models.py @@ -107,17 +107,6 @@ def test_FilePermission_delete_object(app, db, db_FilePermission): assert db.session.query(FilePermission).count() == 0 -def test_FileOnetimeDownload_update_download(app, db, db_FileOneTimeDownload): - data1 = { - "file_name": db_FileOneTimeDownload.file_name, - "user_mail": db_FileOneTimeDownload.user_mail, - "record_id": db_FileOneTimeDownload.record_id, - } - - db_FileOneTimeDownload.update_download( - data=data1 - ) - # def find_list_permission_approved(record_id, file_name): # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::test_find_list_permission_approved -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp def test_find_list_permission_approved(app, records_restricted, users,db_file_permission): @@ -177,7 +166,8 @@ def test_create(self, users): assert getattr(rec, key) == value bad_data1 = self.base_data.copy() - bad_data1['expiration_date'] = datetime.now() - timedelta(hours=24) + bad_data1['expiration_date'] = (datetime.now(timezone.utc) + - timedelta(hours=24)) with pytest.raises(Exception): FileOnetimeDownload.create(**bad_data1) assert FileOnetimeDownload.query.count() == 1 @@ -295,7 +285,8 @@ def test_create(self, users): assert getattr(rec, key) == value bad_data1 = self.base_data.copy() - bad_data1['expiration_date'] = datetime.now() - timedelta(hours=24) + bad_data1['expiration_date'] = (datetime.now(timezone.utc) + - timedelta(hours=24)) with pytest.raises(Exception): FileSecretDownload.create(**bad_data1) assert FileSecretDownload.query.count() == 1 diff --git a/modules/weko-records-ui/tests/test_utils.py b/modules/weko-records-ui/tests/test_utils.py index 9f7c12aed4..eb409d5e8d 100644 --- a/modules/weko-records-ui/tests/test_utils.py +++ b/modules/weko-records-ui/tests/test_utils.py @@ -830,9 +830,8 @@ def test_get_google_detaset_meta(app, records, itemtypes, oaischema, oaiidentify assert get_google_detaset_meta(record) == None - # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_to_utc_datetime -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings -def test_to_utc_datetime(): +def test_to_utc_datetime(app): assert to_utc_datetime('2025-1-1') == datetime( 2025, 1, 1, 0, 0, tzinfo=timezone.utc) assert to_utc_datetime('2025-01-01') == datetime( @@ -843,8 +842,8 @@ def test_to_utc_datetime(): 2024, 12, 31, 12, 0, tzinfo=timezone.utc) assert to_utc_datetime('2025-1-1', -540) == datetime( 2024, 12, 31, 15, 0, tzinfo=timezone.utc) - with pytest.raises(ValueError): - to_utc_datetime('2025/01/01') + assert to_utc_datetime('2025/01/01') is None + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_validate_secret_url_generation_request -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings def test_validate_secret_url_generation_request(app): @@ -999,10 +998,9 @@ def test_create_onetime_download_record(mock_user, mock_get, users): # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_create_download_url -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings -@patch('weko_records_ui.utils.base64.urlsafe_b64encode', return_value=b'test') -def test_create_download_url(mock_encode, app): - with patch('weko_records_ui.utils.base64.urlsafe_b64encode') as m_encode: - m_encode.return_value = b'test' +def test_create_download_url(app): + with patch('weko_records_ui.utils.base64.urlsafe_b64encode') as encoded: + encoded.return_value = b'test' with app.test_request_context(): secret_obj = FileSecretDownload( creator_id=1, @@ -1027,6 +1025,10 @@ def test_create_download_url(mock_encode, app): url = create_download_url(onetime_obj) assert url == (f'http://TEST_SERVER/record/1/file/onetime/test.txt' f'?token={b"test".decode()}') + with app.test_request_context(): + invalid_obj = MagicMock() + url = create_download_url(invalid_obj) + assert url is None # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_generate_sha256_hash -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings diff --git a/modules/weko-records-ui/weko_records_ui/utils.py b/modules/weko-records-ui/weko_records_ui/utils.py index e93f0b2f71..4dbb70d339 100644 --- a/modules/weko-records-ui/weko_records_ui/utils.py +++ b/modules/weko-records-ui/weko_records_ui/utils.py @@ -1682,7 +1682,11 @@ def to_utc_datetime(str_date, offset_minutes=0): Returns: datetime: The datetime object in UTC timezone. """ - local_naive_dt = dt.strptime(str_date, '%Y-%m-%d') + try: + local_naive_dt = dt.strptime(str_date, '%Y-%m-%d') + except ValueError: + current_app.logger.error(f'Failed to parse date string: {str_date}') + return None local_tz = timezone(timedelta(minutes=-offset_minutes)) local_aware_dt = local_naive_dt.replace(tzinfo=local_tz) utc_dt = local_aware_dt.astimezone(timezone.utc) From ce6ecd080095a2003218aa9d1c7b54e9e2ce7f77 Mon Sep 17 00:00:00 2001 From: "sei.nakamura" Date: Wed, 15 Jan 2025 15:36:45 +0900 Subject: [PATCH 22/61] =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E8=A9=B3=E7=B4=B0=E7=94=BB=E9=9D=A2=E3=82=B7=E3=83=BC=E3=82=AF?= =?UTF-8?q?=E3=83=AC=E3=83=83=E3=83=88URL=E7=99=BB=E9=8C=B2=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=80=81=E7=AE=A1=E7=90=86=E8=80=85=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E6=94=B9=E4=BF=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/weko-admin/weko_admin/config.py | 4 +- .../static/js/weko_admin/restricted_access.js | 183 +++++++++----- .../admin/restricted_access_settings.html | 15 +- .../translations/en/LC_MESSAGES/messages.mo | Bin 18820 -> 18980 bytes .../translations/en/LC_MESSAGES/messages.po | 16 +- .../translations/ja/LC_MESSAGES/messages.mo | Bin 21507 -> 21687 bytes .../translations/ja/LC_MESSAGES/messages.po | 16 +- .../static/css/weko_records_ui/style.css | 82 ++++++- .../static/js/weko_records_ui/detail.js | 226 +++++++++++++++++- .../file_details_contents.html | 43 ++++ .../translations/en/LC_MESSAGES/messages.mo | Bin 21466 -> 21520 bytes .../translations/en/LC_MESSAGES/messages.po | 4 + .../translations/ja/LC_MESSAGES/messages.mo | Bin 10376 -> 10964 bytes .../translations/ja/LC_MESSAGES/messages.po | 40 ++++ .../weko-records-ui/weko_records_ui/views.py | 33 +++ 15 files changed, 575 insertions(+), 87 deletions(-) diff --git a/modules/weko-admin/weko_admin/config.py b/modules/weko-admin/weko_admin/config.py index 7f773dd66d..c92f38a013 100644 --- a/modules/weko-admin/weko_admin/config.py +++ b/modules/weko-admin/weko_admin/config.py @@ -1230,7 +1230,7 @@ 'widths': ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'] } -WEKO_ADMIN_RESTRICTED_ACCESS_DISPLAY_FLAG = False +WEKO_ADMIN_RESTRICTED_ACCESS_DISPLAY_FLAG = True """ Restricted access feature display flag. True: display all feature @@ -1240,8 +1240,10 @@ WEKO_ADMIN_RESTRICTED_ACCESS_SETTINGS = { "secret_URL_file_download": { "secret_expiration_date": 30, + "max_secret_expiration_date":30, "secret_expiration_date_unlimited_chk": False, "secret_download_limit": 10, + "max_secret_download_limit":10, "secret_download_limit_unlimited_chk": False, }, "content_file_download": { diff --git a/modules/weko-admin/weko_admin/static/js/weko_admin/restricted_access.js b/modules/weko-admin/weko_admin/static/js/weko_admin/restricted_access.js index 0e6d25e58a..3a82fd47d2 100644 --- a/modules/weko-admin/weko_admin/static/js/weko_admin/restricted_access.js +++ b/modules/weko-admin/weko_admin/static/js/weko_admin/restricted_access.js @@ -2,12 +2,18 @@ const {useState, useEffect} = React; const CONTENT_FILE_DOWNLOAD_LABEL = document.getElementById('content_file_download_label').value; const DOWNLOAD_LIMIT_LABEL = document.getElementById('download_limit_label').value; const EXPIRATION_DATE_LABEL = document.getElementById('expiration_date_label').value; +const MAX_DOWNLOAD_LIMIT_LABEL= document.getElementById('max_download_limit_label').value +const MAX_EXPIRATION_DATE_LABEL= document.getElementById('max_expiration_date_label').value +const EXPIRATION_DATE_INITIAL_LABEL = document.getElementById('expiration_date_initial_label').value; +const DOWNLOAD_LIMIT_INITIAL_LABEL = document.getElementById('download_limit_initial_label').value; const UNLIMITED_LABEL = document.getElementById('unlimited_label').value; const SAVE_LABEL = document.getElementById('save_label').value; const CHECK_INPUT_DOWNLOAD = document.getElementById('check_input_download').value; const CHECK_INPUT_EXPIRATION_DATE = document.getElementById('check_input_expiration_date').value; const EMPTY_DOWNLOAD = document.getElementById('empty_download').value; const EMPTY_EXPIRATION_DATE = document.getElementById('empty_expiration_date').value; +const EMPTY_MAX_EXPIRATION_DATE = document.getElementById('empty_max_expiration_date').value; +const EMPTY_MAX_DOWNLOAD_LIMIT = document.getElementById('empty_max_download_limit').value; const USAGE_REPORT_WORKFLOW_ACCESS_LABEL = document.getElementById('usage_report_workflow_access_label').value const MAXINT = Number(document.getElementById('maxint').value) const MAX_DOWNLOAD_LIMIT = MAXINT; @@ -64,45 +70,65 @@ function InputComponent({ label, currentValue, checkboxValue, + canSetUnlimited, value, setValue, inputId, checkboxId, - disabledAll=false + disabledAll=false, + maxLength=String(MAXINT).length, + max=MAXINT }) { - const style = {marginRight: "5px", marginLeft: "15px"} + const style = {marginRight: "5px", marginLeft: "15px"}; + const containerStyle ={display: 'flex',gap:'15px',marginBottom:'2px'}; function handleChange(event) { event.preventDefault(); let target = event.target; let key = target.id; - let updateValue = target.type === 'checkbox' ? target.checked : target.value; + let updateValue = target.value; - if (target.type !== 'checkbox') { - if (!event.target.validity.valid) { - updateValue = value[key]; - } - if (isNaN(updateValue)) { - try { - updateValue = parseInt(updateValue); - } catch (e) { - console.log(e); - } + if (parseInt(updateValue) > max) { + updateValue = max; + } + + if (!event.target.validity.valid) { + updateValue = value[key]; + } + if (isNaN(updateValue)) { + try { + updateValue = parseInt(updateValue); + } catch (e) { + console.log(e); } } setValue({...value, ...{[key]: updateValue}}); } + function handleUnlimited(event) { + let target = event.target; + let key = target.id; + let updateValue = target.type === 'checkbox' ? target.checked : target.value; + if (canSetUnlimited) { + if (!event.target.validity.valid) { + updateValue = value[key]; + } + setValue({...value, ...{[key]: updateValue}}); + } + } return ( -
- - + + + {canSetUnlimited && ( + )}
) } @@ -121,9 +148,9 @@ function InputComponent({ function SecretURLFileDownloadLayout({value, setValue}) { const { secret_download_limit, - secret_download_limit_unlimited_chk, secret_expiration_date, - secret_expiration_date_unlimited_chk, + max_secret_expiration_date, + max_secret_download_limit, secret_enable } = value; @@ -162,26 +189,50 @@ function SecretURLFileDownloadLayout({value, setValue}) { {/* end enabled checkbox */} - - + + + + @@ -193,9 +244,7 @@ function SecretURLFileDownloadLayout({value, setValue}) { function ContentFileDownloadLayout({value, setValue}) { const { download_limit, - download_limit_unlimited_chk, expiration_date, - expiration_date_unlimited_chk } = value; return ( @@ -210,18 +259,16 @@ function ContentFileDownloadLayout({value, setValue}) { @@ -250,6 +297,7 @@ function UsageReportWorkflowAccessLayout({value, setValue}) { checkboxValue={expiration_date_access_unlimited_chk} inputId="expiration_date_access" checkboxId="expiration_date_access_unlimited_chk" + canSetUnlimited={true} value={value} setValue={setValue} /> @@ -510,46 +558,51 @@ function RestrictedAccessLayout({ function validateSecretURLFileDownload() { const { secret_download_limit, - secret_download_limit_unlimited_chk, secret_expiration_date, - secret_expiration_date_unlimited_chk + max_secret_expiration_date, + max_secret_download_limit } = secretURLFileDownload; let errorMessage; - if (secret_expiration_date === "" && !secret_expiration_date_unlimited_chk) { + if (secret_expiration_date === "" ) { errorMessage = EMPTY_EXPIRATION_DATE; - } else if (secret_download_limit === "" && !secret_download_limit_unlimited_chk) { + } else if (secret_download_limit === "" ) { errorMessage = EMPTY_DOWNLOAD; - } else if ((secret_expiration_date < 1 && !secret_expiration_date_unlimited_chk) - || secret_expiration_date > MAX_EXPIRATION_DATE) { + } else if ((secret_expiration_date < 1 ) + || secret_expiration_date > max_secret_expiration_date) { errorMessage = CHECK_INPUT_EXPIRATION_DATE; - } else if ((secret_download_limit < 1 && !secret_download_limit_unlimited_chk) - || secret_download_limit > MAX_DOWNLOAD_LIMIT) { + } else if ((secret_download_limit < 1 ) + || secret_download_limit > max_secret_download_limit) { errorMessage = CHECK_INPUT_DOWNLOAD; + } else if (max_secret_expiration_date === ""){ + errorMessage = EMPTY_MAX_EXPIRATION_DATE; + } else if (max_secret_download_limit === ""){ + errorMessage = EMPTY_DOWNLOAD; + } else if (max_secret_expiration_date < 1 ){ + errorMessage = EMPTY_MAX_DOWNLOAD_LIMIT; + } else if (max_secret_download_limit < 1 ){ + errorMessage = EMPTY_DOWNLOAD; } - return errorMessage; } function validateContentFileDownload() { const { download_limit, - download_limit_unlimited_chk, expiration_date, - expiration_date_unlimited_chk } = contentFileDownload; let errorMessage; - if (expiration_date === "" && !expiration_date_unlimited_chk) { + if (expiration_date === "" ) { errorMessage = EMPTY_EXPIRATION_DATE; - } else if (download_limit === "" && !download_limit_unlimited_chk) { + } else if (download_limit === "" ) { errorMessage = EMPTY_DOWNLOAD; - } else if ((expiration_date < 1 && !expiration_date_unlimited_chk) + } else if ((expiration_date < 1 ) || expiration_date > MAX_EXPIRATION_DATE) { errorMessage = CHECK_INPUT_EXPIRATION_DATE; - } else if ((download_limit < 1 && !download_limit_unlimited_chk) + } else if ((download_limit < 1 ) || download_limit > MAX_DOWNLOAD_LIMIT) { errorMessage = CHECK_INPUT_DOWNLOAD; } diff --git a/modules/weko-admin/weko_admin/templates/weko_admin/admin/restricted_access_settings.html b/modules/weko-admin/weko_admin/templates/weko_admin/admin/restricted_access_settings.html index 715e652a74..af80be7458 100644 --- a/modules/weko-admin/weko_admin/templates/weko_admin/admin/restricted_access_settings.html +++ b/modules/weko-admin/weko_admin/templates/weko_admin/admin/restricted_access_settings.html @@ -44,20 +44,29 @@ {% from "weko_theme/macros/modal_page.html" import all_modal %} {% set download_limit = _("Download Limit") %} {% set expiration_date = _("Expiration Date") %} + {% set max_download_limit = _("Max Download Limit") %} + {% set max_expiration_date = _("Max Expiration Date") %} + {% set expiration_date_initial = _("Expiration Date Initial Value") %} + {% set download_limit_initial = _("Download Limit Initial Value") %} + + + + + value='{{ _("Must set a positive integer and less than %(name1)s for %(name2)s.", name1=max_download_limit, name2=download_limit) }}'/> + value='{{ _("Must set a positive integer and less than %(name1)s for %(name2)s.", name1=max_expiration_date, name2=expiration_date) }}'/> - + + diff --git a/modules/weko-admin/weko_admin/translations/en/LC_MESSAGES/messages.mo b/modules/weko-admin/weko_admin/translations/en/LC_MESSAGES/messages.mo index a9b27e75fdd3fce264f1eff7ed8891bbbc9a582b..c57d1639861240c675c9818fe166ae13f2760850 100644 GIT binary patch delta 5644 zcmb`~iF2049l-G?5Uwc5DYtpUksuHfLMWhuBqS&x1VNzEsTw6>1j1QF(L7KPuTE{s zrIfZ%4zXaxiXC7SrfR*gD)m6~Cqxi&3|y5QyvYv2F$Z5KstspjH^Sd1;P44dFhcmdvq&2T|zug0d- zSA_b*p}scMHzdZG&6Pg|A@>-$Tba zizF?|=#)IqLMP5e7d8;hTrtu$8s903>kGHj;DF16zd#SqdNj3Lkl$!msK1E@@&UTn z$I*e$q6=xnKfafRj+2iDG6X#XMVP{goyos@vVaC7x(iL|Ds=C6p{acl?ROaM_aU00 zbHPkbto=&S_NmCDXbC#;dNjbzXum(A0lpklaF5>$9gd+Z`T|X915T6?w?peWX#dO5 zEh#|<8iS^IYN$^~GqeyJ;9~T4RHL`%fl!avg~Aq0@n9!9;X(8e9uLpYVQ=c`oID54 zL$|CDUD0rKg_DA}pn=Ur16~s952E8gifm0BZ4V8*l7{FdG-Ypw`XL-d{TMoN2ll5M zc12e_9L?BRbi(q`J_o(7_n?7p3_gL5zXw}0fAnVx4)6{Z;7K&Kom0u4cS8s6g=S~c63VP~ih4w}0mc`fyYkHFZbPCVW z;6%@(^#kZYZ=o4EgdU<3XvV%m&w$I*_Glpe(20xDfPaWCq#`_@g$A$`XW+`1f-5B$kV152L(r6u2=#JoLVZSPpO21H6WV`=ruz5jg!|Dz52E9pL<5ZLLc@7X z(a@|{vXb8D0K?E1C!#649Zl&P^ssIW^<8KN_G2S_8yn-{P(OxEsMn%r;R|Fzan$_M zq@fSy^I$Z3+83h(tO@=aJ%pRlz;@xa_$sm+QD;7#Jd8!?=`Y7ztik#WU{mS`(EyKN z3-A9i3a;c!^tv=)neKHDbT9MK$VZ}EG7b%FA{yvaY=%Dx?J*kQ3N$0XLT}BJ$ftUA z08RZ#OlSV+OnC4gbOr7ECWmYg8u3Up;xf#@N$A8kp@(rX8o-0$`6^sb{kM1_UO^fh zXB4`C31~o5F!oT*r(nc2;lWmPg6Gi}kD>w8q63~rCp?E2U_&myUW8_%v2y^f|9Dv#VlYUdt1>J%z zaXL1~Md&;$&@)kk2DBv(4coC54f}%!gU8VU&!8`yL$~ZadKI^hH8 z;a!Vn?g{i7KZ|DMZFGV0hZM3Y)S?qKXE_eg27NILO=Uis;=$;^rRW*>5&GRxfqseI zgWm5o=wW^a&D^`_gr|aECC}sN-xQpnLt&Dd6uROk7_(>ezI(`!2Ny%Q}(C)|t< z{2Y2#UPM>;DyHyVbewO{3^W^V`J+M#PB0W(VJW6?3c4kW(15DZls*JN^k>*#UHqkA&x+U>oXnXi6Io zP6FYIa4Vv6>?Xomid z?eO#Pyy1}KkJA?Dz(dfj8ioci8eQOx!C7b^i-(YZBYrS6tU@Q)gzoV(p}sHF-$FBX zIMhEvk`bLkC+>Dxa{6=76^}$SHW8h0dT3vWUe{$Y1ylDJ8u4Cqf|o-38|VNZVF8{& zQ`_tEWY6=^fd`@)DZz_zD!P!lSd7ciYxxoy*aDuIEx)H?TX|+cR~XzK-)`0 z{RZ^#%?&QaPSk7AYq5CFW7T5p(h3 zn1ZQ!4&CE7un`_YPybPLC8se9&!e9cStFCJ$VCGmf$2B~8{>F1&?#s}=Y{sW(EyjB z8HpdE;I-L-UGNpG|A0UTtPAaD(Ur8lDhaF*4R{1L!fVjfPe3RBF}i?7XaM(x=QX&V z`g&xZI2v+wGSEnL!ZI|V>(DKjgGT)G(7p+sU_Um&Bj|e{qk;be-NLWYOtl%69NtW9 zOg$IvSBMRx$=n(W?rmv(1OJZ?-W9CI9z6dk_QuE1L;E*$;!n`M{1hFy{b;W8(Y#Sl zPij||(LGf%`;Mxav&*NYuBn_=xgZ)_z9?0?Xinw4@&%Q%t5PN93o2G_$lTt&ylPr% zX2tyZsRg%{SEYLPsw$sV(f`uGte;gWC(7madBxM_!ih=mG>B7>$X2n>>H4~&2m zK@@^hT7)8kRV!#wDRsCg)sDqlN;5@Tr4ucvI0M=Y#Yw65eRFoEGtFf3?C#mK`|Y>8 z|DR{;(^hOui%(sdu_@wbVpVJ#;vXuIPO#8}*;VL@Qs@c?2S=lUO+o{{J=E_($6tzUO&mQI8tRjVXcwBYJ)yo2hfqI^PW&}? z!~dWwPO)ERtS>s@NVNUC=yjce2D&`B4jq3p4q*OhI|WnOfbQ|%(19A!419^6>a0`} zc_H?q-XDn}nubpJAR6FGbj54X_v_INy?}1%A@r6Vw;ogYf`W(bn_%0n$!YI`G)2SE zl}twm{tsWY3K|0 zpr^PF+4kruBpK1G*bf`90@I0ZFb>92tVJ`k0bThk*aP3eDm;hNuun1hH}$KElK|GC z_jMDR(yeF;Uql0Y6W#0M=*rHZ?|+H*yNI6hT<)Ol`RHLQ!ZNJD44i?^b4LmJx1p8> zUzm?(WC?m`R-!3;5rl`lyQ*b4} zM+Z2CzSxAOtWD1(rIqOE9U1CV(D!CxCN9J*TpH@jFq`@+^ejA%2L4j0AH{O-|5*xr zTt@}Hk^w4%Rp=qCMgyCIlkjdN%h4O?VQfTCe;S`oeX#;tGJuy+Uw{T!hi>IEbRp}p ztM~sI3hwo9(XBX!Mt%m}l5^F20hXc}xfZ=O6Om8ts1{BAYIM9! zq5Y>=#Qf1d3PyGUjra^2@p){67to2LK1sg;by!XcLevKnR)~5cLdY%7&=ZvsDBh}44y{& zf8Lk;yBGhW!98our-~65pczS_E9{Ru()f16mP5*hJ3te93p_~%l7|L(HM)Qj^!>8% zyfW0U5B2fo<1J|B z7NZlc2|gK~{{$WX*Vx|sf0%+RZon(?1N2aSf&QzNJt+CU?}|=XjSf5mJu7q272b_0 zT#SzM6qkL>mg`{MqtbR4)t0zkh;(w zqZ$2Ks6UUs_cpfN3N(<9FwgscnnDUMqI;5GnFQ1YP3cf{@4tsv;XJh8!)V}-U@E_7Id?Wu1-Q@RWDaDS*DMf;yZx8yT) zyz^*^GlnMP=Aju%VJj>_Z%5Ce9DV4S0G?!3pN#0K5-P<#u$Be}N9P8_mE0?1+Cx13inq z@DdtG>9xs(HE4j7(G}l{`8XRpVI3MsydgZ;85$0vhwV?nM(j-eE9Bq3DCfFlCH>KX zZ$_{040La2BcICA94y9qbmjZe{vV;^e~K(DjxJI#qO7W9&$^%)>4Tn)LFfxLSb(=+ zAud2~$9n9Cd$0meAr~oXcYX3nHwMkjz3ACki#>2VR(b#5r7(?#>>HBQ&qf27hn|V~ z=-w_xw_p_-*cNoJUqV;*Cc1S;(SFC#Q~p_KZ$h`M8Otzd1X~nS7)rs3s%*e9=s-83 zr+6~jemi;=?n2MNqTpj_AkUx^zm5j{9=ec|=<`N&+zVKXmoRoE^F}5UEky@-6z%vl zy0T}{33rG3+vvc5#6moQj&mWjXN^iy-4$qPU`vSd)?7nGXz~gRj5~^0Z+g#I2jFm!D#Yt!+ILZaW{I} z|BepO989ZD4&h~JVEH%+`(hEs!ENa2KZJepJerxpnq-UnVM~DMR#wFnT**z?6=$Iz z49n52ScgWw12gbNG_aS^K=+{;JRaIVL<9T;&B)j2t;rda{3Fv7O??eIUOYKGxD{Q= zA~dpk^ib}=O#CI9`d^_FA3*zkfClhsc>Wb`p?(RSX!F=)oL%UId(ePhMYbT0j!`h; zbKybyxMYGN%%*)f`r;^bz_I9-O+hoY5WOWaX5l)tUp=Pdc64iZhWcNEe{Hdn4-5*$ zbodzkQpp~loZ12C#MS6tjzbO;5bxR%o8&St`n&FlDs{Q}J6%mdTPIV;)<0cHnJy;KqVgo#jL3qX5t1ysyjn%)k z`oFC1MAnXXoj?le7>Zgb3Kd8@Y=v3a0Si$5wxK`nMFnyY8{#Qc?4RIMScOmE4_1GG zji?7V_Kd03b(~ad=z*GeIBMXF*c`{=lQ<$5YT-5*ja^X-jKV}L#3Wo}?I%zR+`zh6 zjoRsL^x;2I^F=Tjr{N?;J8o@5IB7gHfq_8GSehbwux>0$PvC;D@L? z@+qqSZB)Oel{DK{jBUdTpZ7haFKnAHg3j{2Kb> z>*$9?*dA9PF*}v$!zxt&Ur_S~C3riJLIse7I^u2#EjGL?tQ#7b<1o3IOhj3Y6S&N_;hQS0TR?#!%~G3qwYd~mx4ZpBh8su z8wl10f(me-)jvYrmCHC4Z=znu!Aaf%IjHe7PyxJ-+Q@QLX5X>;eym5`t+WRxQ4{^$ z9^6Nz^k>w9p~+sLai|?+peF8(3NYL1qtQow5^4i)qUKqP8n+i)VJ1PTzLcJB2klzvK8w|$!M5%gX)LZg2Dxl%0fXAZ(o`&@?AGP2DRKNEy z2)CiD4*MvSVI^vTk?p*RCZQsqfeNG$b%}~mftFkQ39S9OMFsE>6@Wi!)qFvy1tT#8 zeW*;eZ%_VJc#a08Dibvz8~t%KYQk|=&o!r;vrzqKqwdgRRKTUEh1Q@pxC6`T@C8H# zJ|xZSKPip;>k~PT1_khYR6uJ`6KzH9WS2ePXY~rJAF=uwtDi>&d=;O>D&)R7|G*9y z#ScSo9Ej>y;!@D1C_`PE&1MBE@(b7sucK1v$1Lg>jM{k|s=d9{Gt55LKFsQ4P)9cf z_4X{o40N|q$fEEiCScpAy#=X$!1OF{4N8>j(?P&+<`itrA0#CZP5m-wTUzKxo&5;fsDY=alA{umW_ z-A>+JYK)Q8qftkmjxl=wvnX_>VG4G}ou~!ALQNd@D{p5}sCqJLf>hK3{ZIjqMxFUm zRKIuZ`C8PMwHy`j5gdYl#u2RV#IQ{b_zh};1*nNvViVkm%1{MH;9XRI=UMNytcPlk zM;%QDYJ6YR(Tu`II36|6Y)r?k=qlycC`4g3#^Pht1kIoGUYAr-j=cgD5n>pPSGXd23CP|80>P4FXX=Z*Oh*ML-1ziiYcor(&u z7?p|5sEzDIEp*VHpF?HjhCTlp^$(XHt^Gk4^52+-de3{WTP$kg6x709Pz#SnvhK`9 zJ>QQy%TpMGmr*RHz5Bz=lP>A>r;JrlUU!$q@V@Y zp$7aFTcTg4cbSs$CF;323r}JIcI@lzq>DMw9D@pMIx6rYREAbreIq7N-;K4u|Cg=9 z=cvfPvU)^6Z(uBH!DKTPwc}2x1$v|I!cf%2lTk;s(%QG8E?p(6{{?J?H~Nu(UAnsc zy?@Pyqt0|8YJp9tiT9!cIF0Im-r7G$O?cnh^-HEqhN8yxM~xeYx?8Vc9KL~xxFd`F ztK$V4g76k9@^4WS2M+MgDgw2$eyIL==2GO1bk?ADcpZcB8`Q!NtzKuKR}V2G%w{eH zr6L}6W?85VykZ@SP=OVrCSHU3ux!JnSc&P_eUM!?Nk=_* zyINraYG*~*5I3L}+Kn1;92Lj~^BVe5ug11mW9{LCy`9FOHkOFW=s?s4Ct)fsM&@;$ z0~FNZC~Bf>s8rS);+=I=;vsP{#E7WLZpLr zt2Z3ZIR1^{gsXtjs56Pf6wE{|oR7-PeAI+HvE;kd$ODTbrZ$N8^_np!Z`zEU3BCcj z({l@*fjM)1z30x%&Ce;!ossA3l~XveqOpWgDo}y!$~->0a+a$?k3!($bR) ssu%4^yLtEn8mkwT*OVTlL-nG!suykf-}JRlX(_+8aLa+WyXS}f7sD@z!2kdN delta 5601 zcmYM$2~?L=8prV$F#<$D6ctgyh0P6F4AXghnB2@$xYo z=b*+bMpdj5gP7mjKtls;MooMIwW8Cg2QOh1)}gi_EXEBSgDQC{GPlhRW#A$#yR3gc!!_pH~x*-^k6H!~V0F_WNs)Dbh&d3L- z`wyV*JA4CaF2sO?G)K>YA(9i^rqaJw1;@_i6wh|lRtEkuOHPmaj*5WGEN~=-N z@3a2TF`oFe_5XrOB$RULt!Rsk=X>ciy3jEIwc;mHEBKwc9F<@ND)S8%Z$l0EF>33+ zu=upaS5OtbjahgX^?X*mTi9Ui$oyV54P`zZRnqyW2}@D^6{z?8UDTmFXr4e#a2C_? z5~`%_I=G5-Mvc=2wci$2WCfJBt`7Trf2T)u3EvjNyPzm1ZMEyCqUQ}oI8ui2;#N#jxee8u>F%d7I zDizMVqe^tgBpiYRa0ce%2GmwG;vr2Kf;uD3P?c+gs%TOo_1B&cphGLj#UOmxZg>=R z>K9ml33eqe#m2Y;HSlhW_hAt6r>GSlMV*;%P!+j^IsWr0zego_)#7@LCk{$+3rIqZ(+~CBIP8iAs7fwHeTd3j?0fIh(8{;sJ-FZeCo1F5 zQG0&M{15gdZpB9<2#1?vQHSp#R02~m7k`T!2k#K_3Gi;94smm`>aO>HC=IQ6E(T)> zDv_5^1FlBx-C9)UTT!QfA2z`n)D~VxB^=z%ZABz1!D!T5))qrB%lZdn3%&otX{Z8^ zpk9l)$nS;sGKS%M7Vku@;7e3OKcNy1?Cuf{!%*TV)P(I&_w`0iJj|}=VkL0_`e8Kc zXlS6I9xn3;R3a@)CMUIF`JsrQ1?fpwxT2I)c3-0?2D?v2&@d?3y4bmM-Ghc4@z@i$QV=t z>1otonf0YZ17)LDGRkhqwK(76$rewucs45GB8YJj7t2`-`%u1DS1xwm^R6Lq~G>eHHoN_aBn z;w+qq)u?$|vf2pd_uA3WgWa(;4nmbEAEU7p^}t)G*K)n}??r9RDb({nptj~`Y=(h- z-8j*hPMnR}qUSIMOR(YZ|2Jr8fWKo4Jc?S`_o!0cG<)@P*Nag7rKm6ALDc=1u_@N0 z#tH53N*;&0em}-y4yxiaRDTUnPKQ>$6SYT2?Z&I9Q`&?dcO}>pRf(afm5fA9lxNqc zqbjn{t}jRZ;Zkn>uVX9X^{BV)qXE=k10SSA6Q4n4SdUcEYkj}FJ|4A~PoVCbhg#7q zs7m}9mB@Ff@ot$RTvS4BQCry+wcx?1EqctS(eMGWj$(^fp(dz6zRuo8R03b2DsUd# zW3Bav4swaNLRB&YHQ{JfLXVn7sQ#s>@%=Yx^q}!Rs-$O7ug?Y4jkTx*0tUOSNJXt6 z4~O7<)E90SD#5#`1jD$fiJM~wOhhF-9ChC`q|(0ktZR6y%uT2j96&wz9V)TFq3*$^ zW*ajJ6S$s)_#rm>HTlYQKc-f>wiE!w+VH)s<0y-!vwsE8Yh;Yybw%7CEgD;Zr&*Bue~ayLn|vsJ@BD< z6bBO5pjMbT+CA6@HF37ZV=SIz7Mf3@DlrGOWv`(sP-WMT`!tl})Q98-F2Ue2 z?%Vw`b|Kz_99*v!m2hf~`$DG<4&9vjnww zt1ujQpjNiut{=fl;?ovCm+QWuFQ8uEm8e9@upMqjt@M!fpT#!BcQ8orf6RC{K|Csv zE~vwlg%$V^YQ@(u3U6B+I>FuF40XLd>MSK#oNrDsr=hmwDb!Y$VTgW>s%fai2T%#z z!m@E`Evwc>&umhb* { + if (!response.ok) { + throw new Error(`Network response was not ok: ${response.statusText}`); + } + return response.json(); + }) + .then(data => { + // APIからのデータをフォームに反映 + const secretDownloadLimit = data.secret_download_limit; + const secretExpirationDate = data.secret_expiration_date; + const maxSecretExpirationDate = data.max_secret_expiration_date; + const maxSecretDownloadLimit = data.max_secret_download_limit; + + // 今日の日付を取得 + const today = new Date(); + const todayFormatted = today.toISOString().split('T')[0]; + + // secret_expiration_date を使用して日付を計算 + const expirationDate = new Date(); + expirationDate.setDate(today.getDate() + parseInt(secretExpirationDate, 10)); + const formattedDate = expirationDate.toISOString().split('T')[0]; + + // maxSecretExpirationDate を適切な日付形式に変換 + const maxDate = new Date(); + maxDate.setDate(maxDate.getDate() + parseInt(maxSecretExpirationDate, 10)); + const maxFormattedDate = maxDate.toISOString().split('T')[0]; + + // 要素に設定 + const expirationDateInput = document.querySelector('#expiration_date'); + if (expirationDateInput) { + expirationDateInput.value = formattedDate; + expirationDateInput.max = maxFormattedDate; + expirationDateInput.min = todayFormatted; + console.log(`Expiration date set to: ${formattedDate}`); + } else { + console.error('Error: #expiration_date element not found.'); + } + + // フォームのフィールドに値を設定 + const downloadLimitInput = document.querySelector('#download_limit'); + if (downloadLimitInput) { + downloadLimitInput.value = secretDownloadLimit; + console.log(`Download limit set to: ${secretDownloadLimit}`); + // maxSecretDownloadLimit 以上の入力を制御 + downloadLimitInput.addEventListener('input', function () { + if (parseInt(downloadLimitInput.value, 10) > maxSecretDownloadLimit) { + downloadLimitInput.value = maxSecretDownloadLimit; + } + }); + } else { + console.error('Error: #download_limit element not found.'); + } + + // 有効期限の下に表示するメッセージを設定 + const maxExpirationData = document.querySelector('#max_date_display'); + if (maxExpirationData) { + const translationDate = maxExpirationData.dataset.expirationDate; + maxExpirationData.textContent = ` ※${translationDate} ${maxFormattedDate}`; + console.log(`Max Expiration Data displayed as: ${translationDate}`); + } else { + console.error('Error: #max_download_display element not found.'); + } + + // ダウンロード制限の下に表示するメッセージを設定 + const maxDownloadDisplay = document.querySelector('#max_download_display'); + if (maxDownloadDisplay) { + const downloadcount = maxDownloadDisplay.dataset.downloadCount; + maxDownloadDisplay.textContent = ` ※${downloadcount} ${maxSecretDownloadLimit}`; + console.log(`Max download limit displayed as: ${downloadcount}`); + } else { + console.error('Error: #max_download_display element not found.'); + } + }) + .catch(error => { + console.error('Error fetching secret settings:', error); + }); + }); + + + $("#secret_url").click(function() { + $("#secret_url_section").toggle(); + }); + + $('#create_secret_url')?.on('click', function(event) { + event.preventDefault(); + const webelement = $('#create_secret_url'); + if (webelement) { const url = webelement.attr('url'); - webelement.prop('disabled' ,true); + const linkName = $('#link_name').val(); + const expirationDate = $('#expiration_date').val(); + const downloadLimit = $('#download_limit').val(); + const sendEmail = $('#send_email').is(':checked'); + const linkNameError = $('#link_name_error'); + const expirationDateError = $('#expiration_date_error'); + const downloadLimitError = $('#download_limit_error'); + + let hasError = false; + + // エラーチェック関数 + function checkError(input, errorElement) { + if (!input) { + errorElement.css('display', 'block'); // エラーメッセージを表示 + hasError = true; + } else { + errorElement.css('display', 'none'); // エラーメッセージを非表示 + } + } + + // 各項目のエラーチェック + checkError(linkName, linkNameError); + checkError(expirationDate, expirationDateError); + checkError(downloadLimit, downloadLimitError); + + // エラーがある場合は処理を停止 + if (hasError) { + return; + } + + + // AJAXリクエストのデータをコンソールに出力 + const requestData = { + link_name: linkName, + expiration_date: expirationDate, + download_limit: downloadLimit, + send_email: sendEmail + }; + console.log('AJAX request data:', requestData); + $.ajax({ url: url, method: 'POST', contentType: 'application/json', - data: null, - success: function (responce) { - webelement.prop('disabled',false); - alert(responce); + dataType: 'json', + data: JSON.stringify({ + link_name: linkName, + expiration_date: expirationDate, + download_limit: downloadLimit, + send_email: sendEmail + }), + success: function(response) { + webelement.prop('disabled', false); + alert(response.message || "Success!"); }, - error: function (jqXHE, status ,msg) { - webelement.prop('disabled',false); - alert(msg); + error: function(jqXHR, status, msg) { + webelement.prop('disabled', false); + console.error('Errorです:', jqXHR.responseJSON || msg); + alert("Error: " + (jqXHR.responseJSON?.message || msg)); } }); } }); -}); + + $('#print-btn')?.on('click',() => { + const iframe = document.createElement('iframe'); + iframe.srcdoc = "" + document.body.appendChild(iframe); + iframe.onload = () => { + iframe.contentDocument.body.appendChild(document.getElementById('terms').cloneNode(true)); + iframe.contentWindow.print(); + } + }) + + +$('#close_btn, #modal_close_btn').on('click', function () { + document.location.href = location.pathname; + }) +$('#mailcheck_download_modal').on('hidden.bs.modal', function () { + document.location.href = location.pathname; +}) +$('#mailaddress_confirm_download').click(function () { + let mailaddress = document.getElementById('mail_form').value; + let password_checkflag = document.getElementById("password_checkflag").value; + var input_password; + var post_data = {}; + if(password_checkflag == "True"){ + input_password = document.getElementById('input_password').value; + post_data = {'input_password': input_password}; +} + let input_error = document.getElementById('input_error_messsge').value; + let url_element = document.getElementById('url_element'); + let onetime_file_url = url_element.dataset.onetime_file_url; + const get_uri = onetime_file_url + '&mailaddress='+ mailaddress + '&isajax=true'; + let item_detailes_url = location.pathname; + if(mailaddress == null || mailaddress == ""){ + alert(input_error); + document.location.href = onetime_file_url; + }else{ + $.ajax({ + url: get_uri, + method: 'POST', + async: true, + data: JSON.stringify(post_data), + contentType: 'application/json', + success: function (response) { + let link = document.createElement("a"); + link.download = ""; + if (!!response.guest_token) { + link.href = get_uri + "&guest-token=" + response.guest_token; + } else { + link.href = get_uri; + } + link.click(); + $('#mailcheck_download_modal').modal('hide'); + document.location.href = item_detailes_url; + }, + error: function (error) { + response_text = error['responseText']; + alert(response_text); + $('#mailcheck_download_modal').modal('hide'); + document.location.href = item_detailes_url; + } + }) + } +} +); + diff --git a/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details_contents.html b/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details_contents.html index 129c88d50e..a19f573df9 100644 --- a/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details_contents.html +++ b/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details_contents.html @@ -167,6 +167,49 @@

{{ filename.
+ {% from "weko_records_ui/output_detail_data.html" import output_attribute_value_mlt %} {%- set display_file_info = record.display_file_info -%} diff --git a/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.mo b/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.mo index 77e8ab730fda8c0eec2e4e4a9ea0228312561548..a1ee4db54d3ac8fb759373002f2d1bed9118e1d7 100644 GIT binary patch delta 6162 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 VhFg~we|zQ9rNv9O7MGsM`X6v_e6j!l diff --git a/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.po b/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.po index 6561112995..2b1d7c8d08 100644 --- a/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.po +++ b/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.po @@ -1385,3 +1385,7 @@ msgstr "" msgid "Success Secret URL Generate" msgstr "Success: Secret URL Generate is succeed. Sent the URL to your email adress." + +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html +msgid "Max Download Count" +msgstr "Max Download Limit" \ No newline at end of file diff --git a/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.mo b/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.mo index e4d42d46aea5bb29874ceb5d0f7fb8b7f6a163a7..7a7d2b6051affeab64b4c07468e69cbe2925daf2 100644 GIT binary patch delta 3145 zcmZ|Pe{79c9LMpaE&YK~>i3U6w0{4ZBG#@WrWK{LsIqmF*+tEby7l84%f{8-khVi? zcV@IkLu)Z(auY^IB*bLPeh`FiI^5nOnJp|s`p35S$2}YSL-X{$p65L0Ip=%6-}5}> z(=W{QHFSvCV)(1!zbF4`Ep_$yZ)k!st+*V9@i-be%s76!VlKAExtNX~Ou`y$k3sB+ zr!f{U+xBZnKcBhFiNlR(M(KlTI0h$SCccYP@d!@9=r-KL98~)ToQ;<;AIG#crU1*a z5U=7y9Ghs&WPAs+@H8edziH9Vm<}{_MEaWks7OcK>n!UOjHCS(EOLm1dwK9FINFg_?LZYQS~a z4L6}u`YCdlGyJGuJ!+yGs6cPq_GZ+?QKVhZCn^8h6hlxeorEgIY$RJ|Ii}z`bg&kc zsWYevzO|l54SWUl+$~fIno*f)$>6$9vi3v;>Knj`Ry+bV@EFtpnf5x*-k*-j#LLK= zW|m_=T#KWy4rgFJa+u+)VjyOtcKc#f02@#x--?6u{vYFH9u18+87Fs%ywC5WCj1Rm z%LKkQN@X%Cz<$=TsM6&kT}>e>GfPnmT7{2FhmT5!TJR=}*8BezaYLSP~)CMJ$DT?-aS-i;`*y3)IXUMtuzG{=?H9zV^IS?j~d_wdtG4f7ve(N=b%b> z7PVJ?vUVB}`69lAns_%3#G|O%-@pXsH!WyY&D&rOj=%-D0w?3osQ0+IYOWseH3taDGf?$iFsJRu}RIL;laz+g}l``lM;g7v^aaHzpnr>`b}T_4clHH>(bWN;foB?+dT14Odo$ zO816Jw*|b;ur?w8K22VBFuZvq+2}d)Y4bLIS;-vc$q6G(|7IuH;B<;^7xLFUTE=I$ c*KWVLvo>(C6nfIOvFd+cm?{537I+?i@DH_E3gk&;YF-P7tvQ^9af=(8o!E77`)e*b+`}faRM9h z!Fk41VmDUfSNIru?u#b)BzlOY2t%4^nin^z!7`HpdEi7mBT!iK5!;Pp4b)n*Shisz<71%*NcnnqYQB-Bl+WsX}2`5l1 zok9h?fqL$i?a$fkIsmFb0`g)QKjvW}7Gr2Vji+h6fm*>N=3@kPioFafkuua?SK$Nr zB0h%>R^u${b*y0dn(!Ue7M?~`@Ej_^aqBO*K=1z@8Y)F9J*{K`YK30ZUIkDqU50U3 zjCzktP!n{Z&c>^#E$Kl*nqeG@G3GPW+1SWM-QR|quM@NM{&&$(iQmQ|Jb}vedsODr zsKYdeN-UQR*LWWKa3$(`1FpbMT!kZ8i_^FoS5lTLd>J)<7Mt|`r+A~kU|X=B{-@Z8 zshmvxg0-Sbdu)*A>*qEo0uKeFQ& zP!nH4Js+B+p*^2Pt<_iz`ggnLjc>qP~A7x`^5=TMcnhVl3_ zCgQKCt-6gayn}K0H|jNxCCeo3T{#UMjvCaSG@wrV_I#%x`D^F4KPMbWSsU*haxV|x za1X{fE&ip>cWGXy*punp^%sXXc+ST<%YzSv8!{rXPHMsV@WSlQSXX8H^UWR3AAWCm zJm>NFq&023UfSB$ym^OH5y*6Q`hw0+?lEV_;t3~|=K6ok@Lj(zE?gM=Hcqkr0a)G; Ar2qf` diff --git a/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.po b/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.po index 4532b82131..56b61b1c23 100644 --- a/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.po +++ b/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.po @@ -273,6 +273,46 @@ msgstr "アクション" msgid "Secret URL" msgstr "シークレットURL" +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html +msgid "Link Name" +msgstr "リンク名" + +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html +msgid "URL Expiry Date" +msgstr "URL有効期限" + +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html +msgid "Download Limit" +msgstr "ダウンロード回数" + +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html +msgid "Max Expiry Date" +msgstr "有効期限上限" + +#: weko_admin/templates/weko_admin/admin/restricted_access_settings.html:45 +msgid "Download Limit Initial Value" +msgstr "ダウンロード回数初期値" + +#: weko_admin/templates/weko_admin/admin/restricted_access_settings.html:46 +msgid "Expiration Date Initial Value" +msgstr "有効期限日数初期値" + +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html +msgid "Max Download Count" +msgstr "ダウンロード回数上限" + +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html +msgid "Create Secret URL" +msgstr "シークレットURL作成" + +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html +msgid "Send Email" +msgstr "メール通知" + +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html +msgid "Item has not been filled in." +msgstr "項目が未入力です" + #: weko_records_ui/templates/weko_records_ui/box/stats.html:29 #: weko_records_ui/templates/weko_records_ui/file_details_contents.html:208 msgid "See details" diff --git a/modules/weko-records-ui/weko_records_ui/views.py b/modules/weko-records-ui/weko_records_ui/views.py index 1280ff9562..c601ad3ea3 100644 --- a/modules/weko-records-ui/weko_records_ui/views.py +++ b/modules/weko-records-ui/weko_records_ui/views.py @@ -727,6 +727,39 @@ def _get_rights_title(result, rights_key_str, rights_values, current_lang, meta_ **kwargs ) +@blueprint.route('/get-secret-settings', methods=['GET']) +def get_secret_setting(): + """ + Get secret URL settings. + + :return: JSON result containing secret download settings. + """ + try: + # 初期化 + result = {} + + # AdminSettingsから設定を取得 + admin_settings = AdminSettings.query.filter_by(name='restricted_access').first() + if admin_settings: + settings = admin_settings.settings + # 値を取得し、結果に追加 + result['secret_expiration_date'] = settings.get('secret_URL_file_download', {}).get('secret_expiration_date',30) + result['secret_download_limit'] = settings.get('secret_URL_file_download', {}).get('secret_download_limit',10) + result['max_secret_expiration_date'] = settings.get('secret_URL_file_download', {}).get('max_secret_expiration_date',30) + result['max_secret_download_limit'] = settings.get('secret_URL_file_download', {}).get('max_secret_download_limit',10) + print(f'secret_expiration_dateです', result['secret_expiration_date']) + else: + # デフォルト値を設定 + result['secret_expiration_date'] = 30 + result['secret_download_limit'] = 10 + result['max_secret_expiration_date'] = 30 + result['max_secret_download_limit'] = 10 + + # JSON形式で返す + return jsonify(result) + except Exception as e: + print(f"Error: {e}") + return jsonify({"error": str(e)}), 500 def create_secret_url_and_send_mail(pid:PersistentIdentifier, record:WekoRecord, filename:str, **kwargs) -> str: """on click button 'Secret URL' From b28016240c5a3841e1434b920485c71e35f59139 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Wed, 15 Jan 2025 16:16:54 +0900 Subject: [PATCH 23/61] Fix token validation error --- modules/weko-records-ui/tests/test_utils.py | 61 ++++++++++--------- .../weko-records-ui/weko_records_ui/models.py | 2 +- .../weko-records-ui/weko_records_ui/utils.py | 12 ++-- .../weko-records-ui/weko_records_ui/views.py | 2 +- 4 files changed, 43 insertions(+), 34 deletions(-) diff --git a/modules/weko-records-ui/tests/test_utils.py b/modules/weko-records-ui/tests/test_utils.py index eb409d5e8d..321356aabf 100644 --- a/modules/weko-records-ui/tests/test_utils.py +++ b/modules/weko-records-ui/tests/test_utils.py @@ -875,6 +875,17 @@ def test_validate_secret_url_generation_request(app): 'send_email' : None, 'timezone_offset_minutes': '0'}, False), + # When each keys do not exist + ({key: value for key, value in base_case.items() + if key != 'link_name'}, False), + ({key: value for key, value in base_case.items() + if key != 'expiration_date'}, False), + ({key: value for key, value in base_case.items() + if key != 'download_limit'}, False), + ({key: value for key, value in base_case.items() + if key != 'send_email'}, False), + ({key: value for key, value in base_case.items() + if key != 'timezone_offset_minutes'}, False), # For link_name ({**base_case, 'link_name': '123' }, True), ({**base_case, 'link_name': 123 }, False), @@ -884,6 +895,7 @@ def test_validate_secret_url_generation_request(app): ({**base_case, 'expiration_date': tomorrow }, True), ({**base_case, 'expiration_date': yesterday}, False), ({**base_case, 'expiration_date': 'abc' }, False), + ({**base_case, 'expiration_date': 20250101 }, False), # For download_limit ({**base_case, 'download_limit': 1 }, True), ({**base_case, 'download_limit': 0 }, False), @@ -1161,19 +1173,27 @@ def test_validate_token(app, users): match = re.search(r'[?&]token=([^&]+)', url) onetime_token = match.group(1) assert validate_token(onetime_token, is_secret_url=False) is True - wrong_token = b'\xb2q\xff\x19\xaf\xfc\xc6T\x8bt\xd6\xf6\xc6 \x08D\xe7\xf3G;cN\x1bn|\xa2\x88\x01v\xed\x1cA_1' + invalid_bytes = b'\xb2q\xff\x19\xaf\xfc\xc6T\x8bt\xd6\xf6\xc6 \ + \x08D\xe7\xf3G;cN\x1bn|\xa2\x88\x01v\xed\x1cA_1' with app.test_request_context(): secret_token = base64.urlsafe_b64decode(secret_token.encode()) - assert secret_token.split(b'_')[1] == wrong_token.split(b'_')[1] - assert validate_token(wrong_token, is_secret_url=True) is False + assert secret_token.split(b'_')[-1] == invalid_bytes.split(b'_')[-1] + invalid_token = base64.urlsafe_b64encode(invalid_bytes).decode() + assert validate_token(invalid_token, is_secret_url=True) is False with app.test_request_context(): onetime_token = base64.urlsafe_b64decode(onetime_token.encode()) - assert onetime_token.split(b'_')[1] == wrong_token.split(b'_')[1] - assert validate_token(wrong_token, is_secret_url=False) is False + assert onetime_token.split(b'_')[-1] == invalid_bytes.split(b'_')[-1] + invalid_token = base64.urlsafe_b64encode(invalid_bytes).decode() + assert validate_token(invalid_token, is_secret_url=False) is False + with app.test_request_context(): + assert validate_token('', is_secret_url=True) is False + assert validate_token(123, is_secret_url=True) is False # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_convert_token_into_obj -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings -def test_convert_token_into_obj(app, users): +@patch('weko_records_ui.utils.validate_token') +def test_convert_token_into_obj(vldt_token, app, users): + vldt_token.return_value = True created_at = dt.now(timezone.utc) with app.test_request_context(): secret_obj = FileSecretDownload.create( @@ -1197,6 +1217,8 @@ def test_convert_token_into_obj(app, users): expected_date = (created_at + timedelta(days=30)).replace(tzinfo=None) assert secret_obj.expiration_date == expected_date assert secret_obj.download_limit == 10 + vldt_token.assert_called_once_with(secret_token, True) + vldt_token.reset_mock() with app.test_request_context(): onetime_obj = FileOnetimeDownload.create( approver_id=1, @@ -1222,7 +1244,11 @@ def test_convert_token_into_obj(app, users): assert onetime_obj.download_limit == 10 assert onetime_obj.is_guest == False assert onetime_obj.extra_info == {'activity_id': 1} - + vldt_token.assert_called_once_with(onetime_token, False) + vldt_token.return_value = False + with app.test_request_context(): + assert convert_token_into_obj(secret_token, True) is None + assert convert_token_into_obj(onetime_token, False) is None # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_validate_url_download -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -p no:warnings @patch('weko_records_ui.utils.validate_token') @@ -1337,27 +1363,6 @@ def test_is_onetime_file(): assert is_onetime_file(mock_record, "file3.txt") is False -# def update_secret_download(**kwargs) -> Optional[List[FileSecretDownload]]: -# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_get_data_usage_application_data -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -def test_get_data_usage_application_data(app ,db): - with app.test_request_context(): - with db.session.begin_nested(): - secret_download=FileSecretDownload( - file_name= "eee.txt", record_id= '1',user_mail="repoadmin@example.org",expiration_date=999999,download_count=10 - ) - db.session.add(secret_download) - update_data = dict( - file_name = secret_download.file_name - , record_id = secret_download.record_id - , download_count = 100 - , created = secret_download.created - , id = secret_download.id - ) - res = update_secret_download(**update_data) - assert len(res) == 1 - assert res[0].download_count == 100 - - # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_RoCrateConverter_convert -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp def test_RoCrateConverter_convert(app, db): with open('tests/data/rocrate/rocrate_mapping.json', 'r') as f: diff --git a/modules/weko-records-ui/weko_records_ui/models.py b/modules/weko-records-ui/weko_records_ui/models.py index 6334bb467e..ebf55ba82b 100644 --- a/modules/weko-records-ui/weko_records_ui/models.py +++ b/modules/weko-records-ui/weko_records_ui/models.py @@ -485,7 +485,7 @@ def find_downloadable_only(cls, **obj) -> list: cls.record_id == obj.get("record_id"), cls.user_mail == obj.get("user_mail"), cls.download_count < cls.download_limit, - cls.expiration_date > now(), + cls.expiration_date > now(timezone.utc), cls.is_deleted == False ) return query.order_by(desc(cls.id)).all() diff --git a/modules/weko-records-ui/weko_records_ui/utils.py b/modules/weko-records-ui/weko_records_ui/utils.py index 4dbb70d339..22b98fb1cc 100644 --- a/modules/weko-records-ui/weko_records_ui/utils.py +++ b/modules/weko-records-ui/weko_records_ui/utils.py @@ -1958,11 +1958,15 @@ def validate_token(token, is_secret_url): """ try: bytes = base64.urlsafe_b64decode(token.encode()) - token_hash, token_id = bytes.split(b'_') + parts = bytes.split(b'_') + if len(parts) < 2: # Generated hash may contain additional '_' + return False + token_id = parts[-1].decode() # The last part is the URL object ID + token_hash = b'_'.join(parts[:-1]) # The rest is hash value if is_secret_url: - url_obj = FileSecretDownload.get_by_id(token_id.decode()) + url_obj = FileSecretDownload.get_by_id(token_id) else: - url_obj = FileOnetimeDownload.get_by_id(token_id.decode()) + url_obj = FileOnetimeDownload.get_by_id(token_id) if url_obj and (token_hash == generate_sha256_hash(url_obj)): return True else: @@ -1987,7 +1991,7 @@ def convert_token_into_obj(token, is_secret_url): if not validate_token(token, is_secret_url): return None bytes = base64.urlsafe_b64decode(token.encode()) - url_obj_id = bytes.split(b'_')[1].decode() + url_obj_id = bytes.split(b'_')[-1].decode() if is_secret_url: url_obj = FileSecretDownload.get_by_id(url_obj_id) else: diff --git a/modules/weko-records-ui/weko_records_ui/views.py b/modules/weko-records-ui/weko_records_ui/views.py index e77e84694b..cbdf3c8e38 100644 --- a/modules/weko-records-ui/weko_records_ui/views.py +++ b/modules/weko-records-ui/weko_records_ui/views.py @@ -761,7 +761,7 @@ def create_secret_url_and_send_mail(pid, record, filename, **kwargs): url = create_download_url(url_obj) - message = f'Secret URL generated successfully: "{url}"' + message = 'Secret URL generated successfully' if request.json['send_email'] is True: sending_result = send_secret_url_mail( pid.object_uuid, url_obj, record.get('item_title', '')) From 68240205545dae3431caba0d82eac97a5b366ef7 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Wed, 15 Jan 2025 17:40:32 +0900 Subject: [PATCH 24/61] Add a saving feature of download log --- modules/weko-records-ui/weko_records_ui/fd.py | 36 ++++-- .../weko-records-ui/weko_records_ui/models.py | 110 +++++++++++++++++- .../weko-records-ui/weko_records_ui/utils.py | 47 +++++++- 3 files changed, 183 insertions(+), 10 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/fd.py b/modules/weko-records-ui/weko_records_ui/fd.py index 5030c7263f..7bb000361a 100644 --- a/modules/weko-records-ui/weko_records_ui/fd.py +++ b/modules/weko-records-ui/weko_records_ui/fd.py @@ -46,14 +46,16 @@ from werkzeug.datastructures import Headers from werkzeug.urls import url_quote -from .models import FileOnetimeDownload, PDFCoverPageSettings +from .models import FileOnetimeDownload, FileSecretDownload, \ + PDFCoverPageSettings from .pdf import make_combined_pdf from .permissions import check_original_pdf_download_permission, \ file_permission_factory, is_owners_or_superusers from .utils import check_and_send_usage_report, convert_token_into_obj, \ create_download_url, get_billing_file_download_permission, \ get_groups_price, get_min_price_billing_file_download, \ - get_onetime_download, is_billing_item, validate_url_download + get_onetime_download, is_billing_item, save_download_log, \ + validate_url_download def weko_view_method(pid, record, template=None, **kwargs): @@ -423,18 +425,21 @@ def error_response(error_message, status_code=400): error_template = "weko_theme/error.html" return render_template(error_template, error_message), status_code + # Validate the one-time download URL token = request.args.get('token', type=str) is_validated, error_msg = validate_url_download( record, filename, token, is_secret_url=False) if not is_validated: return error_response(error_msg, 403) + # Locate the file object _record_file_factory = _record_file_factory or record_file_factory file_object = _record_file_factory(pid, record, filename) if not file_object or not file_object.obj: return error_response(f'The file "{filename}" does not exist.', 404) - url_obj = convert_token_into_obj(token) + # Update extra_info of the one-time URL object + url_obj:FileOnetimeDownload = convert_token_into_obj(token) if (url_obj.extra_info and (file_object.get('accessrole') == 'open_restricted')): extra_info = url_obj.extra_info @@ -455,10 +460,17 @@ def error_response(error_message, status_code=400): db.session.rollback() return error_response('Unexpected error occurred.', 500) - # Update download count + # Increase the download count and save the download log + target_data = {} + for file_data in record.get_file_data(): + if file_data.get('filename') == filename: + target_data = file_data + break try: url_obj.increment_download_count() - except: + save_download_log(token, target_data, is_secret_url=False) + except Exception as e: + current_app.logger.error(e) return error_response('Unexpected error occurred.', 500) return _download_file( @@ -515,12 +527,14 @@ def error_response(error_message, status_code=400): error_template = "weko_theme/error.html" return render_template(error_template, error=error_message), status_code + # Validate the secret URL token = request.args.get('token', type=str) is_validated, error_msg = ( validate_url_download(record, filename, token, is_secret_url=True)) if not is_validated: return error_response(error_msg, 403) + # Locate the file object _record_file_factory = _record_file_factory or record_file_factory file_object = _record_file_factory(pid, record, filename) if not file_object or not file_object.obj: @@ -532,10 +546,18 @@ def error_response(error_message, status_code=400): user_profile = UserProfile.get_by_userid(current_user.get_id()) lang = user_profile.language if user_profile else 'en' - url_obj = convert_token_into_obj(token, is_secret_url=True) + # Increase the download count and save the download log + url_obj:FileSecretDownload = convert_token_into_obj(token, is_secret_url=True) + target_data = {} + for file_data in record.get_file_data(): + if file_data.get('filename') == filename: + target_data = file_data + break try: url_obj.increment_download_count() - except: + save_download_log(token, target_data, is_secret_url=True) + except Exception as e: + current_app.logger.error(e) return error_response('Unexpected error occurred.', 500) return _download_file( diff --git a/modules/weko-records-ui/weko_records_ui/models.py b/modules/weko-records-ui/weko_records_ui/models.py index ebf55ba82b..1fd5e983c5 100644 --- a/modules/weko-records-ui/weko_records_ui/models.py +++ b/modules/weko-records-ui/weko_records_ui/models.py @@ -22,13 +22,14 @@ """Database models for weko-admin.""" from datetime import datetime, timezone +import enum from typing import List from flask import current_app from invenio_db import db from sqlalchemy import CheckConstraint, desc, func from sqlalchemy.dialects import postgresql -from sqlalchemy.dialects.postgresql import INTERVAL +from sqlalchemy.dialects.postgresql import INET, INTERVAL from sqlalchemy.sql.functions import concat ,now from sqlalchemy_utils.models import Timestamp from sqlalchemy_utils.types import JSONType @@ -431,6 +432,7 @@ def create(cls, **data): FileOnetimeDownload: The created instance. Raises: + ValueError: If the arguments are invalid. Exception: If an unexpected error occurs during the creation. """ if data['expiration_date'] < datetime.now(tz=timezone.utc): @@ -630,4 +632,108 @@ def find(cls, **obj) -> list: return query.order_by(desc(cls.id)).all() -__all__ = ('PDFCoverPageSettings', 'FilePermission', 'FileOnetimeDownload' ,'FileSecretDownload') +class UrlType(enum.Enum): + """An ENUM data type for the used URL.""" + SECRET = 'SECRET' + ONETIME = 'ONETIME' + + +class AccessStatus(enum.Enum): + """An ENUM data type for the access status of the downloaded file.""" + OPEN_NO = 'OPEN_NO' + OPEN_DATE = 'OPEN_DATE' + OPEN_RESTRICTED = 'OPEN_RESTRICTED' + + +class FileUrlDownloadLog(db.Model, Timestamp): + """Stores information of the executed download by download-URLs. + + This class(table) is used to store information of the executed download of + a file using either secret URL or onetime URL. + + Attributes: + id (int): The identifier of each download information. + url_type (UrlType): The used URL type('SECRET' or 'ONETIME'). + secret_url_id (int): The secret URL record ID. + onetime_url_id (int): The onetime URL record ID. + ip_address (str): The IP address of the downloader. + access_status (AccessStatus): The access status of the downloaded file. + used_token (str): The URL token used to access the file. + """ + __tablename__ = 'file_url_download_log' + id = db.Column(db.Integer(), + primary_key=True, + autoincrement=True) + url_type = db.Column(db.Enum(UrlType), nullable=False) + secret_url_id = db.Column(db.Integer(), + db.ForeignKey(FileSecretDownload.id)) + onetime_url_id = db.Column(db.Integer(), + db.ForeignKey(FileOnetimeDownload.id)) + ip_address = db.Column(INET() + .with_variant(db.String(255), 'sqlite') + .with_variant(db.String(255), 'mysql')) + access_status = db.Column(db.Enum(AccessStatus), nullable=False) + used_token = db.Column(db.String(255), nullable=False) + __table_args__ = ( + CheckConstraint( + "((url_type = 'SECRET' AND secret_url_id IS NOT NULL AND " + "onetime_url_id IS NULL)" + "OR" + "(url_type = 'ONETIME' AND onetime_url_id IS NOT NULL AND " + "secret_url_id IS NULL))", + name="chk_url_type",), + CheckConstraint( + "(url_type = 'SECRET' AND ip_address IS NOT NULL)" + "OR" + "(url_type = 'ONETIME' AND ip_address IS NULL)", + name="chk_ip_address",), + ) + + def __init__(self, url_type, secret_url_id, onetime_url_id, ip_address, + access_status, used_token): + """Initializes the FileUrlDownloadLog instance. + + Args: + url_type (UrlType): The used URL type. + secret_url_id (int): The secret URL record ID. + onetime_url_id (int): The onetime URL record ID. + ip_address (str): The IP address of the downloader. + access_status (AccessStatus): The status of the downloaded file. + used_token (str): The URL token used to access the file. + """ + self.url_type = url_type + self.secret_url_id = secret_url_id + self.onetime_url_id = onetime_url_id + self.ip_address = ip_address + self.access_status = access_status + self.used_token = used_token + + @classmethod + def create(cls, **data): + """Create a new instance and save it to the database. + + Args: + **data: The attributes for the new instance. + + Returns: + FileUrlDownloadLog: The created instance. + + Raises: + Exception: If an unexpected error occurs during the creation. + """ + try: + file_download = cls(**data) + db.session.add(file_download) + db.session.commit() + return file_download + except Exception as ex: + db.session.rollback() + current_app.logger.error(ex) + raise ex + + +__all__ = ('PDFCoverPageSettings', + 'FilePermission', + 'FileOnetimeDownload', + 'FileSecretDownload', + 'FileUrlDownloadLog',) diff --git a/modules/weko-records-ui/weko_records_ui/utils.py b/modules/weko-records-ui/weko_records_ui/utils.py index 22b98fb1cc..dba57f867c 100644 --- a/modules/weko-records-ui/weko_records_ui/utils.py +++ b/modules/weko-records-ui/weko_records_ui/utils.py @@ -61,7 +61,7 @@ from weko_workflow.models import Activity from weko_workflow.utils import get_item_info, process_send_mail, set_mail_info -from .models import FileOnetimeDownload, FilePermission, FileSecretDownload +from .models import AccessStatus, FileOnetimeDownload, FilePermission, FileSecretDownload, FileUrlDownloadLog, UrlType from .permissions import check_create_usage_report, \ check_file_download_permission, check_user_group_permission, \ is_open_restricted @@ -2050,3 +2050,48 @@ def is_onetime_file(record, file_name): if file_data.get('filename') == file_name: return file_data.get('accessrole') == 'open_restricted' return False + + +def save_download_log(token, file_data, is_secret_url): + """Save the download log for the given token. + + Befor calling this function, the token must be validated by the function + 'validate_url_download()' to ensure that the token is valid. Especially, + the 'accessrole' value in the 'file_data' must be already checked. + + Args: + token (str): The token used for the download. + file_data (dict): The downloaded file data. + is_secret_url (bool): True if for secret URL, False if for onetime URL. + + Raises: + Exception: If an unexpected error occurs during the log creation. + + Returns: + FileUrlDownloadLog: The created download log object. + """ + url_obj = convert_token_into_obj(token, is_secret_url) + if is_secret_url: + file_access_role = file_data.get('accessrole') + access_status = ( + AccessStatus.OPEN_NO + if file_access_role == 'open_no' + else AccessStatus.OPEN_DATE # Assume 'open_date' + ) + url_type = UrlType.SECRET + secret_url_id = url_obj.id + onetime_url_id = None + else: + access_status = AccessStatus.OPEN_RESTRICTED + url_type = UrlType.ONETIME + secret_url_id = None + onetime_url_id = url_obj.id + + return FileUrlDownloadLog.create( + url_type = url_type, + secret_url_id = secret_url_id, + onetime_url_id = onetime_url_id, + ip_address = request.remote_addr, + access_status = access_status, + used_token = token, + ) From 326309a596d46ab15257dfb5f6486c22953f655e Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Thu, 16 Jan 2025 12:01:18 +0900 Subject: [PATCH 25/61] Refactored to share some duplicated logic --- modules/weko-records-ui/weko_records_ui/fd.py | 26 +++++-------------- .../weko-records-ui/weko_records_ui/utils.py | 12 ++++++--- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/fd.py b/modules/weko-records-ui/weko_records_ui/fd.py index 7bb000361a..e1104d91b3 100644 --- a/modules/weko-records-ui/weko_records_ui/fd.py +++ b/modules/weko-records-ui/weko_records_ui/fd.py @@ -408,6 +408,10 @@ def add_signals_info(record, obj): obj.item_id = record['_deposit']['id'] +def error_response(error_message, status_code=400): + error_template = "weko_theme/error.html" + return render_template(error_template, error=error_message), status_code + def file_download_onetime(pid, record, filename, _record_file_factory=None, **kwargs): @@ -421,10 +425,6 @@ def file_download_onetime(pid, record, filename, _record_file_factory=None, Returns: Response: The Flask wrapper object for the file download """ - def error_response(error_message, status_code=400): - error_template = "weko_theme/error.html" - return render_template(error_template, error_message), status_code - # Validate the one-time download URL token = request.args.get('token', type=str) is_validated, error_msg = validate_url_download( @@ -461,14 +461,9 @@ def error_response(error_message, status_code=400): return error_response('Unexpected error occurred.', 500) # Increase the download count and save the download log - target_data = {} - for file_data in record.get_file_data(): - if file_data.get('filename') == filename: - target_data = file_data - break try: url_obj.increment_download_count() - save_download_log(token, target_data, is_secret_url=False) + save_download_log(record, filename, token, is_secret_url=False) except Exception as e: current_app.logger.error(e) return error_response('Unexpected error occurred.', 500) @@ -523,10 +518,6 @@ def file_download_secret(pid, record, filename, _record_file_factory=None, Returns: Response: The Flask wrapper object for the file download. """ - def error_response(error_message, status_code=400): - error_template = "weko_theme/error.html" - return render_template(error_template, error=error_message), status_code - # Validate the secret URL token = request.args.get('token', type=str) is_validated, error_msg = ( @@ -548,14 +539,9 @@ def error_response(error_message, status_code=400): # Increase the download count and save the download log url_obj:FileSecretDownload = convert_token_into_obj(token, is_secret_url=True) - target_data = {} - for file_data in record.get_file_data(): - if file_data.get('filename') == filename: - target_data = file_data - break try: url_obj.increment_download_count() - save_download_log(token, target_data, is_secret_url=True) + save_download_log(record, filename, token, is_secret_url=True) except Exception as e: current_app.logger.error(e) return error_response('Unexpected error occurred.', 500) diff --git a/modules/weko-records-ui/weko_records_ui/utils.py b/modules/weko-records-ui/weko_records_ui/utils.py index dba57f867c..65b8d8a427 100644 --- a/modules/weko-records-ui/weko_records_ui/utils.py +++ b/modules/weko-records-ui/weko_records_ui/utils.py @@ -2052,7 +2052,7 @@ def is_onetime_file(record, file_name): return False -def save_download_log(token, file_data, is_secret_url): +def save_download_log(record, file_name, token, is_secret_url): """Save the download log for the given token. Befor calling this function, the token must be validated by the function @@ -2060,8 +2060,9 @@ def save_download_log(token, file_data, is_secret_url): the 'accessrole' value in the 'file_data' must be already checked. Args: + record (WekoRecord): The record metadata of the item. + file_name (str): The name of the downloaded file. token (str): The token used for the download. - file_data (dict): The downloaded file data. is_secret_url (bool): True if for secret URL, False if for onetime URL. Raises: @@ -2070,9 +2071,14 @@ def save_download_log(token, file_data, is_secret_url): Returns: FileUrlDownloadLog: The created download log object. """ + target_data = {} + for file_data in record.get_file_data(): + if file_data.get('filename') == file_name: + target_data = file_data + break url_obj = convert_token_into_obj(token, is_secret_url) if is_secret_url: - file_access_role = file_data.get('accessrole') + file_access_role = target_data.get('accessrole') access_status = ( AccessStatus.OPEN_NO if file_access_role == 'open_no' From 50b9164621d85b57f31d4681de748e9a03d16fa1 Mon Sep 17 00:00:00 2001 From: "sei.nakamura" Date: Thu, 16 Jan 2025 17:22:40 +0900 Subject: [PATCH 26/61] =?UTF-8?q?=E3=82=A2=E3=82=AF=E3=82=BB=E3=82=B9?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E6=99=82=E3=82=B7=E3=83=BC=E3=82=AF=E3=83=AC?= =?UTF-8?q?=E3=83=83=E3=83=88URL=E3=81=AE=E8=AB=96=E7=90=86=E5=89=8A?= =?UTF-8?q?=E9=99=A4=E6=A9=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/weko-workflow/weko_workflow/api.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/weko-workflow/weko_workflow/api.py b/modules/weko-workflow/weko_workflow/api.py index 7ef1f2c466..5396bfc8e6 100644 --- a/modules/weko-workflow/weko_workflow/api.py +++ b/modules/weko-workflow/weko_workflow/api.py @@ -2718,6 +2718,8 @@ def publish(self, record): :return: The rendered template. """ from weko_deposit.api import WekoIndexer + from weko_records_ui.models import FileOnetimeDownload + publish_status = record.get('publish_status') if not publish_status: record.update({'publish_status': PublishStatus.PUBLIC.value}) @@ -2727,6 +2729,14 @@ def publish(self, record): record.commit() db.session.commit() + # シークレットURLの論理削除を実行 + control_number = record.get('control_number')#control_nuibmer=record_id + secret_urls = FileOnetimeDownload.query.filter_by(record_id=control_number, is_deleted=False).all() + for urls in secret_urls: + #論理削除メソッドを使用 + urls.delete_logically() + db.session.commit() + indexer = WekoIndexer() indexer.update_es_data(record, update_revision=False, field='publish_status') From bd3b741baa9d3157353eb9711afa2ae23233b578 Mon Sep 17 00:00:00 2001 From: "sei.nakamura" Date: Fri, 17 Jan 2025 16:38:18 +0900 Subject: [PATCH 27/61] =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E8=A9=B3=E7=B4=B0=E7=94=BB=E9=9D=A2=E3=83=AC=E3=83=93=E3=83=A5?= =?UTF-8?q?=E5=BE=8C=E4=BF=AE=E6=AD=A3=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/weko-admin/weko_admin/config.py | 2 +- .../static/js/weko_records_ui/detail.js | 28 +++---------------- .../file_details_contents.html | 2 ++ .../weko-records-ui/weko_records_ui/views.py | 1 + 4 files changed, 8 insertions(+), 25 deletions(-) diff --git a/modules/weko-admin/weko_admin/config.py b/modules/weko-admin/weko_admin/config.py index c92f38a013..307ab0ecd3 100644 --- a/modules/weko-admin/weko_admin/config.py +++ b/modules/weko-admin/weko_admin/config.py @@ -1230,7 +1230,7 @@ 'widths': ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'] } -WEKO_ADMIN_RESTRICTED_ACCESS_DISPLAY_FLAG = True +WEKO_ADMIN_RESTRICTED_ACCESS_DISPLAY_FLAG = False """ Restricted access feature display flag. True: display all feature diff --git a/modules/weko-records-ui/weko_records_ui/static/js/weko_records_ui/detail.js b/modules/weko-records-ui/weko_records_ui/static/js/weko_records_ui/detail.js index d0cd79aa18..0daa2520e2 100644 --- a/modules/weko-records-ui/weko_records_ui/static/js/weko_records_ui/detail.js +++ b/modules/weko-records-ui/weko_records_ui/static/js/weko_records_ui/detail.js @@ -272,11 +272,13 @@ require([ } }); - + // シークレットURL作成フォーム // link_name フィールドの入力を検証 const MAX_LENGTH=50; const linkNameInput = document.querySelector('#link_name'); - linkNameInput.setAttribute('maxlength', MAX_LENGTH.toString()); + if (linkNameInput) { + linkNameInput.maxLength = MAX_LENGTH; + } // APIエンドポイントから設定を取得 fetch('/get-secret-settings') @@ -314,10 +316,7 @@ require([ expirationDateInput.max = maxFormattedDate; expirationDateInput.min = todayFormatted; console.log(`Expiration date set to: ${formattedDate}`); - } else { - console.error('Error: #expiration_date element not found.'); } - // フォームのフィールドに値を設定 const downloadLimitInput = document.querySelector('#download_limit'); if (downloadLimitInput) { @@ -329,28 +328,20 @@ require([ downloadLimitInput.value = maxSecretDownloadLimit; } }); - } else { - console.error('Error: #download_limit element not found.'); } - // 有効期限の下に表示するメッセージを設定 const maxExpirationData = document.querySelector('#max_date_display'); if (maxExpirationData) { const translationDate = maxExpirationData.dataset.expirationDate; maxExpirationData.textContent = ` ※${translationDate} ${maxFormattedDate}`; console.log(`Max Expiration Data displayed as: ${translationDate}`); - } else { - console.error('Error: #max_download_display element not found.'); } - // ダウンロード制限の下に表示するメッセージを設定 const maxDownloadDisplay = document.querySelector('#max_download_display'); if (maxDownloadDisplay) { const downloadcount = maxDownloadDisplay.dataset.downloadCount; maxDownloadDisplay.textContent = ` ※${downloadcount} ${maxSecretDownloadLimit}`; console.log(`Max download limit displayed as: ${downloadcount}`); - } else { - console.error('Error: #max_download_display element not found.'); } }) .catch(error => { @@ -398,16 +389,6 @@ require([ return; } - - // AJAXリクエストのデータをコンソールに出力 - const requestData = { - link_name: linkName, - expiration_date: expirationDate, - download_limit: downloadLimit, - send_email: sendEmail - }; - console.log('AJAX request data:', requestData); - $.ajax({ url: url, method: 'POST', @@ -425,7 +406,6 @@ require([ }, error: function(jqXHR, status, msg) { webelement.prop('disabled', false); - console.error('Errorです:', jqXHR.responseJSON || msg); alert("Error: " + (jqXHR.responseJSON?.message || msg)); } }); diff --git a/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details_contents.html b/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details_contents.html index a19f573df9..a73d343bc8 100644 --- a/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details_contents.html +++ b/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details_contents.html @@ -167,6 +167,7 @@

{{ filename.

+ {%- if secret_url_section -%} + {%- endif -%} {% from "weko_records_ui/output_detail_data.html" import output_attribute_value_mlt %} {%- set display_file_info = record.display_file_info -%} diff --git a/modules/weko-records-ui/weko_records_ui/views.py b/modules/weko-records-ui/weko_records_ui/views.py index c601ad3ea3..2c6bb8d838 100644 --- a/modules/weko-records-ui/weko_records_ui/views.py +++ b/modules/weko-records-ui/weko_records_ui/views.py @@ -723,6 +723,7 @@ def _get_rights_title(result, rights_key_str, rights_values, current_lang, meta_ flg_display_resourcetype = current_app.config.get('WEKO_RECORDS_UI_DISPLAY_RESOURCE_TYPE') , search_author_flg=search_author_flg, show_secret_URL=can_manage_secret_url(record,filename), + secret_url_section=can_manage_secret_url(record, filename), **ctx, **kwargs ) From dcb2a2cc9551ef23e4ef0c51330600e5c359cb18 Mon Sep 17 00:00:00 2001 From: "sei.nakamura" Date: Mon, 20 Jan 2025 17:13:53 +0900 Subject: [PATCH 28/61] =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E8=A9=B3=E7=B4=B0=E7=94=BB=E9=9D=A2=E3=83=AC=E3=83=93=E3=83=A5?= =?UTF-8?q?=E5=BE=8C=E4=BF=AE=E6=AD=A3=E6=9C=80=E6=96=B0=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/weko_records_ui/file_details_contents.html | 2 +- modules/weko-records-ui/weko_records_ui/views.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details_contents.html b/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details_contents.html index a73d343bc8..23e2c904d8 100644 --- a/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details_contents.html +++ b/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details_contents.html @@ -167,7 +167,7 @@

{{ filename.

- {%- if secret_url_section -%} + {%- if show_secret_URL -%} - + {%- if active_secret_URLs -%} +
+ + + + + + + + + + + + + + + {%- for url in active_secret_URLs -%} + + + + + + + + {%- endfor -%} + +
{{_('Secret URL')}}
{{_('label Name')}}{{_('Create Date')}}{{_('Expiration Date')}}{{_('Download Count')}}{{_('Action')}}
{{ url.label_name }}{{ url.created }}{{ url.expiration_date }}{{ url.download_count }}/{{url.download_limit}} + + +
+
+ {%- endif -%} + {%- if active_onetime_URLs -%} +
+ + + + + + + + + + + + + + + {%- for url in active_onetime_URLs -%} + + + + + + + {%- endfor -%} + +
ワンタイムURL
アクセスユーザー名作成日時DL期限DL回数Action
{{ url.file_name }}{{ url.created }}{{ url.expiration_date }}{{ url.download_count }}
+
+ {%- endif -%}
{%- endif -%} {%- if active_onetime_URLs -%} -
+
- - - - - - - - - - - - - - {%- for url in active_onetime_URLs -%} + + + + + + + + + + + + + + {%- for url in active_onetime_URLs -%} - - - - + + + + + - {%- endfor -%} - + {%- endfor -%} +
ワンタイムURL
アクセスユーザー名作成日時DL期限DL回数Action
{{_('Onetime URL')}}
{{_('User Name')}}{{_('Create Date')}}{{_('Expiration Date')}}{{_('Download Count')}}{{_('Action')}}
{{ url.file_name }}{{ url.created }}{{ url.expiration_date }}{{ url.download_count }}{{ url.user_name }}{{ url.created }}{{ url.expiration_date }}{{ url.download_count }}/{{url.download_limit}} +
+ + +
+
-
+
{%- endif -%}
{%- endif -%}
diff --git a/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.mo b/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.mo index 77e8ab730fda8c0eec2e4e4a9ea0228312561548..a88891e098cd756c5f002fa37cb13da6e8f1b4d8 100644 GIT binary patch literal 21723 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 delta 6110 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 diff --git a/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.po b/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.po index 67a184c5a5..966bce3c0d 100644 --- a/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.po +++ b/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.po @@ -677,4 +677,16 @@ msgid "Onetime URL" msgstr "ワンタイムURL" msgid "User Name" -msgstr "ユーザー名" \ No newline at end of file +msgstr "ユーザー名" + +msgid "Copy" +msgstr "コピー" + +msgid "massage_del_check" +msgstr "このURLを削除すると、利用できなくなります。本当に削除しますか?" + +msgid "massage_del_success" +msgstr "URLが削除されました" + +msgid "massage_copy_success" +msgstr "URLがクリップボードにコピーされました" \ No newline at end of file From b16a6aa0e8ec80db11ea0c0bdf899544e21c67d2 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Thu, 6 Feb 2025 16:39:16 +0900 Subject: [PATCH 48/61] Add tests for URL delete functions --- modules/weko-records-ui/tests/test_views.py | 90 +++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/modules/weko-records-ui/tests/test_views.py b/modules/weko-records-ui/tests/test_views.py index 17eb6b5edc..3d463e2ba3 100644 --- a/modules/weko-records-ui/tests/test_views.py +++ b/modules/weko-records-ui/tests/test_views.py @@ -699,6 +699,96 @@ def test_copy_onetime_url(client, records): assert res.json['url'] is None +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_views.py::test_delete_secret_url -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp +def test_delete_secret_url(client, records): + _, records = records + url = url_for('invenio_records_ui.recid_delete_secret_url', + pid_value=records[1]['recid'].pid_value, + filename=records[1]['filename'], + secret_url_id=1) + secret_obj = FileSecretDownload.create( + creator_id=1, + record_id=records[1]['recid'].pid_value, + file_name=records[1]['filename'], + label_name='test link', + expiration_date=datetime.now(timezone.utc) + timedelta(days=1), + download_limit=1, + ) + assert secret_obj.is_deleted == False + with patch('weko_records_ui.views.can_manage_secret_url', + return_value=True): + res = client.delete(url) + assert res.status_code == 200 + assert ('The secret URL has been successfully deleted.' + in res.get_data(as_text=True)) + assert secret_obj.is_deleted == True + with patch('weko_records_ui.views.can_manage_secret_url', + return_value=False): + with pytest.raises(Exception): + res = client.delete(url) + assert res.status_code == 403 + with patch('weko_records_ui.models.FileSecretDownload.delete_logically', + side_effect=Exception('Test Error')): + with pytest.raises(Exception): + res = client.delete(url) + assert res.status_code == 500 + with patch('weko_records_ui.views.can_manage_secret_url', + return_value=True): + url = url_for('invenio_records_ui.recid_delete_secret_url', + pid_value=records[1]['recid'].pid_value, + filename=records[1]['filename'], + secret_url_id=99) # invalid secret_url_id + with pytest.raises(Exception): + res = client.delete(url) + assert res.status_code == 404 + + +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_views.py::test_delete_onetime_url -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp +def test_delete_onetime_url(client, records): + _, records = records + url = url_for('invenio_records_ui.recid_delete_onetime_url', + pid_value=records[1]['recid'].pid_value, + filename=records[1]['filename'], + onetime_url_id=1) + onetime_obj = FileOnetimeDownload.create( + approver_id=1, + record_id=records[1]['recid'].pid_value, + file_name=records[1]['filename'], + expiration_date=datetime.now(timezone.utc) + timedelta(days=1), + download_limit=1, + user_mail='test@example.org', + is_guest=False, + extra_info={} + ) + assert onetime_obj.is_deleted == False + with patch('weko_records_ui.views.can_manage_onetime_url', + return_value=True): + res = client.delete(url) + assert res.status_code == 200 + assert ('The one-time URL has been successfully deleted.' + in res.get_data(as_text=True)) + assert onetime_obj.is_deleted == True + with patch('weko_records_ui.views.can_manage_onetime_url', + return_value=False): + with pytest.raises(Exception): + res = client.delete(url) + assert res.status_code == 403 + with patch('weko_records_ui.models.FileOnetimeDownload.delete_logically', + side_effect=Exception('Test Error')): + with pytest.raises(Exception): + res = client.delete(url) + assert res.status_code == 500 + with patch('weko_records_ui.views.can_manage_onetime_url', + return_value=True): + url = url_for('invenio_records_ui.recid_delete_onetime_url', + pid_value=records[1]['recid'].pid_value, + filename=records[1]['filename'], + onetime_url_id=99) + with pytest.raises(Exception): + res = client.delete(url) + assert res.status_code == 404 + + # def doi_ish_view_method(parent_pid_value=0, version=0): # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_views.py::test_doi_ish_view_method_acl_guest -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp def test_doi_ish_view_method_acl_guest(app,client,records): From 35255503ec7854845dd8a24487c221a1dd8c1d26 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Thu, 6 Feb 2025 17:44:54 +0900 Subject: [PATCH 49/61] Add Japanese localization --- modules/weko-records-ui/weko_records_ui/fd.py | 8 +- .../translations/ja/LC_MESSAGES/messages.mo | Bin 10376 -> 11136 bytes .../translations/ja/LC_MESSAGES/messages.po | 509 +++++--- .../weko_records_ui/translations/messages.pot | 1095 +++++------------ .../weko-records-ui/weko_records_ui/utils.py | 12 +- .../weko-records-ui/weko_records_ui/views.py | 16 +- 6 files changed, 609 insertions(+), 1031 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/fd.py b/modules/weko-records-ui/weko_records_ui/fd.py index d44c1535c6..4fc12ea107 100644 --- a/modules/weko-records-ui/weko_records_ui/fd.py +++ b/modules/weko-records-ui/weko_records_ui/fd.py @@ -436,7 +436,7 @@ def file_download_onetime(pid, record, filename, _record_file_factory=None, _record_file_factory = _record_file_factory or record_file_factory file_object = _record_file_factory(pid, record, filename) if not file_object or not file_object.obj: - return error_response(f'The file "{filename}" does not exist.', 404) + return error_response(_('The file "%s" does not exist.') % filename, 404) # Update 'extra_info' of the one-time URL object url_obj = convert_token_into_obj(token, is_secret_url=False) @@ -465,7 +465,7 @@ def file_download_onetime(pid, record, filename, _record_file_factory=None, save_download_log(record, filename, token, is_secret_url=False) except Exception as e: current_app.logger.error(e) - return error_response('Unexpected error occurred.', 500) + return error_response(_('Unexpected error occurred.'), 500) return _download_file( file_object, False, 'en', file_object.obj, pid, record) @@ -528,7 +528,7 @@ def file_download_secret(pid, record, filename, _record_file_factory=None, _record_file_factory = _record_file_factory or record_file_factory file_object = _record_file_factory(pid, record, filename) if not file_object or not file_object.obj: - return error_response(f'The file "{filename}" does not exist.', 404) + return error_response(_('The file "%s" does not exist.') % filename, 404) # Set language for PDF cover page lang = 'en' @@ -543,7 +543,7 @@ def file_download_secret(pid, record, filename, _record_file_factory=None, save_download_log(record, filename, token, is_secret_url=True) except Exception as e: current_app.logger.error(e) - return error_response('Unexpected error occurred.', 500) + return error_response(_('Unexpected error occurred.'), 500) return _download_file( file_object, False, lang, file_object.obj, pid, record) diff --git a/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.mo b/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.mo index e4d42d46aea5bb29874ceb5d0f7fb8b7f6a163a7..e710cb429d20d6eba45f78e6b0aadb087f22acad 100644 GIT binary patch literal 11136 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 WId)i1i)L*ZbD#3W{r};r=f43=2#?JG delta 3220 zcmZ|Pacq?J9mnzCDTM*DmX_K=TYM@<*J1!|1x7Ie$Gyh=C z^c$SrxhAA2WwCjS%oU@cae|T$k%k@nOEe83J^z~jbEU)B!f(8hHx-o%$umQ@dSgq zKZ1(47t8hjCuyj}hp`?{qB8vpD)TF-!*mmsSQQ(t{)K4aa@6=1T!wqG5r=U-UcrZP zIb~_WpQ8FN;5NPgGb{7IU^{U$*KgofEaGJ97i<@*#Ai_}zky2dLH37pXPQtI*osPU zhkqUMeHwM&epCgHBD-ZyVIoLljD||$A<3F6$j20LbU6klhzht8wMFYuCH+2X!XKfw zXqVsLiMlU|Iy>E{!~Gm;-T_p?WA{*h4ZKf>O8P0XICB*>!3-;(Kp|>RgSZk`p#pcI z0w+-SccUuMiyH4k^&decGKfn271WlET8aFrzC?#^4A84eOhx^I%|ex`2z3~1Pyv>r z5?bY*zihqTg{|IW~FD7U#qVXy!vx}$|T|+)*k}tidrM!Cz zybd*C3vwRK4^b71V+r=5?i)e{I*Us5HNXEIRNxO#&nGU@(4OBwt+aSSzSOm-JztIN zhG{_y_n<1(hYE1W_b6)Olc?v;q2_xBRhiFFTW}eb;3Trpgth_@NYN`$5C5#9SiVFyc7R}dX4WO%jqiZ zY8pBm>ri{L1$El@VD^tQmJ}@7-5Tn!THD*Bb}VMre*LO8YPUtg+e6Xat?^JKT<6ZO z{(-x{db#^y^-}kptx&kLD{k3e zpEjEaZV9-r%~|H&Rl2rfQ&(FXk*!U3ThxwQk8RvwJ!*&TXlvYd?b1E&KTB=*L~Ydl zdD&9eDVz75u5h~@-4ShV@8S+C7Vm1e!||9EiCXcU5duE7Gh($ztZ*c5MeQA-Slrf# z9g13=kyuFUh%Iq5buYP{Ht z+xA``?HNCQ{_>dvNx$cv>-WNRQ zkmn3}&a<9#%ySNU&NE&zl}!fw1MZB{M-yN4JeTVoXtcc4>&iAYtUS|cFWp1EnPj|w zfLk<4((#;>=bZ7J0na&m+n~9#gnM}+>3n`+@K)ThTi*jjG`XJBtd3#NIiVz+A!?Cy z&)4j6|6UqqBgfpzy5K{(-waKpPZ8B~c$n|u+?ykpo!31lGk*AuiOj27Ln?D?NlZdE md=Gdjhq`4mbp?U>r2f@PvU=*7!Cdc%=e*=Ohsopi8~I\n" "Language: ja\n" @@ -17,494 +17,607 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.8.0\n" +"Generated-By: Babel 2.5.1\n" -#: weko_records_ui/admin.py:87 +#: tests/test_utils.py:541 weko_records_ui/fd.py:468 weko_records_ui/fd.py:546 +#: weko_records_ui/utils.py:1109 +msgid "Unexpected error occurred." +msgstr "予期しないエラーが発生しました" + +#: tests/test_utils.py:545 weko_records_ui/utils.py:1111 +msgid "Failed to send mail." +msgstr "" + +#: weko_records_ui/admin.py:89 msgid "Author flag was updated." msgstr "" -#: weko_records_ui/admin.py:148 +#: weko_records_ui/admin.py:165 msgid "Institution Name was updated." msgstr "" -#: weko_records_ui/admin.py:212 weko_records_ui/admin.py:221 -#: weko_records_ui/admin.py:230 +#: weko_records_ui/admin.py:228 weko_records_ui/admin.py:237 +#: weko_records_ui/admin.py:246 msgid "Setting" msgstr "" -#: weko_records_ui/admin.py:213 +#: weko_records_ui/admin.py:229 msgid "Others" msgstr "" -#: weko_records_ui/admin.py:222 weko_records_ui/admin.py:239 +#: weko_records_ui/admin.py:238 weko_records_ui/admin.py:255 msgid "Items" msgstr "" -#: weko_records_ui/admin.py:231 -#: weko_records_ui/templates/weko_records_ui/admin/pdfcoverpage.html:91 +#: weko_records_ui/admin.py:247 +#: weko_records_ui/templates/weko_records_ui/admin/pdfcoverpage.html:98 msgid "PDF Cover Page" msgstr "" -#: weko_records_ui/admin.py:240 +#: weko_records_ui/admin.py:256 msgid "Bulk Update" msgstr "" -#: weko_records_ui/config.py:296 +#: weko_records_ui/config.py:394 msgid "write your own license" msgstr "" -#: weko_records_ui/config.py:301 +#: weko_records_ui/config.py:399 msgid "Creative Commons CC0 1.0 Universal Public Domain Designation" msgstr "" -#: weko_records_ui/config.py:316 +#: weko_records_ui/config.py:414 msgid "Creative Commons Attribution 3.0 Unported (CC BY 3.0)" msgstr "" -#: weko_records_ui/config.py:328 +#: weko_records_ui/config.py:426 msgid "Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)" msgstr "" -#: weko_records_ui/config.py:342 +#: weko_records_ui/config.py:440 msgid "Creative Commons Attribution-NoDerivs 3.0 Unported (CC BY-ND 3.0)" msgstr "" -#: weko_records_ui/config.py:356 +#: weko_records_ui/config.py:454 msgid "Creative Commons Attribution-NonCommercial 3.0 Unported (CC BY-NC 3.0)" msgstr "" -#: weko_records_ui/config.py:370 +#: weko_records_ui/config.py:468 msgid "" "Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC " "BY-NC-SA 3.0)" msgstr "" -#: weko_records_ui/config.py:384 +#: weko_records_ui/config.py:482 msgid "" "Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported (CC BY-" "NC-ND 3.0)" msgstr "" -#: weko_records_ui/config.py:399 +#: weko_records_ui/config.py:497 msgid "Creative Commons Attribution 4.0 International (CC BY 4.0)" msgstr "" -#: weko_records_ui/config.py:411 +#: weko_records_ui/config.py:509 msgid "Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)" msgstr "" -#: weko_records_ui/config.py:425 +#: weko_records_ui/config.py:523 msgid "" "Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND " "4.0)" msgstr "" -#: weko_records_ui/config.py:439 +#: weko_records_ui/config.py:537 msgid "" "Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC " "4.0)" msgstr "" -#: weko_records_ui/config.py:453 +#: weko_records_ui/config.py:551 msgid "" "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International " "(CC BY-NC-SA 4.0)" msgstr "" -#: weko_records_ui/config.py:467 +#: weko_records_ui/config.py:565 msgid "" "Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 " "International (CC BY-NC-ND 4.0)" msgstr "" -#: weko_records_ui/pdf.py:556 +#: weko_records_ui/fd.py:439 weko_records_ui/fd.py:531 +#, python-format +msgid "The file \"%s\" does not exist." +msgstr "指定されたファイル「\"%s\"」が存在しません" + +#: weko_records_ui/pdf.py:662 msgid "The storage path is incorrect." msgstr "" -#: weko_records_ui/pdf.py:558 weko_records_ui/pdf.py:571 -#: weko_records_ui/pdf.py:584 +#: weko_records_ui/pdf.py:664 weko_records_ui/pdf.py:677 +#: weko_records_ui/pdf.py:690 msgid "Please contact the administrator." msgstr "" -#: weko_records_ui/pdf.py:569 +#: weko_records_ui/pdf.py:675 msgid "The storage location cannot be accessed." msgstr "" -#: weko_records_ui/pdf.py:583 +#: weko_records_ui/pdf.py:689 msgid "There is not enough storage space." msgstr "" -#: weko_records_ui/utils.py:185 +#: weko_records_ui/utils.py:436 msgid "Item cannot be deleted because the import is in progress." msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:113 -#: weko_records_ui/utils.py:570 +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:147 +#: weko_records_ui/utils.py:889 msgid "Restricted Access" msgstr "" -#: weko_records_ui/views.py:681 +#: weko_records_ui/utils.py:1373 +msgid "Guest" +msgstr "" + +#: weko_records_ui/utils.py:1384 +msgid "Free Input" +msgstr "" + +#: weko_records_ui/utils.py:2080 +#, fuzzy +msgid "The provided token is invalid." +msgstr "トークンが無効です。" + +#: weko_records_ui/utils.py:2084 +msgid "This feature is currently disabled." +msgstr "この機能は現在ご使用頂けません。" + +#: weko_records_ui/utils.py:2089 +#, fuzzy +msgid "This file is currently not available for this feature." +msgstr "このファイルは現在ダウンロードできません。" + +#: weko_records_ui/utils.py:2094 +#, fuzzy +msgid "This URL has been deactivated." +msgstr "このURLは削除されました。" + +#: weko_records_ui/utils.py:2096 +msgid "The download limit has been exceeded." +msgstr "ダウンロード制限回数を超過しています。" + +#: weko_records_ui/utils.py:2099 +msgid "The expiration date for download has been exceeded." +msgstr "ダウンロード有効期限を超過しています。" + +#: weko_records_ui/views.py:769 +msgid "Secret URL generated successfully" +msgstr "シークレットURLの作成に成功しました" + +#: weko_records_ui/views.py:774 +msgid ", please check your email inbox" +msgstr "。メールをご確認ください" + +#: weko_records_ui/views.py:776 +msgid "" +", but there was an error while sending the email. To use the URL, please " +"refresh the page and copy it from the issued URL list" +msgstr "が、メール送信エラーが発生しました。ページを更新し、URL一覧表からご利用ください" + +#: weko_records_ui/views.py:779 +msgid "." +msgstr "。" + +#: weko_records_ui/views.py:812 +msgid "The secret URL copied to your clipboard." +msgstr "シークレットURLをコピーしました。" + +#: weko_records_ui/views.py:845 +msgid "The onetime URL copied to your clipboard." +msgstr "ワンタイムURLをコピーしました。" + +#: weko_records_ui/views.py:880 +msgid "The secret URL has been successfully deleted." +msgstr "シークレットURLは正常に削除されました。" + +#: weko_records_ui/views.py:915 +msgid "The one-time URL has been successfully deleted." +msgstr "ワンタイムURLは正常に削除されました。" + +#: weko_records_ui/views.py:1008 msgid "PDF cover page settings have been updated." msgstr "" -#: weko_records_ui/templates/weko_records_ui/_macros.html:31 -#: weko_records_ui/templates/weko_records_ui/_macros.html:46 -#: weko_records_ui/templates/weko_records_ui/_macros.html:78 -#: weko_records_ui/templates/weko_records_ui/_macros.html:94 -#: weko_records_ui/templates/weko_records_ui/_macros.html:113 -#: weko_records_ui/templates/weko_records_ui/box/preview.html:55 -msgid "Download" -msgstr "ダウンロード" +#: weko_records_ui/templates/weko_records_ui/_macros.html:23 +msgid "This data is not available for this user." +msgstr "このデータは利用できません(権限がないため)。" -#: weko_records_ui/templates/weko_records_ui/creator_detail_template.html:77 +#: weko_records_ui/templates/weko_records_ui/_macros.html:45 +#: weko_records_ui/templates/weko_records_ui/_macros.html:58 +#: weko_records_ui/templates/weko_records_ui/_macros.html:70 +#: weko_records_ui/templates/weko_records_ui/_macros.html:83 +#: weko_records_ui/templates/weko_records_ui/_macros.html:101 +#: weko_records_ui/templates/weko_records_ui/_macros.html:130 +#: weko_records_ui/templates/weko_records_ui/_macros.html:144 +#: weko_records_ui/templates/weko_records_ui/_macros.html:159 +#: weko_records_ui/templates/weko_records_ui/_macros.html:175 +#: weko_records_ui/templates/weko_records_ui/_macros.html:193 +msgid "Apply JGSS" +msgstr "申請" + +#: weko_records_ui/templates/weko_records_ui/_macros.html:210 +msgid "Terms and Conditions" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/_macros.html:232 +#, fuzzy +msgid "I have read and agreed to the Terms and Conditions" +msgstr "上記の条件に同意する" + +#: weko_records_ui/templates/weko_records_ui/_macros.html:242 +#: weko_records_ui/templates/weko_records_ui/box/analysis.html:79 +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:55 +msgid "Next" +msgstr "次へ" + +#: weko_records_ui/templates/weko_records_ui/creator_detail_template.html:110 +#: weko_records_ui/templates/weko_records_ui/creator_detail_template.html:115 msgid "Search repository" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/export.html:24 -#: weko_records_ui/templates/weko_records_ui/export_well.html:21 +#: weko_records_ui/templates/weko_records_ui/box/export.html:26 +#: weko_records_ui/templates/weko_records_ui/export_well.html:24 msgid "OAI-PMH" msgstr "" #: weko_records_ui/templates/weko_records_ui/box/export.html:23 -#: weko_records_ui/templates/weko_records_ui/export_well.html:31 +#: weko_records_ui/templates/weko_records_ui/export_well.html:38 msgid "Export" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details.html:80 +#: weko_records_ui/templates/weko_records_ui/file_details.html:116 msgid "Confirm" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details.html:83 +#: weko_records_ui/templates/weko_records_ui/file_details.html:119 msgid "This file is a Billing file. (Price: XXXXX). Do you want to download it?" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details.html:87 +#: weko_records_ui/templates/weko_records_ui/file_details.html:123 msgid "Yes" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details.html:88 +#: weko_records_ui/templates/weko_records_ui/file_details.html:124 msgid "No" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:30 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:33 msgid "Item" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:54 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:33 +#: weko_records_ui/templates/weko_records_ui/box/head.html:80 +#: weko_records_ui/templates/weko_records_ui/box/head.html:83 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:36 msgid "No title" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:58 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:64 msgid "File" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:59 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:65 msgid "License" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:86 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:67 +msgid "Action" +msgstr "アクション" + +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:100 msgid "" "The file cannot be downloaded because you do not have permission to view " "this file." msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:101 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:120 msgid "Original" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:119 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:143 +msgid "Secret URL" +msgstr "シークレットURL" + +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:156 msgid "Plagarism Check" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:142 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:163 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:266 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:289 msgid "Version" msgstr "" #: weko_records_ui/templates/weko_records_ui/box/stats.html:5 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:143 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:267 msgid "Stats" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:151 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:169 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:275 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:295 msgid "Show" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:152 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:169 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:276 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:295 msgid "Hide" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:164 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:290 msgid "Date Modified" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:165 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:291 msgid "Object File Name" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:166 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:292 msgid "File Size" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:167 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:293 msgid "File Hash Value" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:168 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:294 msgid "Contributor Name" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:190 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:316 msgid "Downloads" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:198 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:324 msgid "Plays" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:67 -msgid "Action" -msgstr "アクション" - -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:142 -msgid "Secret URL" -msgstr "シークレットURL" - #: weko_records_ui/templates/weko_records_ui/box/stats.html:29 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:208 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:334 msgid "See details" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:29 -msgid "item type" +#: weko_records_ui/templates/weko_records_ui/item_detail.html:38 +msgid "Item type" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:79 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:104 msgid "Thumbnail" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:97 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:136 msgid "Link" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:108 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:148 msgid "Publish Status" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:119 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:162 msgid "Public" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:124 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:167 msgid "Change to Private" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:127 -#: weko_records_ui/templates/weko_records_ui/item_detail.html:137 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:170 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:180 msgid "Private" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:132 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:175 msgid "Change to Public" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:136 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:179 msgid "Publish" msgstr "" +#: weko_records_ui/templates/weko_records_ui/output_detail_data.html:131 +#: weko_records_ui/templates/weko_records_ui/output_detail_data.html:172 +#: weko_records_ui/templates/weko_records_ui/output_detail_data.html:227 +msgid "Language:" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/tombstone.html:13 +msgid "This item has been deleted." +msgstr "このアイテムは削除されています。" #: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:25 msgid "Fields For Update" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:54 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:58 msgid "Open Access" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:57 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:61 msgid "Open Access Date" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:61 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:65 msgid "Login User Only" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:72 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:76 msgid "Add Field" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:80 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:84 msgid "Search" msgstr "検索" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:101 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:110 msgid "Item list" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:112 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:123 msgid "Export Checked Items" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:113 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:124 msgid "Export All Displayed Items" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:114 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:125 msgid "Export All Items Of This Index" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:115 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:126 msgid "Print Checked Items" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:116 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:127 msgid "Print All Displayed Items" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:117 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:128 msgid "Print All Items Of This Index" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:121 -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:144 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:132 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:159 msgid "Display order" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:123 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:134 msgid "Title(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:124 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:135 msgid "Title(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:125 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:136 msgid "Registrant(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:126 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:137 msgid "Registrant(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:127 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:138 msgid "Item Types(Asending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:128 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:139 msgid "Item Types(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:129 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:140 msgid "ID(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:130 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:141 msgid "ID(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:131 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:142 msgid "Modified Date and Time(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:132 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:143 msgid "Modified Date and Time(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:133 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:144 msgid "Created Date and Time(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:134 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:145 msgid "Created Date and Time(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:135 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:146 msgid "Review Date and Time(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:136 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:147 msgid "Review Date and Time(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:137 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:148 msgid "Published Year(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:138 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:149 msgid "Published Year(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:139 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:150 msgid "Custom(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:140 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:151 msgid "Custom(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:158 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:178 msgid "The number of display" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:173 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:194 msgid "Select All" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:176 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:198 msgid "Search failed." msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:180 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:204 msgid "Loading..." msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:203 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:233 msgid "Update" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/institution_name_setting.html:27 -msgid "Life Time" -msgstr "" - -#: weko_records_ui/templates/weko_records_ui/admin/institution_name_setting.html:40 +#: weko_records_ui/templates/weko_records_ui/admin/institution_name_setting.html:43 msgid "Institution Name" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/institution_name_setting.html:51 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:78 -#: weko_records_ui/templates/weko_records_ui/admin/pdfcoverpage.html:234 +#: weko_records_ui/templates/weko_records_ui/admin/institution_name_setting.html:56 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:90 +#: weko_records_ui/templates/weko_records_ui/admin/pdfcoverpage.html:241 msgid "Save" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:29 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:34 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:42 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:35 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:41 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:50 msgid "Display Email" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:38 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:46 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:45 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:54 msgid "Hide Email" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:52 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:61 msgid "Open Date" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:57 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:65 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:67 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:76 msgid "Display" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:61 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:69 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:71 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:80 msgid "Hide Open Date" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/pdfcoverpage.html:130 +#: weko_records_ui/templates/weko_records_ui/admin/pdfcoverpage.html:137 msgid "Header Settings" msgstr "" @@ -521,79 +634,75 @@ msgstr "利用について" msgid "I have read and agreed to the Terms of Use" msgstr "上記の条件に同意する" -#: weko_records_ui/templates/weko_records_ui/box/analysis.html:79 -msgid "Next" -msgstr "次へ" - -#: weko_records_ui/templates/weko_records_ui/box/export.html:34 +#: weko_records_ui/templates/weko_records_ui/box/export.html:40 msgid "Other Formats" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:23 +#: weko_records_ui/templates/weko_records_ui/box/head.html:26 msgid "There is a" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:24 +#: weko_records_ui/templates/weko_records_ui/box/head.html:27 msgid "newer version" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:24 +#: weko_records_ui/templates/weko_records_ui/box/head.html:27 msgid "of this record available." msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/meta.html:36 +#: weko_records_ui/templates/weko_records_ui/box/meta.html:42 msgid "Publication date" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/meta.html:39 +#: weko_records_ui/templates/weko_records_ui/box/meta.html:48 msgid "Schema" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview.html:32 -#: weko_records_ui/templates/weko_records_ui/box/preview.html:55 -#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:27 +#: weko_records_ui/templates/weko_records_ui/box/preview.html:34 +#: weko_records_ui/templates/weko_records_ui/box/preview.html:61 +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:34 msgid "Preview" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview.html:43 +#: weko_records_ui/templates/weko_records_ui/box/preview.html:48 msgid "Name" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview.html:44 +#: weko_records_ui/templates/weko_records_ui/box/preview.html:49 msgid "Size" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview.html:52 +#: weko_records_ui/templates/weko_records_ui/box/preview.html:58 msgid "" "This is the file fingerprint (MD5 checksum), which can be used to verify " "the file integrity." msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:39 +#: weko_records_ui/templates/weko_records_ui/box/preview.html:61 +msgid "Download" +msgstr "ダウンロード" + +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:47 msgid "First" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:43 +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:51 msgid "Previous" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:47 -msgid "Next" -msgstr "" - -#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:51 +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:59 msgid "Last" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:89 +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:119 msgid "Cannot preview because the file size is too large." msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:105 +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:138 msgid "No preview available." msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:120 +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:155 msgid "Unable to load preview." msgstr "" @@ -601,7 +710,7 @@ msgstr "" msgid "Share" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/share.html:39 +#: weko_records_ui/templates/weko_records_ui/box/share.html:55 msgid "Your record could not be processed by the citation formatter" msgstr "" @@ -613,47 +722,43 @@ msgstr "表示する統計期間を選択" msgid "Views" msgstr "" +#: weko_records_ui/templates/weko_records_ui/box/tools.html:23 +msgid "Tools" +msgstr "" + #: weko_records_ui/templates/weko_records_ui/box/versions.html:2 msgid "Versions" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/versions.html:2 +#: weko_records_ui/templates/weko_records_ui/box/versions.html:5 msgid "Ver." msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/versions.html:21 +#: weko_records_ui/templates/weko_records_ui/box/versions.html:37 msgid "Show All versions" msgstr "" -msgid "This item has been deleted." -msgstr "このアイテムは削除されています。" +#~ msgid "item type" +#~ msgstr "" -msgid "Apply JGSS" -msgstr "申請" +#~ msgid "Life Time" +#~ msgstr "" -msgid "This data is not available for undergraduate students or those who do not register their positions." -msgstr "このデータは利用できません(学部生または役職が登録されていないため)" +#~ msgid "" +#~ "This data is not available for " +#~ "undergraduate students or those who do" +#~ " not register their positions." +#~ msgstr "このデータは利用できません(学部生または役職が登録されていないため)" -msgid "Please input email address." -msgstr "メールアドレスを入力してください。" +#~ msgid "Please input email address." +#~ msgstr "メールアドレスを入力してください。" -msgid "Email address" -msgstr "メールアドレス" +#~ msgid "Email address" +#~ msgstr "メールアドレス" -msgid "Email address(reconfirmation)" -msgstr "メールアドレス(確認用)" - -msgid "Token is invalid." -msgstr "トークンが無効です。" +#~ msgid "Email address(reconfirmation)" +#~ msgstr "メールアドレス(確認用)" -msgid "The expiration date for download has been exceeded." -msgstr "ダウンロード有効期限を超過しています。" - -msgid "The download limit has been exceeded." -msgstr "ダウンロード制限回数を超過しています。" - -msgid "This data is not available for this user." -msgstr "このデータは利用できません(権限がないため)。" +#~ msgid "Success Secret URL Generate" +#~ msgstr "成功: シークレットURLを生成し、あなたのメールアドレス宛に送信しました。" -msgid "Success Secret URL Generate" -msgstr "成功: シークレットURLを生成し、あなたのメールアドレス宛に送信しました。" diff --git a/modules/weko-records-ui/weko_records_ui/translations/messages.pot b/modules/weko-records-ui/weko_records_ui/translations/messages.pot index dc2230acab..ae6568da2a 100644 --- a/modules/weko-records-ui/weko_records_ui/translations/messages.pot +++ b/modules/weko-records-ui/weko_records_ui/translations/messages.pot @@ -1,319 +1,429 @@ # Translations template for weko-records-ui. -# Copyright (C) 2021 National Institute of Informatics +# Copyright (C) 2025 National Institute of Informatics # This file is distributed under the same license as the weko-records-ui # project. -# FIRST AUTHOR , 2021. +# FIRST AUTHOR , 2025. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: weko-records-ui 0.1.0.dev20170000\n" "Report-Msgid-Bugs-To: wekosoftware@nii.ac.jp\n" -"POT-Creation-Date: 2021-03-25 14:23+0900\n" +"POT-Creation-Date: 2025-02-06 17:25+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.8.0\n" +"Generated-By: Babel 2.5.1\n" -#: weko_records_ui/admin.py:87 +#: tests/test_utils.py:541 weko_records_ui/fd.py:468 weko_records_ui/fd.py:546 +#: weko_records_ui/utils.py:1109 +msgid "Unexpected error occurred." +msgstr "" + +#: tests/test_utils.py:545 weko_records_ui/utils.py:1111 +msgid "Failed to send mail." +msgstr "" + +#: weko_records_ui/admin.py:89 msgid "Author flag was updated." msgstr "" -#: weko_records_ui/admin.py:148 +#: weko_records_ui/admin.py:165 msgid "Institution Name was updated." msgstr "" -#: weko_records_ui/admin.py:212 weko_records_ui/admin.py:221 -#: weko_records_ui/admin.py:230 +#: weko_records_ui/admin.py:228 weko_records_ui/admin.py:237 +#: weko_records_ui/admin.py:246 msgid "Setting" msgstr "" -#: weko_records_ui/admin.py:213 +#: weko_records_ui/admin.py:229 msgid "Others" msgstr "" -#: weko_records_ui/admin.py:222 weko_records_ui/admin.py:239 +#: weko_records_ui/admin.py:238 weko_records_ui/admin.py:255 msgid "Items" msgstr "" -#: weko_records_ui/admin.py:231 -#: weko_records_ui/templates/weko_records_ui/admin/pdfcoverpage.html:91 +#: weko_records_ui/admin.py:247 +#: weko_records_ui/templates/weko_records_ui/admin/pdfcoverpage.html:98 msgid "PDF Cover Page" msgstr "" -#: weko_records_ui/admin.py:240 +#: weko_records_ui/admin.py:256 msgid "Bulk Update" msgstr "" -#: weko_records_ui/config.py:296 +#: weko_records_ui/config.py:394 msgid "write your own license" msgstr "" -#: weko_records_ui/config.py:301 +#: weko_records_ui/config.py:399 msgid "Creative Commons CC0 1.0 Universal Public Domain Designation" msgstr "" -#: weko_records_ui/config.py:316 +#: weko_records_ui/config.py:414 msgid "Creative Commons Attribution 3.0 Unported (CC BY 3.0)" msgstr "" -#: weko_records_ui/config.py:328 +#: weko_records_ui/config.py:426 msgid "Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)" msgstr "" -#: weko_records_ui/config.py:342 +#: weko_records_ui/config.py:440 msgid "Creative Commons Attribution-NoDerivs 3.0 Unported (CC BY-ND 3.0)" msgstr "" -#: weko_records_ui/config.py:356 +#: weko_records_ui/config.py:454 msgid "Creative Commons Attribution-NonCommercial 3.0 Unported (CC BY-NC 3.0)" msgstr "" -#: weko_records_ui/config.py:370 +#: weko_records_ui/config.py:468 msgid "" "Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC " "BY-NC-SA 3.0)" msgstr "" -#: weko_records_ui/config.py:384 +#: weko_records_ui/config.py:482 msgid "" "Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported (CC BY-" "NC-ND 3.0)" msgstr "" -#: weko_records_ui/config.py:399 +#: weko_records_ui/config.py:497 msgid "Creative Commons Attribution 4.0 International (CC BY 4.0)" msgstr "" -#: weko_records_ui/config.py:411 +#: weko_records_ui/config.py:509 msgid "Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)" msgstr "" -#: weko_records_ui/config.py:425 +#: weko_records_ui/config.py:523 msgid "" "Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND " "4.0)" msgstr "" -#: weko_records_ui/config.py:439 +#: weko_records_ui/config.py:537 msgid "" "Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC " "4.0)" msgstr "" -#: weko_records_ui/config.py:453 +#: weko_records_ui/config.py:551 msgid "" "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International " "(CC BY-NC-SA 4.0)" msgstr "" -#: weko_records_ui/config.py:467 +#: weko_records_ui/config.py:565 msgid "" "Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 " "International (CC BY-NC-ND 4.0)" msgstr "" -#: weko_records_ui/pdf.py:556 +#: weko_records_ui/fd.py:439 weko_records_ui/fd.py:531 +#, python-format +msgid "The file \"%s\" does not exist." +msgstr "" + +#: weko_records_ui/pdf.py:662 msgid "The storage path is incorrect." msgstr "" -#: weko_records_ui/pdf.py:558 weko_records_ui/pdf.py:571 -#: weko_records_ui/pdf.py:584 +#: weko_records_ui/pdf.py:664 weko_records_ui/pdf.py:677 +#: weko_records_ui/pdf.py:690 msgid "Please contact the administrator." msgstr "" -#: weko_records_ui/pdf.py:569 +#: weko_records_ui/pdf.py:675 msgid "The storage location cannot be accessed." msgstr "" -#: weko_records_ui/pdf.py:583 +#: weko_records_ui/pdf.py:689 msgid "There is not enough storage space." msgstr "" -#: weko_records_ui/utils.py:185 +#: weko_records_ui/utils.py:436 msgid "Item cannot be deleted because the import is in progress." msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:113 -#: weko_records_ui/utils.py:570 +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:147 +#: weko_records_ui/utils.py:889 msgid "Restricted Access" msgstr "" -#: weko_records_ui/views.py:681 +#: weko_records_ui/utils.py:1373 +msgid "Guest" +msgstr "" + +#: weko_records_ui/utils.py:1384 +msgid "Free Input" +msgstr "" + +#: weko_records_ui/utils.py:2080 +msgid "The provided token is invalid." +msgstr "" + +#: weko_records_ui/utils.py:2084 +msgid "This feature is currently disabled." +msgstr "" + +#: weko_records_ui/utils.py:2089 +msgid "This file is currently not available for this feature." +msgstr "" + +#: weko_records_ui/utils.py:2094 +msgid "This URL has been deactivated." +msgstr "" + +#: weko_records_ui/utils.py:2096 +msgid "The download limit has been exceeded." +msgstr "" + +#: weko_records_ui/utils.py:2099 +msgid "The expiration date for download has been exceeded." +msgstr "" + +#: weko_records_ui/views.py:769 +msgid "Secret URL generated successfully" +msgstr "" + +#: weko_records_ui/views.py:774 +msgid ", please check your email inbox" +msgstr "" + +#: weko_records_ui/views.py:776 +msgid "" +", but there was an error while sending the email. To use the URL, please " +"refresh the page and copy it from the issued URL list" +msgstr "" + +#: weko_records_ui/views.py:779 +msgid "." +msgstr "" + +#: weko_records_ui/views.py:812 +msgid "The secret URL copied to your clipboard." +msgstr "" + +#: weko_records_ui/views.py:845 +msgid "The onetime URL copied to your clipboard." +msgstr "" + +#: weko_records_ui/views.py:880 +msgid "The secret URL has been successfully deleted." +msgstr "" + +#: weko_records_ui/views.py:915 +msgid "The one-time URL has been successfully deleted." +msgstr "" + +#: weko_records_ui/views.py:1008 msgid "PDF cover page settings have been updated." msgstr "" -#: weko_records_ui/templates/weko_records_ui/_macros.html:31 -#: weko_records_ui/templates/weko_records_ui/_macros.html:46 -#: weko_records_ui/templates/weko_records_ui/_macros.html:78 -#: weko_records_ui/templates/weko_records_ui/_macros.html:94 -#: weko_records_ui/templates/weko_records_ui/_macros.html:113 -#: weko_records_ui/templates/weko_records_ui/box/preview.html:55 -msgid "Download" +#: weko_records_ui/templates/weko_records_ui/_macros.html:23 +msgid "This data is not available for this user." +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/_macros.html:45 +#: weko_records_ui/templates/weko_records_ui/_macros.html:58 +#: weko_records_ui/templates/weko_records_ui/_macros.html:70 +#: weko_records_ui/templates/weko_records_ui/_macros.html:83 +#: weko_records_ui/templates/weko_records_ui/_macros.html:101 +#: weko_records_ui/templates/weko_records_ui/_macros.html:130 +#: weko_records_ui/templates/weko_records_ui/_macros.html:144 +#: weko_records_ui/templates/weko_records_ui/_macros.html:159 +#: weko_records_ui/templates/weko_records_ui/_macros.html:175 +#: weko_records_ui/templates/weko_records_ui/_macros.html:193 +msgid "Apply JGSS" msgstr "" -#: weko_records_ui/templates/weko_records_ui/creator_detail_template.html:77 +#: weko_records_ui/templates/weko_records_ui/_macros.html:210 +msgid "Terms and Conditions" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/_macros.html:232 +msgid "I have read and agreed to the Terms and Conditions" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/_macros.html:242 +#: weko_records_ui/templates/weko_records_ui/box/analysis.html:79 +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:55 +msgid "Next" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/creator_detail_template.html:110 +#: weko_records_ui/templates/weko_records_ui/creator_detail_template.html:115 msgid "Search repository" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/export.html:24 -#: weko_records_ui/templates/weko_records_ui/export_well.html:21 +#: weko_records_ui/templates/weko_records_ui/box/export.html:26 +#: weko_records_ui/templates/weko_records_ui/export_well.html:24 msgid "OAI-PMH" msgstr "" #: weko_records_ui/templates/weko_records_ui/box/export.html:23 -#: weko_records_ui/templates/weko_records_ui/export_well.html:31 +#: weko_records_ui/templates/weko_records_ui/export_well.html:38 msgid "Export" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details.html:80 +#: weko_records_ui/templates/weko_records_ui/file_details.html:116 msgid "Confirm" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details.html:83 +#: weko_records_ui/templates/weko_records_ui/file_details.html:119 msgid "This file is a Billing file. (Price: XXXXX). Do you want to download it?" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details.html:87 +#: weko_records_ui/templates/weko_records_ui/file_details.html:123 msgid "Yes" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details.html:88 +#: weko_records_ui/templates/weko_records_ui/file_details.html:124 msgid "No" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:30 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:33 msgid "Item" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:54 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:33 +#: weko_records_ui/templates/weko_records_ui/box/head.html:80 +#: weko_records_ui/templates/weko_records_ui/box/head.html:83 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:36 msgid "No title" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:58 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:64 msgid "File" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:59 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:65 msgid "License" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:86 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:67 +msgid "Action" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:100 msgid "" "The file cannot be downloaded because you do not have permission to view " "this file." msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:101 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:120 msgid "Original" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:119 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:143 +msgid "Secret URL" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:156 msgid "Plagarism Check" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:142 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:163 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:266 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:289 msgid "Version" msgstr "" #: weko_records_ui/templates/weko_records_ui/box/stats.html:5 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:143 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:267 msgid "Stats" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:151 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:169 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:275 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:295 msgid "Show" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:152 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:169 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:276 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:295 msgid "Hide" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:164 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:290 msgid "Date Modified" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:165 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:291 msgid "Object File Name" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:166 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:292 msgid "File Size" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:167 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:293 msgid "File Hash Value" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:168 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:294 msgid "Contributor Name" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:190 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:316 msgid "Downloads" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:198 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:324 msgid "Plays" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:67 -msgid "Action" -msgstr "" - -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:142 -msgid "Secret URL" -msgstr "" - #: weko_records_ui/templates/weko_records_ui/box/stats.html:29 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:208 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:334 msgid "See details" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:29 -msgid "item type" +#: weko_records_ui/templates/weko_records_ui/item_detail.html:38 +msgid "Item type" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:79 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:104 msgid "Thumbnail" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:97 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:136 msgid "Link" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:108 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:148 msgid "Publish Status" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:119 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:162 msgid "Public" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:124 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:167 msgid "Change to Private" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:127 -#: weko_records_ui/templates/weko_records_ui/item_detail.html:137 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:170 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:180 msgid "Private" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:132 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:175 msgid "Change to Public" msgstr "" -#: weko_records_ui/templates/weko_records_ui/item_detail.html:136 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:179 msgid "Publish" msgstr "" +#: weko_records_ui/templates/weko_records_ui/output_detail_data.html:131 +#: weko_records_ui/templates/weko_records_ui/output_detail_data.html:172 +#: weko_records_ui/templates/weko_records_ui/output_detail_data.html:227 +msgid "Language:" +msgstr "" + #: weko_records_ui/templates/weko_records_ui/tombstone.html:13 msgid "This item has been deleted." msgstr "" @@ -322,191 +432,187 @@ msgstr "" msgid "Fields For Update" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:54 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:58 msgid "Open Access" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:57 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:61 msgid "Open Access Date" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:61 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:65 msgid "Login User Only" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:72 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:76 msgid "Add Field" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:80 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:84 msgid "Search" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:101 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:110 msgid "Item list" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:112 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:123 msgid "Export Checked Items" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:113 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:124 msgid "Export All Displayed Items" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:114 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:125 msgid "Export All Items Of This Index" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:115 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:126 msgid "Print Checked Items" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:116 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:127 msgid "Print All Displayed Items" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:117 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:128 msgid "Print All Items Of This Index" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:121 -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:144 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:132 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:159 msgid "Display order" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:123 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:134 msgid "Title(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:124 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:135 msgid "Title(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:125 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:136 msgid "Registrant(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:126 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:137 msgid "Registrant(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:127 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:138 msgid "Item Types(Asending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:128 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:139 msgid "Item Types(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:129 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:140 msgid "ID(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:130 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:141 msgid "ID(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:131 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:142 msgid "Modified Date and Time(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:132 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:143 msgid "Modified Date and Time(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:133 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:144 msgid "Created Date and Time(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:134 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:145 msgid "Created Date and Time(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:135 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:146 msgid "Review Date and Time(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:136 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:147 msgid "Review Date and Time(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:137 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:148 msgid "Published Year(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:138 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:149 msgid "Published Year(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:139 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:150 msgid "Custom(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:140 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:151 msgid "Custom(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:158 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:178 msgid "The number of display" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:173 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:194 msgid "Select All" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:176 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:198 msgid "Search failed." msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:180 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:204 msgid "Loading..." msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:203 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:233 msgid "Update" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/institution_name_setting.html:27 -msgid "Life Time" -msgstr "" - -#: weko_records_ui/templates/weko_records_ui/admin/institution_name_setting.html:40 +#: weko_records_ui/templates/weko_records_ui/admin/institution_name_setting.html:43 msgid "Institution Name" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/institution_name_setting.html:51 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:78 -#: weko_records_ui/templates/weko_records_ui/admin/pdfcoverpage.html:234 +#: weko_records_ui/templates/weko_records_ui/admin/institution_name_setting.html:56 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:90 +#: weko_records_ui/templates/weko_records_ui/admin/pdfcoverpage.html:241 msgid "Save" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:29 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:34 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:42 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:35 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:41 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:50 msgid "Display Email" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:38 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:46 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:45 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:54 msgid "Hide Email" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:52 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:61 msgid "Open Date" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:57 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:65 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:67 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:76 msgid "Display" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:61 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:69 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:71 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:80 msgid "Hide Open Date" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/pdfcoverpage.html:130 +#: weko_records_ui/templates/weko_records_ui/admin/pdfcoverpage.html:137 msgid "Header Settings" msgstr "" @@ -523,79 +629,75 @@ msgstr "" msgid "I have read and agreed to the Terms of Use" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/analysis.html:79 -msgid "Next" -msgstr "" - -#: weko_records_ui/templates/weko_records_ui/box/export.html:34 +#: weko_records_ui/templates/weko_records_ui/box/export.html:40 msgid "Other Formats" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:23 +#: weko_records_ui/templates/weko_records_ui/box/head.html:26 msgid "There is a" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:24 +#: weko_records_ui/templates/weko_records_ui/box/head.html:27 msgid "newer version" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:24 +#: weko_records_ui/templates/weko_records_ui/box/head.html:27 msgid "of this record available." msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/meta.html:36 +#: weko_records_ui/templates/weko_records_ui/box/meta.html:42 msgid "Publication date" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/meta.html:39 +#: weko_records_ui/templates/weko_records_ui/box/meta.html:48 msgid "Schema" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview.html:32 -#: weko_records_ui/templates/weko_records_ui/box/preview.html:55 -#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:27 +#: weko_records_ui/templates/weko_records_ui/box/preview.html:34 +#: weko_records_ui/templates/weko_records_ui/box/preview.html:61 +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:34 msgid "Preview" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview.html:43 +#: weko_records_ui/templates/weko_records_ui/box/preview.html:48 msgid "Name" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview.html:44 +#: weko_records_ui/templates/weko_records_ui/box/preview.html:49 msgid "Size" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview.html:52 +#: weko_records_ui/templates/weko_records_ui/box/preview.html:58 msgid "" "This is the file fingerprint (MD5 checksum), which can be used to verify " "the file integrity." msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:39 -msgid "First" -msgstr "" - -#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:43 -msgid "Previous" +#: weko_records_ui/templates/weko_records_ui/box/preview.html:61 +msgid "Download" msgstr "" #: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:47 -msgid "Next" +msgid "First" msgstr "" #: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:51 +msgid "Previous" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:59 msgid "Last" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:89 +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:119 msgid "Cannot preview because the file size is too large." msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:105 +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:138 msgid "No preview available." msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:120 +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:155 msgid "Unable to load preview." msgstr "" @@ -603,7 +705,7 @@ msgstr "" msgid "Share" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/share.html:39 +#: weko_records_ui/templates/weko_records_ui/box/share.html:55 msgid "Your record could not be processed by the citation formatter" msgstr "" @@ -615,648 +717,19 @@ msgstr "" msgid "Views" msgstr "" +#: weko_records_ui/templates/weko_records_ui/box/tools.html:23 +msgid "Tools" +msgstr "" + #: weko_records_ui/templates/weko_records_ui/box/versions.html:2 msgid "Versions" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/versions.html:2 +#: weko_records_ui/templates/weko_records_ui/box/versions.html:5 msgid "Ver." msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/versions.html:21 +#: weko_records_ui/templates/weko_records_ui/box/versions.html:37 msgid "Show All versions" msgstr "" -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "write your own license" -msgstr "" - -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution 3.0 Unported (CC BY 3.0)" -msgstr "" - -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)" -msgstr "" - -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-NoDerivs 3.0 Unported (CC BY-ND 3.0)" -msgstr "" - -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-NonCommercial 3.0 Unported (CC BY-NC 3.0)" -msgstr "" - -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)" -msgstr "" - -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported (CC BY-NC-ND 3.0)" -msgstr "" - -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution 4.0 International (CC BY 4.0)" -msgstr "" - -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)" -msgstr "" - -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND 4.0)" -msgstr "" - -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)" -msgstr "" - -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)" -msgstr "" - -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)" -msgstr "" - -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons CC0 1.0 Universal Public Domain Designation" -msgstr "" - -# WEKO_RECORDS_UI_ITEM_DETAIL -msgid "Date (ISO-8601)" -msgstr "" - -msgid "Subject" -msgstr "" - -msgid "Subject Scheme" -msgstr "" - -msgid "Subject URI" -msgstr "" - -msgid "Alternative Title" -msgstr "" - -msgid "Creator" -msgstr "" - -msgid "Creator Name Identifier" -msgstr "" - -msgid "Creator Name Identifier Scheme" -msgstr "" - -msgid "Creator Name Identifier URI" -msgstr "" - -msgid "Creator Name" -msgstr "" - -msgid "Name_" -msgstr "" - -msgid "Creator Family Name" -msgstr "" - -msgid "Family Name" -msgstr "" - -msgid "Creator Given Name" -msgstr "" - -msgid "Given Name" -msgstr "" - -msgid "Creator Alternative Name" -msgstr "" - -msgid "Alternative Name" -msgstr "" - -msgid "Affiliation Name Identifier" -msgstr "" - -msgid "Affiliation Name Identifier Scheme" -msgstr "" - -msgid "Affiliation Name Identifier URI" -msgstr "" - -msgid "Affiliation Name" -msgstr "" - -msgid "Creator Email Address" -msgstr "" - -msgid "Description Type" -msgstr "" - -msgid "Bibliographic Information" -msgstr "" - -msgid "Journal Title" -msgstr "" - -msgid "Volume Number" -msgstr "" - -msgid "Issue Number" -msgstr "" - -msgid "Page Start" -msgstr "" - -msgid "Page End" -msgstr "" - -msgid "Publication year" -msgstr "" - -msgid "Date Type" -msgstr "" - -msgid "Publisher" -msgstr "" - -msgid "Source Identifier" -msgstr "" - -msgid "Source Identifier Type" -msgstr "" - -msgid "Source Identifier" -msgstr "" - -msgid "Relation" -msgstr "" - -msgid "RelationType" -msgstr "" - -msgid "Related Identifier" -msgstr "" - -msgid "Related Identifier Type" -msgstr "" - -msgid "Identifier Type" -msgstr "" - -msgid "Related Title" -msgstr "" - -msgid "Rights" -msgstr "" - -msgid "Resource" -msgstr "" - -msgid "Fileinfo" -msgstr "" - -msgid "Text" -msgstr "" - -msgid "Version Type" -msgstr "" - -msgid "URI" -msgstr "" - -msgid "Label" -msgstr "" - -msgid "Mime Type" -msgstr "" - -msgid "Heading" -msgstr "" - -msgid "Headline" -msgstr "" - -msgid "Subheading" -msgstr "" - -msgid "Access Right" -msgstr "" - -msgid "Access Rights URI" -msgstr "" - -msgid "Contributor" -msgstr "" - -msgid "Contributor Type" -msgstr "" - -msgid "Contributor Name Identifier" -msgstr "" - -msgid "Contributor Name Identifier Scheme" -msgstr "" - -msgid "Contributor Name Identifier URI" -msgstr "" - -msgid "Contributor_Name" -msgstr "" - -msgid "Contributor Family Name" -msgstr "" - -msgid "Contributor Given Name" -msgstr "" - -msgid "Contributor Alternative Name" -msgstr "" - -msgid "Contributor Alternative" -msgstr "" - -msgid "Contributor Email Address" -msgstr "" - -msgid "Degree Name" -msgstr "" - -msgid "Degree Grantor" -msgstr "" - -msgid "Degree Grantor Name Identifier" -msgstr "" - -msgid "Degree Grantor Name" -msgstr "" - -msgid "Date Granted" -msgstr "" - -msgid "Dissertation Number" -msgstr "" - -msgid "Contributor ID" -msgstr "" - -msgid "Funding Reference" -msgstr "" - -msgid "Funder Name" -msgstr "" - -msgid "Award Number" -msgstr "" - -msgid "Book Name" -msgstr "" - -msgid "Date Reported" -msgstr "" - -msgid "Name Identifier" -msgstr "" - -msgid "Name Identifier Scheme" -msgstr "" - -msgid "Description_" -msgstr "" - -msgid "Rights Resource" -msgstr "" - -msgid "Rights Holder" -msgstr "" - -msgid "Rights Holder Name Identifier" -msgstr "" - -msgid "Rights Holder Name Identifier Scheme" -msgstr "" - -msgid "Rights Holder Name Identifier URI" -msgstr "" - -msgid "Rights Holder Name" -msgstr "" - -msgid "Resource Type" -msgstr "" - -msgid "Temporal" -msgstr "" - -msgid "Geo Location" -msgstr "" - -msgid "Geo Location Point" -msgstr "" - -msgid "Point Longitude" -msgstr "" - -msgid "Point Latitude" -msgstr "" - -msgid "Geo Location Box" -msgstr "" - -msgid "West Bound Longitude" -msgstr "" - -msgid "East Bound Longitude" -msgstr "" - -msgid "South Bound Latitude" -msgstr "" - -msgid "North Bound Latitude" -msgstr "" - -msgid "Geo Location Place" -msgstr "" - -msgid "funder Identifier" -msgstr "" - -msgid "Funder Identifier Type" -msgstr "" - -msgid "Award Number URI" -msgstr "" - -msgid "Source Title" -msgstr "" - -msgid "Number of Pages" -msgstr "" - -msgid "Degree Grantor Name Identifier Scheme" -msgstr "" - -msgid "Conference" -msgstr "" - -msgid "Conference Name" -msgstr "" - -msgid "Conference Sequence" -msgstr "" - -msgid "Conference Place" -msgstr "" - -msgid "Conference Country" -msgstr "" - -msgid "URI Object Type" -msgstr "" - -msgid "URI Label" -msgstr "" - -msgid "Format" -msgstr "" - -msgid "Extent" -msgstr "" - -msgid "Issued Date" -msgstr "" - -msgid "Issue" -msgstr "" - -msgid "Volume" -msgstr "" - -msgid "Search repository" -msgstr "" - -msgid "Content File" -msgstr "" - -msgid "Billing File" -msgstr "" - -msgid "ID Agency" -msgstr "" - -msgid "Series" -msgstr "" - -msgid "Version Date" -msgstr "" - -msgid "DateType" -msgstr "" - -msgid "Bibliographic Citation" -msgstr "" - -msgid "Topic" -msgstr "" - -msgid "topic vocabURI" -msgstr "" - -msgid "subjectScheme" -msgstr "" - -msgid "Topic J" -msgstr "" - -msgid "Topic E" -msgstr "" - -msgid "Time Period" -msgstr "" - -msgid "Time Period Event" -msgstr "" - -msgid "Date Of Collection Event" -msgstr "" - -msgid "Geographic Coverage" -msgstr "" - -msgid "Unit of Analysis" -msgstr "" - -msgid "Unit of Analysis J" -msgstr "" - -msgid "Unit of Analysis E" -msgstr "" - -msgid "Sampling Procedure E" -msgstr "" - -msgid "Sampling Procedure J" -msgstr "" - -msgid "Collection Method" -msgstr "" - -msgid "Collection Method J" -msgstr "" - -msgid "Collection Method E" -msgstr "" - -msgid "Sampling Rate" -msgstr "" - -msgid "Access" -msgstr "" - -msgid "Rdf:Resource" -msgstr "" - -msgid "Access E" -msgstr "" - -msgid "Access J" -msgstr "" - -msgid "Study ID" -msgstr "" - -msgid "Copyright" -msgstr "" - -msgid "Topic Vocab" -msgstr "" - -msgid "Topic Vocab URI" -msgstr "" - -msgid "Date Of Collection" -msgstr "" - -msgid "Event" -msgstr "" - -msgid "Universe" -msgstr "" - -msgid "Data Type J" -msgstr "" - -msgid "Data Type E" -msgstr "" - -msgid "Sampling Procedure" -msgstr "" - -msgid "Identifier Registration Type" -msgstr "" - -msgid "Identifier Registration" -msgstr "" - -msgid "Related Study" -msgstr "" - -msgid "Related Study DOI" -msgstr "" - -msgid "Related Publications" -msgstr "" - -msgid "Related Publications DOI" -msgstr "" - -msgid "Fund Agency" -msgstr "" - -msgid "Fund Agency ID" -msgstr "" - -msgid "Funder Identifier Type" -msgstr "" - -msgid "GrantNo" -msgstr "" - -msgid "Award Title" -msgstr "" - -msgid "Distributor Abbreviation" -msgstr "" - -msgid "Distributor Affiliation" -msgstr "" - -msgid "Distributor URI" -msgstr "" - -msgid "Contributor IdentifierType" -msgstr "" - -msgid "Distributor Name" -msgstr "" - -msgid "AwardTitle" -msgstr "" - -msgid "Related Study Title" -msgstr "" - -msgid "Related Study Identifier" -msgstr "" - -msgid "Related Publications Title" -msgstr "" - -msgid "Related Publications Identifier" -msgstr "" - -msgid "Related Publications Identifier Type" -msgstr "" - -msgid "GrantURI" -msgstr "" - -msgid "Related Study Identifier Type" -msgstr "" - -msgid "Grant No" -msgstr "" - -msgid "Summary DDI" -msgstr "" - - -msgid "Apply JGSS" -msgstr "Apply" - -msgid "This data is not available for undergraduate students or those who do not register their positions." -msgstr "" - -msgid "Please input email address." -msgstr "" - -msgid "Email address" -msgstr "" - -msgid "Email address(reconfirmation)" -msgstr "" - -msgid "Token is invalid." -msgstr "" - -msgid "The expiration date for download has been exceeded." -msgstr "" - -msgid "The download limit has been exceeded." -msgstr "" - -msgid "This data is not available for this user." -msgstr "" - -msgid "Success Secret URL Generate" -msgstr "succeess:secret url generate is succeed. send to your mail adress." diff --git a/modules/weko-records-ui/weko_records_ui/utils.py b/modules/weko-records-ui/weko_records_ui/utils.py index cb0055ac6b..bfe52d03e9 100644 --- a/modules/weko-records-ui/weko_records_ui/utils.py +++ b/modules/weko-records-ui/weko_records_ui/utils.py @@ -2077,26 +2077,26 @@ def validate_url_download(record, filename, token, is_secret_url): """ # Check if the token is valid if not validate_token(token, is_secret_url): - return False, 'The provided token is invalid.' + return False, _('The provided token is invalid.') if is_secret_url: if not is_secret_url_feature_enabled(): - return False, 'This feature is currently disabled.' + return False, _('This feature is currently disabled.') # Check if the file is available for download if (not validate_file_access(record, filename, is_secret_url) or not validate_download_record(record)): - return False, 'This file is currently not available for this feature.' + return False, _('This file is currently not available for this feature.') # Check if the URL is still valid url_obj = convert_token_into_obj(token, is_secret_url) if url_obj.is_deleted is True: - return False, 'This URL has been deactivated.' + return False, _('This URL has been deactivated.') if url_obj.download_count >= url_obj.download_limit: - return False, 'The download limit has been exceeded.' + return False, _('The download limit has been exceeded.') limit_date = url_obj.expiration_date.replace(tzinfo=timezone.utc) if limit_date < dt.now(timezone.utc): - return False, 'The expiration date for download has been exceeded.' + return False, _('The expiration date for download has been exceeded.') return True, '' diff --git a/modules/weko-records-ui/weko_records_ui/views.py b/modules/weko-records-ui/weko_records_ui/views.py index 3256509a5d..d3f52ef7a5 100644 --- a/modules/weko-records-ui/weko_records_ui/views.py +++ b/modules/weko-records-ui/weko_records_ui/views.py @@ -766,17 +766,17 @@ def create_secret_url_and_send_mail(pid, record, filename, **kwargs): current_app.logger.error(e) abort(500) - message = 'Secret URL generated successfully' + message = _('Secret URL generated successfully') if request.json['send_email'] is True: sending_result = send_secret_url_mail( pid.object_uuid, url_obj, record.get('item_title', '')) if sending_result: - message += ', please check your email inbox' + message += _(', please check your email inbox') else: - message += (', but there was an error while sending the email. ' + message += _(', but there was an error while sending the email. ' 'To use the URL, please refresh the page and copy it ' 'from the issued URL list') - return jsonify({'message': message + '.'}) + return jsonify({'message': message + _('.')}) def copy_secret_url(pid, record, **kwargs): @@ -809,7 +809,7 @@ def copy_secret_url(pid, record, **kwargs): abort(500) return jsonify({'url': url, - 'message': 'The secret URL copied to your clipboard.'}) + 'message': _('The secret URL copied to your clipboard.')}) def copy_onetime_url(pid, record, **kwargs): @@ -842,7 +842,7 @@ def copy_onetime_url(pid, record, **kwargs): abort(500) return jsonify({'url': url, - 'message': 'The onetime URL copied to your clipboard.'}) + 'message': _('The onetime URL copied to your clipboard.')}) def delete_secret_url(pid, record, **kwargs): @@ -877,7 +877,7 @@ def delete_secret_url(pid, record, **kwargs): abort(500) return jsonify( - {'message': 'The secret URL has been successfully deleted.'}) + {'message': _('The secret URL has been successfully deleted.')}) def delete_onetime_url(pid, record, **kwargs): @@ -912,7 +912,7 @@ def delete_onetime_url(pid, record, **kwargs): abort(500) return jsonify( - {'message': 'The one-time URL has been successfully deleted.'}) + {'message': _('The one-time URL has been successfully deleted.')}) @blueprint.route('/r/', methods=['GET']) From 0eed56d0dbd870cb0c96f29fd58c72678945bc8c Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Mon, 10 Feb 2025 15:12:04 +0900 Subject: [PATCH 50/61] Delete some view messages --- .../translations/en/LC_MESSAGES/messages.mo | Bin 21466 -> 10771 bytes .../translations/en/LC_MESSAGES/messages.po | 1739 +++++++++-------- .../translations/ja/LC_MESSAGES/messages.mo | Bin 11136 -> 10687 bytes .../translations/ja/LC_MESSAGES/messages.po | 18 +- .../weko_records_ui/translations/messages.pot | 18 +- 5 files changed, 942 insertions(+), 833 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.mo b/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.mo index 77e8ab730fda8c0eec2e4e4a9ea0228312561548..83c0bfe8c1914d12f44da4ece106d6cd39f6f3d7 100644 GIT binary patch literal 10771 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 E7*PG*fB*mh literal 21466 zcmeI33vgsrna3{*%nJqtmN#;E&kT0v0Rux|fJ~B^Nyv*NgANa;Zn|%hJLzrABQdvXFTtO zyAi$zz6CxE=fQu4d&B48KJYcDe0%Qi!iT_p39o>vvIFi3H^777FgzT-7aj_4_UZRS z)$=&q9X=1|!0Q;yW4>-ds^HK_DwJpU7_ zox4zpa6SA9JOwUAIoU4{)$e8a7w~c@``!d) z|GS{<`_EAAd;(Hs@C%3uf`v3%{kjAy|0z&*>4oZ#jh+$I_&Xn}KQDxm`?o&+I;iru zLh18mAOASC;|XdWy$IEBuR!U!>k&?$1L0AGPlBp<6V$jHgX*^uRDFNx<39kU&o!Pm zL&^I`sQSMS)!rXKm46jV?|DZ$Jr02C&%+`Af;0J%o?D>i??rHDcokGVAAxjb@F^($ zAAq~S$Dqo64@%!3LygCuLD_i@jgq{hq0&!=YTp{T3+#ci{~+8M4nyfV1||0bD7#$( zRsQ`xd=-?O8=&;L6>465!N-3SO72roc6$-Z4zEG=({4vQ9tvf*6;ShbEmVDHLXEGp zeRv3Je2qcXb1_uC*Fx!g3snBQpvvD1)t(2T&?eFyY3Dp=OCzjOQ8B~ zC1i>P-B9v3!(HIJpxSYf4_^V*zH6ZD@d>DUZ-ODkf|121R3(d$D#DSAFBMf zp~^i0rQdT<_5IeT@4VRQH4myCdqL$t21=h3;hW%TPgw+jKaZy{8@2SC-c7)sA(sCDFYsPgAR*X0r*FgE z;dh|g@f=kBzk zu+-_d2V6k#FsOEApz^JPlGg*(e;c62(RonwA?rB`)y{FKez_3JPFF+ec@tE*yP?Y6 z17)YLLDlyNl>8^4^m-afpBJIV>#v~n*ms$WKLo1$VyJpnK*>D~9tGRrad0ynfHy$3 z=RctAJ9oLO_h`t}36{ewJO{3a_d@OSb62?fBPe|?hHrtF!#&~O`S9(YUxjMtqfqVn zuIG=S%D({jfG@*0!(CBQ<9j|-{60`}kA!={m7eRN+ItR^9db~18iUIJE+{$ggR1u` zD7#$mc`H=;yP)*D4@%AhP~+wsQ2q2hD1YVOp~`2Dcm3H6)$TK)>d8XcdmQcqOHgty zg_^%tLACcbAO9sNeIJ4vA5X$h?Sx+grSG*TIDKw}((@M2+oAOM0#yAEK-K>w+#CJ~ z?gL+j%D3P|7v3N4OE?3iM;nx#`k=;B2(`Yy8){zO=+p0ms^?o!?Rf^uUcZDaZNV$> zXn6Q4SAPdoz2`y6-wajX1yJMqQYbrJ38l|9Q1XQ?${QE5Nw@_{&ta(fI{`KRFM+D(GI$8Q7E1rmL(Rkcp~^iBrSD_#K=>3?yIz5k zw{M$EKNhNeE1=4s3T6K`xHCKpO3!nkkfscHw+Jh6Yy|&C6vB*LY4n2RJpH1>Gy<>f6?7Rw_hc7~n--qB__$*ZWeh#JQ zt5EgK?QnKE0BRjM6sr8mQ29DN&xXps8LGYup!9w()Vh8d)V%rx)cpObPk-F=`%vY6 z0ww3?p1<|!dvv;b_wzg&%1$exLCZG-dR2ci1mdZ=-IE0q23gPI>tK#ju} zpya*j47aZA2M;B@3?2#lpyXDd#`6cD#?uW@>(K2`?RyAHpXZ?Ry#{69d0kGAz2JPp zheD+@qjcS7kg0S){Bl%4K?Dt|XrJ05_l|2t6nz5+EK5AJsTbShN7 z9;kMPo?GDVgx?1x_rsny`uJO*+H)tA{T_nS_h~5oUW5x^(Bs;*8&v=82PJP2)HqlQ zHNQ@Tnh&cz*Fecx2h}ed;cjpOO3!ygmAewE+_g}4x*p1opM;Ws2V4lh0Hx1^5Yq>b zLg`_8UA|+W$}fegXBCv(c6b!*gvY@NhzWyFK(+gqQ1+eE=juHQE+)JTX5razJ-i2M zpPzH4n=d&ieJ+A(=ik6R;SD}~o99=c+W9X~?fH)9524Ec8{7l_7ko3^rQeP3c~J3t zL&-e??gfwcTnE+Ov!U#eg|gFTsQi;q^W-w9dOrkZm+L$~4ORa0Q2N~qCFg#)JA4GH zpS}wh!Cyd?-+#dM=TY!2ginF0rw7X3?|>?I9+aFC)cm~&s=Xia@t=g!_j6F=<7+T5 z8zypLISN{`*(gaoOnddCv-;5=kBpWZ0*Tqs-x0Lra%OED<#T*(iIZZEvJB;&&tR-8A&)5@=Q?C#7jTQ@$>8XqlMWvuBBFdSLLQ;;)6(=n%x&CdOpeYfi zBNr9Q@o=1yhe|ecD$NXJMl){P8 zIBQztvg^Ud=#Ij0u{2&y$>-z32)$3+*A$Cm&MvJzjEdHu6XQp?yWuSBkf2 zn5AMW)Rzrfi}`$%Rri|ih(_hMk6|=!m&41ID3>PFQMSA4xlLa_ zY%DVnonNWRiA&=uTxRgNrYTU0hbrY_$tL)3(^4M*GsSz0)CHLK@$^hci8gD)@t6Tq zRkm8CS;vTTqqd=zj<&{NZLkkcPFRD~1Z*c!5vdu_AZJQib#|l`otj0fKstS@0-L-7 z6O$#ohO`z-ECUn8LQbQnw^E*{l#OjpI(|bEGIBJD;;bESHcKgD5amo8za}i?%wRko zEow=!krlQ$Xq`4gTQntyCUI+Ve7smlOtWbzJ6mbNvI1sIHW|}XY>VtzCR4H_*$b*D zDrMu4`5^UPUwSJ0A6Og32S&qE)RK?K7!hw&_3PzLS;!pMw9ITMXo;gE7PYpTH5(P@ zZJ%yjy43NM>R?jYCA3&zne;_Tc>SW828)}S1Z8^Kl(u8ClIdC@32)dCv`P2SG{(sE zwEo^=W^PpL4mB#J(hi%ebw@2Wjqn-lXGQ*=Cd|~w-SnN(ni**MeGQrx`Z~6inB$Y>T}sXX|oO`btTzSJYl^m zuS+dr6RBmKkf1G2Ci3B=b4hBUVkw8iOj;6^oC{d>CW)*06fHwTrD%(H@*5KBZnT8d z3OMaj5$f)?;?_dG7-Dz-VV2@3A~)J<@QA}Ls2L9BBQsnoj++bKz103|s%PJkOi9ol zCS|h*zc6RIiiHtQ6S*j8&&B1yO@h#$OBzCpN>LUE6K~K?ti?flL8>gJQDWc*LHo8S z>&_~9s!{FMZhRtDkIfd3^8_uEiXxo(aQK;4qMCCFqN&%Nf;aUw>r6Pz&X}|MyLHT?s%xMm7kz`)zeNz zHnc!_^QdbDBtd7`)oNNx#Ux>b=K`oToHZktRIK11=6$tY;Sfj2t{CSY3wFiB z*c5l4ufkZ+#j-)?G&MExIl?B0l}o0#ke>{?BYTI?Ohf;vPB7iEVTK&(hV`^{ z8QU1CZpVv$Qgg#broz?bS=lxPJ<+yu(8I{EHw8U-&!g2PiGH;!lDqrRi0NazCqZvZ zN2afPeb9SG&^xr5GqOF7x*nvVj7ZahwI-98yfoZ|`FwFJV=_Oi%L21JGc;L_5|iZ+ z5N2_`4Q7f9oVJ4A367Nh#wYb{4HWcpOp6g5^fHV~$aW_*JD~d7*0MP=Bdv~sf>|4! zKxM<8Yp0Hz7ef)1)Gj8}CevQX1@>d09CAg%{2vKRaWZb~s%OLTl!(cM=qOAgr@D#k z0opIwO@Uo6Ojp&0K3H4j`-(}dU2u_Y%VM&p!wC_kXgJ;`XxD)(Khv(>^%RZ#WXj_0 zQXALq>Xir1ng(9roHh>>=Efsek9QH1QCM<0lF=#+#wu4ZgPgu(loh5f&IY@qTpYS_ zQO~oXzsoFQtkT3!(!;%)R0{fY!_ECs!qnu_q`DLp%4s>N*tCrPC{O#? z{%Usqrf4)J4z`UHSsbND6^}Cq5 z;3-+_35_+nVvSX$LUQA+27K5_Cnu9(TjW-Yj-U^-qq@UVE+49vuJw}ps8%aCj0J#g z-d*zcI~Qxl<#5{chMv_$4jp7+))({g85=@wIBp-+&`s&$wm>Six_rK_2z#mNbJWJ@ zKse5&5k5&@shEv&+_?tZM{#19CbF|0*{`W-?}o!*z}noyxVzVCXlT+$$YF9BO+C(T z6h3wsZ#LqJ)(1=VULD|=kh4yCLaMGqwFL1w9%n-C9p!>%%|Hd;dK|>*SU+#TPSlhkgY9D6u<+4t=n&X`L(;lKMdjiIKgNW5e!D-IMM7p4ZhQ$w$4F3s$BJy zkdMdXGMh|d>>d+s%i?#*w8|B2n~0sSWS3XNg>}8;3}uGpHfO^^p;$&_Eju%2D9VNv zHXmKz=ZZ!dtkXAvcgGn?i#YzCJ+atZd`WV86zo%mw)31j2w3g57E5Ep+NSaQha!zq z7H_7eEyL@8PBdf$>*lonJDzl|g2hcDH=|I-DV2;Ot=c-u z8-#0B$vT;A>}>jR$=DT#)+N3UYauCT*2M1G-?r2&VwcNC&E_5YZ*db>64q3;2aal& z#pTnSvdEwYW91Fg{-`uz_uNI@Z7XqAIQ%4)@x^aLwU}j&K2e9zSnF5gc*Mh#bpdSF zoT}sUq}LKlj@QS9dtH%fWBMl;#8f!&i}8t1s`>*)@Jcff3DEsd;o$BdBG&s7^)cN+=5a zA<*5L=c~sr>lCNoY;1&Gl+L1TsTv2_8 z`AuQf+Z~>IE)8tQm-X_I=bR8!4X{Zk-s*c*A==9D-s0Pht?fMWde&xGQ^3?7W(j8B zNP@(zasG{vm5_CKi@I06xs{P;SB8>dm~-lJCl$6GdFz>uT*jNQ*=&u*iWyf`lBvXI zSrZp}O}S{xie<}BT*iO%-Az)aI~j>{nKhM>Br{la<&xrXnYX&=^g?EZ2^=7o0tEcPvRw>`|70S2l1edRi zE0WA0N9P2~7fa1%?S`%{)6>%3Zcb_MZt3Vcy``|tE`M9ZA8aZ`-U%tX>p3#j%(rgM7Hkhnlak6ors}~i_VXMvZ#q+y6y4&m4 z!Z!2uqQqjA(NJtQW%huj+@{2ZlkItilg8>wc{sDGo=?hi2a#znWVy~Oj5M27LveY2 zb-%%=lg(z0CZAc+w6baW{Mo1PKkC!>%um_>>sN2L$l=D;U%zJ`yk{T0|F1oGyR%Pg zarVLcjbEJ`pHu&^=k?hKZ}%4ssdxA4Yw!#YwzCi3(^+ix!5eS0MSnFi?Th^EgZGq+ z|LR-&lm~(8g~&|FIzqJ0K6t0Es{SYs-m~xBv+v!r@7?@u_w0LjrS=!*v+v#dgQqDk z;^`~TKj^*t&-A&wdjC*=_O8BoyI1Ym=k2U}ZT5e$%@aoT+1uXXn%U=V{eK>;EoPs$ zxs$f<471PMiGM=h?hRZ=gg@KoZ9NO<%_sLK{IcCV-P5)n3KPA2^Q=p{{;yuVc4#TU%}%tkE1kq_W7I6uKmTr?DMylGXD4f;Q5;e8UN&+ M@K-s1)=%C41Lq-5QUCw| diff --git a/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.po b/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.po index 6561112995..d66332e98a 100644 --- a/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.po +++ b/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: weko-records-ui 0.1.0.dev20170000\n" "Report-Msgid-Bugs-To: wekosoftware@nii.ac.jp\n" -"POT-Creation-Date: 2019-04-25 18:57+0900\n" +"POT-Creation-Date: 2025-02-10 15:07+0900\n" "PO-Revision-Date: 2018-04-12 18:06+0900\n" "Last-Translator: FULL NAME \n" "Language: en\n" @@ -19,548 +19,706 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.5.1\n" -#: weko_records_ui/admin.py:73 +#: tests/test_utils.py:541 weko_records_ui/fd.py:468 weko_records_ui/fd.py:546 +#: weko_records_ui/utils.py:1109 +msgid "Unexpected error occurred." +msgstr "" + +#: tests/test_utils.py:545 weko_records_ui/utils.py:1111 +msgid "Failed to send mail." +msgstr "" + +#: weko_records_ui/admin.py:89 msgid "Author flag was updated." msgstr "Updated Author flag" -#: weko_records_ui/admin.py:137 weko_records_ui/admin.py:146 -#: weko_records_ui/admin.py:155 weko_records_ui/admin.py:353 +#: weko_records_ui/admin.py:165 +#, fuzzy +msgid "Institution Name was updated." +msgstr "Updated Author flag" + +#: weko_records_ui/admin.py:228 weko_records_ui/admin.py:237 +#: weko_records_ui/admin.py:246 msgid "Setting" msgstr "" -#: weko_records_ui/admin.py:138 +#: weko_records_ui/admin.py:229 msgid "Others" msgstr "" -#: weko_records_ui/admin.py:147 +#: weko_records_ui/admin.py:238 weko_records_ui/admin.py:255 msgid "Items" msgstr "" -#: weko_records_ui/admin.py:156 +#: weko_records_ui/admin.py:247 #: weko_records_ui/templates/weko_records_ui/admin/pdfcoverpage.html:98 msgid "PDF Cover Page" msgstr "" -#: weko_records_ui/admin.py:196 -msgid "Prefix" +#: weko_records_ui/admin.py:256 +msgid "Bulk Update" +msgstr "" + +#: weko_records_ui/config.py:394 +msgid "write your own license" +msgstr "" + +#: weko_records_ui/config.py:399 +msgid "Creative Commons CC0 1.0 Universal Public Domain Designation" +msgstr "" + +#: weko_records_ui/config.py:414 +msgid "Creative Commons Attribution 3.0 Unported (CC BY 3.0)" +msgstr "" + +#: weko_records_ui/config.py:426 +msgid "Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)" +msgstr "" + +#: weko_records_ui/config.py:440 +msgid "Creative Commons Attribution-NoDerivs 3.0 Unported (CC BY-ND 3.0)" msgstr "" -#: weko_records_ui/admin.py:202 -msgid "Suffix" +#: weko_records_ui/config.py:454 +msgid "Creative Commons Attribution-NonCommercial 3.0 Unported (CC BY-NC 3.0)" msgstr "" -#: weko_records_ui/admin.py:204 -msgid "Enable/Disable" +#: weko_records_ui/config.py:468 +msgid "" +"Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC " +"BY-NC-SA 3.0)" msgstr "" -#: weko_records_ui/admin.py:214 -msgid "Repository" +#: weko_records_ui/config.py:482 +msgid "" +"Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported (CC BY-" +"NC-ND 3.0)" msgstr "" -#: weko_records_ui/admin.py:214 -msgid "JaLC DOI" +#: weko_records_ui/config.py:497 +msgid "Creative Commons Attribution 4.0 International (CC BY 4.0)" msgstr "" -#: weko_records_ui/admin.py:215 -msgid "JaLC CrossRef DOI" +#: weko_records_ui/config.py:509 +msgid "Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)" msgstr "" -#: weko_records_ui/admin.py:216 -msgid "JaLC DataCite DOI" +#: weko_records_ui/config.py:523 +msgid "" +"Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND " +"4.0)" msgstr "" -#: weko_records_ui/admin.py:829 -msgid "NDL JaLC DOI" +#: weko_records_ui/config.py:537 +msgid "" +"Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC " +"4.0)" msgstr "" -#: weko_records_ui/admin.py:217 -msgid "CNRI" +#: weko_records_ui/config.py:551 +msgid "" +"Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International " +"(CC BY-NC-SA 4.0)" msgstr "" -#: weko_records_ui/admin.py:218 -msgid "Semi-automatic Suffix" -msgstr "Formatted Suffix" +#: weko_records_ui/config.py:565 +msgid "" +"Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 " +"International (CC BY-NC-ND 4.0)" +msgstr "" -#: weko_records_ui/admin.py:235 -msgid "Only allow halfwith 1-bytes character in input" +#: weko_records_ui/fd.py:439 weko_records_ui/fd.py:531 +#, python-format +msgid "The file \"%s\" does not exist." msgstr "" -#: weko_records_ui/admin.py:354 -msgid "Identifier" +#: weko_records_ui/pdf.py:662 +msgid "The storage path is incorrect." msgstr "" -#: weko_records_ui/config.py:46 -msgid "write your own license" +#: weko_records_ui/pdf.py:664 weko_records_ui/pdf.py:677 +#: weko_records_ui/pdf.py:690 +msgid "Please contact the administrator." msgstr "" -#: weko_records_ui/config.py:47 weko_records_ui/views.py:178 -msgid "Creative Commons : Attribution" +#: weko_records_ui/pdf.py:675 +msgid "The storage location cannot be accessed." msgstr "" -#: weko_records_ui/config.py:48 weko_records_ui/views.py:179 -msgid "Creative Commons : Attribution - ShareAlike" +#: weko_records_ui/pdf.py:689 +msgid "There is not enough storage space." msgstr "" -#: weko_records_ui/config.py:49 weko_records_ui/views.py:180 -msgid "Creative Commons : Attribution - NoDerivatives" +#: weko_records_ui/utils.py:436 +msgid "Item cannot be deleted because the import is in progress." msgstr "" -#: weko_records_ui/config.py:50 weko_records_ui/views.py:181 -msgid "Creative Commons : Attribution - NonCommercial" +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:147 +#: weko_records_ui/utils.py:889 +msgid "Restricted Access" +msgstr "" + +#: weko_records_ui/utils.py:1373 +msgid "Guest" +msgstr "" + +#: weko_records_ui/utils.py:1384 +msgid "Free Input" +msgstr "" + +#: weko_records_ui/utils.py:2080 +msgid "The provided token is invalid." +msgstr "" + +#: weko_records_ui/utils.py:2084 +msgid "This feature is currently disabled." +msgstr "" + +#: weko_records_ui/utils.py:2089 +msgid "This file is currently not available for this feature." +msgstr "" + +#: weko_records_ui/utils.py:2094 +msgid "This URL has been deactivated." +msgstr "" + +#: weko_records_ui/utils.py:2096 +msgid "The download limit has been exceeded." msgstr "" -#: weko_records_ui/config.py:51 weko_records_ui/views.py:182 -msgid "Creative Commons : Attribution - NonCommercial - ShareAlike" +#: weko_records_ui/utils.py:2099 +msgid "The expiration date for download has been exceeded." +msgstr "" + +#: weko_records_ui/views.py:769 +msgid "Secret URL generated successfully" +msgstr "" + +#: weko_records_ui/views.py:774 +msgid ", please check your email inbox" +msgstr "" + +#: weko_records_ui/views.py:776 +msgid "" +", but there was an error while sending the email. To use the URL, please " +"refresh the page and copy it from the issued URL list" msgstr "" -#: weko_records_ui/config.py:52 weko_records_ui/views.py:183 -msgid "Creative Commons : Attribution - NonCommercial - NoDerivatives" +#: weko_records_ui/views.py:779 +msgid "." msgstr "" -#: weko_records_ui/views.py:479 +#: weko_records_ui/views.py:1008 msgid "PDF cover page settings have been updated." msgstr "Updated PDF cover settings" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:34 -msgid "Index" +#: weko_records_ui/templates/weko_records_ui/_macros.html:23 +msgid "This data is not available for this user." msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:60 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:29 -msgid "Item" +#: weko_records_ui/templates/weko_records_ui/_macros.html:45 +#: weko_records_ui/templates/weko_records_ui/_macros.html:58 +#: weko_records_ui/templates/weko_records_ui/_macros.html:70 +#: weko_records_ui/templates/weko_records_ui/_macros.html:83 +#: weko_records_ui/templates/weko_records_ui/_macros.html:101 +#: weko_records_ui/templates/weko_records_ui/_macros.html:130 +#: weko_records_ui/templates/weko_records_ui/_macros.html:144 +#: weko_records_ui/templates/weko_records_ui/_macros.html:159 +#: weko_records_ui/templates/weko_records_ui/_macros.html:175 +#: weko_records_ui/templates/weko_records_ui/_macros.html:193 +msgid "Apply JGSS" +msgstr "Apply" + +#: weko_records_ui/templates/weko_records_ui/_macros.html:210 +msgid "Terms and Conditions" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:63 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:32 -msgid "No title" +#: weko_records_ui/templates/weko_records_ui/_macros.html:232 +msgid "I have read and agreed to the Terms and Conditions" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:85 -#: weko_records_ui/templates/weko_records_ui/box/preview.html:48 -msgid "Name" +#: weko_records_ui/templates/weko_records_ui/_macros.html:242 +#: weko_records_ui/templates/weko_records_ui/box/analysis.html:79 +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:55 +msgid "Next" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/creator_detail_template.html:110 +#: weko_records_ui/templates/weko_records_ui/creator_detail_template.html:115 +msgid "Search repository" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/box/export.html:26 +#: weko_records_ui/templates/weko_records_ui/export_well.html:24 +msgid "OAI-PMH" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/box/export.html:23 +#: weko_records_ui/templates/weko_records_ui/export_well.html:38 +msgid "Export" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/file_details.html:116 +msgid "Confirm" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:85 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:53 +#: weko_records_ui/templates/weko_records_ui/file_details.html:119 +msgid "This file is a Billing file. (Price: XXXXX). Do you want to download it?" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/file_details.html:123 +msgid "Yes" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/file_details.html:124 +msgid "No" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:33 +msgid "Item" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/box/head.html:80 +#: weko_records_ui/templates/weko_records_ui/box/head.html:83 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:36 +msgid "No title" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:64 msgid "File" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:86 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:54 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:65 msgid "License" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:98 -msgid "Detail" +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:67 +msgid "Action" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:127 -msgid "Restricted Access" +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:100 +msgid "" +"The file cannot be downloaded because you do not have permission to view " +"this file." msgstr "" +"The file cannot be downloaded because you do not have permission to view " +"it." -#: weko_records_ui/templates/weko_records_ui/body_contents.html:136 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:77 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:120 msgid "Original" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:147 -msgid "Plagiarism Check" +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:143 +msgid "Secret URL" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:161 -#: weko_records_ui/templates/weko_records_ui/body_contents.html:188 -#: weko_records_ui/templates/weko_records_ui/box/preview.html:34 -#: weko_records_ui/templates/weko_records_ui/box/preview.html:61 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:114 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:139 -msgid "Preview" +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:156 +msgid "Plagarism Check" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:226 -msgid "item type" +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:266 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:289 +msgid "Version" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:289 -msgid "Link" +#: weko_records_ui/templates/weko_records_ui/box/stats.html:5 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:267 +msgid "Stats" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:299 -msgid "Publish Status" +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:275 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:295 +msgid "Show" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:312 -msgid "Public" +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:276 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:295 +msgid "Hide" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:314 -msgid "Change to Private" +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:290 +msgid "Date Modified" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:317 -#: weko_records_ui/templates/weko_records_ui/body_contents.html:323 -msgid "Private" +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:291 +msgid "Object File Name" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:319 -msgid "Change to Public" +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:292 +msgid "File Size" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:323 -msgid "Publish" +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:293 +msgid "File Hash Value" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:343 -msgid "Back" +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:294 +msgid "Contributor Name" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:347 -msgid "Edit" +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:316 +msgid "Downloads" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:352 -msgid "Delete" +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:324 +msgid "Plays" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:359 -msgid "Confirm" +#: weko_records_ui/templates/weko_records_ui/box/stats.html:29 +#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:334 +msgid "See details" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/item_detail.html:38 +msgid "Item type" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/item_detail.html:104 +msgid "Thumbnail" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/item_detail.html:136 +msgid "Link" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/item_detail.html:148 +msgid "Publish Status" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/item_detail.html:162 +msgid "Public" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/item_detail.html:167 +msgid "Change to Private" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:349 -msgid "The workflow is being edited." +#: weko_records_ui/templates/weko_records_ui/item_detail.html:170 +#: weko_records_ui/templates/weko_records_ui/item_detail.html:180 +msgid "Private" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/item_detail.html:175 +msgid "Change to Public" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:362 -msgid "Are you sure you want to delete this item?" +#: weko_records_ui/templates/weko_records_ui/item_detail.html:179 +msgid "Publish" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:365 -msgid "OK" +#: weko_records_ui/templates/weko_records_ui/output_detail_data.html:131 +#: weko_records_ui/templates/weko_records_ui/output_detail_data.html:172 +#: weko_records_ui/templates/weko_records_ui/output_detail_data.html:227 +msgid "Language:" msgstr "" -#: weko_records_ui/templates/weko_records_ui/body_contents.html:366 -msgid "Cancel" +#: weko_records_ui/templates/weko_records_ui/tombstone.html:13 +msgid "This item has been deleted." msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:34 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:25 msgid "Fields For Update" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:65 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:58 msgid "Open Access" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:68 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:61 msgid "Open Access Date" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:72 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:65 msgid "Login User Only" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:83 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:76 msgid "Add Field" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:91 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:84 msgid "Search" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:119 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:110 msgid "Item list" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:132 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:123 msgid "Export Checked Items" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:133 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:124 msgid "Export All Displayed Items" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:134 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:125 msgid "Export All Items Of This Index" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:135 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:126 msgid "Print Checked Items" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:136 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:127 msgid "Print All Displayed Items" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:137 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:128 msgid "Print All Items Of This Index" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:139 -msgid "Execution" -msgstr "" - -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:142 -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:169 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:132 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:159 msgid "Display order" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:144 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:134 msgid "Title(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:145 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:135 msgid "Title(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:146 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:136 msgid "Registrant(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:147 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:137 msgid "Registrant(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:148 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:138 msgid "Item Types(Asending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:149 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:139 msgid "Item Types(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:150 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:140 msgid "ID(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:151 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:141 msgid "ID(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:152 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:142 msgid "Modified Date and Time(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:153 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:143 msgid "Modified Date and Time(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:154 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:144 msgid "Created Date and Time(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:155 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:145 msgid "Created Date and Time(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:156 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:146 msgid "Review Date and Time(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:157 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:147 msgid "Review Date and Time(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:158 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:148 msgid "Published Year(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:159 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:149 msgid "Published Year(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:160 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:150 msgid "Custom(Ascending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:161 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:151 msgid "Custom(Descending)" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:188 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:178 msgid "The number of display" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:204 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:194 msgid "Select All" msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:208 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:198 msgid "Search failed." msgstr "" -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:214 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:204 msgid "Loading..." msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/institution_name_setting.html:57 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:89 -#: weko_records_ui/templates/weko_records_ui/bulk_update_display.html:243 +#: weko_records_ui/templates/weko_records_ui/admin/bulk_update_display.html:233 msgid "Update" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/export.html:25 -#: weko_records_ui/templates/weko_records_ui/export_well.html:24 -msgid "OAI-PMH" -msgstr "" - -#: weko_records_ui/templates/weko_records_ui/box/export.html:39 -#: weko_records_ui/templates/weko_records_ui/export_well.html:38 -msgid "Export" -msgstr "" - -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:101 -msgid "Plagarism Check" -msgstr "" - -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:177 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:194 -msgid "Version" -msgstr "" - -#: weko_records_ui/templates/weko_records_ui/box/stats.html:4 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:178 -msgid "Stats" -msgstr "" - -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:186 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:200 -msgid "Show" +#: weko_records_ui/templates/weko_records_ui/admin/institution_name_setting.html:43 +msgid "Institution Name" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:187 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:200 -msgid "Hide" +#: weko_records_ui/templates/weko_records_ui/admin/institution_name_setting.html:56 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:90 +#: weko_records_ui/templates/weko_records_ui/admin/pdfcoverpage.html:241 +msgid "Save" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:195 -msgid "Date Modified" +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:35 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:41 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:50 +msgid "Display Email" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:196 -msgid "Object File Name" +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:45 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:54 +msgid "Hide Email" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:197 -msgid "File Size" +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:61 +msgid "Open Date" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:198 -msgid "File Hash Value" +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:67 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:76 +msgid "Display" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:199 -msgid "Contributor Name" +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:71 +#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:80 +msgid "Hide Open Date" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:220 -msgid "Downloads" +#: weko_records_ui/templates/weko_records_ui/admin/pdfcoverpage.html:137 +msgid "Header Settings" msgstr "" -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:228 -msgid "Plays" +#: weko_records_ui/templates/weko_records_ui/box/analysis.html:27 +#: weko_records_ui/templates/weko_records_ui/box/analysis.html:31 +msgid "Online Analysis" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/stats.html:22 -#: weko_records_ui/templates/weko_records_ui/file_details_contents.html:238 -msgid "See details" +#: weko_records_ui/templates/weko_records_ui/box/analysis.html:42 +msgid "Terms of Use" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/institution_name_setting.html:29 -msgid "Life Time" +#: weko_records_ui/templates/weko_records_ui/box/analysis.html:70 +msgid "I have read and agreed to the Terms of Use" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/institution_name_setting.html:46 -msgid "Institution Name" +#: weko_records_ui/templates/weko_records_ui/box/export.html:40 +msgid "Other Formats" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:34 -msgid "Search Author" +#: weko_records_ui/templates/weko_records_ui/box/head.html:26 +msgid "There is a" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:40 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:49 -msgid "Search by Author Name" +#: weko_records_ui/templates/weko_records_ui/box/head.html:27 +msgid "newer version" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:44 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:53 -msgid "Search by Author ID" +#: weko_records_ui/templates/weko_records_ui/box/head.html:27 +msgid "of this record available." msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:60 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:66 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:75 -msgid "Display Email" +#: weko_records_ui/templates/weko_records_ui/box/meta.html:42 +msgid "Publication date" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:70 -#: weko_records_ui/templates/weko_records_ui/admin/item_setting.html:79 -msgid "Hide Email" +#: weko_records_ui/templates/weko_records_ui/box/meta.html:48 +msgid "Schema" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/pdfcoverpage.html:137 -msgid "Header Settings" +#: weko_records_ui/templates/weko_records_ui/box/preview.html:34 +#: weko_records_ui/templates/weko_records_ui/box/preview.html:61 +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:34 +msgid "Preview" msgstr "" -#: weko_records_ui/templates/weko_records_ui/admin/pdfcoverpage.html:241 -msgid " Update" +#: weko_records_ui/templates/weko_records_ui/box/preview.html:48 +msgid "Name" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/analysis.html:27 -#: weko_records_ui/templates/weko_records_ui/box/analysis.html:31 -msgid "Online Analysis" +#: weko_records_ui/templates/weko_records_ui/box/preview.html:49 +msgid "Size" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/analysis.html:42 -msgid "Terms of Use" +#: weko_records_ui/templates/weko_records_ui/box/preview.html:58 +msgid "" +"This is the file fingerprint (MD5 checksum), which can be used to verify " +"the file integrity." msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/analysis.html:70 -msgid "I have read and agreed to the Terms of Use" +#: weko_records_ui/templates/weko_records_ui/box/preview.html:61 +msgid "Download" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/analysis.html:79 -msgid "Next" +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:47 +msgid "First" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/meta.html:42 -msgid "Publication date" +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:51 +msgid "Previous" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/meta.html:48 -msgid "Schema" +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:59 +msgid "Last" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview.html:49 -msgid "Size" +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:119 +msgid "Cannot preview because the file size is too large." msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview.html:58 -msgid "" -"This is the file fingerprint (MD5 checksum), which can be used to verify " -"the file integrity." +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:138 +msgid "No preview available." msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/preview.html:61 -msgid "Download" +#: weko_records_ui/templates/weko_records_ui/box/preview_carousel.html:155 +msgid "Unable to load preview." msgstr "" #: weko_records_ui/templates/weko_records_ui/box/share.html:23 msgid "Share" msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/share.html:41 +#: weko_records_ui/templates/weko_records_ui/box/share.html:55 msgid "Your record could not be processed by the citation formatter" msgstr "Could not show by the citation formatter" -#: weko_records_ui/templates/weko_records_ui/box/stats.html:29 +#: weko_records_ui/templates/weko_records_ui/box/stats.html:21 +msgid "Choose stats period" +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/box/stats.html:36 msgid "Views" msgstr "" +#: weko_records_ui/templates/weko_records_ui/box/tools.html:23 +msgid "Tools" +msgstr "" + #: weko_records_ui/templates/weko_records_ui/box/versions.html:2 msgid "Versions" msgstr "" -#: weko_workflow/templates/weko_workflow/modal_withdraw_confirmation.html:44 -msgid "Are you sure you want to withdraw DOI?" +#: weko_records_ui/templates/weko_records_ui/box/versions.html:5 +msgid "Ver." +msgstr "" + +#: weko_records_ui/templates/weko_records_ui/box/versions.html:37 +msgid "Show All versions" msgstr "" #~ msgid "Privating" @@ -584,804 +742,787 @@ msgstr "" #~ msgid "Period" #~ msgstr "" -#: weko-records-ui/weko_records_ui/templates/weko_records_ui/body_contents.html:146 -#: weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details_contents.html:86 -msgid "The file cannot be downloaded because you do not have permission to view this file." -msgstr "The file cannot be downloaded because you do not have permission to view it." +#~ msgid "Prefix" +#~ msgstr "" -#: weko-records-ui/weko_records_ui/templates/weko_records_ui/body_contents.html:436 -#: weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details.html:112 -msgid "This file is a Billing file. (Price: XXXXX). Do you want to download it?" -msgstr "" +#~ msgid "Suffix" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "There is a" -msgstr "" +#~ msgid "Enable/Disable" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "newer version" -msgstr "" +#~ msgid "Repository" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "of this record available." -msgstr "" +#~ msgid "JaLC DOI" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "User Name" -msgstr "" +#~ msgid "JaLC CrossRef DOI" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Corresponding Usage Application ID" -msgstr "" +#~ msgid "JaLC DataCite DOI" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Annual Report" -msgstr "" +#~ msgid "NDL JaLC DOI" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Corresponding Output ID" -msgstr "" +#~ msgid "CNRI" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Data Type" -msgstr "" +#~ msgid "Semi-automatic Suffix" +#~ msgstr "Formatted Suffix" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Content File" -msgstr "" +#~ msgid "Only allow halfwith 1-bytes character in input" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Affiliated Institution" -msgstr "" +#~ msgid "Identifier" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "User Information" -msgstr "" +#~ msgid "Creative Commons : Attribution" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "FullName" -msgstr "" +#~ msgid "Creative Commons : Attribution - ShareAlike" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Usage location" -msgstr "" +#~ msgid "Creative Commons : Attribution - NoDerivatives" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Advisor Name" -msgstr "" +#~ msgid "Creative Commons : Attribution - NonCommercial" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Advisor Affiliation" -msgstr "" +#~ msgid "Creative Commons : Attribution - NonCommercial - ShareAlike" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Advisor Mail Address" -msgstr "" +#~ msgid "Creative Commons : Attribution - NonCommercial - NoDerivatives" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Advisor Phone Number" -msgstr "" +#~ msgid "Index" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Research Title" -msgstr "" +#~ msgid "Detail" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Research Plan" -msgstr "" +#~ msgid "Plagiarism Check" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Guarantor Name" -msgstr "" +#~ msgid "item type" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Guarantor Affiliation" -msgstr "" +#~ msgid "Back" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Guarantor Mail Address" -msgstr "" +#~ msgid "Edit" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Item Title" -msgstr "" +#~ msgid "Delete" +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Position(Other)" -msgstr "" +#~ msgid "The workflow is being edited." +#~ msgstr "" -#: weko_records_ui/templates/weko_records_ui/box/head.html:6 -msgid "Affiliation" -msgstr "" +#~ msgid "Are you sure you want to delete this item?" +#~ msgstr "" -msgid "PubDate" -msgstr "" +#~ msgid "OK" +#~ msgstr "" -msgid "life" -msgstr "" +#~ msgid "Cancel" +#~ msgstr "" -msgid "accumulation" -msgstr "" +#~ msgid "Execution" +#~ msgstr "" -msgid "combinational_analysis" -msgstr "" +#~ msgid "Life Time" +#~ msgstr "" -msgid "perfectures" -msgstr "" +#~ msgid "Search Author" +#~ msgstr "" -msgid "location_information" -msgstr "" +#~ msgid "Search by Author Name" +#~ msgstr "" -msgid "Output Type" -msgstr "" +#~ msgid "Search by Author ID" +#~ msgstr "" -msgid "Published Media Name" -msgstr "" +#~ msgid " Update" +#~ msgstr "" -msgid "Published URL (DOI)" -msgstr "" +#~ msgid "Are you sure you want to withdraw DOI?" +#~ msgstr "" -msgid "Published Date" -msgstr "" +#~ msgid "User Name" +#~ msgstr "" -msgid "Field" -msgstr "" +#~ msgid "Corresponding Usage Application ID" +#~ msgstr "" -msgid "Member" -msgstr "" +#~ msgid "Annual Report" +#~ msgstr "" -msgid "Dataset Usage" -msgstr "" +#~ msgid "Corresponding Output ID" +#~ msgstr "" -msgid "Stop" -msgstr "" +#~ msgid "Data Type" +#~ msgstr "" -msgid "Position(Others)" -msgstr "" +#~ msgid "Content File" +#~ msgstr "" -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "write your own license" -msgstr "" +#~ msgid "Affiliated Institution" +#~ msgstr "" -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution 3.0 Unported (CC BY 3.0)" -msgstr "" +#~ msgid "User Information" +#~ msgstr "" -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)" -msgstr "" +#~ msgid "FullName" +#~ msgstr "" -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-NoDerivs 3.0 Unported (CC BY-ND 3.0)" -msgstr "" +#~ msgid "Usage location" +#~ msgstr "" -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-NonCommercial 3.0 Unported (CC BY-NC 3.0)" -msgstr "" +#~ msgid "Advisor Name" +#~ msgstr "" -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)" -msgstr "" +#~ msgid "Advisor Affiliation" +#~ msgstr "" -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported (CC BY-NC-ND 3.0)" -msgstr "" +#~ msgid "Advisor Mail Address" +#~ msgstr "" -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution 4.0 International (CC BY 4.0)" -msgstr "" +#~ msgid "Advisor Phone Number" +#~ msgstr "" -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)" -msgstr "" +#~ msgid "Research Title" +#~ msgstr "" -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND 4.0)" -msgstr "" +#~ msgid "Research Plan" +#~ msgstr "" -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)" -msgstr "" +#~ msgid "Guarantor Name" +#~ msgstr "" -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)" -msgstr "" +#~ msgid "Guarantor Affiliation" +#~ msgstr "" -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)" -msgstr "" +#~ msgid "Guarantor Mail Address" +#~ msgstr "" -# WEKO_RECORDS_UI_LICENSE_DICT -msgid "Creative Commons CC0 1.0 Universal Public Domain Designation" -msgstr "" +#~ msgid "Item Title" +#~ msgstr "" -# WEKO_RECORDS_UI_ITEM_DETAIL -msgid "Date (ISO-8601)" -msgstr "" +#~ msgid "Position(Other)" +#~ msgstr "" -msgid "Subject" -msgstr "" +#~ msgid "Affiliation" +#~ msgstr "" -msgid "Subject Scheme" -msgstr "" +#~ msgid "PubDate" +#~ msgstr "" -msgid "Subject URI" -msgstr "" +#~ msgid "life" +#~ msgstr "" -msgid "Alternative Title" -msgstr "" +#~ msgid "accumulation" +#~ msgstr "" -msgid "Creator" -msgstr "" +#~ msgid "combinational_analysis" +#~ msgstr "" -msgid "Creator Name Identifier" -msgstr "" +#~ msgid "perfectures" +#~ msgstr "" -msgid "Creator Name Identifier Scheme" -msgstr "" +#~ msgid "location_information" +#~ msgstr "" -msgid "Creator Name Identifier URI" -msgstr "" +#~ msgid "Output Type" +#~ msgstr "" -msgid "Creator Name" -msgstr "" +#~ msgid "Published Media Name" +#~ msgstr "" -msgid "Name_" -msgstr "Name" +#~ msgid "Published URL (DOI)" +#~ msgstr "" -msgid "Creator Family Name" -msgstr "" +#~ msgid "Published Date" +#~ msgstr "" -msgid "Family Name" -msgstr "" +#~ msgid "Field" +#~ msgstr "" -msgid "Creator Given Name" -msgstr "" +#~ msgid "Member" +#~ msgstr "" -msgid "Given Name" -msgstr "" +#~ msgid "Dataset Usage" +#~ msgstr "" -msgid "Creator Alternative Name" -msgstr "" +#~ msgid "Stop" +#~ msgstr "" -msgid "Alternative Name" -msgstr "" +#~ msgid "Position(Others)" +#~ msgstr "" -msgid "Affiliation Name Identifier" -msgstr "" +# WEKO_RECORDS_UI_ITEM_DETAIL +#~ msgid "Date (ISO-8601)" +#~ msgstr "" -msgid "Affiliation Name Identifier Scheme" -msgstr "" +#~ msgid "Subject" +#~ msgstr "" -msgid "Affiliation Name Identifier URI" -msgstr "" +#~ msgid "Subject Scheme" +#~ msgstr "" -msgid "Affiliation Name" -msgstr "" +#~ msgid "Subject URI" +#~ msgstr "" -msgid "Creator Email Address" -msgstr "" +#~ msgid "Alternative Title" +#~ msgstr "" -msgid "Description Type" -msgstr "" +#~ msgid "Creator" +#~ msgstr "" -msgid "Bibliographic Information" -msgstr "" +#~ msgid "Creator Name Identifier" +#~ msgstr "" -msgid "Journal Title" -msgstr "" +#~ msgid "Creator Name Identifier Scheme" +#~ msgstr "" -msgid "Volume Number" -msgstr "" +#~ msgid "Creator Name Identifier URI" +#~ msgstr "" -msgid "Issue Number" -msgstr "" +#~ msgid "Creator Name" +#~ msgstr "" -msgid "Page Start" -msgstr "" +#~ msgid "Name_" +#~ msgstr "Name" -msgid "Page End" -msgstr "" +#~ msgid "Creator Family Name" +#~ msgstr "" -msgid "Publication year" -msgstr "" +#~ msgid "Family Name" +#~ msgstr "" -msgid "Date Type" -msgstr "" +#~ msgid "Creator Given Name" +#~ msgstr "" -msgid "Publisher" -msgstr "" +#~ msgid "Given Name" +#~ msgstr "" -msgid "Source Identifier" -msgstr "" +#~ msgid "Creator Alternative Name" +#~ msgstr "" -msgid "Source Identifier Type" -msgstr "" +#~ msgid "Alternative Name" +#~ msgstr "" -msgid "Source Identifier" -msgstr "" +#~ msgid "Affiliation Name Identifier" +#~ msgstr "" -msgid "Relation" -msgstr "" +#~ msgid "Affiliation Name Identifier Scheme" +#~ msgstr "" -msgid "RelationType" -msgstr "" +#~ msgid "Affiliation Name Identifier URI" +#~ msgstr "" -msgid "Related Identifier" -msgstr "" +#~ msgid "Affiliation Name" +#~ msgstr "" -msgid "Related Identifier Type" -msgstr "" +#~ msgid "Creator Email Address" +#~ msgstr "" -msgid "Identifier Type" -msgstr "" +#~ msgid "Description Type" +#~ msgstr "" -msgid "Related Title" -msgstr "" +#~ msgid "Bibliographic Information" +#~ msgstr "" -msgid "Rights" -msgstr "" +#~ msgid "Journal Title" +#~ msgstr "" -msgid "Resource" -msgstr "" +#~ msgid "Volume Number" +#~ msgstr "" -msgid "Fileinfo" -msgstr "" +#~ msgid "Issue Number" +#~ msgstr "" -msgid "Text" -msgstr "" +#~ msgid "Page Start" +#~ msgstr "" -msgid "Version Type" -msgstr "" +#~ msgid "Page End" +#~ msgstr "" -msgid "URI" -msgstr "" +#~ msgid "Publication year" +#~ msgstr "" -msgid "Label" -msgstr "" +#~ msgid "Date Type" +#~ msgstr "" -msgid "Mime Type" -msgstr "" +#~ msgid "Publisher" +#~ msgstr "" -msgid "Heading" -msgstr "" +#~ msgid "Source Identifier" +#~ msgstr "" -msgid "Headline" -msgstr "" +#~ msgid "Source Identifier Type" +#~ msgstr "" -msgid "Subheading" -msgstr "" +#~ msgid "Relation" +#~ msgstr "" -msgid "Access Right" -msgstr "" +#~ msgid "RelationType" +#~ msgstr "" -msgid "Access Rights URI" -msgstr "" +#~ msgid "Related Identifier" +#~ msgstr "" -msgid "Contributor" -msgstr "" +#~ msgid "Related Identifier Type" +#~ msgstr "" -msgid "Contributor Type" -msgstr "" +#~ msgid "Identifier Type" +#~ msgstr "" -msgid "Contributor Name Identifier" -msgstr "" +#~ msgid "Related Title" +#~ msgstr "" -msgid "Contributor Name Identifier Scheme" -msgstr "" +#~ msgid "Rights" +#~ msgstr "" -msgid "Contributor Name Identifier URI" -msgstr "" +#~ msgid "Resource" +#~ msgstr "" -msgid "Contributor_Name" -msgstr "Contributor Name" +#~ msgid "Fileinfo" +#~ msgstr "" -msgid "Contributor Family Name" -msgstr "" +#~ msgid "Text" +#~ msgstr "" -msgid "Contributor Given Name" -msgstr "" +#~ msgid "Version Type" +#~ msgstr "" -msgid "Contributor Alternative Name" -msgstr "" +#~ msgid "URI" +#~ msgstr "" -msgid "Contributor Alternative" -msgstr "" +#~ msgid "Label" +#~ msgstr "" -msgid "Contributor Email Address" -msgstr "" +#~ msgid "Mime Type" +#~ msgstr "" -msgid "Degree Name" -msgstr "" +#~ msgid "Heading" +#~ msgstr "" -msgid "Degree Grantor" -msgstr "" +#~ msgid "Headline" +#~ msgstr "" -msgid "Degree Grantor Name Identifier" -msgstr "" +#~ msgid "Subheading" +#~ msgstr "" -msgid "Degree Grantor Name" -msgstr "" +#~ msgid "Access Right" +#~ msgstr "" -msgid "Date Granted" -msgstr "" +#~ msgid "Access Rights URI" +#~ msgstr "" -msgid "Dissertation Number" -msgstr "" +#~ msgid "Contributor" +#~ msgstr "" -msgid "Contributor ID" -msgstr "" +#~ msgid "Contributor Type" +#~ msgstr "" -msgid "Funding Reference" -msgstr "" +#~ msgid "Contributor Name Identifier" +#~ msgstr "" -msgid "Funder Name" -msgstr "" +#~ msgid "Contributor Name Identifier Scheme" +#~ msgstr "" -msgid "Award Number" -msgstr "" +#~ msgid "Contributor Name Identifier URI" +#~ msgstr "" -msgid "Book Name" -msgstr "" +#~ msgid "Contributor_Name" +#~ msgstr "Contributor Name" -msgid "Date Reported" -msgstr "" +#~ msgid "Contributor Family Name" +#~ msgstr "" -msgid "Name Identifier" -msgstr "" +#~ msgid "Contributor Given Name" +#~ msgstr "" -msgid "Name Identifier Scheme" -msgstr "" +#~ msgid "Contributor Alternative Name" +#~ msgstr "" -msgid "Description_" -msgstr "Description" +#~ msgid "Contributor Alternative" +#~ msgstr "" -msgid "Rights Resource" -msgstr "" +#~ msgid "Contributor Email Address" +#~ msgstr "" -msgid "Rights Holder" -msgstr "" +#~ msgid "Degree Name" +#~ msgstr "" -msgid "Rights Holder Name Identifier" -msgstr "" +#~ msgid "Degree Grantor" +#~ msgstr "" -msgid "Rights Holder Name Identifier Scheme" -msgstr "" +#~ msgid "Degree Grantor Name Identifier" +#~ msgstr "" -msgid "Rights Holder Name Identifier URI" -msgstr "" +#~ msgid "Degree Grantor Name" +#~ msgstr "" -msgid "Rights Holder Name" -msgstr "" +#~ msgid "Date Granted" +#~ msgstr "" -msgid "Resource Type" -msgstr "" +#~ msgid "Dissertation Number" +#~ msgstr "" -msgid "Temporal" -msgstr "" +#~ msgid "Contributor ID" +#~ msgstr "" -msgid "Geo Location" -msgstr "" +#~ msgid "Funding Reference" +#~ msgstr "" -msgid "Geo Location Point" -msgstr "" +#~ msgid "Funder Name" +#~ msgstr "" -msgid "Point Longitude" -msgstr "" +#~ msgid "Award Number" +#~ msgstr "" -msgid "Point Latitude" -msgstr "" +#~ msgid "Book Name" +#~ msgstr "" -msgid "Geo Location Box" -msgstr "" +#~ msgid "Date Reported" +#~ msgstr "" -msgid "West Bound Longitude" -msgstr "" +#~ msgid "Name Identifier" +#~ msgstr "" -msgid "East Bound Longitude" -msgstr "" +#~ msgid "Name Identifier Scheme" +#~ msgstr "" -msgid "South Bound Latitude" -msgstr "" +#~ msgid "Description_" +#~ msgstr "Description" -msgid "North Bound Latitude" -msgstr "" +#~ msgid "Rights Resource" +#~ msgstr "" -msgid "Geo Location Place" -msgstr "" +#~ msgid "Rights Holder" +#~ msgstr "" -msgid "funder Identifier" -msgstr "" +#~ msgid "Rights Holder Name Identifier" +#~ msgstr "" -msgid "Funder Identifier Type" -msgstr "" +#~ msgid "Rights Holder Name Identifier Scheme" +#~ msgstr "" -msgid "Award Number URI" -msgstr "" +#~ msgid "Rights Holder Name Identifier URI" +#~ msgstr "" -msgid "AwardTitle" -msgstr "" +#~ msgid "Rights Holder Name" +#~ msgstr "" -msgid "Source Title" -msgstr "" +#~ msgid "Resource Type" +#~ msgstr "" -msgid "Number of Pages" -msgstr "" +#~ msgid "Temporal" +#~ msgstr "" -msgid "Degree Grantor Name Identifier Scheme" -msgstr "" +#~ msgid "Geo Location" +#~ msgstr "" -msgid "Conference" -msgstr "" +#~ msgid "Geo Location Point" +#~ msgstr "" -msgid "Conference Name" -msgstr "" +#~ msgid "Point Longitude" +#~ msgstr "" -msgid "Conference Sequence" -msgstr "" +#~ msgid "Point Latitude" +#~ msgstr "" -msgid "Conference Place" -msgstr "" +#~ msgid "Geo Location Box" +#~ msgstr "" -msgid "Conference Country" -msgstr "" +#~ msgid "West Bound Longitude" +#~ msgstr "" -msgid "URI Object Type" -msgstr "" +#~ msgid "East Bound Longitude" +#~ msgstr "" -msgid "URI Label" -msgstr "" +#~ msgid "South Bound Latitude" +#~ msgstr "" -msgid "Format" -msgstr "" +#~ msgid "North Bound Latitude" +#~ msgstr "" -msgid "Extent" -msgstr "" +#~ msgid "Geo Location Place" +#~ msgstr "" -msgid "Issued Date" -msgstr "" +#~ msgid "funder Identifier" +#~ msgstr "" -msgid "Issue" -msgstr "" +#~ msgid "Funder Identifier Type" +#~ msgstr "" -msgid "Volume" -msgstr "" +#~ msgid "Award Number URI" +#~ msgstr "" -msgid "Search repository" -msgstr "" +#~ msgid "AwardTitle" +#~ msgstr "" -msgid "Content File" -msgstr "" +#~ msgid "Source Title" +#~ msgstr "" -msgid "Billing File" -msgstr "" +#~ msgid "Number of Pages" +#~ msgstr "" -msgid "ID Agency" -msgstr "" +#~ msgid "Degree Grantor Name Identifier Scheme" +#~ msgstr "" -msgid "Series" -msgstr "" +#~ msgid "Conference" +#~ msgstr "" -msgid "Version Date" -msgstr "" +#~ msgid "Conference Name" +#~ msgstr "" -msgid "DateType" -msgstr "" +#~ msgid "Conference Sequence" +#~ msgstr "" -msgid "Bibliographic Citation" -msgstr "" +#~ msgid "Conference Place" +#~ msgstr "" -msgid "Topic" -msgstr "" +#~ msgid "Conference Country" +#~ msgstr "" -msgid "Topic" -msgstr "" +#~ msgid "URI Object Type" +#~ msgstr "" -msgid "topic vocabURI" -msgstr "" +#~ msgid "URI Label" +#~ msgstr "" -msgid "subjectScheme" -msgstr "" +#~ msgid "Format" +#~ msgstr "" -msgid "Topic J" -msgstr "" +#~ msgid "Extent" +#~ msgstr "" -msgid "Topic E" -msgstr "" +#~ msgid "Issued Date" +#~ msgstr "" -msgid "Time Period" -msgstr "" +#~ msgid "Issue" +#~ msgstr "" -msgid "Time Period Event" -msgstr "" +#~ msgid "Volume" +#~ msgstr "" -msgid "Date Of Collection Event" -msgstr "" +#~ msgid "Billing File" +#~ msgstr "" -msgid "Geographic Coverage" -msgstr "" +#~ msgid "ID Agency" +#~ msgstr "" -msgid "Unit of Analysis" -msgstr "" +#~ msgid "Series" +#~ msgstr "" -msgid "Unit of Analysis J" -msgstr "" +#~ msgid "Version Date" +#~ msgstr "" -msgid "Unit of Analysis E" -msgstr "" +#~ msgid "DateType" +#~ msgstr "" -msgid "Sampling Procedure E" -msgstr "" +#~ msgid "Bibliographic Citation" +#~ msgstr "" -msgid "Sampling Procedure J" -msgstr "" +#~ msgid "Topic" +#~ msgstr "" -msgid "Collection Method" -msgstr "" +#~ msgid "topic vocabURI" +#~ msgstr "" -msgid "Collection Method J" -msgstr "" +#~ msgid "subjectScheme" +#~ msgstr "" -msgid "Collection Method E" -msgstr "" +#~ msgid "Topic J" +#~ msgstr "" -msgid "Sampling Rate" -msgstr "" +#~ msgid "Topic E" +#~ msgstr "" -msgid "Access" -msgstr "" +#~ msgid "Time Period" +#~ msgstr "" -msgid "Access" -msgstr "" +#~ msgid "Time Period Event" +#~ msgstr "" -msgid "Rdf:Resource" -msgstr "" +#~ msgid "Date Of Collection Event" +#~ msgstr "" -msgid "Access E" -msgstr "" +#~ msgid "Geographic Coverage" +#~ msgstr "" -msgid "Access J" -msgstr "" +#~ msgid "Unit of Analysis" +#~ msgstr "" -msgid "Study ID" -msgstr "" +#~ msgid "Unit of Analysis J" +#~ msgstr "" -msgid "Copyright" -msgstr "" +#~ msgid "Unit of Analysis E" +#~ msgstr "" -msgid "Topic Vocab" -msgstr "" +#~ msgid "Sampling Procedure E" +#~ msgstr "" -msgid "Topic Vocab URI" -msgstr "" +#~ msgid "Sampling Procedure J" +#~ msgstr "" -msgid "Date Of Collection" -msgstr "" +#~ msgid "Collection Method" +#~ msgstr "" -msgid "Event" -msgstr "" +#~ msgid "Collection Method J" +#~ msgstr "" -msgid "Universe" -msgstr "" +#~ msgid "Collection Method E" +#~ msgstr "" -msgid "Data Type J" -msgstr "" +#~ msgid "Sampling Rate" +#~ msgstr "" -msgid "Data Type E" -msgstr "" +#~ msgid "Access" +#~ msgstr "" -msgid "Sampling Procedure" -msgstr "" +#~ msgid "Rdf:Resource" +#~ msgstr "" -msgid "Identifier Registration Type" -msgstr "" +#~ msgid "Access E" +#~ msgstr "" -msgid "Identifier Registration" -msgstr "" +#~ msgid "Access J" +#~ msgstr "" -msgid "Related Study" -msgstr "" +#~ msgid "Study ID" +#~ msgstr "" -msgid "Related Study DOI" -msgstr "" +#~ msgid "Copyright" +#~ msgstr "" -msgid "Related Publications" -msgstr "" +#~ msgid "Topic Vocab" +#~ msgstr "" -msgid "Related Publications DOI" -msgstr "" +#~ msgid "Topic Vocab URI" +#~ msgstr "" -msgid "Fund Agency" -msgstr "" +#~ msgid "Date Of Collection" +#~ msgstr "" -msgid "Fund Agency ID" -msgstr "" +#~ msgid "Event" +#~ msgstr "" -msgid "Funder Identifier Type" -msgstr "" +#~ msgid "Universe" +#~ msgstr "" -msgid "GrantNo" -msgstr "" +#~ msgid "Data Type J" +#~ msgstr "" -msgid "Distributor Abbreviation" -msgstr "" +#~ msgid "Data Type E" +#~ msgstr "" -msgid "Distributor Affiliation" -msgstr "" +#~ msgid "Sampling Procedure" +#~ msgstr "" -msgid "Distributor URI" -msgstr "" +#~ msgid "Identifier Registration Type" +#~ msgstr "" -msgid "Contributor IdentifierType" -msgstr "" +#~ msgid "Identifier Registration" +#~ msgstr "" -msgid "Distributor Name" -msgstr "" +#~ msgid "Related Study" +#~ msgstr "" -msgid "Award Title" -msgstr "" +#~ msgid "Related Study DOI" +#~ msgstr "" -msgid "Related Study Title" -msgstr "" +#~ msgid "Related Publications" +#~ msgstr "" -msgid "Related Study Identifier" -msgstr "" +#~ msgid "Related Publications DOI" +#~ msgstr "" -msgid "Related Publications Title" -msgstr "" +#~ msgid "Fund Agency" +#~ msgstr "" -msgid "Related Publications Identifier" -msgstr "" +#~ msgid "Fund Agency ID" +#~ msgstr "" -msgid "Related Publications Identifier Type" -msgstr "" +#~ msgid "GrantNo" +#~ msgstr "" -msgid "GrantURI" -msgstr "" +#~ msgid "Distributor Abbreviation" +#~ msgstr "" -msgid "Related Study Identifier Type" -msgstr "" +#~ msgid "Distributor Affiliation" +#~ msgstr "" -msgid "Grant No" -msgstr "" +#~ msgid "Distributor URI" +#~ msgstr "" -msgid "Summary DDI" -msgstr "Summary" +#~ msgid "Contributor IdentifierType" +#~ msgstr "" -msgid "This item has been deleted." -msgstr "" +#~ msgid "Distributor Name" +#~ msgstr "" -msgid "Download is available from {}/{}/{}." -msgstr "Download is available from {}/{}/{}." +#~ msgid "Award Title" +#~ msgstr "" -msgid "Download / Preview is available from {}/{}/{}." -msgstr "Download / Preview is available from {}/{}/{}." -msgid "Apply JGSS" -msgstr "Apply" +#~ msgid "Related Study Title" +#~ msgstr "" -msgid "This data is not available for undergraduate students or those who do not register their positions." -msgstr "" +#~ msgid "Related Study Identifier" +#~ msgstr "" -msgid "Please input email address." -msgstr "" +#~ msgid "Related Publications Title" +#~ msgstr "" -msgid "Email address" -msgstr "" +#~ msgid "Related Publications Identifier" +#~ msgstr "" -msgid "Email address(reconfirmation)" -msgstr "" +#~ msgid "Related Publications Identifier Type" +#~ msgstr "" -msgid "Token is invalid." -msgstr "" +#~ msgid "GrantURI" +#~ msgstr "" -msgid "The expiration date for download has been exceeded." -msgstr "" +#~ msgid "Related Study Identifier Type" +#~ msgstr "" -msgid "The download limit has been exceeded." -msgstr "" +#~ msgid "Grant No" +#~ msgstr "" -msgid "This data is not available for this user." -msgstr "" +#~ msgid "Summary DDI" +#~ msgstr "Summary" + +#~ msgid "Download is available from {}/{}/{}." +#~ msgstr "Download is available from {}/{}/{}." + +#~ msgid "Download / Preview is available from {}/{}/{}." +#~ msgstr "Download / Preview is available from {}/{}/{}." + +#~ msgid "" +#~ "This data is not available for " +#~ "undergraduate students or those who do" +#~ " not register their positions." +#~ msgstr "" + +#~ msgid "Please input email address." +#~ msgstr "" + +#~ msgid "Email address" +#~ msgstr "" + +#~ msgid "Email address(reconfirmation)" +#~ msgstr "" + +#~ msgid "Token is invalid." +#~ msgstr "" + +#~ msgid "Success Secret URL Generate" +#~ msgstr "" +#~ "Success: Secret URL Generate is succeed." +#~ " Sent the URL to your email " +#~ "adress." -msgid "Success Secret URL Generate" -msgstr "Success: Secret URL Generate is succeed. Sent the URL to your email adress." diff --git a/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.mo b/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.mo index e710cb429d20d6eba45f78e6b0aadb087f22acad..743f322282bbf3be425a32da8ae98db40b597468 100644 GIT binary patch delta 2597 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 diff --git a/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.po b/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.po index f9440e6489..8947ee7772 100644 --- a/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.po +++ b/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: weko-records-ui 0.1.0.dev20170000\n" "Report-Msgid-Bugs-To: wekosoftware@nii.ac.jp\n" -"POT-Creation-Date: 2025-02-06 17:25+0900\n" +"POT-Creation-Date: 2025-02-10 15:07+0900\n" "PO-Revision-Date: 2021-02-02 03:25+0000\n" "Last-Translator: FULL NAME \n" "Language: ja\n" @@ -210,22 +210,6 @@ msgstr "が、メール送信エラーが発生しました。ページを更新 msgid "." msgstr "。" -#: weko_records_ui/views.py:812 -msgid "The secret URL copied to your clipboard." -msgstr "シークレットURLをコピーしました。" - -#: weko_records_ui/views.py:845 -msgid "The onetime URL copied to your clipboard." -msgstr "ワンタイムURLをコピーしました。" - -#: weko_records_ui/views.py:880 -msgid "The secret URL has been successfully deleted." -msgstr "シークレットURLは正常に削除されました。" - -#: weko_records_ui/views.py:915 -msgid "The one-time URL has been successfully deleted." -msgstr "ワンタイムURLは正常に削除されました。" - #: weko_records_ui/views.py:1008 msgid "PDF cover page settings have been updated." msgstr "" diff --git a/modules/weko-records-ui/weko_records_ui/translations/messages.pot b/modules/weko-records-ui/weko_records_ui/translations/messages.pot index ae6568da2a..ac0a4d44dd 100644 --- a/modules/weko-records-ui/weko_records_ui/translations/messages.pot +++ b/modules/weko-records-ui/weko_records_ui/translations/messages.pot @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: weko-records-ui 0.1.0.dev20170000\n" "Report-Msgid-Bugs-To: wekosoftware@nii.ac.jp\n" -"POT-Creation-Date: 2025-02-06 17:25+0900\n" +"POT-Creation-Date: 2025-02-10 15:07+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -206,22 +206,6 @@ msgstr "" msgid "." msgstr "" -#: weko_records_ui/views.py:812 -msgid "The secret URL copied to your clipboard." -msgstr "" - -#: weko_records_ui/views.py:845 -msgid "The onetime URL copied to your clipboard." -msgstr "" - -#: weko_records_ui/views.py:880 -msgid "The secret URL has been successfully deleted." -msgstr "" - -#: weko_records_ui/views.py:915 -msgid "The one-time URL has been successfully deleted." -msgstr "" - #: weko_records_ui/views.py:1008 msgid "PDF cover page settings have been updated." msgstr "" From 8e69598d477da1646d07ec4c9db3dce9ef9b2049 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Mon, 10 Feb 2025 15:13:02 +0900 Subject: [PATCH 51/61] Adjust views messages --- modules/weko-records-ui/weko_records_ui/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/views.py b/modules/weko-records-ui/weko_records_ui/views.py index d3f52ef7a5..be6acfe137 100644 --- a/modules/weko-records-ui/weko_records_ui/views.py +++ b/modules/weko-records-ui/weko_records_ui/views.py @@ -809,7 +809,7 @@ def copy_secret_url(pid, record, **kwargs): abort(500) return jsonify({'url': url, - 'message': _('The secret URL copied to your clipboard.')}) + 'message': 'The secret URL copied to your clipboard.'}) def copy_onetime_url(pid, record, **kwargs): @@ -842,7 +842,7 @@ def copy_onetime_url(pid, record, **kwargs): abort(500) return jsonify({'url': url, - 'message': _('The onetime URL copied to your clipboard.')}) + 'message': 'The onetime URL copied to your clipboard.'}) def delete_secret_url(pid, record, **kwargs): @@ -877,7 +877,7 @@ def delete_secret_url(pid, record, **kwargs): abort(500) return jsonify( - {'message': _('The secret URL has been successfully deleted.')}) + {'message': 'The secret URL has been successfully deleted.'}) def delete_onetime_url(pid, record, **kwargs): @@ -912,7 +912,7 @@ def delete_onetime_url(pid, record, **kwargs): abort(500) return jsonify( - {'message': _('The one-time URL has been successfully deleted.')}) + {'message': 'The one-time URL has been successfully deleted.'}) @blueprint.route('/r/', methods=['GET']) From da64a8906f48254f196e4c0cab2ffe34ca894ccd Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Fri, 14 Feb 2025 17:13:55 +0900 Subject: [PATCH 52/61] Fix integration test failures --- .../static/js/weko_admin/restricted_access.js | 15 +- .../admin/restricted_access_settings.html | 4 +- modules/weko-records-ui/tests/test_utils.py | 52 +++-- modules/weko-records-ui/weko_records_ui/fd.py | 4 +- .../static/js/weko_records_ui/detail.js | 9 +- .../file_details_contents.html | 180 +++++++++--------- .../translations/ja/LC_MESSAGES/messages.mo | Bin 11990 -> 11990 bytes .../translations/ja/LC_MESSAGES/messages.po | 4 +- .../weko-records-ui/weko_records_ui/utils.py | 59 ++++-- .../weko-records-ui/weko_records_ui/views.py | 3 +- 10 files changed, 192 insertions(+), 138 deletions(-) diff --git a/modules/weko-admin/weko_admin/static/js/weko_admin/restricted_access.js b/modules/weko-admin/weko_admin/static/js/weko_admin/restricted_access.js index 3a82fd47d2..1f9cb325be 100644 --- a/modules/weko-admin/weko_admin/static/js/weko_admin/restricted_access.js +++ b/modules/weko-admin/weko_admin/static/js/weko_admin/restricted_access.js @@ -528,13 +528,22 @@ function RestrictedAccessLayout({ showErrorMessage(MESSAGE_MISSING_DATA); return false; } - + + // Ensure the values are integers + const parsedSecretURLFileDownload = { + ...secretURLFileDownload, + secret_download_limit: parseInt(secretURLFileDownload.secret_download_limit, 10), + max_secret_expiration_date: parseInt(secretURLFileDownload.max_secret_expiration_date, 10), + max_secret_download_limit: parseInt(secretURLFileDownload.max_secret_download_limit, 10), + secret_expiration_date: parseInt(secretURLFileDownload.secret_expiration_date, 10) + }; + let data = { - secret_URL_file_download:secretURLFileDownload, + secret_URL_file_download:parsedSecretURLFileDownload, content_file_download: contentFileDownload, usage_report_workflow_access: usageReportWorkflowAccess, terms_and_conditions: terms_data["data"] - } + }; $.ajax({ url: URL, diff --git a/modules/weko-admin/weko_admin/templates/weko_admin/admin/restricted_access_settings.html b/modules/weko-admin/weko_admin/templates/weko_admin/admin/restricted_access_settings.html index af80be7458..3efa7d2f0c 100644 --- a/modules/weko-admin/weko_admin/templates/weko_admin/admin/restricted_access_settings.html +++ b/modules/weko-admin/weko_admin/templates/weko_admin/admin/restricted_access_settings.html @@ -60,9 +60,9 @@ + value='{{ _("Must set a positive integer and less than %(name1)s for %(name2)s.", name1=max_download_limit, name2=download_limit_initial) }}'/> + value='{{ _("Must set a positive integer and less than %(name1)s for %(name2)s.", name1=max_expiration_date, name2=expiration_date_initial) }}'/> diff --git a/modules/weko-records-ui/tests/test_utils.py b/modules/weko-records-ui/tests/test_utils.py index 9c8edc1a76..f033376fd4 100644 --- a/modules/weko-records-ui/tests/test_utils.py +++ b/modules/weko-records-ui/tests/test_utils.py @@ -45,6 +45,7 @@ get_terms, get_roles, check_items_settings, + validate_expiration_date, validate_file_access, validate_secret_url_generation_request, #RoCrateConverter, @@ -706,7 +707,7 @@ def test_is_secret_file(file_data, filename, expected): # dt(日時関連)をモックして、現在の日付や日付文字列の変換を制御する with patch('weko_records_ui.utils.dt') as mock_dt: - mock_dt.now.return_value = dt(2024, 1, 1) # 現在の日付を2024年1月1日に設定 + mock_dt.utcnow.return_value = dt(2024, 1, 1) # 現在の日付を2024年1月1日に設定 mock_dt.strptime.side_effect = lambda *args, **kwargs: dt.strptime(*args, **kwargs) # strptimeの動作をモック # is_secret_file関数を実行して、結果が期待される値と一致するかを確認 @@ -907,31 +908,27 @@ def test_to_utc_datetime(app): # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_validate_secret_url_generation_request -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp -def test_validate_secret_url_generation_request(app): - today = datetime.now().strftime("%Y-%m-%d") - tomorrow = (datetime.now() + timedelta(1)).strftime("%Y-%m-%d") - yesterday = (datetime.now() - timedelta(1)).strftime("%Y-%m-%d") +@patch('weko_records_ui.utils.validate_expiration_date') +def test_validate_secret_url_generation_request(mock_date, app): + mock_date.return_value = True base_case = {'link_name' : '', - 'expiration_date' : '', - 'download_limit' : None, + 'expiration_date' : 'mocked', + 'download_limit' : 1, 'send_email' : False, 'timezone_offset_minutes': 0} test_cases = [ (None, False), - # Base case is valid - (base_case, - True), # When all fields are valid ({'link_name' : '123', - 'expiration_date' : tomorrow, + 'expiration_date' : 'mocked', 'download_limit' : 1, 'send_email' : False, 'timezone_offset_minutes': 0}, True), # When all fields are invalid ({'link_name' : 123, - 'expiration_date' : yesterday, + 'expiration_date' : 'mocked', 'download_limit' : 0, 'send_email' : None, 'timezone_offset_minutes': '0'}, @@ -951,12 +948,6 @@ def test_validate_secret_url_generation_request(app): ({**base_case, 'link_name': '123' }, True), ({**base_case, 'link_name': 123 }, False), ({**base_case, 'link_name': 'a' * 256}, False), - # For expiration_date - ({**base_case, 'expiration_date': today }, True), - ({**base_case, 'expiration_date': tomorrow }, True), - ({**base_case, 'expiration_date': yesterday}, False), - ({**base_case, 'expiration_date': 'abc' }, False), - ({**base_case, 'expiration_date': 20250101 }, False), # For download_limit ({**base_case, 'download_limit': 1 }, True), ({**base_case, 'download_limit': 0 }, False), @@ -980,6 +971,31 @@ def test_validate_secret_url_generation_request(app): assert validate_secret_url_generation_request(request_data) is expected +# .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_validate_expiration_date -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp +def test_validate_expiration_date(app): + assert validate_expiration_date('1999-12-31', 0) is False + + yesterday = (datetime.now() - timedelta(1)).strftime("%Y-%m-%d") + assert validate_expiration_date(yesterday, 0) is False + + tomorrow = (datetime.now() + timedelta(1)).strftime("%Y-%m-%d") + with patch('weko_records_ui.utils.get_restricted_access') as mock_settings: + mock_settings.return_value = None + assert validate_expiration_date(tomorrow, 0) is False + + in_a_week = (datetime.now() + timedelta(7)).strftime("%Y-%m-%d") + with patch('weko_records_ui.utils.get_restricted_access') as mock_settings: + mock_settings.return_value = {'secret_expiration_date': 1} + assert validate_expiration_date(in_a_week, 0) is False + + in_an_year = (datetime.now() + timedelta(365)).strftime("%Y-%m-%d") + with patch('weko_records_ui.utils.get_restricted_access') as mock_settings: + mock_settings.return_value = {} + assert validate_expiration_date(in_an_year, 0) is False + + assert validate_expiration_date(tomorrow, 0) is True + + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_create_secret_url_record -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp @patch('weko_records_ui.utils.get_restricted_access') @patch('weko_records_ui.utils.current_user') diff --git a/modules/weko-records-ui/weko_records_ui/fd.py b/modules/weko-records-ui/weko_records_ui/fd.py index 4fc12ea107..2f59e403b7 100644 --- a/modules/weko-records-ui/weko_records_ui/fd.py +++ b/modules/weko-records-ui/weko_records_ui/fd.py @@ -461,8 +461,8 @@ def file_download_onetime(pid, record, filename, _record_file_factory=None, # Increase the download count and save the download log try: - url_obj.increment_download_count() save_download_log(record, filename, token, is_secret_url=False) + url_obj.increment_download_count() except Exception as e: current_app.logger.error(e) return error_response(_('Unexpected error occurred.'), 500) @@ -539,8 +539,8 @@ def file_download_secret(pid, record, filename, _record_file_factory=None, # Increase the download count and save the download log url_obj = convert_token_into_obj(token, is_secret_url=True) try: - url_obj.increment_download_count() save_download_log(record, filename, token, is_secret_url=True) + url_obj.increment_download_count() except Exception as e: current_app.logger.error(e) return error_response(_('Unexpected error occurred.'), 500) diff --git a/modules/weko-records-ui/weko_records_ui/static/js/weko_records_ui/detail.js b/modules/weko-records-ui/weko_records_ui/static/js/weko_records_ui/detail.js index 298fcd95c1..c23f99c194 100644 --- a/modules/weko-records-ui/weko_records_ui/static/js/weko_records_ui/detail.js +++ b/modules/weko-records-ui/weko_records_ui/static/js/weko_records_ui/detail.js @@ -359,10 +359,11 @@ require([ const expirationDate = $('#expiration_date').val(); const downloadLimit = $('#download_limit').val(); const sendEmail = $('#send_email').is(':checked'); + const timezoneOffsetMinutes = new Date().getTimezoneOffset(); const linkNameError = $('#link_name_error'); const expirationDateError = $('#expiration_date_error'); const downloadLimitError = $('#download_limit_error'); - + let hasError = false; // エラーチェック関数 @@ -393,12 +394,14 @@ require([ data: JSON.stringify({ link_name: linkName, expiration_date: expirationDate, - download_limit: downloadLimit, - send_email: sendEmail + download_limit: parseInt(downloadLimit), + send_email: sendEmail, + timezone_offset_minutes: timezoneOffsetMinutes, }), success: function(response) { webelement.prop('disabled', false); alert(response.message || "Success!"); + location.reload(); }, error: function(jqXHR, status, msg) { webelement.prop('disabled', false); diff --git a/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details_contents.html b/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details_contents.html index 363151b005..197844b5bc 100644 --- a/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details_contents.html +++ b/modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/file_details_contents.html @@ -224,95 +224,99 @@

{{ filename. {%- endfor -%}

- {%- if active_secret_URLs -%} -
- - - - - - - - - - - - - - - {%- for url in active_secret_URLs -%} - - - - - - - - {%- endfor -%} - -
{{_('Secret URL')}}
{{_('Label Name')}}{{_('Create Date')}}{{_('Expiration Date')}}{{_('Download Count')}}{{_('Action')}}
{{ url.label_name }}{{ url.created }}{{ url.expiration_date }}{{ url.download_count }}/{{url.download_limit}} -
- - -
-
-
-
-
-
+ {%- if show_secret_URL -%} + {%- if active_secret_URLs -%} +
+ + + + + + + + + + + + + + + {%- for url in active_secret_URLs -%} + + + + + + + + {%- endfor -%} + +
{{_('Secret URL')}}
{{_('Label Name')}}{{_('Create Date')}}{{_('Expiration Date')}}{{_('Download Count')}}{{_('Action')}}
{{ url.label_name }}{{ url.created }}{{ url.expiration_date }}{{ url.download_count }}/{{url.download_limit}} +
+ + +
+
+
+
+
+
+ {%- endif -%} {%- endif -%} - {%- if active_onetime_URLs -%} -
- - - - - - - - - - - - - - - {%- for url in active_onetime_URLs -%} - - - - - - - - {%- endfor -%} - -
{{_('Onetime URL')}}
{{_('User Name')}}{{_('Create Date')}}{{_('Expiration Date')}}{{_('Download Count')}}{{_('Action')}}
{{ url.user_mail }}{{ url.created }}{{ url.expiration_date }}{{ url.download_count }}/{{url.download_limit}} -
- - -
-
-
-
-
-
+ {%- if show_onetime_URL -%} + {%- if active_onetime_URLs -%} +
+ + + + + + + + + + + + + + + {%- for url in active_onetime_URLs -%} + + + + + + + + {%- endfor -%} + +
{{_('Onetime URL')}}
{{_('User Name')}}{{_('Create Date')}}{{_('Expiration Date')}}{{_('Download Count')}}{{_('Action')}}
{{ url.user_mail }}{{ url.created }}{{ url.expiration_date }}{{ url.download_count }}/{{url.download_limit}} +
+ + +
+
+
+
+
+
+ {%- endif -%} {%- endif -%}
{%- endif -%} {%- endif -%} @@ -312,9 +312,9 @@

{{ filename. {%- endfor -%} -
-
-
+
+
+

{%- endif -%} {%- endif -%} diff --git a/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.mo b/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.mo index f6df3455ddebd759b3a77304df0f6ea8e34e4f26..64569bd09361929fe4c7bb5bea653b6a8e355983 100644 GIT binary patch delta 53 zcmewp{U>_E8!3&{;^M^g)cEB5g39>f(&S_ypCK12n39?kpPZ4JoDCG&EF%3v2mpa+ B75e}H delta 53 zcmewp{U>_E8!3&%;^M^g)cEB5g39>f(&XgS;$ntes9;KJPJD7kYH~JEWV49$3n2i2 CI~DH$ diff --git a/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.po b/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.po index b5f6b38615..8e462f94c8 100644 --- a/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.po +++ b/modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.po @@ -1563,11 +1563,11 @@ msgstr "Success: Secret URL Generate is succeed. Sent the URL to your email adre msgid "Max Download Count" msgstr "Max Download Limit" -msgid "massage_del_check" +msgid "message_del_check" msgstr "If you delete this URL, it will no longer be available. Are you sure you want to delete it?" -msgid "massage_del_success" +msgid "message_del_success" msgstr "URL has been removed" -msgid "massage_copy_success" +msgid "message_copy_success" msgstr "URL has been copied to the clipboard" diff --git a/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.mo b/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.mo index 7617a04930b149e34144a711e0074a333028c5f7..0d4b76b585406ecf4e42b25b3a3309f02c1859ef 100644 GIT binary patch delta 53 zcmcZ@cQI~5jI>5-adBdLYJ75jL1lb#X>u}<&yWihOi9g&PtHh9&IXEXZjnAK3;5#adBdLYJ75jL1lb#X>xLEaWO+KR4^qqCq6kNH8~q7vbjb2tS|sp CuN7th diff --git a/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.po b/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.po index fa341bcccf..55774890aa 100644 --- a/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.po +++ b/modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.po @@ -381,17 +381,17 @@ msgstr "コピー" #: weko_records_ui/templates/weko_records_ui/file_details_contents.html:268 #: weko_records_ui/templates/weko_records_ui/file_details_contents.html:315 -msgid "massage_del_check" +msgid "message_del_check" msgstr "このURLを削除すると、利用できなくなります。本当に削除しますか?" #: weko_records_ui/templates/weko_records_ui/file_details_contents.html:269 #: weko_records_ui/templates/weko_records_ui/file_details_contents.html:316 -msgid "massage_del_success" +msgid "message_del_success" msgstr "URLが削除されました" #: weko_records_ui/templates/weko_records_ui/file_details_contents.html:270 #: weko_records_ui/templates/weko_records_ui/file_details_contents.html:317 -msgid "massage_copy_success" +msgid "message_copy_success" msgstr "URLがクリップボードにコピーされました" #: weko_records_ui/templates/weko_records_ui/file_details_contents.html:280 diff --git a/modules/weko-records-ui/weko_records_ui/translations/messages.pot b/modules/weko-records-ui/weko_records_ui/translations/messages.pot index 86018e9be6..ecdeac9987 100644 --- a/modules/weko-records-ui/weko_records_ui/translations/messages.pot +++ b/modules/weko-records-ui/weko_records_ui/translations/messages.pot @@ -380,17 +380,17 @@ msgstr "" #: weko_records_ui/templates/weko_records_ui/file_details_contents.html:268 #: weko_records_ui/templates/weko_records_ui/file_details_contents.html:315 -msgid "massage_del_check" +msgid "message_del_check" msgstr "" #: weko_records_ui/templates/weko_records_ui/file_details_contents.html:269 #: weko_records_ui/templates/weko_records_ui/file_details_contents.html:316 -msgid "massage_del_success" +msgid "message_del_success" msgstr "" #: weko_records_ui/templates/weko_records_ui/file_details_contents.html:270 #: weko_records_ui/templates/weko_records_ui/file_details_contents.html:317 -msgid "massage_copy_success" +msgid "message_copy_success" msgstr "" #: weko_records_ui/templates/weko_records_ui/file_details_contents.html:280 From 9e560aa495caab2f351e0b37bb9e6673b4e3fb09 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Wed, 12 Mar 2025 17:37:38 +0900 Subject: [PATCH 59/61] Modify the way the table is changed --- ...d08c_create_file_url_download_log_table.py | 199 +++++++----------- 1 file changed, 71 insertions(+), 128 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/alembic/e0b1ef08d08c_create_file_url_download_log_table.py b/modules/weko-records-ui/weko_records_ui/alembic/e0b1ef08d08c_create_file_url_download_log_table.py index 5ce933c85e..cb177f2593 100644 --- a/modules/weko-records-ui/weko_records_ui/alembic/e0b1ef08d08c_create_file_url_download_log_table.py +++ b/modules/weko-records-ui/weko_records_ui/alembic/e0b1ef08d08c_create_file_url_download_log_table.py @@ -19,11 +19,57 @@ branch_labels = () depends_on = 'invenio_accounts' + def upgrade(): """Upgrade database.""" bind = op.get_bind() session = Session(bind=bind) + # Recreate 'file_onetime_download' table + op.drop_table('file_onetime_download') + op.create_table( + 'file_onetime_download', + sa.Column('created', sa.TIMESTAMP(timezone=False), nullable=False), + sa.Column('updated', sa.TIMESTAMP(timezone=False), nullable=False), + sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True), + sa.Column('approver_id', sa.Integer(), nullable=False), + sa.Column('record_id', sa.String(255), nullable=False), + sa.Column('file_name', sa.String(255), nullable=False), + sa.Column('expiration_date', sa.TIMESTAMP(timezone=False), nullable=False), + sa.Column('download_limit', sa.Integer(), nullable=False), + sa.Column('download_count', sa.Integer(), nullable=False, server_default=sa.text('0')), + sa.Column('user_mail', sa.String(255), nullable=False), + sa.Column('is_guest', sa.Boolean(), nullable=False), + sa.Column('is_deleted', sa.Boolean(), nullable=False, server_default=sa.text('FALSE')), + sa.Column('extra_info', sa.JSON(), nullable=False, server_default=sa.text("'{}'")), + sa.ForeignKeyConstraint(['approver_id'], ['accounts_user.id'], name='fk_file_onetime_download_approver_id'), + sa.CheckConstraint('created < expiration_date', name='check_expiration_date'), + sa.CheckConstraint('download_limit > 0', name='check_download_limit_positive'), + sa.CheckConstraint('download_count <= download_limit', name='check_download_count_limit') + ) + + # Recreate 'file_secret_download' table + op.drop_table('file_secret_download') + op.create_table( + 'file_secret_download', + sa.Column('created', sa.TIMESTAMP(timezone=False), nullable=False), + sa.Column('updated', sa.TIMESTAMP(timezone=False), nullable=False), + sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True), + sa.Column('creator_id', sa.Integer(), nullable=False), + sa.Column('record_id', sa.String(255), nullable=False), + sa.Column('file_name', sa.String(255), nullable=False), + sa.Column('label_name', sa.String(255), nullable=False), + sa.Column('expiration_date', sa.TIMESTAMP(timezone=False), nullable=False), + sa.Column('download_limit', sa.Integer(), nullable=False), + sa.Column('download_count', sa.Integer(), nullable=False, server_default=sa.text('0')), + sa.Column('is_deleted', sa.Boolean(), nullable=False, server_default=sa.text('FALSE')), + sa.ForeignKeyConstraint(['creator_id'], ['accounts_user.id'], name='fk_file_secret_download_creator_id'), + sa.CheckConstraint('created < expiration_date', name='check_expiration_date'), + sa.CheckConstraint('download_limit > 0', name='check_download_limit_positive'), + sa.CheckConstraint('download_count <= download_limit', name='check_download_count_limit') + ) + + # Add 'file_url_download_log' table op.create_table( 'file_url_download_log', sa.Column('created', sa.TIMESTAMP(timezone=False), nullable=False), @@ -61,80 +107,6 @@ def upgrade(): ) ) - # Modify 'file_onetime_download' table - op.execute("TRUNCATE TABLE file_onetime_download RESTART IDENTITY CASCADE") - op.add_column('file_onetime_download', sa.Column('approver_id', sa.Integer(), nullable=False)) - op.add_column('file_onetime_download', sa.Column('is_guest', sa.Boolean(), nullable=True)) - session.execute(""" - UPDATE file_onetime_download f - SET is_guest = NOT EXISTS ( - SELECT 1 FROM accounts_user a WHERE a.email = f.user_mail) - """) - op.alter_column('file_onetime_download', 'is_guest', nullable=False) - op.add_column('file_onetime_download', sa.Column('is_deleted', sa.Boolean(), nullable=False, server_default=sa.text('FALSE'))) - op.add_column('file_onetime_download', sa.Column('download_limit', sa.Integer(), nullable=True)) - session.execute(""" - UPDATE file_onetime_download - SET download_limit = download_count - """) - session.execute(""" - UPDATE file_onetime_download - SET download_count = 0 - """) - op.alter_column('file_onetime_download', 'download_limit', nullable=False) - op.add_column('file_onetime_download', sa.Column('new_expiration_date', sa.DateTime(), nullable=True)) - session.execute(""" - UPDATE file_onetime_download - SET new_expiration_date = created + INTERVAL '1 day' * expiration_date - """) - op.drop_column('file_onetime_download', 'expiration_date') - op.alter_column('file_onetime_download', 'new_expiration_date', new_column_name='expiration_date') - - # Add constraints to 'file_onetime_download' table - op.create_foreign_key('fk_file_onetime_download_approver_id', 'file_onetime_download', 'accounts_user', ['approver_id'], ['id']) - op.create_check_constraint('check_expiration_date', 'file_onetime_download', 'created < expiration_date') - op.create_check_constraint('check_download_limit_positive', 'file_onetime_download', 'download_limit > 0') - op.create_check_constraint('check_download_count_limit', 'file_onetime_download', 'download_count <= download_limit') - - # Modify 'file_secret_download' table - op.add_column('file_secret_download', sa.Column('creator_id', sa.Integer(), nullable=True)) - session.execute(""" - UPDATE file_secret_download f - SET creator_id = (SELECT id FROM accounts_user WHERE email = f.user_mail) - """) - op.alter_column('file_secret_download', 'creator_id', nullable=False) - op.drop_column('file_secret_download', 'user_mail') - op.add_column('file_secret_download', sa.Column('label_name', sa.String(255), nullable=True)) - session.execute(""" - UPDATE file_secret_download - SET label_name = TO_CHAR(created, 'YYYY-MM-DD') || '_' || file_name - """) - op.alter_column('file_secret_download', 'label_name', nullable=False) - op.add_column('file_secret_download', sa.Column('is_deleted', sa.Boolean(), nullable=False, server_default=sa.text('FALSE'))) - op.add_column('file_secret_download', sa.Column('download_limit', sa.Integer(), nullable=True)) - session.execute(""" - UPDATE file_secret_download - SET download_limit = download_count - """) - session.execute(""" - UPDATE file_secret_download - SET download_count = 0 - """) - op.alter_column('file_secret_download', 'download_limit', nullable=False) - op.add_column('file_secret_download', sa.Column('new_expiration_date', sa.DateTime(), nullable=True)) - session.execute(""" - UPDATE file_secret_download - SET new_expiration_date = created + INTERVAL '1 day' * expiration_date - """) - op.drop_column('file_secret_download', 'expiration_date') - op.alter_column('file_secret_download', 'new_expiration_date', new_column_name='expiration_date') - - # Add constraints to 'file_secret_download' table - op.create_foreign_key('fk_file_secret_download_creator_id', 'file_secret_download', 'accounts_user', ['creator_id'], ['id']) - op.create_check_constraint('check_expiration_date', 'file_secret_download', 'created < expiration_date') - op.create_check_constraint('check_download_limit_positive', 'file_secret_download', 'download_limit > 0') - op.create_check_constraint('check_download_count_limit', 'file_secret_download', 'download_count <= download_limit') - def downgrade(): """Downgrade database.""" bind = op.get_bind() @@ -143,57 +115,28 @@ def downgrade(): op.drop_table('file_url_download_log') op.execute("DROP TYPE IF EXISTS urltype;") op.execute("DROP TYPE IF EXISTS accessstatus;") - - # Remove constraints from 'file_onetime_download' table - op.drop_constraint('check_download_count_limit', 'file_onetime_download', type_='check') - op.drop_constraint('check_download_limit_positive', 'file_onetime_download', type_='check') - op.drop_constraint('check_expiration_date', 'file_onetime_download', type_='check') - op.drop_constraint('fk_file_onetime_download_approver_id', 'file_onetime_download', type_='foreignkey') - - # Modify 'file_onetime_download' table - op.alter_column('file_onetime_download', 'expiration_date', new_column_name='new_expiration_date') - op.add_column('file_onetime_download', sa.Column('expiration_date', sa.Integer(), nullable=True)) - session.execute(""" - UPDATE file_onetime_download - SET expiration_date = EXTRACT(DAY FROM (new_expiration_date - created)) - """) - op.alter_column('file_onetime_download', 'expiration_date', nullable=False) - op.drop_column('file_onetime_download', 'new_expiration_date') - session.execute(""" - UPDATE file_onetime_download - SET download_count = download_limit - """) - op.drop_column('file_onetime_download', 'download_limit') - op.drop_column('file_onetime_download', 'is_deleted') - op.drop_column('file_onetime_download', 'is_guest') - op.drop_column('file_onetime_download', 'approver_id') - - # Remove constraints from 'file_secret_download' table - op.drop_constraint('check_download_count_limit', 'file_secret_download', type_='check') - op.drop_constraint('check_download_limit_positive', 'file_secret_download', type_='check') - op.drop_constraint('check_expiration_date', 'file_secret_download', type_='check') - op.drop_constraint('fk_file_secret_download_creator_id', 'file_secret_download', type_='foreignkey') - - # Modify 'file_secret_download' table - op.alter_column('file_secret_download', 'expiration_date', new_column_name='new_expiration_date') - op.add_column('file_secret_download', sa.Column('expiration_date', sa.Integer(), nullable=True)) - session.execute(""" - UPDATE file_secret_download - SET expiration_date = EXTRACT(DAY FROM (new_expiration_date - created)) - """) - op.alter_column('file_secret_download', 'expiration_date', nullable=False) - op.drop_column('file_secret_download', 'new_expiration_date') - session.execute(""" - UPDATE file_secret_download - SET download_count = download_limit - """) - op.drop_column('file_secret_download', 'download_limit') - op.drop_column('file_secret_download', 'is_deleted') - op.drop_column('file_secret_download', 'label_name') - op.add_column('file_secret_download', sa.Column('user_mail', sa.String(255), nullable=True)) - session.execute(""" - UPDATE file_secret_download f - SET user_mail = (SELECT email FROM accounts_user WHERE id = f.creator_id) - """) - op.alter_column('file_secret_download', 'user_mail', nullable=False) - op.drop_column('file_secret_download', 'creator_id') + op.drop_table('file_onetime_download') + op.create_table( + 'file_onetime_download', + sa.Column('created', sa.TIMESTAMP(timezone=False), nullable=False), + sa.Column('updated', sa.TIMESTAMP(timezone=False), nullable=False), + sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True), + sa.Column('file_name', sa.String(255), nullable=False), + sa.Column('user_mail', sa.String(255), nullable=False), + sa.Column('record_id', sa.String(255), nullable=False), + sa.Column('download_count', sa.Integer(), nullable=False, server_default=sa.text('0')), + sa.Column('expiration_date', sa.Integer(), nullable=False, server_default=sa.text('0')), + sa.Column('extra_info', sa.JSON(), nullable=True, server_default=sa.text("'{}'")), + ) + op.drop_table('file_secret_download') + op.create_table( + 'file_secret_download', + sa.Column('created', sa.TIMESTAMP(timezone=False), nullable=False), + sa.Column('updated', sa.TIMESTAMP(timezone=False), nullable=False), + sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True), + sa.Column('file_name', sa.String(255), nullable=False), + sa.Column('user_mail', sa.String(255), nullable=False), + sa.Column('record_id', sa.String(255), nullable=False), + sa.Column('download_count', sa.Integer(), nullable=False, server_default=sa.text('0')), + sa.Column('expiration_date', sa.Integer(), nullable=False, server_default=sa.text('0')), + ) From 77fd4d414fae2605079fa7716a96b1576baef9d6 Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Wed, 12 Mar 2025 17:41:36 +0900 Subject: [PATCH 60/61] Remove unnecessary codes --- .../e0b1ef08d08c_create_file_url_download_log_table.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/weko-records-ui/weko_records_ui/alembic/e0b1ef08d08c_create_file_url_download_log_table.py b/modules/weko-records-ui/weko_records_ui/alembic/e0b1ef08d08c_create_file_url_download_log_table.py index cb177f2593..5fc43128b4 100644 --- a/modules/weko-records-ui/weko_records_ui/alembic/e0b1ef08d08c_create_file_url_download_log_table.py +++ b/modules/weko-records-ui/weko_records_ui/alembic/e0b1ef08d08c_create_file_url_download_log_table.py @@ -22,8 +22,6 @@ def upgrade(): """Upgrade database.""" - bind = op.get_bind() - session = Session(bind=bind) # Recreate 'file_onetime_download' table op.drop_table('file_onetime_download') @@ -109,8 +107,6 @@ def upgrade(): def downgrade(): """Downgrade database.""" - bind = op.get_bind() - session = Session(bind=bind) op.drop_table('file_url_download_log') op.execute("DROP TYPE IF EXISTS urltype;") From ae9f2784e8fa87a4824a4bc62e5dfde27103922e Mon Sep 17 00:00:00 2001 From: Shunta Yachi Date: Thu, 13 Mar 2025 09:13:31 +0900 Subject: [PATCH 61/61] Fix tests --- modules/weko-records-ui/tests/test_fd.py | 10 ------- modules/weko-records-ui/tests/test_models.py | 29 ++++++++++++++++++++ modules/weko-records-ui/tests/test_utils.py | 13 ++++++++- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/modules/weko-records-ui/tests/test_fd.py b/modules/weko-records-ui/tests/test_fd.py index 5b974dd173..f8cdc99b18 100644 --- a/modules/weko-records-ui/tests/test_fd.py +++ b/modules/weko-records-ui/tests/test_fd.py @@ -315,11 +315,6 @@ def test_file_download_onetime(dl_file, save_log, chk_and_send, err_res, assert file_download_onetime( pid, record, filename) == 'ERROR' - # 'accessrole' of file object is not 'open_restricted' - with patch.object(file_obj, 'get', return_value='open_no'): - assert file_download_onetime( - pid, record, filename, _record_file_factory) == 'ERROR' - # check_and_send_usage_report() returns an error with patch('weko_records_ui.fd.check_and_send_usage_report', return_value='ERROR'): @@ -460,11 +455,6 @@ def test_file_download_secret(dl_file, save_log, current_user, err_res, assert file_download_secret( pid, record, filename) == 'ERROR' - # 'accessrole' of file object is not 'open_no' - with patch.object(file_obj, 'get', return_value='open_restricted'): - assert file_download_secret( - pid, record, filename, _record_file_factory) == 'ERROR' - # save_download_log() raises an exception with patch('weko_records_ui.fd.save_download_log', side_effect=Exception): diff --git a/modules/weko-records-ui/tests/test_models.py b/modules/weko-records-ui/tests/test_models.py index c24620e7b4..b58b743035 100644 --- a/modules/weko-records-ui/tests/test_models.py +++ b/modules/weko-records-ui/tests/test_models.py @@ -270,6 +270,21 @@ def test_delete_logically(self, users): mock_commit.assert_called_once() assert rec2.is_deleted is False + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileOnetimeDownload::test_fetch_active_urls -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp + def test_fetch_active_urls(self, users): + recid = self.base_data['record_id'] + filename = self.base_data['file_name'] + assert FileOnetimeDownload.fetch_active_urls(recid, filename) == [] + FileOnetimeDownload.create(**self.base_data) + assert len(FileOnetimeDownload.fetch_active_urls(recid, filename)) == 1 + FileOnetimeDownload.create(**self.base_data) + assert len(FileOnetimeDownload.fetch_active_urls(recid, filename)) == 2 + assert FileOnetimeDownload.fetch_active_urls( + recid, filename, ascending=True)[0].id == 1 + assert FileOnetimeDownload.fetch_active_urls( + recid, filename, ascending=False)[0].id == 2 + + class TestFileSecretDownload: expiration_date = datetime.now(timezone.utc) + timedelta(hours=24) no_tz = expiration_date.replace(tzinfo=None) @@ -372,6 +387,20 @@ def test_delete_logically(self, users): mock_commit.assert_called_once() assert rec2.is_deleted is False + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::TestFileSecretDownload::test_fetch_active_urls -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp + def test_fetch_active_urls(self, users): + recid = self.base_data['record_id'] + filename = self.base_data['file_name'] + assert FileSecretDownload.fetch_active_urls(recid, filename) == [] + FileSecretDownload.create(**self.base_data) + assert len(FileSecretDownload.fetch_active_urls(recid, filename)) == 1 + FileSecretDownload.create(**self.base_data) + assert len(FileSecretDownload.fetch_active_urls(recid, filename)) == 2 + assert FileSecretDownload.fetch_active_urls( + recid, filename, ascending=True)[0].id == 1 + assert FileSecretDownload.fetch_active_urls( + recid, filename, ascending=False)[0].id == 2 + # def find_downloadable_only(cls, **obj) -> list: # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_models.py::test_find_downloadable_only -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp diff --git a/modules/weko-records-ui/tests/test_utils.py b/modules/weko-records-ui/tests/test_utils.py index f033376fd4..e2e1dbefee 100644 --- a/modules/weko-records-ui/tests/test_utils.py +++ b/modules/weko-records-ui/tests/test_utils.py @@ -928,7 +928,7 @@ def test_validate_secret_url_generation_request(mock_date, app): True), # When all fields are invalid ({'link_name' : 123, - 'expiration_date' : 'mocked', + 'expiration_date' : 123, 'download_limit' : 0, 'send_email' : None, 'timezone_offset_minutes': '0'}, @@ -948,6 +948,9 @@ def test_validate_secret_url_generation_request(mock_date, app): ({**base_case, 'link_name': '123' }, True), ({**base_case, 'link_name': 123 }, False), ({**base_case, 'link_name': 'a' * 256}, False), + # For expiration_date + ({**base_case, 'expiration_date': '2025-01-00'}, True), + ({**base_case, 'expiration_date': 20250101}, False), # For download_limit ({**base_case, 'download_limit': 1 }, True), ({**base_case, 'download_limit': 0 }, False), @@ -970,6 +973,10 @@ def test_validate_secret_url_generation_request(mock_date, app): for request_data, expected in test_cases: assert validate_secret_url_generation_request(request_data) is expected + # if validate_expiration_date is False + mock_date.return_value = False + assert validate_secret_url_generation_request(base_case) is False + # .tox/c1/bin/pytest --cov=weko_records_ui tests/test_utils.py::test_validate_expiration_date -vv -s --cov-branch --cov-report=term --basetemp=/code/modules/weko-records-ui/.tox/c1/tmp def test_validate_expiration_date(app): @@ -993,6 +1000,9 @@ def test_validate_expiration_date(app): mock_settings.return_value = {} assert validate_expiration_date(in_an_year, 0) is False + invalid_date = '2025-01-00' + assert validate_expiration_date(invalid_date, 0) is False + assert validate_expiration_date(tomorrow, 0) is True @@ -1440,6 +1450,7 @@ def test_save_download_log(request, secret_url, onetime_url): # When accessrole is open_no record.get_file_data.return_value = [ + {'filename': 'other_file', 'accessrole': 'open_yes'}, {'filename': file_name, 'accessrole': 'open_no'} ] open_no_dl = save_download_log(